在上文中提到的一级缓存中,其最大的共享范围就是一个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下的全部操做都会对缓存使用形成影响,其实这个缓存存在的意义已经不大了。
文章参考:https://www.jianshu.com/p/c553169c5921