mybatis之缓存机制

软件结
• 阅读 839

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

知识点

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

什么是缓存

对于缓存的概念,我相信学过编程的都知道,它主要针对的是访问效率。我们的程序如果去磁盘或者远程获取资源都是有消耗的,磁盘的消耗在IO这块,远程的消耗在网络这块,这里又涉及到用户态和内核态的切换消耗,那怎么来减少这些访问呢?我们可以把经常需要访问的数据存到磁盘之后再复制一份到jvm内存里,对于这部分数据,我们直接去jvm里获取,而不用去远程或者磁盘上获取,这样就提高了程序的性能,这部分内存里的数据就叫做缓存。它本质上是一种空间换时间的性能优化方式。

mybatis缓存

mybatis是一款优秀的持久化框架,当然也有自己的缓存机制。这一点相信大家想想也能知道为什么,去数据库获取数据肯定是有一定性能损耗的,那我们就可以对于同样的sql查询操作做一些缓存,减少数据库的访问并提高数据获取效率。那么mybatis有哪些缓存并且要如何来使用呢?

缓存类型

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

  • 一级缓存

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

mybatis之缓存机制

  • 二级缓存

二级缓存是需要我们手动开启的,非查询类操作每次操作会清理一遍,缓存是基于 namespace 级别的(可以理解为一个mapper),多个 session 可以共用。还是引用这篇文章的图

mybatis之缓存机制

我们在执行一个查询操作的时候,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);

看下执行结果:

mybatis之缓存机制

可以看到请求了两次数据库,这就符合不同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);

看下结果

mybatis之缓存机制

可以看到就访问了一次数据库!

我们再来试一下二级缓存,二级缓存是需要单独配置的。有两个地方要配置,第一个是全局配置文件,这个不配默认也是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的,看下结果:

mybatis之缓存机制

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

缓存实现机制

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

mybatis之缓存机制

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

mybatis之缓存机制

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

mybatis之缓存机制

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

mybatis之缓存机制

我们也可以自定义缓存实现,只要实现这个Cache接口,然后在配置文件中指定type即可,参考官方文档

mybatis之缓存机制

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

二级缓存实现

我们在打开一个session的时候,会创建一个执行器,直接看创建执行器的逻辑

mybatis之缓存机制

可以看到这里有个判断,这个就是二级缓存的全局启用配置,而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)

mybatis之缓存机制

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

mybatis之缓存机制

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

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

mybatis之缓存机制

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

mybatis之缓存机制

TransactionalCache中可以看到我们存入缓存的时候是加入 entriesToAddOnCommit 变量中的,只有在调了 commit() 之后才会刷入到缓存中,也就是事务机制

mybatis之缓存机制

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

mybatis之缓存机制

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

mybatis之缓存机制

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

一级缓存实现

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

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

mybatis之缓存机制

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

mybatis之缓存机制

可以看到获取到之后会存入一级缓存。

<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的缓存内容还是有不少的,主要使用到了装饰器模式进行解耦,通过以上介绍,相信大家对于mybatis的缓存都了解很深入了,我们平时开发也是可以基于二级缓存实现来设计的。

参考资料

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

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

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

点赞
收藏
评论区
推荐文章
好买-葡萄 好买-葡萄
4年前
影响MySQL性能的硬件因素
好买网www.goodmai.comIT技术交易平台第一部分 磁盘I/O与内存影响MySQLInnoDB引擎性能的最主要因素就是磁盘I/O,目前磁盘都是机械方式运作的,主要体现在读写前寻找此道的过程中。磁盘自带的读写缓存大小,对于磁盘的读写速度至关重要。读写速度快的磁盘,通常都带有较大的读写缓存。磁盘的寻道过程是机械方式。决定了其随机读
Wesley13 Wesley13
4年前
Spring Cloud Eureka解析(3) EurekaClient 重要缓存解析
EurekaClient也存在缓存,应用服务实例列表信息在每个EurekaClient服务消费端都有缓存。一般的,Ribbon的LoadBalancer会读取这个缓存,来知道当前有哪些实例可以调用,从而进行负载均衡。这个loadbalancer同样也有缓存。首先看这个LoadBalancer的缓存更新机制,相关类是PollingServerListUpd
Easter79 Easter79
4年前
Vue 的计算属性如何实现缓存?(原理深入揭秘)
前言很多人提起Vue中的computed,第一反应就是计算属性会缓存,那么它到底是怎么缓存的呢?缓存的到底是什么,什么时候缓存会失效,相信还是有很多人对此很模糊。本文以Vue2.6.11版本为基础,就深入原理,带你来看看所谓的缓存到底是什么样的。注意本文假定你对Vue响应式原理已经有了基础的了解,如果对于Wat
Wesley13 Wesley13
4年前
mybatis整合redies,使用redis作为二级缓存
方法一(一)、RedisCache(mybatis二级缓存实现类)/Createdbyhhjianon17627./publicclassRedisCacheimplementsCache{
Stella981 Stella981
4年前
MyBatis 面试题(附答案解析)
目录MyBatis的实现逻辑MyBatis的缓存实现逻辑{}和${}的区别是什么?MyBatis中自定义标签的执行原理简述Mapper接口的工作原理在Spring中Mapper接口是如何被注入的?在Mapper接口中是否可以有重载方法?当实体类中的属性名和表中的字
Stella981 Stella981
4年前
Mybatis一二级缓存实现原理与使用指南
Mybatis与Hibernate一样,支持一二级缓存。一级缓存指的是Session级别的缓存,即在一个会话中多次执行同一条SQL语句并且参数相同,则后面的查询将不会发送到数据库,直接从Session缓存中获取。二级缓存,指的是SessionFactory级别的缓存,即不同的会话可以共享。缓存,通常涉及到缓存的写、读、过期(更新缓存
Wesley13 Wesley13
4年前
Spring缓存机制的理解
在Spring缓存机制中,包括了两个方面的缓存操作:1.缓存某个方法返回的结果;2.在某个方法执行前或后清空缓存。下面写两个类来模拟Spring的缓存机制:!复制代码(http://static.oschina.net/uploads/img/201412/27165131_1BWJ.gif)(https://www.oschina.net/
Stella981 Stella981
4年前
MyBatis的缓存配置(Cache)
一、MyBatis的Cache配置1、全局开关:默认是true,如果它配成false,其余各个MapperXML文件配成支持cache也没用。<settings<settingname"cacheEnabled"value"true"/</settings2、各个MapperXML文件,默
Stella981 Stella981
4年前
Mybatis(四)—— Mybatis 缓存
一、Mybatis缓存MyBatis包含一个非常强大的查询缓存特性,使用缓存可以使应用更快地获取数据,避免频繁的数据库交互二、Mybatis缓存分类1.一级缓存:SqlSession的缓存一级缓存默认会启用,想要关闭一级缓存可以在select标签上配置flushCache“true”;
Stella981 Stella981
4年前
Flink的分布式缓存
分布式缓存Flink提供了一个分布式缓存,类似于hadoop,可以使用户在并行函数中很方便的读取本地文件,并把它放在taskmanager节点中,防止task重复拉取。此缓存的工作机制如下:程序注册一个文件或者目录(本地或者远程文件系统,例如hdfs或者s3),通过ExecutionEnvironment注册缓存文件并为它起一个名称。
使用Scrapy进行网络爬取时的缓存策略与User-Agent管理
缓存策略的重要性缓存策略在网络爬虫中扮演着至关重要的角色。合理利用缓存可以显著减少对目标网站的请求次数,降低服务器负担,同时提高数据抓取的效率。Scrapy提供了多种缓存机制,包括HTTP缓存和Scrapy内置的缓存系统。HTTP缓存HTTP缓存是基于HT