MyBaties的二级缓存

二级缓存介绍

在上文中提到的一级缓存中,其最大的共享范围就是一个SqlSession内部,那么如何让多个SqlSession之间也能够共享缓存呢,答案是二级缓存。
当开启二级缓存后,会使用CachingExecutor装饰Executor,在进入后续执行前,先在CachingExecutor进行二级缓存的查询,具体的工做流程以下所示。sql

在二级缓存的使用中,一个namespace下的全部操做语句,都影响着同一个Cache,即二级缓存是被多个SqlSession共享着的,是一个全局的变量。
当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。数据库

二级缓存配置

要正确的使用二级缓存,需完成以下配置的。
1 在Mybatis的配置文件中开启二级缓存。缓存

<setting name="cacheEnabled" value="true"/>安全

2 在Mybatis的映射XML中配置cache或者 cache-ref 。session

<cache/>app

<cache-ref namespace="mapper.StudentMapper"/>框架

cache-ref表明引用别的命名空间的Cache配置,两个命名空间的操做使用的是同一个Cache。分布式

二级缓存实验

实验1测试

测试二级缓存效果,不提交事务,sqlSession1查询完数据后,sqlSession2相同的查询是否会从缓存中获取数据。spa

@Test
public 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));
}

执行结果:

 

咱们能够看到,当sqlsession没有调用commit()方法时,二级缓存并无起到做用。

实验2

测试二级缓存效果,当提交事务时,sqlSession1查询完数据后,sqlSession2相同的查询是否会从缓存中获取数据。

@Test
public 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));
}

 

从图上可知,sqlsession2的查询,使用了缓存,缓存的命中率是0.5。

实验3

测试update操做是否会刷新该namespace下的二级缓存。

@Test
public 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));
}

 

咱们能够看到,在sqlSession3更新数据库,并提交事务后,sqlsession2的StudentMapper namespace下的查询走了数据库,没有走Cache。

实验4

验证Mybatis的二级缓存不适应用于映射文件中存在多表查询的状况。通常来讲,咱们会为每个单表建立一个单独的映射文件,若是存在涉及多个表的查询的话,因为Mybatis的二级缓存是基于namespace的,多表查询语句所在的namspace没法感应到其余namespace中的语句对多表查询中涉及的表进行了修改,引起脏数据问题。

@Test
public 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));
}

执行结果:

 

在这个实验中,咱们引入了两张新的表,一张class,一张classroom。class中保存了班级的id和班级名,classroom中保存了班级id和学生id。咱们在StudentMapper中增长了一个查询方法getStudentByIdWithClassInfo,用于查询学生所在的班级,涉及到多表查询。在ClassMapper中添加了updateClassName,根据班级id更新班级名的操做。当sqlsession1的studentmapper查询数据后,二级缓存生效。保存在StudentMapper的namespace下的cache中。当sqlSession3的classMapper的updateClassName方法对class表进行更新时,updateClassName不属于StudentMapper的namespace,因此StudentMapper下的cache没有感应到变化,没有刷新缓存。当StudentMapper中一样的查询再次发起时,从缓存中读取了脏数据。

实验5

为了解决实验4的问题呢,可使用Cache ref,让ClassMapper引用StudenMapper命名空间,这样两个映射文件对应的Sql操做都使用的是同一块缓存了。
执行结果:

 

不过这样作的后果是,缓存的粒度变粗了,多个Mapper namespace下的全部操做都会对缓存使用形成影响,其实这个缓存存在的意义已经不大了。

总结

  1. Mybatis的二级缓存相对于一级缓存来讲,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,可以到Mapper级别,经过Cache接口实现类不一样的组合,对Cache的可控性也更强。
  2. Mybatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用的条件比较苛刻。
  3. 在分布式环境下,因为默认的Mybatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,须要使用集中式缓存将Mybatis的Cache接口实现,有必定的开发成本,不如直接用Redis,Memcache实现业务上的缓存就行了
  4. 最终的结论是Mybatis的缓存机制设计的不是很完善,在使用上容易引发脏数据问题,我的建议不要使用Mybatis缓存,在业务层面上使用其余机制实现须要的缓存功能,让Mybatis老老实实作它的ORM框架就行了。

 

文章参考:https://www.jianshu.com/p/c553169c5921

相关文章
相关标签/搜索