mybatis二级缓存原理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis二级缓存原理相关的知识,希望对你有一定的参考价值。

参考技术A

mybatis篇

一级缓存的作用域是Sqlsession级别的,也就是说不同的Sqlsession是不会走一级缓存的,那么如果需要跨Sqlsession的缓存,就需要使用到二级缓存了。

二级缓存的话默认是关闭的,所以需要我们开启,开启的方式官网也有介绍,需要在mybatis-config.xml核心配置文件中开启二级缓存功能,并且我们mapper.xml中也需要加入<cache/>标签,二者缺一不可,后面我们看源码就能知道为啥这两个缺一不可。

先来看个例子

执行结果很意外,为什么二级缓存的功能都开启了,结果sql还是执行了2次,并没有走缓存,其实,二级缓存还有一个要注意的点那就是必须要提交事务二级缓存才会保存记录,因为已经是跨SqlSession共享缓存了,所以事务必须要提交,否则会读取到因混滚导致的错误数据。

有个地方需要注意,二级缓存的Sqlsession中的Executor实际上是CachingExecutor

我们知道getMapper最终的执行都会走到MapperProxy类中的invoker方法,具体就来分析这个类。

最后来到了重点的地方

CacheKey我们可以认为他就是每个方法对应的一个唯一标识符。

这里我们就可以看出为什么之前两者必须要配置,cacheEnable开启了才会用CachingExecutor包装一下BaseExecutor,而<cache/>标签只有配置了才会走缓存的逻辑

这里的tcm

到这,我们就差不多揭开了二级缓存的秘密,重要的还是<cache/>这个标签,因为它的存在就对应着每个mapper.xml中的一个具体Cache类,而这个类在每个mapper.xml中又是同一个,所以最终的值是放入了Cache类中,key为CacheKey,value就是sql执行的结果。
至于为什么需要事务提交才能命中二级缓存,我们看下put方法就知道

这里的putObject并没有真正的把值存入Cache中,而是存入了待提交的Map中,所以再来看下commit做了什么

具体看tcm.commit()

而这里可以看到此处会遍历所有的TransactionCache并执行commit方法

真相就出来了,会遍历待提交的Map然后把里面的值都存入Cache中,所以后面的查询就能直接从Cache中拿到值了。

总结
二级缓存先会把Sqlsession中的Executor包装成包装成CacheingExecutor,所有的sql都会经过这个类,而该类通过mapper.xml中配置的唯一<cache/>标签生成的Cache类存放每个方法执行的结果

MyBatis框架查询缓存-二级缓存原理

二级缓存原理

1.原理

首先看图

技术分享


首先开启mybatis的二级缓存。

sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。

如果SqlSession3去执行相同 mapper下sql,执行commit提交,清空该 mapper下的二级缓存区域的数据。

sqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。

二级缓存与一级缓存区别,二级缓存的范围更大,多个sqlSession可以共享一个UserMapper的二级缓存区域。
UserMapper有一个二级缓存区域(按namespace分) ,其它mapper也有自己的二级缓存区域(按namespace分)。
每一个namespace的mapper都有一个二缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同 的二级缓存区域中。

2.开启二级缓存

mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存。

在核心配置文件SqlMapConfig.xml中加入

[html] view plain copy
 
  1. <setting name="cacheEnabled" value="true"/>  

描述允许值 默认值
cacheEnabled 对在此配置文件下的所有cache 进行全局性开/关设置。true false true

在UserMapper.xml中开启二缓存,UserMapper.xml下的sql执行完成会存储到它的缓存区域(HashMap)。

<!-- 开启本Mapper的namespace下的二级缓存 -->
    <cache/>

3.调用pojo类实现序列化接口

[java] view plain copy
 
  1. public class User implements Serializable{  
  2.     private int id;  
  3.     private String username;// 用户姓名  
  4.     private String sex;// 性别  
  5.     private Date birthday;// 生日  
  6.     private String address;// 地址  
  7.     //...  
  8. }  

为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样(内存、硬盘、服务器),不一样在内存。

4.测试方法
先测试二级缓存的存在

[java] view plain copy
 
  1. //测试二级缓存  
  2. @Test  
  3. public void testCache2() throws Exception{  
  4.     SqlSession sqlSession1 = sqlSessionFactory.openSession();  
  5.     SqlSession sqlSession2 = sqlSessionFactory.openSession();  
  6.     SqlSession sqlSession3 = sqlSessionFactory.openSession();  
  7.       
  8.     UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);//创建代理对象  
  9.     //下边查询使用一个SqlSession  
  10.     //第一次发起请求,查询id为1的用户  
  11.     User user1 = userMapper1.findUserById(1);  
  12.     System.out.println(user1.getUsername());  
  13.     //不关闭SqlSession无法写进二级缓存区域中  
  14.     sqlSession1.close();  
  15.       
  16.     UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);//创建代理对象  
  17.     //第二次发起请求,查询id为1的用户  
  18.     User user2 = userMapper2.findUserById(1);  
  19.     System.out.println(user2.getUsername());  
  20.     sqlSession2.close();  
  21.       
  22.       
  23. }  

测试结果和输出日志:

[plain] view plain copy
 
  1. DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.0  
  2. DEBUG [main] - Opening JDBC Connection  
  3. DEBUG [main] - Created connection 26255574.  
  4. DEBUG [main] - Setting autocommit to false on JDBC Connection [[email protected]]  
  5. DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE id=?   
  6. DEBUG [main] - ==> Parameters: 1(Integer)  
  7. DEBUG [main] - <==      Total: 1  
  8. 张三  
  9. DEBUG [main] - Resetting autocommit to true on JDBC Connection [[email protected]]  
  10. DEBUG [main] - Closing JDBC Connection [[email protected]]  
  11. DEBUG [main] - Returned connection 26255574 to pool.  
  12. DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.5  
  13. 张三  


我们可以发现,日志输出中有一句我们之前没有见过,是:
Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.0
这句的意思是缓存命中率为0.0,说明第一次是在缓存中找,可是没找到
DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.5
这句的意思是缓存命中率为0.5,说明第一次是在缓存中找,可是没找到,第二次找到了。

这个测试证明了二级缓存的存在

下面证明第三方sqlSession执行增删改会清空缓存的事实:

[java] view plain copy
 
  1. //测试二级缓存  
  2. @Test  
  3. public void testCache2() throws Exception{  
  4.     SqlSession sqlSession1 = sqlSessionFactory.openSession();  
  5.     SqlSession sqlSession2 = sqlSessionFactory.openSession();  
  6.     SqlSession sqlSession3 = sqlSessionFactory.openSession();  
  7.       
  8.     UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);//创建代理对象  
  9.     //下边查询使用一个SqlSession  
  10.     //第一次发起请求,查询id为1的用户  
  11.     User user1 = userMapper1.findUserById(1);  
  12.     System.out.println(user1.getUsername());  
  13.     //不关闭SqlSession无法写进二级缓存区域中  
  14.     sqlSession1.close();  
  15.       
  16.     UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);//创建代理对象  
  17.     User user=userMapper3.findUserById(1);  
  18.     user.setUsername("张明明");  
  19.     userMapper3.updateUser(user);  
  20.     //执行提交,清空UserMapper二级缓存  
  21.     sqlSession3.commit();  
  22.     sqlSession3.close();  
  23.       
  24.     UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);//创建代理对象  
  25.     //第二次发起请求,查询id为1的用户  
  26.     User user2 = userMapper2.findUserById(1);  
  27.     System.out.println(user2.getUsername());  
  28.     sqlSession2.close();  
  29.       
  30.       
  31. }  


输出结果和日志信息:

[plain] view plain copy
 
  1. DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.0  
  2. DEBUG [main] - Opening JDBC Connection  
  3. DEBUG [main] - Created connection 189219.  
  4. DEBUG [main] - Setting autocommit to false on JDBC Connection [[email protected]]  
  5. DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE id=?   
  6. DEBUG [main] - ==> Parameters: 1(Integer)  
  7. DEBUG [main] - <==      Total: 1  
  8. 张三  
  9. DEBUG [main] - Resetting autocommit to true on JDBC Connection [[email protected]]  
  10. DEBUG [main] - Closing JDBC Connection [[email protected]]  
  11. DEBUG [main] - Returned connection 189219 to pool.  
  12. DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.5  
  13. DEBUG [main] - Opening JDBC Connection  
  14. DEBUG [main] - Checked out connection 189219 from pool.  
  15. DEBUG [main] - Setting autocommit to false on JDBC Connection [[email protected]]  
  16. DEBUG [main] - ==>  Preparing: update user set username=?,birthday=?,sex=?,address=? where id=?   
  17. DEBUG [main] - ==> Parameters: 张明明(String), 2015-06-07(Date), 男(String), 河南焦作(String), 1(Integer)  
  18. DEBUG [main] - <==    Updates: 1  
  19. DEBUG [main] - Committing JDBC Connection [[email protected]]  
  20. DEBUG [main] - Resetting autocommit to true on JDBC Connection [[email protected]]  
  21. DEBUG [main] - Closing JDBC Connection [[email protected]]  
  22. DEBUG [main] - Returned connection 189219 to pool.  
  23. DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.3333333333333333  
  24. DEBUG [main] - Opening JDBC Connection  
  25. DEBUG [main] - Checked out connection 189219 from pool.  
  26. DEBUG [main] - Setting autocommit to false on JDBC Connection [[email protected]]  
  27. DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE id=?   
  28. DEBUG [main] - ==> Parameters: 1(Integer)  
  29. DEBUG [main] - <==      Total: 1  
  30. 张明明  
  31. DEBUG [main] - Resetting autocommit to true on JDBC Connection [[email protected]]  
  32. DEBUG [main] - Closing JDBC Connection [[email protected]]  
  33. DEBUG [main] - Returned connection 189219 to pool.  

发现第二次再去查1号用户的时候,又再一次查询了数据库,查到了最新的数据"张明明"。

二级缓存远远没有那么简单,他还有一些配置的参数。

5.useCache配置

在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。

[html] view plain copy
 
  1. <select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">  


总结:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。

6.刷新缓存(就是清空缓存)
在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。

 设置statement配置中的flushCache="true" 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
如下:

[html] view plain copy
 
  1. <insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">  

总结:一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。

 

出处:http://blog.csdn.net/acmman/article/details/46793289






















































以上是关于mybatis二级缓存原理的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis框架查询缓存-二级缓存原理

mybatis二级缓存架构原理

mybatis结合redis实战二级缓存

Mybatis 一二级缓存实现原理与使用指南

mybatis学习笔记(13)-查询缓存之二级缓存

MyBatis之二级缓存