每天给你诚意满满的干货
目录本文按照以下顺序展开。
二级缓存二级缓存介绍在上文中提到的一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示。 二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。 二级缓存配置要正确的使用二级缓存,需完成如下配置的。 1.在MyBatis的配置文件中开启二级缓存。 <setting name='cacheEnabled' value='true'/> 2.在MyBatis的映射XML中配置cache或者 cache-ref 。 cache标签用于声明这个namespace使用二级缓存,并且可以自定义配置。 <cache/>
cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache。 <cache-ref namespace='mapper.StudentMapper'/> 二级缓存实验接下来我们通过实验,了解MyBatis二级缓存在使用上的一些特点。 实验1 测试二级缓存效果,不提交事务,sqlSession1查询完数据后,sqlSession2相同的查询是否会从缓存中获取数据。 @Testpublic void testCacheWithoutCommitOrClose() throws Exception {
SqlSession sqlSession1 = factory.openSession(true);
SqlSession sqlSession2 = factory.openSession(true);
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
System.out.println('studentMapper读取数据: ' studentMapper.getStudentById(1));
System.out.println('studentMapper2读取数据: ' studentMapper2.getStudentById(1)); } 执行结果: 实验2 测试二级缓存效果,当提交事务时,sqlSession1查询完数据后,sqlSession2相同的查询是否会从缓存中获取数据。 @Testpublic void testCacheWithCommitOrClose() throws Exception {
SqlSession sqlSession1 = factory.openSession(true);
SqlSession sqlSession2 = factory.openSession(true);
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
System.out.println('studentMapper读取数据: ' studentMapper.getStudentById(1));
sqlSession1.commit();
System.out.println('studentMapper2读取数据: ' studentMapper2.getStudentById(1)); }
实验3 测试update操作是否会刷新该namespace下的二级缓存。 @Testpublic void testCacheWithUpdate() throws Exception {
SqlSession sqlSession1 = factory.openSession(true);
SqlSession sqlSession2 = factory.openSession(true);
SqlSession sqlSession3 = factory.openSession(true);
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
StudentMapper studentMapper3 = sqlSession3.getMapper(StudentMapper.class);
System.out.println('studentMapper读取数据: ' studentMapper.getStudentById(1));
sqlSession1.commit();
System.out.println('studentMapper2读取数据: ' studentMapper2.getStudentById(1));
studentMapper3.updateStudentName('方方',1);
sqlSession3.commit();
System.out.println('studentMapper2读取数据: ' studentMapper2.getStudentById(1)); }
实验4 验证MyBatis的二级缓存不适应用于映射文件中存在多表查询的情况。 @Testpublic void testCacheWithDiffererntNamespace() throws Exception {
SqlSession sqlSession1 = factory.openSession(true);
SqlSession sqlSession2 = factory.openSession(true);
SqlSession sqlSession3 = factory.openSession(true);
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
ClassMapper classMapper = sqlSession3.getMapper(ClassMapper.class);
System.out.println('studentMapper读取数据: ' studentMapper.getStudentByIdWithClassInfo(1));
sqlSession1.close();
System.out.println('studentMapper2读取数据: ' studentMapper2.getStudentByIdWithClassInfo(1));
classMapper.updateClassName('特色一班',1);
sqlSession3.commit();
System.out.println('studentMapper2读取数据: ' studentMapper2.getStudentByIdWithClassInfo(1)); } 执行结果:
实验5 为了解决实验4的问题呢,可以使用Cache ref,让ClassMapper引用StudenMapper命名空间,这样两个映射文件对应的Sql操作都使用的是同一块缓存了。 执行结果: 二级缓存源码分析MyBatis二级缓存的工作流程和前文提到的一级缓存类似,只是在一级缓存处理前,用CachingExecutor装饰了BaseExecutor的子类,在委托具体职责给delegate之前,实现了二级缓存的查询和写入功能,具体类关系图如下图所示。 源码分析 源码分析从CachingExecutor的query方法展开,源代码走读过程中涉及到的知识点较多,不能一一详细讲解,读者朋友可以自行查询相关资料来学习。 Cache cache = ms.getCache(); 本质上是装饰器模式的使用,具体的装饰链是
以下是具体这些Cache实现类的介绍,他们的组合为Cache赋予了不同的能力。
然后是判断是否需要刷新缓存,代码如下所示: flushCacheIfRequired(ms); 在默认的设置中SELECT语句不会刷新缓存,insert/update/delte会刷新缓存。进入该方法。代码如下所示: private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}} MyBatis的CachingExecutor持有了TransactionalCacheManager,即上述代码中的tcm。 private Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>(); 这个Map保存了Cache和用TransactionalCache包装后的Cache的映射关系。
@Overridepublic void clear() {
clearOnCommit = true;
entriesToAddOnCommit.clear();} CachingExecutor继续往下走,ensureNoOutParams主要是用来处理存储过程的,暂时不用考虑。 if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql); 之后会尝试从tcm中获取缓存的列表。 List<E> list = (List<E>) tcm.getObject(cache, key); 在getObject方法中,会把获取值的职责一路传递,最终到PerpetualCache。如果没有查到,会把key加入Miss集合,这个主要是为了统计命中率。 Object object = delegate.getObject(key);if (object == null) {
entriesMissedInCache.add(key); } CachingExecutor继续往下走,如果查询到数据,则调用tcm.putObject方法,往缓存中放入值。 if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116 } tcm的put方法也不是直接操作缓存,只是在把这次的数据和key放入待提交的Map中。 @Overridepublic void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);} 从以上的代码分析中,我们可以明白,如果不调用commit方法的话,由于TranscationalCache的作用,并不会对二级缓存造成直接的影响。因此我们看看Sqlsession的commit方法中做了什么。代码如下所示: @Overridepublic void commit(boolean force) {
try {
executor.commit(isCommitOrRollbackRequired(force)); 因为我们使用了CachingExecutor,首先会进入CachingExecutor实现的commit方法。 @Overridepublic void commit(boolean required) throws SQLException {
delegate.commit(required);
tcm.commit();} 会把具体commit的职责委托给包装的Executor。主要是看下tcm.commit(),tcm最终又会调用到TrancationalCache。 public void commit() { if (clearOnCommit) {
delegate.clear();
}
flushPendingEntries();
reset();} 看到这里的clearOnCommit就想起刚才TrancationalCache的clear方法设置的标志位,真正的清理Cache是放到这里来进行的。具体清理的职责委托给了包装的Cache类。之后进入flushPendingEntries方法。代码如下所示: private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
................} 在flushPendingEntries中,将待提交的Map进行循环处理,委托给包装的Cache类,进行putObject的操作。 private void flushCacheIfRequired(MappedStatement ms) 在二级缓存执行流程后就会进入一级缓存的执行流程,因此不再赘述。 总结
全文总结本文对介绍了MyBatis一二级缓存的基本概念,并从应用及源码的角度对MyBatis的缓存机制进行了分析。最后对MyBatis缓存机制做了一定的总结,个人建议MyBatis缓存特性在生产环境中进行关闭,单纯作为一个ORM框架使用可能更为合适。 |
|