本篇来聊一下mybatis的缓存机制,基于3.4.6版本。

知识点

  • 什么是缓存
  • mybatis缓存
  • 缓存实现机制

什么是缓存

对于缓存的概念,我置信学过编程的都晓得,它次要针对的是拜访效率。咱们的程序如果去磁盘或者近程获取资源都是有耗费的,磁盘的耗费在IO这块,近程的耗费在网络这块,这里又波及到用户态和内核态的切换耗费,那怎么来缩小这些拜访呢?咱们能够把常常须要拜访的数据存到磁盘之后再复制一份到jvm内存里,对于这部分数据,咱们间接去jvm里获取,而不必去近程或者磁盘上获取,这样就进步了程序的性能,这部分内存里的数据就叫做缓存。它实质上是一种空间换工夫的性能优化形式。

mybatis缓存

mybatis是一款优良的长久化框架,当然也有本人的缓存机制。这一点置信大家想想也能晓得为什么,去数据库获取数据必定是有肯定性能损耗的,那咱们就能够对于同样的sql查问操作做一些缓存,缩小数据库的拜访并进步数据获取效率。那么mybatis有哪些缓存并且要如何来应用呢?

缓存类型

这里先介绍一下mybatis有哪些缓存类型。mybatis分为一级缓存和二级缓存,什么是一级,什么是二级呢?

  • 一级缓存

    mybatis 默认开启的,是基于 SqlSession 级别的缓存,也就是说同一个session中是能够对缓存做复用的,然而不同的session中,缓存就是各管各的。援用这篇文章一幅图

  • 二级缓存

二级缓存是须要咱们手动开启的,非查问类操作每次操作会清理一遍,缓存是基于 namespace 级别的(能够了解为一个mapper),多个 session 能够共用。还是援用这篇文章的图

咱们在执行一个查问操作的时候,mybatis 的执行程序是:二级缓存 -> 一级缓存 -> 数据库。

如何应用

上面咱们来通过案例应用一下mybatis的缓存,看下成果。

下面说过一级缓存是默认就有的,所以咱们间接用,上代码

        DefaultSqlSessionFactory sqlSessionFactory = (DefaultSqlSessionFactory)applicationContext.getBean("sqlSessionFactory");        DefaultSqlSession sqlSession = (DefaultSqlSession)sqlSessionFactory.openSession();        UserInfo userInfo = sqlSession.getMapper(UserInfoMapper.class).selectById(1);        DefaultSqlSession sqlSession1 = (DefaultSqlSession)sqlSessionFactory.openSession();        UserInfo userInfo1 = sqlSession1.getMapper(UserInfoMapper.class).selectById(1);

看下执行后果:

能够看到申请了两次数据库,这就合乎不同session不共享一级缓存的状况。

再改下代码

        DefaultSqlSessionFactory sqlSessionFactory = (DefaultSqlSessionFactory)applicationContext.getBean("sqlSessionFactory");        DefaultSqlSession sqlSession = (DefaultSqlSession)sqlSessionFactory.openSession();        UserInfo userInfo = sqlSession.getMapper(UserInfoMapper.class).selectById(1);        UserInfo userInfo1 = sqlSession.getMapper(UserInfoMapper.class).selectById(1);

看下后果

能够看到就拜访了一次数据库!

咱们再来试一下二级缓存,二级缓存是须要独自配置的。有两个中央要配置,第一个是全局配置文件,这个不配默认也是true。

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration>    <settings>        <setting name="cacheEnabled" value="true"/>    </settings></configuration>

第二个是mapper文件,只有加<cache/>标签即可(也能够是应用cache-ref来援用其余namespace的缓存),当然你能够对<cache/>做一些更具体的配置,参照官网文档

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.mybatisanalyze.mapper.UserInfoMapper">    <cache/>    <select id="selectById" resultType="com.example.mybatisanalyze.po.UserInfo">        select * from user_info where id = #{id}    </select></mapper>

这两步配置确认没问题之后,二级缓存曾经开启了,上代码

DefaultSqlSessionFactory sqlSessionFactory = (DefaultSqlSessionFactory)applicationContext.getBean("sqlSessionFactory");        DefaultSqlSession sqlSession = (DefaultSqlSession)sqlSessionFactory.openSession();        UserInfo userInfo = sqlSession.getMapper(UserInfoMapper.class).selectById(1);        sqlSession.close();        DefaultSqlSession sqlSession1 = (DefaultSqlSession)sqlSessionFactory.openSession();        UserInfo userInfo1 = sqlSession1.getMapper(UserInfoMapper.class).selectById(1);

这里是验证二级缓存是跨session的,看下后果:

的确二级缓存失效,只拜访了一次数据库。这里代码会发现两个session之间有一个sqlSession.close();,为什么须要这一句呢?因为sql查问一次之后mybatis只会将后果存到待提交map里,只有做了commit或者close,二级缓存才会刷入,如果没有这一步操作来刷入,则二级缓存不会失效。

缓存实现机制

最初来介绍一下 mybatis 的缓存实现机制。mybatis 的缓存实现都在这个包下

从包名咱们也大略看出一些端倪,很显著decorators包的意思就是装璜的意思,也就是该包下的缓存实现都是应用了装璜器模式(针对的都是二级缓存),有哪些实现呢?

对于各个实现就不做介绍了,援用这篇文章一副图阐明

对于一级缓存,都是用的`PerpetualCache

咱们也能够自定义缓存实现,只有实现这个Cache接口,而后在配置文件中指定type即可,参考官网文档

再来看下一级缓存和二级缓存是在什么时候用起来的。

二级缓存实现

咱们在关上一个session的时候,会创立一个执行器,间接看创立执行器的逻辑

能够看到这里有个判断,这个就是二级缓存的全局启用配置,而CachingExecutor就是一个装璜了二级缓存性能的执行器。再来看下查问逻辑org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)

这里会从 MappedStatement 中获取对应的缓存对象,如果缓存对象为空,则不会用到二级缓存,来看下这个缓存对象是怎么生成的。之前在聊二级缓存应用的时候说到须要配置cachecache-ref,其实就是为了生成这个缓存对象。

这里跟进去逻辑比较简单,cache-ref是援用另一个mapper的namespace缓存对象,cache是创立新的缓存对象,就不一一介绍了。

回到下面的org.apache.ibatis.executor.CachingExecutor#query,咱们会看到上面这行去取缓存

这个 tcm 是一个org.apache.ibatis.cache.TransactionalCacheManager类型的对象,负责对执行器下所有的二级缓存对象进行治理,实质上是用的org.apache.ibatis.cache.decorators.TransactionalCache对缓存包了一层,这里用到了装璜器模式,增加了相似事务的性能

TransactionalCache中能够看到咱们存入缓存的时候是退出 entriesToAddOnCommit 变量中的,只有在调了 commit() 之后才会刷入到缓存中,也就是事务机制

二级缓存什么时候被清理呢?</font>这就和咱们的配置相干了。再来看下创立缓存对象的中央org.apache.ibatis.mapping.CacheBuilder#build

缓存的类型默认是PerpetualCache,当然你也能够通过 type 来指定,从下面的代码能够看到,会通过装璜器模式在PerpetualCache缓存上加一层,在setStandardDecorators办法中再加层层装璜

这里有一个ScheduledCache缓存类型,如果咱们设置了flushInterval则会在每次调用的时候判断缓存是否过期,过期则清理。当然在上一层装璜会生成对应的删除缓存规定的缓存类,目前有四种,别离为FifoCache、LruCache、WeakCache、SoftCache,咱们能够本人配置,后面两种在缓存数量超过指定大小(默认1024)的时候删除指定缓存,后两种由援用规定来删除。

一级缓存实现

当二级缓存取不到的时候,就会开始去一级缓存获取。看下

org.apache.ibatis.executor.BaseExecutor#query(...)

一级缓存获取不到则去数据库获取

能够看到获取到之后会存入一级缓存。

<font color=#F08080> 一级缓存什么时候清理呢? </font>

  • 在做更新、插入等批改操作之后会进行一次清理,
  • 将mapper配置中对应 id 节点的flushCache设置为true,在每次查问之前判断是否有正在查的,没有则清理
  • 全局的setting配置中将localCacheScope设置为STATEMENT,则在每次查问之后会判断是否还有正在查的,没有则清理

CacheKey

这个是二级缓存的key,mybatis反对动静sql,所以对应的key不能间接用String来设置,才有了CacheKey。CacheKey的设计次要是为了缩小hash抵触,不同的内容是有可能产生雷同的hashcode(参考hashmap实现),所以这里生成hashcode应用了multiplier来进行倍乘来缩小hash抵触,初始hashcode为什么是17?17是质子数中一个“不大不小”的存在,如果你应用的是一个如2的较小质数,那么得出的乘积会在一个很小的范畴,很容易造成哈希值的抵触。而如果抉择一个100以上的质数,得出的哈希值会超出int的最大范畴,这两种都不适合。

而对于倍乘数为什么取37,如果对超过 50,000 个英文单词(由两个不同版本的 Unix 字典合并而成)进行 hash code 运算,并应用常数 31, 33, 37, 39 和 41 作为乘子(cachekey应用37),每个常数算出的哈希值抵触数都小于7个(国外大神做的测试),那么这几个数就被作为生成hashCode值得备选乘数了。取自这篇文章。

尽管缩小hash抵触进步了hashmap的存入效率,然而还是会呈现hash抵触的状况,所以重写了equals,避免sql/后果集配对谬误。

总结

mybatis的缓存内容还是有不少的,次要应用到了装璜器模式进行解耦,通过以上介绍,置信大家对于mybatis的缓存都理解很深刻了,咱们平时开发也是能够基于二级缓存实现来设计的。

参考资料

https://www.cnblogs.com/wuzhe...

https://mybatis.org/mybatis-3/

https://blog.csdn.net/xl33793...