MyBatis缓存机制详解MyBatis 缓存机制详解 1 MyBatis 缓存 1 1 MyBatis 缓存概述 1 2 MyBatis 一二级缓存区别 2 MyBatis 一级缓存 2 1 MyBatis 一级缓存概述 2 2 MyBatis 一级缓存配置 2 3 MyBatis 一级缓存原理分析 2 4 MyBatis 一级缓存总结 3 MyBatis 二级缓存 3 1 MyBatis 二级缓存概述 3 2
MyBatis缓存机制详解
1. MyBatis缓存
1.1 MyBatis缓存概述
1.2 MyBatis一二级缓存区别
2. MyBatis一级缓存
2.1 MyBatis一级缓存概述
2.2 MyBatis一级缓存配置
2.3 MyBatis一级缓存原理分析
2.4 MyBatis一级缓存总结
3. MyBatis二级缓存
3.1 MyBatis二级缓存概述
3.2 MyBatis二级缓存配置
3.3 MyBatis二级缓存原理分析
3.4 MyBatis二级缓存总结
4. MyBatis缓存测试

5. 参考文档
1. MyBatis缓存
1.1 MyBatis缓存概述
MyBatis作为目前最常用的ORM数据库访问持久层框架,其本身支持动态SQL存储映射等高级特性也非常优秀,通过Mapper文件采用动态代理模式使SQL与业务代码相解耦,日常开发中使用也非常广泛,本文主要讨论mybatis缓存功能,mybatis缓存本身设计初衷是为了解决同一会话相同查询的效率问题,单机环境下也确实起到了提高查询效率的作用,但是随着业务场景变化以及分布式微服务的出现,其弊端也渐渐显现出来,不同会话间操作数据,关联查询数据采用mybatis缓存时会存在出现脏数据的风险。
1.2 MyBatis一二级缓存区别
1.Mybatis一级缓存是SQLSession级别的,一级缓存的作用域是SQlSession;Mabits一级缓存默认是开启的。 在同一个SqlSession中,执行相同的SQL查询时;第一次会去查询数据库,并写在缓存中,第二次会直接从缓存中取。 在同一次会话中执行两次相同查询中间执行了更新操作的时候,缓存会被清空,第二次相同查询仍然会去查询数据库。
2.Mybatis二级缓存是Mapper级别的,二级缓存的作用域是全局的,多个SQlSession共享的,二级缓存的作用域更大;Mybatis二级缓存默认是没有开启的。 第一次调用mapper下的SQL去查询用户的信息,查询到的信息会存放在该mapper对应的二级缓存区域。 第二次调用namespace下的mapper映射文件中,相同的sql去查询用户信息,会去对应的二级缓存内取结果。
2. MyBatis一级缓存
2.1 MyBatis一级缓存概述
默认情况下,只启用了本地的会话缓存,也就是一级缓存,它仅仅对一个会话中的数据进行缓存。 mybatis一级缓存指的是在应用运行过程中,一次数据库会话中,执行多次相同的查询,会优先查询缓存中的数据,减少数据库查询次数,提高查询效率。
每个SqlSession中持有了Executor,每个Executor中有一个LocalCache。当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。
2.2 MyBatis一级缓存配置
mybatis一级缓存默认是开启的,可根据需要选择级别是session或这statement。开发者只需在MyBatis的配置文件中,添加如下语句,就可以使用一级缓存。共有两个选项,session或者statement,默认是session级别,即在一个MyBatis会话中执行的所有语句,都会共享这一个缓存。一种是statement级别,可以理解为缓存只对当前执行的这一个Statement有效。
2.3 MyBatis一级缓存原理分析
1.在初始化SqlSesion时,会使用Configuration类创建一个全新的Executor,作为DefaultSqlSession构造函数的参数。
// newExecutor 尤其可以注意这里,如果二级缓存开关开启的话,是使用CahingExecutor装饰BaseExecutor的子类
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
2.SqlSession创建完毕后,根据Statment的不同类型,会进入SqlSession的不同方法中,如果是Select语句的话,最后会执行到SqlSession的selectList,代码如下所示:
@Override
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}
3.SqlSession把具体的查询职责委托给了Executor。如果只开启了一级缓存的话,首先会进入BaseExecutor的query方法。代码如下所示:
@Override
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
4.在上述代码中,会先根据传入的参数生成CacheKey,进入该方法查看CacheKey是如何生成的,代码如下所示:
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
//后面是update了sql中带的参数
cacheKey.update(value);
在上述的代码中,将MappedStatement的Id、SQL的offset、SQL的limit、SQL本身以及SQL中的参数传入了CacheKey这个类,最终构成CacheKey。以下是这个类的内部结构:
public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLYER;
this.count = 0;
this.updateList = new ArrayList