从调用 openSession 获得一个 SqlSession 开始分析,再到从调用 getMapper 方法获得一个接口的实现,调用接口的方法执行 SQL 获得结果。这其中 Mybatis 是都做了些什么?是如何运作的?
SqlSession sqlSession = sqlSessionFactory.openSession();
在上一篇文章了已经分析了我们如何获得构建一个 sqlSessionFactory ,现在我们就可以通过 sqlSessionFactory 获得一个 SqlSession ,主要是向用户提供操作数据库的方法,但和数据库操作有关的职责都会委托给 Executor ,我们先看一下 openSession 方法做了些什么:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 获取当前的环境配置对象
final Environment environment = configuration.getEnvironment();
// 获取环境配置对象的 TransactionFactory,如果不存在创建默认的 ManagedTransactionFactory
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 创建一个事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建执行器对象
final Executor executor = configuration.newExecutor(tx, execType);
// 将 Executor 包装到 DefaultSqlSession 返回
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
首先获取通过配置类 configuration 获取当前执行的环境变量对象,而在 Environment 对象就保存了 DataSource 数据源和事务工厂 TransactionFactory ,然后通过事务工厂创建了一个事务。
接下来看一下 newExecutor 方法,将我们当前创建的事务和参数 ExecutorType 传入来创建一个指定类型的 Executor 。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// 确定要使用的的 Executor 类型
// 先判断参数 ExecutorType 是否为空,如果为空使用成员变量的 defaultExecutorType
executorType = executorType == null ? defaultExecutorType : executorType;
// 如果成员变量的 defaultExecutorType 为空,使用 ExecutorType.SIMPLE
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
// 批处理执行器
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
// 可重用执行器
executor = new ReuseExecutor(this, transaction);
} else {
// 默认的每次开启一个Statement对象,用完立刻关闭Statement对象
executor = new SimpleExecutor(this, transaction);
}
// 如果二级缓存开关开启的话,是使用CachingExecutor装饰BaseExecutor的子类
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 为 Executor 进行织入拦截器,生成代理对象
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
首先会判断你是否有传 ExecutorType ,没有的话就会去推断到底应该使用那个类型去创建对应的 Executor ,而 Mybatis 提供了三种 Executor 类型:
在确定了要创建那种类型的 ExecutorType 后,会去判断 cacheEnabled 这个值是否为 true,即判断你是否开启了二级缓存,如果开启了二级缓存会使用 CachingExecutor 对创建的 Executor 作一层包装,这是装饰器模式的设计体现。
创建完 Executor 对象以后,会判断当前是否有 Executor 类型匹配的拦截器插件,如果有的话会为其织入拦截器插件产生代理对象。首先了解一下如何定义一个插件:
@Intercepts({@Signature(
type= Executor.class,
method = "query",
args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})})
public class MyExecutorInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyExecutorInterceptor1");
return invocation.proceed();
}
}
首先你的插件要实现 Interceptor 接口,然后需要使用 @Intercepts 注解指定你要切面的方法,而 @Signature 注解的 type,就是指定你要拦截的类型接口,由于是基于 JDK 的动态代理,所以只能拦截接口,另外值的注意的事 Mybatis 只提供了四个接口能够拦截有效,分别是 Executor ,StatementHandler ,ParameterHandler ,ResultSetHandler ,如果你指定其它的接口是不会有任何效果的,因为在源码就已经硬编码好了,只有在创建这四个接口的实现类,才会去生成代理对象,而 method 属性就是指定你要拦截的接口方法,但是方法是可以重载的,所以光有方法名不能定位唯一,还需要使用 args 方法参数类型签名。
最后你定义了一个拦截器插件,还需要注册配置,如果你是使用 xml 配置文件的话:
<plugins>
<plugin interceptor="com.demo.interceptor.MyExecutorInterceptor"/>
</plugins>
经过上面的两个步骤以后,我们已经定义一个拦截器插件,接下来看 Mybatis 是如何生成代理对象的:
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
首先会遍历所有的 Interceptor 调用 plugin 方法去匹配:
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
plugin 方法是 Interceptor 接口的默认实现方法,然后又调用了 Plugin 的静态方法 wrap ,将当前拦截器对象和当前被拦截的对象,在本例子中是 Executor 对象:
public static Object wrap(Object target, Interceptor interceptor) {
// 解析当前拦截器需要拦截那个类的那个方法
// Key 为被拦截的类型,value 为被拦截的方法
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 获取被代理对象的类型
Class<?> type = target.getClass();
// 获取被代理对象的接口
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 使用 JDK 动态代理创建对象
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
首先调用 getSignatureMap 方法去解析当前拦截器对象要拦截那些类和方法,就是解析 @Signature 注解,最后再通过 getAllInterfaces 方法判断当前拦截器拦截的接口,有没有能和 target 匹配的接口。
最后就是创建 new DefaultSqlSession(configuration, executor, autoCommit); 了一个默认的 DefaultSqlSession,将当前的配置类对象,以及 Executor 和是否自动提交布尔值传递进去了。
通过上面的步骤。我们获得了一个 SqlSession 对象,接下来就可以通过 getMapper 方法获取一个接口的实现类:
// DefaultSqlSession 类的
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
内部实际是调用了 configuration 配置类的 getMapper 方法:
// Configuration 类的
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
接着又调用了 configuration 配置类成员变量 mapperRegistry 方法,MapperRegistry 类主要职责就是注册我们的 Mapper 接口和获取 Mapper 接口的实现类
// MapperRegistry 类的
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 根据类型去获取对应的 MapperProxyFactory
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 基于 JDK 动态代理创建代理对象返回
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
首先以当前 Mapper 接口类型为 Key,去 knownMappers Map 集合去找对应的 MapperProxyFactory ,而 MapperProxyFactory 就是用来创建代理对象的,所以重点看一下 newInstance 方法,是如何创建代理对象的:
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
首先将当前的 SqlSession 作为 MapperProxy 构造函数的参数传递进去,而 MapperProxy 实现了 InvocationHandler 接口,所以我们对 Mapper 接口的方法调用最终都会调用到 MapperProxy 对象的 invoke 方法。
public class MapperProxy<T> implements InvocationHandler, Serializable {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 判断当前方法是不是来自 Object 类的
if (Object.class.equals(method.getDeclaringClass())) {
// 如果是的话,直接反射调用不进行任何
return method.invoke(this, args);
} else {
// 判断缓存是否存在该方法对应的 PlainMethodInvoker
// 如果没有创建返回
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
最后就是使用 JDK 的动态代理创建代理对象并返回给调用者了:
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
在上一步我们通过 SqlSession 的 getMapper 获得了 Mapper 接口的代理对象,然后又知道了,所有 Mapper 接口的方法最终都会被代理拦截到 MapperProxy 对象的 invoke 方法,接下来就分析 invoke 方法都做了些什么?
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 判断当前方法是不是来自 Object 类的
if (Object.class.equals(method.getDeclaringClass())) {
// 如果是的话,直接反射调用不进行任何
return method.invoke(this, args);
} else {
// 判断缓存是否存在该方法对应的 PlainMethodInvoker
// 如果没有创建返回
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
首先判断当前被代理的方法,是不是来自 Object 类,比方说 hashCode 这些方法都是会忽略的,不会把它当做一个 SQL 接口方法,当确认了不是来自 Object 类后,会把当前被代理的方法传递给 cachedInvoker 方法:
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;
}
}
它这里用了会先判断 methodCache 缓存中是否有解析好的该方法对应的 MapperMethodInvoker 对象,如果没有才会调用第三个参数 lambda 表达式去执行创建并放入 methodCache 缓存中,而在这个 lambda 表达式主要有两个分支,先判断你是不是接口 default 方法,如果不是直接创建一个 PlainMethodInvoker 对象,而在 PlainMethodInvoker 的构造函数参数还创建了 MapperMethod 对象,我们先分析怎么创建 MapperMethod 对象的:
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
// 确定 SQL 操作类型
this.command = new SqlCommand(config, mapperInterface, method);
// 确定方法的形参位置,以及返回值类型判断
this.method = new MethodSignature(config, mapperInterface, method);
}
}
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
// 解析该方法对应的 MappedStatement,即 SQL 片段
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
// 如果没找到对应的 SQL 片段,那么检查是否有 @Flush 注解
if (method.getAnnotation(Flush.class) != null) {
name = null;
// 设置 SQL 操作类型为 FLUSH
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
}
SqlCommand 主要封装了你当前执行的 SQL 类型和你所关联的 MappedStatement ID,一般来说这会 ID 就是接口全限定类名加方法名,这就导致了我们在同一个Mapper 中不能出现重载的接口方法,每个 MappedStatement 对应了我们自定义 Mapper 接口中的一个方法,它保存了我们编写的SQL语句、参数结构、返回值结构、Mybatis 对它的处理方式的配置等细节要素,是对一个SQL 命令是什么、执行方式的完整定义。
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
// 判断是不是泛化类型
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
// 返回值是否为 void
this.returnsVoid = void.class.equals(this.returnType);
// 返回值是否为数组或集合
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
// 是否游标查询
this.returnsCursor = Cursor.class.equals(this.returnType);
// 是否为 Optional 封装
this.returnsOptional = Optional.class.equals(this.returnType);
// 检查是否有 @MapKey
this.mapKey = getMapKey(method);
// 检查返回结果是否为 Map 集合
this.returnsMap = this.mapKey != null;
// 确定 RowBounds 在方法参数的索引位置
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
// 确定 ResultHandler 在方法参数的索引位置
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
// 解析方法的索引以及对应形参名字
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
MethodSignature 主要封装了你当前方法的一些参数解析信息和返回值信息,我们主要看一下 new ParamNameResolver 对象这一句,这个和我们在 xml 使用接口的参数息息相关,比方说我有如下一个接口方法签名:
List<User> select(@Param("myName") String name, Integer age, @Param("myMileNo") String mobileNo);
// 我的调用方法参数是这样的
List<User> userList = userMapper.select("李四",22,"188110");
那么通过创建 ParamNameResolver 对象会在构造函数里解析我当前的方法形参名

最终会得到一个这样的形参名映射到实参的 Map 集合,优先使用 @Param 注解的 value 值作为形参,那么你可能会想不是还支持 param1 param2 … 这样的映射关系吗,的确是支持的,不过不是在这一步处理的,后面的分析会看到的。
最后就是创建 PlainMethodInvoker 对象了
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
在 PlainMethodInvoker 构造方法并没有做什么逻辑处理,只是简单的将参数 MapperMethod 对象记录到了直接的成员变量上面。
在 cachedInvoker 方法会将创建好后的 PlainMethodInvoker 对象放入缓存缓存,并返回给调用者,当我们得到了 PlainMethodInvoker 对象以后,然后就调用了它的 invoke 方法:
// PlainMethodInvoker 类的
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
在 invoke 方法又调用了 MapperMethod 对象的 execute 方法:
// MapperMethod 类的
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根据 SQL 操作的类型不同,调用不同的方法
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()) {
// 如果方法返回值为 void,并且方法参数有 ResultHandler
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 如果方法返回值为集合或数组
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
// 如果方法返回值为Map 集合
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;
}
上面 execute 方法就会根据你当前的 SQL 类型以及返回值的差异,从而决定调用 SqlSession 的那一个方法去执行 SQL 来获取结果。
虽然上面的步骤分支很多,但是最终都会委托调用 SqlSession 的方法去干活,这里我就以 executeForMany 这个分支为例子,这个分支主要是用来处理,返回结果为集合或数组的,也是我们平常用的比较多的。
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
// 对参数添加映射关系,param1 => 实参
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
// 如果方法参数有 RowBounds,通过预先找到的索引位置,取出RowBounds
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// 判断方法的返回值是否实际结果的返回值是否兼容一致
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
// 如果方法的返回值数组
if (method.getReturnType().isArray()) {
// 那么将集合转换为数组返回
return convertToArray(result);
} else {
// 如果不是数组,将实际结果类型转换为方法返回值期望类型
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
首先看一下 convertArgsToSqlCommandParam 这个方法就会为我们添加新的形参名到实参的映射关系,就是 param1,param2…. 这样的映射关系。然后判断你的参数是否有 RowBounds ,这个参数是 Mybatis 内置的一个设置内存分页的参数,一般来说比较少使用,最后就是会对执行的结果作一些处理判断,判断你的执行结果返回类型与方法定义的返回值是否能够兼容。
result = sqlSession.selectList(command.getName(), param);
如果你的参数没有 RowBounds 的话,那么就会执行到上面这个语句调用 SqlSession 的 selectList 方法,这里将当前的 command 对象的 name 属性和方法参数映射对象传递进去了,而 command 对象的 name 其实就是 MappedStatement ID。
// DefaultSqlSession 类的
@Override
public <E> List<E> selectList(String statement, Object parameter) {
// 调用了重载带 RowBounds 参数的 selectList 方法
// RowBounds.DEFAULT 就是传了个 null
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
// DefaultSqlSession 类的
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
// 调用了重载带 ResultHandler 参数的 selectList 方法
// Executor.NO_RESULT_HANDLER 就是传了个 null
return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
// 通常来说是接口名加方法名组成 statement 的Id
MappedStatement ms = configuration.getMappedStatement(statement);
// 这里会执行 Executor 的拦截器插件方法,如果有设置对应的插件的话
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
首先通过参数 statement 去配置类 configuration 获取对应的 MappedStatement ,然后调用的 Executor 的 query 方法去执行查询操作,如果你有设置拦截 Executor 接口的对应的方法拦截器插件的话,会先执行拦截器的插件方法。
如果你开启了二级缓存的话,会先执行 CachingExecutor 的 query 方法,如果没有二级缓存的话,就直接执行 BaseExecutor 的 query 方法。我这里我假设我开启了二级缓存:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 根据当前参数解析动态 SQL,为静态 SQL
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 生成 SQL 语句的缓存 Key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 二级缓存的实现
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
首先会将当前的动态 SQL 解析成静态的 SQL,因为对于动态 SQL 来说,有些语句片段需要在明确参数的条件下才能得出。

在 xml 文件定义的 SQL 语句会被解析成一个个 SqlNode 节点对象,而每个节点对象都会有有一个方法,根据当前 context 参数决定自己的节点需要添加什么样的 SQL 片段到 context。
public interface SqlNode {
boolean apply(DynamicContext context);
}
public class DynamicContext {
/**
* 里面包含了当前的方法实参
*/
private final ContextMap bindings;
/**
* SQL 语句拼接最终字符串
*/
private final StringJoiner sqlBuilder = new StringJoiner(" ");
}
在 DynamicContext 有两个比较重要的成员变量,比方说我有一个如下的动态 SQL 语句:
<select id="select1" resultType="user">
SELECT * FROM user
<if test="myName != null and myName != ''">
WHERE name like #{myName}
</if>
</select>
那么 <if> 标签就会对应 IfSqlNode 节点对象,Mybatis 会结合当前的参数与 if 标签的 test 条件使用 ognl 表达式,判断条件是否成立,如果成立的话,会把 if 标签的 WHERE name like #{myName} 内容添加到 DynamicContext 的 sqlBuilder 属性上最终形成一个完整的 SQL 语句。这里 Mybatis 使用了组合设计模式。
解析完动态 SQL 以后,就会根据当前的 SQL 语句参数以及 MappedStatement ID,还有环境变量对象的ID生成一个缓存 Key,用来后续作为一二级缓存的 Key。
二级缓存是由 CachingExecutor 类实现的
// CachingExecutor 类的
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
// 判断是否有开启二级缓存
if (cache != null) {
flushCacheIfRequired(ms);
// 如果使用了 resultHandler,二级缓存也不会生效
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 先从事务缓存中获取
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 事务缓存中不存在,执行SQL获取结果
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 放入事务缓存
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 执行SQL获取结果
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

可以看到 Cache 使用了装饰器模式去组合,最底层的 PerpetualCache 是实际负责存储我们的缓存数据的,而第二层的 FifoCache 是一种缓存清楚策略,Mybatis 除了提供 FIFO 先进先出:按对象进入缓存的顺序来移除它们的策略,还提供了 LRU 最近最少使用,SOFT 软引用:基于垃圾回收器状态和软引用规则移除对象,WEAK 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。这种模式实现的好处就是职责单一,方便替换。
在二级缓存中还引入了一层事务缓存的概念,比方说 SQL 执行成功,它会先放入事务缓存,等你的事务提交后才会把它放入真正的缓存中,如果你回滚了,那么只需要清空事务缓存内存即可。
当二级缓存没找到的话,就会调用 BaseExecutor 的 query 方法,而一级缓存是由 BaseExecutor 实现的:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 先从缓存中获取
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 缓存存在
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 缓存不存在,从数据库查询获取
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
在 BaseExecutor 的 query 方法里,就没有判断是否开启一级缓存的逻辑了,而是直接使用 localCache 尝试从缓存获取一个结果集,没有找到的话就调用 queryFromDatabase 方法从数据库获取结果:
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 调用子类的doQuery方法执行查询获取结果
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
在前面 newExecutor 方法的时候,我们会根据 ExecutorType 从而创建不同的 Executor ,而这些不同实现的 Executor 其实就对应了不同的 JDBC Statement 。
这里以 SimpleExecutor 为例进行分析
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 创建 StatementHandler,如果此时有设置拦截器插件,会生成代理对象返回
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 创建数据库连接和Statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行SQL获取返回值
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
首先调用 configuration 配置类的 newStatementHandler 方法去创建一个 StatementHandler :
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 根据 MappedStatement 类型不同创建不同的 StatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 插件织入,生成代理对象
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
在 newStatementHandler 方法内部又创建了一个 RoutingStatementHandler 对象,由它的内部决定去创建一个什么样的 StatementHandler :
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 根据当前的 MappedStatement 类型不同
// 创建不同的 StatementHandler
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获取数据库连接
Connection connection = getConnection(statementLog);
// 创建JDBC 的 Statement
stmt = handler.prepare(connection, transaction.getTimeout());
// 执行 ParameterHandler 的 setParameters 方法
// 将参数绑定到 JDBC 的 Statement
// 如果有插件会在这里执行
handler.parameterize(stmt);
return stmt;
}
// PreparedStatementHandler 为例
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// JDBC 的 API,执行SQL语句
ps.execute();
// 处理结果返回值
return resultSetHandler.handleResultSets(ps);
}