在 MyBatis 中,SqlSession 缓存查询结果的主要机制包括两种:一级缓存(Local Cache)和二级缓存(Second Level Cache)。这两种缓存机制可以帮助提高查询性能和减少数据库访问次数。
一级缓存(Local Cache)
一级缓存是 SqlSession 的默认缓存机制,它的特点如下:
生命周期:一级缓存是 SqlSession 级别的缓存,也就是说,在同一个 SqlSession 中执行的所有查询操作可以共享这个缓存。
作用范围:一级缓存作用于 Session 内部,即同一个 SqlSession 对象中的多次查询可以共享缓存。
缓存规则:当执行一次查询时,查询结果会被存储在一级缓存中。如果相同的查询再次被执行,并且 SqlSession 还处于打开状态,MyBatis 会直接从缓存中获取结果,而不会再次查询数据库。
失效条件:一级缓存会在以下情况下失效:
手动清除缓存(调用 clearCache() 方法)
SqlSession 执行了任何一个会修改数据库内容的操作(比如增加、修改、删除操作),这会导致缓存失效,以保证数据的一致性。
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
上面描述的是: 怎么创建缓存key ;
mapperstatement id 就是这个 com.example.mapper.UserMapper.selectUserById
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectUserById" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
User user = sqlSession.selectOne("com.example.mapper.UserMapper.selectUserById", 1);
@Override
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();
}
}
删除、修改等,会清除缓存。 比如
BaseExecutor 中update方法
commit、rollback、close都会清空缓存的。
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}
思考一下 springboot 中,整合mybatis ,注入Mapper,执行select方法,会走缓存吗,那么什么时候执行,多个浏览器请求,执行Mapper方法,会是不是同一个SqlSession?
1、 在springboot中,直接注入 Mapper 接口,其实是Spring的MapperFactoryBean 通过getObject方法,从sqlSessionTemplate 去获取的。另外注意一下,因为SqlSessionTemplate是线程安全的,所以说所有的controller里面注入Mapper接口,是安全的。
2、 因为获取SqlSessionTemplate涉及到,事务管理,目前是同一个事务,一定是拿到同一个sqlSession。
3、 浏览器一个请求,来到controller,如果该controller,执行两次同一个mapper方法,且开启了事务管理,那么该缓存肯定是生效的。多个浏览器请求肯定不是同一个session。
4、 如果没有开启事务,调用一次mapper里的方法将会新建一个sqlSession来执行方法。
SqlSessionTemplate#SqlSessionInterceptor 中有个getSqlSession()方法。
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
只有TransactionSynchronizationManager.isSynchronizationActive() 激活才会可以拿到sqlSession。下面是激活的状态,只有事务才会激活。
public interface TransactionSynchronization extends Ordered, Flushable {
/** Completion status in case of proper commit. */
int STATUS_COMMITTED = 0;
/** Completion status in case of proper rollback. */
int STATUS_ROLLED_BACK = 1;
/** Completion status in case of heuristic mixed completion or system errors. */
int STATUS_UNKNOWN = 2;
}
二级缓存(Second Level Cache)
二级缓存是跨 SqlSession 的缓存机制,它的特点如下:
生命周期:二级缓存是 Mapper 级别的缓存,多个 SqlSession 可以共享同一个 Mapper 的二级缓存。
作用范围:二级缓存的作用范围是跨 SqlSession 的,不同的 SqlSession 对象可以共享同一个 Mapper 的二级缓存。
配置:二级缓存需要在 MyBatis 的配置文件中明确配置开启,并在 Mapper 的 XML 文件或接口中设置适当的缓存策略(如 <cache> 标签或 @CacheNamespace 注解)。
缓存规则:查询结果会被存储在二级缓存中,当同一个 Mapper 的不同 SqlSession 执行相同的查询时,MyBatis 会先从二级缓存中查找结果。如果找到,则直接返回缓存中的数据,不再执行数据库查询。
失效条件:二级缓存会在以下情况下失效:
手动清除缓存(比如调用 clearCache() 方法)
由于配置了缓存刷新策略,比如定时刷新或根据特定条件刷新
SqlSession 执行了对应 Mapper 的增删改操作,这会导致该 Mapper 的二级缓存失效。
总结
SqlSession 在 MyBatis 中通过一级缓存和二级缓存来提高查询性能和减少数据库访问次数。一级缓存适用于单个 SqlSession 内的查询结果共享,而二级缓存则适用于多个 SqlSession 间的 Mapper 结果共享。这两种缓存机制可以根据实际应用场景进行合理配置和使用,以达到最佳的性能优化效果。