目录
前言
mybatis中的缓存分为一级缓存和二级缓存,在查询相同内容的时候能够提高查询速度。本文从源码上分析缓存的实现以及mybatis和spring结合之后一级缓存失效的问题。
一、mybatis中的缓存相关源码分析
1.二级缓存
在查询过程中,首先在二级缓存中查找,当二级缓存中找不到时继续接下来的查询,源码如下,可以看出二级缓存是mapperStatement对象中的。
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
/*
mybatis的二级缓存
*/
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//进一步查询
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
2.一级缓存
在二级缓存中无法查找到当前内容时,会 在一级缓存中查找,之后会在数据库中查找,相关代码如下,二级缓存是baseExecutor中一个属性,每个sqlsession都有自己的baseExecutor,所以当sqlsession不同时,一级缓存会失效。
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++;
/*
去localCache本地缓存中查,一级缓存
*/
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--;
}
return list;
}
二、mybatis与spring结合时的一级缓存失效问题
上面说到sqlsession不同会导致缓存失效,这一块内容在spring与mybatis的结合中也有体现。
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取sqlsession
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
......
}
}
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
//在TransactionSynchronizationManager中获取session
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
//获取不到session时新创建session
session = sessionFactory.openSession(executorType);
//如果存在事务,将当前的sessionFactory作为key注册到TransactionSynchronizationManager中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
当存在事获取sqlsession时会首先尝试从TransactionSynchronizationmanager中获取,当获取不到时就会重新创建,当存在事物的时候,会将当前的sqlsession注册到TransactionSynchronizationManager中,所以存在事务的时候一级缓存不会失效。
当最近项目组有人在一个事务方法中连续进行了三次同样的查询,第三次查询之后修改结果的值,执行了插入操作,但是对象的修改导致前两次查询的结果也发生了变化。这里就是使用到了一级缓存,三次查询产生的对象是同一个,相当于三个变量指向同一个对象,修改之后都会改变。这里我建议将数据查出来,然后执行插入操作,可以新建一个对象,避免影响其他值。
总结
本文主要分析了mybatis中的缓存相关原理,一级spring结合mybatis时缓存失效的原因,但spring为啥会考虑在有事务的情况下使用同一个sqlsession等相关问题有待分析。