关于mybatis:mybatis之缓存机制

7次阅读

共计 5461 个字符,预计需要花费 14 分钟才能阅读完成。

本篇来聊一下 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…

正文完
 0