org.springframework.boot spring-boot-starter-jdbc org.springframework.boot spring-boot-starter-web org.mybatis.spring.boot mybatis-spring-boot-starter 2.3.0 com.mysql mysql-connector-j runtime
server:port: 9994 #服务端口号logging:level:root: INFOspring:application:name: mybatis-test #提供者的服务名称--调用的时候根据改名称来调用对应服务的方法datasource:url: jdbc:mysql://127.0.0.1:3326/db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: rootmybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.johar.mybatis.mybatistest.mapper
public class Company {private Integer id;private String name;private String address;
}
public class User {private Integer id;private String name;private Integer age;private Integer sex;private Company company;
}
public interface CompanyMapper {Company selectCompanyById(Integer id);
}
public interface UserMapper {User selectUserById(Integer id);
}
@SpringBootApplication
@MapperScan(basePackages = {"com.johar.mybatis.mybatistest.mapper"})
public class MybatisTestApplication {public static void main(String[] args) {SpringApplication.run(MybatisTestApplication.class, args);}}
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public User getById(Integer id){return userMapper.selectUserById(id);}}
MyBatis作为常用的ORM框架之一,了解MyBatis中sql执行过程是应用开发必备的技能。如下图是MyBatis 的整体架构:
MyBatis的整体流程如下所示:
MyBatis的详细执行流程如下所示:
@MapperScan注解中默认的factoryBean是MapperFactoryBean,负责将CompanyMapper,UserMapper注入到Spring IOC。
@Overridepublic T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}
最终调用的是MapperProxyFactory的newInstance,最终生成的是一个MapperProxy。
protected T newInstance(MapperProxy mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}public T newInstance(SqlSession sqlSession) {final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}
前面说到,Mapper实际上时一个MapperProxy的实例,因此调用selectUserById实际调用的是MapperProxy的invoke方法。
@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {// Object的方法直接调用return method.invoke(this, args);} else {// 将mapper中方法映射为Mapper.xml的sqlreturn cachedInvoker(method).invoke(proxy, method, args, sqlSession);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}}private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {try {return MapUtil.computeIfAbsent(methodCache, method, m -> {if (m.isDefault()) {// 默认方法执行逻辑try {if (privateLookupInMethod == null) {return new DefaultMethodInvoker(getMethodHandleJava8(method));} else {return new DefaultMethodInvoker(getMethodHandleJava9(method));}} catch (IllegalAccessException | InstantiationException | InvocationTargetException| NoSuchMethodException e) {throw new RuntimeException(e);}} else {// 非默认方法执行逻辑return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));}});} catch (RuntimeException re) {Throwable cause = re.getCause();throw cause == null ? re : cause;}}
接下来就是实际映射逻辑:
public MapperMethod(Class> mapperInterface, Method method, Configuration config) {// 映射逻辑见其构造函数中调用的resolveMappedStatementthis.command = new SqlCommand(config, mapperInterface, method);this.method = new MethodSignature(config, mapperInterface, method);}// 实际执行的逻辑,将方法转换成sql:insert,update,delete,selectpublic Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));break;}case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));break;}case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));break;}case SELECT:if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);} else {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);if (method.returnsOptional()&& (result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + command.getName());}if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException("Mapper method '" + command.getName()+ "' attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");}return result;}
private MappedStatement resolveMappedStatement(Class> mapperInterface, String methodName,Class> declaringClass, Configuration configuration) {// 接口类名.方法名 作为唯一值,查询对应的sqlString statementId = mapperInterface.getName() + "." + methodName;if (configuration.hasStatement(statementId)) {return configuration.getMappedStatement(statementId);} else if (mapperInterface.equals(declaringClass)) {return null;}for (Class> superInterface : mapperInterface.getInterfaces()) {if (declaringClass.isAssignableFrom(superInterface)) {MappedStatement ms = resolveMappedStatement(superInterface, methodName,declaringClass, configuration);if (ms != null) {return ms;}}}return null;}}