我们可以看到,当 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 操作都使用的是同一块缓存了。
执行结果: