阅读 983

Mybatis缓存的一次踩坑记录

一、什么是mybatis缓存:

       为了避免频繁的与数据库进行交互,Mybatis对缓存提供支持,在没有配置的默认情况下,它只开启一级缓存,二级缓存需要通过配置来控制开关。

      一级缓存:一级缓存只是相对于同一个SqlSession而言 ,同一个 SqlSesson 对象, 在参数和 SQL 完全一样的情况先, 只执行一次 SQL 语句,后续结果就从一级缓存中取。在一个sqlSession中只有在sql语句和参数都完全一致的情况下才会触发一级缓存。


     二级缓存:二级缓存是对于一个NameSpace而言,多个sqlsession去操作同一个mapper的sql语句,多个sqlsession可以共用二级缓存,所得到的数据会存在二级缓存区域,默认是不开启的,可以通过配置mybatis-config.xml配置文件来进行全局开启

<settings>   
 <!--开启二级缓存-->  
  <setting name="cacheEnabled" value="true"/>    
</settings>复制代码

然后通过对每一个*Mapper.xml来单独配置是否开启二级缓存

<!--开启本Mapper的namespace下的二级缓存-->
<cache eviction="LRU" flushInterval="10000"/>复制代码

使用二级缓存,需要对POJO实体类型进行序列话,实现Serializable接口。

mybatis二级缓存主要对于访问多的查询请求并且用户对查询结果实时性要求不高的情况,可以降低数据库访问量,从而提高访问速度。可以通过flushInterval访配置来控制缓存刷新时间

    

二:踩坑前提

  使用mybatis和分页工具进行分页查询。同时分页查询是在同一个事务下,也就是同一个SqlSession对象下,循环调用分页查询接口。


查询的结果是每一次循环查询的结果都是一样的,不管是循环到那一页,结果都是取的第一次查询的值,通过日志查询是只调用了一次sql查询语句。猜想原因是由于mybatis缓存导致的。


三、原因分析和解决方案:

    原因:本项目目前是没有显示开启二级缓存,所以使用的是默认的mybatis一级缓存,由于多次查询是处于同一个事务下面,所以使用的是同一个SqlSession对象。一级缓存生效前提是在一个sqlSession中只有在sql语句和参数都完全一致的情况下才会触发,那么也就是通过page分页工具设置的分页参数没有生效。

    实际上通过debug调用流程,也是看到先是去一级缓存中去获取相应查询语句的缓存结果,在同一个 SqlSession 中, Mybatis 会把执行的方法和参数通过算法生成缓存的键值, 将键值和结果存放在一个 Map 中, 如果后续的键值一样, 则直接从 Map 中获取数据,如果从Map中获取的缓存结果是空值,那么分页查询语句会被分页拦截器拦截到,然后设置分页参数,最后再调用查询语句,只有在第一次进行分页查询的时候,从一级缓存中是获取不到结果的,后续的分页查询都是会一级缓存中获取到结果,因为还没有设置分页参数,而其他的参数都是一致,那么生成的查询语句的key都是一样的,因而能从Map中获取到相应的缓存值。

   解决方案:

   1、去除@Transactional注解,去掉事务后,那么每一个分页查询都创建新的sqlSession对象,在一级缓存中,不同的sqlSession对象的缓存是隔离的,所以根据sql语句生成的键是查询不到缓存结果的,所以就会直接执行查询语句从数据库总取结果。

   2、清空缓存,使用flushCache标签可以强制刷新缓存(一级缓存和二级缓存)

<select id="selectPage" parameterType="java.lang.Integer",flushCache="true">复制代码

每次语句查询之后都会清空对应缓存。这个是通过语句主动显示刷新,当然insert,update,delete更新语句也能刷新缓存。


四:总结

1、mybatis中一级缓存是默认开启的,二级缓存默认是不开启的,一级缓存是对于一个sqlSeesion而言,而二级缓存是对于一个nameSpace而言,可以多个SqlSession共享。

2、对于一级缓存而言,在同一个 SqlSession 中, Mybatis 会把执行的方法和参数通过算法生成缓存的键值, 将键值和结果存放在一个 Map 中,如果后续的键值一样, 则直接从 Map 中获取数据。

3、不同的 SqlSession 之间的缓存是相互隔离的,可以通过flushCache标签来强制刷新缓存,

当然任何的 UPDATE, INSERT, DELETE 语句也都会清空缓存。