深度解析Mybatis缓存
本文从源码分析Mybatis一级和二级缓存的应用,进而阐述Mybatis缓存的“坑”。
在介绍Mybatis一级缓存和二级缓存之前,需要首先理解两个概念:
-
SqlSession:引用官方文档中对这个接口作用的说明—SqlSession完全包含了面向数据库执行SQL命令所需的所有方法。你可以通过SqlSession实例来直接执行已映射的SQL语句,也可以通过SqlSession得到映射和管理事务。
-
namespace:这里提到的namespace指代的是在应用中配置的mapper配置文件中的namespace。
eg:
1 |
|
下面开始正式介绍Mybatis的一级和二级缓存。
一级缓存:SqlSession维度的缓存,也就是每个SqlSession独享的缓存,我们在使用Mybatis的时候,通常会使用SqlSession的getMapper方法获取到映射。
eg:
1 |
|
获取到映射之后执行相关方法进行数据库操作,获取到的映射对象是通过动态代理生成的代理类MapperProxy对象,Mybatis执行SQL的过程不是本文的重点,这里不过多赘述,有兴趣的读者可以自行查阅Mybatis源码。Mybatis的数据库操作是通过Executor来执行,一级缓存也是通过Executor来维护,每个SqlSession都会持有一个Executor:

图中LocalCache就是Mybatis的一级缓存,LocalCache的查询和写入是在Executor内部完成的,BaseExecutor是一个实现了Executor接口的抽象类,通过阅读源码发现,一级缓存LocalCache就是BaseExecutor内部的一个成员变量。
1 2 3 4 |
|
一级缓存保存在PerpetualCache维护的一个Map中,下面为一级缓存执行的流程图:

从流程图中看到,在执行Executor query动作时会尝试从一级缓存中获取缓存数据,下面为相关源码:

在BaseExecutor的query方法中发现了缓存key的构建过程,Mybatis用CacheKey这个对象作为缓存的key,上文中说到一级缓存最终会保存在PerpetualCache维护的一个Map中,我们知道,Map用hashcode和equals来确定对象的唯一性,在CacheKey的update方法中,我们发现了hashcode的运算:

update方法会计算hashcode和checksum并将参数对象保存到了updateList中,而在CacheKey的equals方法中,除去hashcode、checksum和count的比较外,只要updatelist中的元素一一对应相等,那么就可以认为是相等:

所以,只要知道创建Cachekey时updateList中都存放了哪些元素,就可以知道Mybatis是通过哪些因素来确定缓存唯一性,在上文标注的createCacheKey方法中,我们找到了这些元素:

继续分析上文中提到的query方法中调用的重载query方法:

在query方法中发现了清空一级缓存的两处代码,这两处的判断在Mybatis中都是可以配置的。可以在mapper配置文件中配置flushCache来控制数据库操作是否要清空缓存,select默认为false,insert、update、delete默认为true,注意,这个配置一级缓存和二级缓存都会生效,下文会介绍二级缓存。可以使用localCacheScope选项来控制一级缓存的范围,默认为SESSION,会缓存一个SqlSession中的所有查询,如果设置为STATEMENT,则查询会清除缓存。在查询数据库queryFromDatabase方法中,Mybatis做数据库查询操作,并将返回的结果存入一级缓存:

数据库的写操作会清除缓存,同时确认的是insert、update、delete都会统一走update方法:

二级缓存:namespace维度的缓存。上文提到SqlSession中会持有一个Executor,在构建SqlSession的时候,Mybatis会根据cacheEnable选项来确定是否使用一个缓存的Executor,也就是CachingExecutor。cacheEnable可以配置,默认为true:


cacheEnable开启的状态下在mapper配置文件中配置

这是一个典型的按需加载缓存的方式,注意,tcm.putObject方法执行完之后缓存并没有真正的生效,这里只是记录了这次查询将要产生缓存变更,这时候相同的sql查询缓存是不会生效的。同样的,写操作也不是马上会清除缓存:






在执行SqlSession的commit方法之后,缓存的变更会真正的被刷新到缓存中去,开始真正的发挥作用:



我们看到在保存缓存和刷新缓存时用到的delegate对象就是上文中提到的构造TransactionalCache对象时传入的Cache对象了,在构建Cache对象时,Mybatis采用装饰者模式为Cache装饰了不同的功能,有兴趣的读者可以阅读相关源码来了解这部分的内容。缓存保存在PerpetualCache中:



到这里,整个Mybatis缓存的分析就结束了,最后我们分析一下Mybatis缓存到底有什么“坑”。在介绍一级缓存时我们提到Mybatis的一级缓存是SqlSession级别的缓存,不同的SqlSession之间缓存是不共享的,如果两个SqlSession操作同一张表,这时候就可能出现其中一个SqlSession获取到的数据是过期的,我们在使用这个SqlSession查询就有可能读取过期的脏数据。在介绍二级缓存时我们提到二级缓存是namespace维度的缓存,全局共享整个namespace的缓存,当我们把针对同一张表的sql操作写到两个不同的mapper文件中或者使用表关联查询时,就很容易出现两个mapper中查询出来的同一条数据不一致的情况。所以在实际应用中,我们建议将cacheEnable设置为false、localCacheScope设置为STATEMENT,不使用mybatis的缓存,需要缓存的时候用应用程序中的缓存来控制来避免Mybatis的缓存坑。
转载请并标注: “本文转载自 linkedkeeper.com (文/张强)” ©著作权归作者所有
Page 2
阅读数
Page 3
阅读数
Page 4
阅读数
Page 5
阅读数
Page 6
阅读数
Page 7
阅读数
Page 8
阅读数
Page 9
阅读数
Page 10
阅读数
Page 11
阅读数
Page 12
阅读数
Page 13
阅读数
Page 14
阅读数
Page 15
阅读数
Page 16
阅读数
Page 17
阅读数
Page 18
阅读数
Page 19
阅读数
Page 20
阅读数
Page 21
阅读数
Page 22
阅读数
Page 23
阅读数
Page 24
阅读数
Page 25
阅读数
Page 26
阅读数