这篇文章将为大家详细讲解有关如何在SpringBoot中使用Mybatis进行缓存,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。
缓存的配置
SpringBoot + mybatis 环境搭建很简单而且网上一堆教程,这里不班门弄斧了,记得在项目中将 mytatis 的源码下载下来即可。mybaits 一共有两级缓存:一级缓存的配置 key 是 localCacheScope,而二级缓存的配置 key 是 cacheEnabled,从名字上可以得出以下信息:
一级缓存是本地或者说局部缓存,它不能被关闭,只能配置缓存范围。SESSION 或者 STATEMENT。
二级缓存才是 mybatis 的正统,功能会更强大些。
先来看下在 SpringBoot中 如何配置 mybatis 缓存的相关信息。默认情况下 SpringBoot 下的 mybatis 一级缓存为 SESSION 级别,二级缓存也是打开的,可以在 mybatis 源码中的 org.apache.ibatis.session.Configuration.class 文件中看到(idea中打开),如下图:
也可以通过以下测试程序查看缓存开启情况:
@RunWith(SpringRunner.class) @SpringBootTest public class LearnApplicationTests { private SqlSessionFactory factory; @Before public void setUp() throws Exception { InputStream inputStream = Resources.getResourceAsStream("mybatis/mybatis-config.xml"); factory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void showDefaultCacheConfiguration() { System.out.println("一级缓存范围: " + factory.getConfiguration().getLocalCacheScope()); System.out.println("二级缓存是否被启用: " + factory.getConfiguration().isCacheEnabled()); } }
如果要设置一级缓存的缓存级别和开关二级缓存,在 mybatis-config.xml (当然也可以在 application.xml/yml 中配置)加入如下配置即可:
<settings> <setting name="cacheEnabled" value="true/false"/> <setting name="localCacheScope" value="SESSION/STATEMENT"/> </settings>
但需要注意的是二级缓存 cacheEnabled 只是个总开关,如果要让二级缓存真正生效还需要在 mapper xml 文件中加入 。一级缓存只在同一 SESSION 或者 STATEMENT 之间共享,二级缓存可以跨 SESSION,开启后它们默认具有如下特性:
映射文件中所有的 select 语句将被缓存
映射文件中所有的 insert/update/delete 语句将刷新缓存
一二级缓存同时开启的情况下,数据的查询顺序是 二级缓存 -> 一级缓存 -> 数据库。一级缓存比较简单,而二级缓存可以设置更多的属性,只需要在 mapper 的 xml 文件中的 中配置即可,具体如下:
<cache type = "org.mybatis.caches.ehcache.LoggingEhcache" //指定使用的缓存类,mybatis默认使用HashMap进行缓存,可以指定第三方缓存 eviction = "LRU" //默认是 LRU 淘汰缓存的算法,有如下几种: //1.LRU – 最近最少使用的:移除最长时间不被使用的对象。 //2.FIFO – 先进先出:按对象进入缓存的顺序来移除它们。 //3.SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。 //4.WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象 flushInterval = "1000" //清空缓存的时间间隔,单位毫秒,可以被设置为任意的正整数。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。 size = "100" //缓存对象的个数,任意正整数,默认值是1024。 readOnly = "true" //缓存是否只读,提高读取效率 blocking = "true" //是否使用阻塞缓存,默认为false,当指定为true时将采用BlockingCache进行封装,blocking, //阻塞的意思,使用BlockingCache会在查询缓存时锁住对应的Key,如果缓存命中了则会释放对应的锁, //否则会在查询数据库以后再释放锁这样可以阻止并发情况下多个线程同时查询数据,详情可参考BlockingCache的源码。 />
触发缓存
配置一级缓存为 SESSION 级别
Controller 中调用两次 getOne,代码如下:
@RequestMapping("/getUser") public UserEntity getUser(Long id) { //第一次调用 UserEntity user1=userMapper.getOne(id); //第二次调用 UserEntity user2=userMapper.getOne(id); return user1; }
调用: http://localhost:8080/getUser?id=1,打印结果如下:
从图中的 1/2/3/4 可以看出每次 mapper 层的一次接口调用如 getOne 就会创建一个 session,并且在执行完毕后关闭 session。所以两次调用并不在一个 session 中,一级缓存并没有发生作用。开启事务,Controller 层代码如下:
@RequestMapping("/getUser") @Transactional(rollbackFor = Throwable.class) public UserEntity getUser(Long id) { //第一次调用 UserEntity user1=userMapper.getOne(id); //第二次调用 UserEntity user2=userMapper.getOne(id); return user1; }
打印结果如下:
由于在同一个事务中,虽然调用了 select 操作两次但是只执行了一次 sql ,缓存发挥了作用。这就跟一开始我遇到的那个 BUG 场景一样:同一 session 且 select 调用 > 1 次。如果在两次调用中间插入 update 操作,缓存会立即失效。只要 session 中有 insert、update 和 delete 语句,该 session 中的缓存会立即被刷新。但是注意这只是在同一 session 之间。不同 session 之间如 session1 和 session2,session1 里的 insert/update/delete 并不会影响 session 2 下的缓存,这在高并发或者分布式的情况下会产生脏数据。所以建议将一级缓存级别调成 statement。
配置一级缓存为 STATEMENT 级别
再次将(1)中的无事务和有事务的代码分别执行一遍,打印结果始终如下:
配置成 SATEMENT 后,一级缓存相当于被关闭了。STATEMENT 级别暂时不好模拟,但是我猜测 STATEMENT 级别即在同一执行 sql 的接口中(如上面的 getOne 中)缓存,出了 getOne 缓存即失效。
配置二级缓存,同时为了避免一级缓存的干扰,将一级缓存设置为 STATEMENT
Controller 中去掉 @Transactional 注解代码如下:
@RequestMapping("/getUser") public UserEntity getUser(Long id) { UserEntity user1=userMapper.getOne(id); UserEntity user2=userMapper.getOne(id); return user1; }
当然二级缓存开关保证打开,在 mapper xml 文件中加入 ,整个文件代码如下:
<?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.binggle.learn.dao.mapper.UserMapper" > <resultMap id="BaseResultMap" type="com.binggle.learn.dao.entity.UserEntity" > <id column="id" property="id" jdbcType="BIGINT" /> <result column="name" property="name" jdbcType="VARCHAR" /> <result column="sex" property="sex"/> </resultMap> <sql id="Base_Column_List" > id, name, sex </sql> <select id="getOne" parameterType="java.lang.Long" resultMap="BaseResultMap" > SELECT <include refid="Base_Column_List" /> FROM users WHERE id = #{id}; </select> <cache /> </mapper>
执行 http://localhost:8080/getUser?id=1,打印结果如下:
从图中红框可以看出第二次查询命中缓存,0.5 是命中率。再次执行 http://localhost:8080/getUser?id=1
打印结果如下:
这次一次 sql 也没执行了,缓存命中率上升到 0.75了,所以说二级缓存全局缓存。但它的缓存范围也是有限的,一级缓存在同一个 session 中。二级缓存虽然可以跨 session 但也只能在同一 namespace 中,所谓 namespace 即 mapper xml 文件。具体实验请看《聊聊 mybatis 的缓存机制》中的关于二级缓存的实验 4 和 5。再看下二级缓存配置对二级缓存的影响,为了明显的看出效果,只改如下配置:
<cache size="1" //一次只能缓存一个对象 flushInterval="5000" //刷新时间为 5s />
controller 代码:
@RequestMapping("/getUser") public UserEntity getUser(Long id, Long id2) { //第一个对象 1 System.out.println("================缓存对象 1================="); UserEntity user1 = userMapper.getOne(id); //另一个对象 2 System.out.println("========缓存对象 2,剔除缓存中的对象 1======="); UserEntity user2=userMapper.getOne(id2); user2 = userMapper.getOne(id2); //再次读取第一个对象 System.out.println("==========缓存被剔除,执行查询 sql==========="); user1 = userMapper.getOne(id); //暂停 5s try { sleep(5000); }catch (Exception e){ e.printStackTrace(); } System.out.println("============5s 后再次查询对象 2============="); user2 = userMapper.getOne(id2); return user1; }
执行 http://localhost:8080/getUser?id=1&id2=2 最后打印的结果如下:
太长了,拼接下:
可以看出二级缓存只能缓存一个对象且 5s 后就失效了,配置生效。缓存配置中还有一个重要的配置 type,该配置可以配置第三方的 cache,特别在高并发和分布式情况下。当然,使用更专业的分布式缓存才是王道,例如 redis 等。
总结
本来想总结点什么的,但是觉得推荐文章中总结的非常好,直接引用了:
MyBatis一级缓存的生命周期和SqlSession一致。
MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。
MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。
个人建议MyBatis缓存特性在生产环境中进行关闭,单纯作为一个ORM框架使用可能更为合适。
关于如何在SpringBoot中使用Mybatis进行缓存就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。