SpringBoot下Mybatis的缓存的实现步骤

说起 mybatis,作为 Java 程序员应该是无人不知,它是常用的数据库访问框架。与 Spring 和 Struts 组成了 Java Web 开发的三剑客--- SSM。当然随着 Spring Boot 的发展,现在越来越多的企业采用的是 SpringBoot + mybatis 的模式开发,我们公司也不例外。而 mybatis 对于我也仅仅停留在会用而已,没想过怎么去了解它,更不知道它的缓存机制了,直到那个生死难忘的 BUG。故事的背景比较长,但并不是啰嗦,只是让读者知道这个 BUG 触发的场景,加深记忆。在遇到类似问题时,可以迅速定位。

先说下故事的前提,为了防止用户在动态中输入特殊字符,用户的动态都是编码后发到后台,而后台在存入到 DB 表之前会解码以方便在 DB 中查看以及上报到搜索引擎。在查询用户动态的时候先从 DB 表中读取并在后台做一次编码再传到前端,前端再解码就可以正常展示了。流程如下图:

有一天后端预发环境发布完毕后,用户的动态页面有的动态显示正常,而有的却是被编码过的。看到现象后的第一个反应就是有问题的动态被编码了两次,但是编码操作只会在 service 层的 findById 中有。理论不会在上层犯这种低级错误。话不多说便开始排查新增加的代码,发现只要进入了新增加代码中的某个 if 分支则被编码了两次。分支中除了再次调用 findById(必要性不讨论),也无其他特殊代码了。百思不得其解后请教了旁边的老司机,老司机说可能是 mybatis 缓存。于是看了下我代码,将编码的操作从 findById 中移出来后再次发布到预发,正常了,心想老司机不愧是老司机。本次 BUG 触发的有两个条件需要注意:

  • 整个操作过程都在一个函数中,而函数上面加了 @Transactional 的注解(对 mybatis 来说是在同一个 SESSION 中)
  • 一般只会调用 findByIdy 一次,如果进入分支则会调用两次 (第一次调用后做了编码后被缓存,第二次从缓存读后继续被编码)

便开始谷歌 mybatis 的缓存机制,搜到了一篇非常不错的文章《聊聊 mybatis 的缓存机制》,推荐大家看一下。但是这篇文章讲到了源码,涉及的比较深。而且并没讲 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 等。

总结

本来想总结点什么的,但是觉得推荐文章中总结的非常好,直接引用了:

  1. MyBatis一级缓存的生命周期和SqlSession一致。
  2. MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
  3. MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。
  4. MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
  5. MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
  6. 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。
  7. 个人建议MyBatis缓存特性在生产环境中进行关闭,单纯作为一个ORM框架使用可能更为合适。

参考

聊聊MyBatis缓存机制

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Mybatis 缓存原理及失效情况解析

    这篇文章主要介绍了Mybatis 缓存原理及失效情况解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.什么是缓存[Cache] 存在内存中的临时数据. 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题. 2.为什么要使用缓存 减少和数据库的交互次数,减少系统开销,提高系统效率. 3.什么样的数据需要使用缓存 经常查询且不易改变的数据

  • mybatis plus使用redis作为二级缓存的方法

    建议缓存放到 service 层,你可以自定义自己的 BaseServiceImpl 重写注解父类方法,继承自己的实现.为了方便,这里我们将缓存放到mapper层.mybatis-plus整合redis作为二级缓存与mybatis整合redis略有不同. 1. mybatis-plus开启二级缓存 mybatis-plus.configuration.cache-enabled=true 2. 定义RedisTemplate的bean交给spring管理,这里为了能将对象直接存取到redis中,

  • MyBatis一级缓存避坑完全指南

    一级缓存概念 当我们使用Mybatis进行数据库的操作时候,会创建一个SqlSession来进行一次数据库的会话,会话结束则关闭SqlSession对象.那么一个SqlSession的生命周期即对应于Mybatis的一次会话.在Mybatis的一次会话中,我们很有可能多次查询完全相同的sql语句,如果不采取措施的话,每一次查询都查询一次数据库.而一次会话时间一般都是极短的,相同Sql的查询结果极有可能完全相同.由于查询数据库代价是比较大的,这会导致系统的资源浪费. 为了解决这个问题,Mybati

  • mybatis二级缓存的实现代码

    二级缓存需要手动的配置和开启,具体如下 在总的配置件中设置开启二级缓存 /Mybatis02/config/mybatis-conf.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-

  • springboot+mybatis+redis 二级缓存问题实例详解

    前言 什么是mybatis二级缓存? 二级缓存是多个sqlsession共享的,其作用域是mapper的同一个namespace. 即,在不同的sqlsession中,相同的namespace下,相同的sql语句,并且sql模板中参数也相同的,会命中缓存. 第一次执行完毕会将数据库中查询的数据写到缓存,第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率. Mybatis默认没有开启二级缓存,需要在全局配置(mybatis-config.xml)中开启二级缓存. 本文讲述的是使用Redi

  • 深入理解MyBatis中的一级缓存与二级缓存

    前言 先说缓存,合理使用缓存是优化中最常见的,将从数据库中查询出来的数据放入缓存中,下次使用时不必从数据库查询,而是直接从缓存中读取,避免频繁操作数据库,减轻数据库的压力,同时提高系统性能. 一级缓存 一级缓存是SqlSession级别的缓存.在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构用于存储缓存数据.不同的sqlSession之间的缓存数据区域是互相不影响的.也就是他只能作用在同一个sqlSession中,不同的sqlSession中的缓存是互相不能读取的. 一级缓

  • redis与ssm整合方法(mybatis二级缓存)

    SSM+redis整合 ssm框架之前已经搭建过了,这里不再做代码复制工作. 这里主要是利用redis去做mybatis的二级缓存,mybaits映射文件中所有的select都会刷新已有缓存,如果不存在就会新建缓存,所有的insert,update操作都会更新缓存. redis的好处也显而易见,可以使系统的数据访问性能更高.本节只是展示了整合方法和效果,后面会补齐redis集群.负载均衡和session共享的文章. 下面就开始整合工作: 后台首先启动redis-server(后台启动与远程连接l

  • MyBatis查询缓存实例详解

    查询缓存的使用,主要是为了提高查询访问速度.将用户对同一数据的重复查询过程简化,不再每次均从数据库查询获取结果数据,从而提高访问速度. MyBatis的查询缓存机制,根据缓存区的作用域(生命周期)可划分为两种:一级缓存与二级缓存 一.一级查询缓存 MyBatis一级缓存是基于org.apache.ibatis.cache.impl.PerpetualCache类的HashMap本地缓存,其作用域是Sqlsession.在同一个Sqlsession中两次执行相同的sql语句,第一次执行完毕后,会将

  • SpringBoot下Mybatis的缓存的实现步骤

    说起 mybatis,作为 Java 程序员应该是无人不知,它是常用的数据库访问框架.与 Spring 和 Struts 组成了 Java Web 开发的三剑客--- SSM.当然随着 Spring Boot 的发展,现在越来越多的企业采用的是 SpringBoot + mybatis 的模式开发,我们公司也不例外.而 mybatis 对于我也仅仅停留在会用而已,没想过怎么去了解它,更不知道它的缓存机制了,直到那个生死难忘的 BUG.故事的背景比较长,但并不是啰嗦,只是让读者知道这个 BUG 触

  • SpringBoot集成Mybatis的实现步骤

    通过 SpringBoot +MyBatis 实现对数据库学生表的查询操作 一.实现步骤 新建一个09-springboot-web-mybatis项目,方式和之前一样,基于springboot的 1. 准备数据库 新建一个数据库springboot,指定字符编码集utf-8,并创建数据表 表名为:t_student,并插入几条数据 2. 在pom.xml 中添加相关 jar 依赖 <!--Mybatis整合springboot的起步依赖--> <dependency> <g

  • Springboot整合mybatis的步骤

    前期工作 1.导入mybatis整合依赖 <!-- mybatis整合 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> 2.连接数据库 3.连接完

  • 使用sts工具、SpringBoot整合mybatis的详细步骤

    SpringBoot 集成 Mybatis 框架 一.1.SpringBoot 集成 Mybatis 的基本步骤 第一步:添加依赖: 第二步:配置数据源: 第三步:扫描接口包. 二.详细的集成步骤如下: 1.第一步:添加依赖: 添加依赖:除了常规依赖外,需要加入 Mybatis 代码如下(示例): <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XM

  • Springboot整合mybatis开启二级缓存的实现示例

    目录 前言 mybatis 一级缓存和二级缓存的概念 pom引入依赖 application.properties 文件配置 mapper.xml 文件配置 cache-ref 完整示例代码 踩坑 参考资料 前言 下面大部分内容来源于网上的相关帖子和官网,自己简单写了个demo体验了下,个人感觉mybatis的缓存并不是很合适 查询做缓存时,遇到更新操作就会刷新缓存,尤其是多表查询时,就会很难控制.对于那些需要缓存的热数据应该抽出来放到redis上做. mybatis 一级缓存和二级缓存的概念

  • SpringBoot 整合mybatis+mybatis-plus的详细步骤

    目录 前言 准备工作 整合步骤 前言 在真实的项目开发中,使用SpringBoot可以说非常普遍了,而在框架整合中,与数据库的交互无外乎使用jpa,mybatis,mybatis-plus这几种,虽然hibernate仍然有在使用,毕竟框架毕竟重,而且用起来相较于mybatis还是差了那么点意思: 接下来演示下使用 SpringBoot 同时与mybatis,mybatis-plus的整合步骤: 准备工作 1.准备如下一个数据表 CREATE TABLE `student` ( `id` var

  • springboot下使用mybatis的方法

    使用mybatis-spring-boot-starter即可. 简单来说就是mybatis看见spring boot这么火,于是搞出来mybatis-spring-boot-starter这个解决方案来与springboot更好的集成 详见 http://www.mybatis.org/spring/zh/index.html 引入mybatis-spring-boot-starter的pom文件 <dependency> <groupId>org.mybatis.spring.

  • SpringBoot项目中使用redis缓存的方法步骤

    本文介绍了SpringBoot项目中使用redis缓存的方法步骤,分享给大家,具体如下: Spring Data Redis为我们封装了Redis客户端的各种操作,简化使用. - 当Redis当做数据库或者消息队列来操作时,我们一般使用RedisTemplate来操作 - 当Redis作为缓存使用时,我们可以将它作为Spring Cache的实现,直接通过注解使用 1.概述 在应用中有效的利用redis缓存可以很好的提升系统性能,特别是对于查询操作,可以有效的减少数据库压力. 具体的代码参照该

  • SpringBoot集成Mybatis过程步骤图解

    添加mybatis的起步依赖 添加数据库的驱动坐标 添加数据库的连接信息(需要重点注意) 与SpringBoot建立联系 创建User表 创建User实体 编写mapper 配置Mapper映射文件 编写测试Controller 测试 在上述的这些步骤中,前面几步是比较核心的东西,后面只是验证SpringBoot和Mybatis是否整合成功,在整合是还需要注意一些细节,比如数据库的版本问题等 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们.

  • Intellij 下 mybatis 插件 MyBatisCodeHelperPro破解步骤详解

    步骤1.破解包下载地址:https://gitee.com/pengzhile/MyBatisCodeHelper-Pro-Crack/releases 步骤2.下载:Intellij IDEA plugins 搜索安装:MyBatisCodeHelperPro(会要求输入key激活使用收费版功能,暂时忽略)重启IDEA: 步骤三破解: 下载完成后,进入Intellij IDEA plugins 从硬盘安装步骤一中下载的MybatisCodeHelperNew-2.5-IDEA173-IDEA1

随机推荐