这里有两种方式,一种为常用的 Spring 依赖注入 Mapper 的方式。另一种为直接使用 SqlSessionTemplate 执行 Sql 的方式。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
在 Service 层或者 Controller 层通过注解引入 Bean,这个时候引入的 Mapper 就是上文创建的 MapperProxy。MapperProxy 的增强逻辑首先过滤掉了 Object 类中的 toString()、equal()等方法。
如果调用的是 Object 类中的方法,直接放过不代理
对于 Mapper 接口中的方法进行代理。代理前先检查 methodCache 是否缓存了该方法的 invoke 逻辑。
default 方法的逻辑
非 default 方法的逻辑比较重要。
通过 PlainMethodInvoker 这个类代理了其他接口方法,代理逻辑在 MapperMethod 中。
MapperMethod 是最为核心的逻辑。MapperMethod 在执行构建方法时,就会创建一个 SqlCommand 和一个 MethodSignature 方法签名。
SqlCommand 封装了从 SqlSession 中 Config 配置中获取到的 MappedStatement。
调用 execute 方法。传参为 MappedStatement 的增删改查的类型和参数
根据增删改查的类型选择不同的执行逻辑
增删改的逻辑:
- 解析参数得到 param,反射根据 mybatis 中参数注解解析
-
sqlSession.insert(this.command.getName(), param)
或者-
sqlSession.update(this.command.getName(), param)
或者-
sqlSession.delete(this.command.getName(), param)
或者- 处理结果返回值
select 语句根据返回值类型不同调用不同执行逻辑
- returnVoid:返回值为空,且有专门的结果类型处理器
- returnsMany:
this.executeForMany(sqlSession, args);
- returnsMap:
this.executeForMap(sqlSession, args);
- returnsCursor:
this.executeForCursor(sqlSession, args);
- returnsOne 返回一行:
sqlSession.selectOne(this.command.getName(), param);
flush 刷新类型的 SQL:
result = sqlSession.flushStatements();
如果调用的是 Object 类中的方法,直接放过布袋里
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 对于Object类中的方法,放过不增强,直接执行即可。
return Object.class.equals(method.getDeclaringClass()) ?
method.invoke(this, args) :
this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
在 MapperProxy 中有一个缓存结构 methodCache:Map
增强逻辑中会先判断当前方法是否被缓存在 methodCache 中,如果没有,则创建一个放到缓存中。
MapUtil.computeIfAbsent(this.methodCache, method, (m)->{/*创建缓存*/});
创建逻辑为:
MapUtil.computeIfAbsent(this.methodCache, method, (m) -> {
// default方法的逻辑DefaultMethodInvoker,java8和Java9的不一样。
if (m.isDefault()) {
return privateLookupInMethod == null ?
new DefaultMethodInvoker(this.getMethodHandleJava8(method)) :
new DefaultMethodInvoker(this.getMethodHandleJava9(method));
} else {
return
new PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));
}
});
default 的先不用管 DefaultMethodInvoker,直接看 else 中的 PlainMethodInvoker:
创建一个 MapperMethod,然后 PlainMethodInvoker 在 invoke 方法中调用 MapperMethod 的方法 execute()。
在构造 MapperMethod 方法中,创建了一个 SqlCommand 。SqlCommand 封装了从 SqlSession 中 Config 配置中获取到的 MappedStatement。在之后的 execute 方法中执行的就是 SqlCommand 中的 Mapped Statement。
// SqlCommand 封装了从SqlSession中Config配置中获取到的MappedStatement。
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
this.mapperMethod = mapperMethod;
}
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return this.mapperMethod.execute(sqlSession, args);
}
}
接下来就是执行 SqlSession 中的增删改查方法了。可以先看一下 SqlCommand
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
String methodName = method.getName();
Class<?> declaringClass = method.getDeclaringClass();
MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
if (ms <span style="font-weight: bold;" class="mark"> null) {
if (method.getAnnotation(Flush.class) </span> null) {
throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName);
}
this.name = null;
this.type = SqlCommandType.FLUSH;
} else {
this.name = ms.getId();
this.type = ms.getSqlCommandType();
if (this.type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + this.name);
}
}
}
参数转化,然后 excute Sql,封装返回值:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch (this.command.getType()) {
case INSERT:
// 将args参数数组转换成方法中的注解的参数
param = this.method.convertArgsToSqlCommandParam(args);
// 调用DefaultSqlSession的insert方法。
// 处理结果返回值
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
// 将args参数数组转换成方法中的注解的参数
param = this.method.convertArgsToSqlCommandParam(args);
// 调用DefaultSqlSession的update方法。
// 处理结果返回值
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
// 将args参数数组转换成方法中的注解的参数
param = this.method.convertArgsToSqlCommandParam(args);
// 调用DefaultSqlSession的delete方法。
// 处理结果返回值
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
// select 语句情况较多,根据返回值类型不同调用不同执行逻辑。
// returnVoid:返回值为空,且有专门的结果类型处理器
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
在创建 SqlSession 的时候已经创建了 Executor。默认为 Simple
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
Assert.notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionInterceptor());
}
在 Spring Boot 自动配置这篇文章中已经讲过
mappedStatements
。这是一个 HashMapMappedStatement
被添加到了 map 中当我们的 SqlSession 在执行 sql 语句时,会先从 configuration 中拿到 sql。然后执行。
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
List var6;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
} catch (Exception var10) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var10, var10);
} finally {
ErrorContext.instance().reset();
}
return var6;
}
然后看一下
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
再往下一层,就是执行 JDBC 那一套了,获取链接,执行,得到 ResultSet,解析 ResultSet 映射成 JavaBean。