从坑中爬出,为大家分享Mybatis缓存机制

简介: 从坑中爬出,为大家分享Mybatis缓存机制

SpringBoot集成Mybatis几乎已经成为大多数项目的标配了,但在使用的过程中Mybatis的缓存功能往往会被大家遗忘,甚至很多开发者都没意识到在SpringBoot集成Mybatis还有一级缓存和二级缓存的事。

本来没计划写本篇文章,但在实践的过程掉坑里了,当从坑中爬起来时,发现有必要给大家写写Mybatis的缓存。

遇到什么样的坑

事情是这样的:项目中使用了乐观锁,并进行了失败尝试(3次)。但运行的时候发现尝试也是失败的。起初以为是并发问题,然后把尝试次数无限放大,发现次次都是失败的。

这其中一定有问题,经过研究发现是Mybatis的一级缓存导致的,于是专门研究了Mybatis的一级和二级缓存分享给大家。

缓存存在的意义

其实在日常的项目中,我们几乎都会用到缓存,比如一些不怎么改变的配置项,会采用缓存来减少数据库的压力。Mybatis的一级二级缓存所起到的作用也是相同的。都是为了减少数据库压力,提高系统性能。

两个基本缓存的区别

Mybatis的一级缓存与二级缓存的主要区别是它们所缓存的范围不同。一级缓存是单个session级别的,二级缓存是多个session级别的,只不过多个session需要是同一个namespace下的。关于细节我们后面会逐一介绍。

这里所说的session与我们在Http请求中所说的session可以类别,但并不是同一个session。Http中是session指定的是HttpSession,而这里所说的session是指的查询数据库的SqlSession。

一次网页请求,可以创建一个session(HttpSession),一次数据库查询操作同样会创建一个session(SqlSession)。对照一下,就会很容易理解。

一级缓存

先通过通过下图我们来看看一级缓存的整个流转过程。image.png当用户第一次查询id为1的订单时,缓存中没有数据,所以从数据库中进行加载,加载完成会进行缓存。这里缓存的位置就是内存中的一块空间,数据格式为HashMap。

当第二次读取时,便会直接读取缓存中的数据。当SqlSession执行commit操作(包括插入、更新、删除)时,会清空SqlSession的一级缓存,主要目的是确保缓存中的数据是最新的,避免脏读。

一级缓存是本地(局部)缓存,不能被关闭,只能配置缓存范围:SESSION或STATEMENT。也就是说一级缓存不需要在配置文件去配置,默认开启。

Spring Boot中Mybatis缓存的默认配置

看一下Mybatis源码中的org.apache.ibatis.session.Configuration类的部分源码:

public Configuration() {
    // ...
    this.cacheEnabled = true;
    this.localCacheScope = LocalCacheScope.SESSION;
    // ...
 }

我们可以看到缓存是默认开启的,而localCacheScope默认为Session级别。LocalCacheScope中只定义了SESSION和STATEMENT两个枚举项。

需要注意的是cacheEnabled配置的是二级缓存,而localCacheScope配置的是一级缓存。默认情况下SpringBoot集成Mybatis时一级缓存和二级缓存都是开启状态。

在Spring Boot集成Mybatis的项目中,执行如下单元测试:









@Resourceprivate SqlSessionFactory sqlSessionFactory;
@Testpublic void showDefaultCacheConfiguration() {  System.out.println("一级缓存范围: " + sqlSessionFactory.getConfiguration().getLocalCacheScope());  System.out.println("二级缓存是否被启用: " + sqlSessionFactory.getConfiguration().isCacheEnabled());}

打印结果

一级缓存范围: SESSION
二级缓存是否被启用: true

也证明了上面的说法。

一级缓存验证

关于Spring Boot集成Mybatis我们在之前文章中已经专门讲过,这里不再赘述,直奔重点。先看一个单元测试:














@Resourceprivate SqlSessionFactory sqlSessionFactory;
@Testvoid userFirstCache() {  SqlSession sqlSession = sqlSessionFactory.openSession();  OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);  for (int i = 0; i < 3; i++) {    Order order = orderMapper.findById(1);    log.info("订单信息:{}", order);  }}

在该单元测试中,手动获取SqlSession,并通过SqlSession获得OrderMapper,然后进行数据的查询。执行单元测试之前需在application.properties中配置打印SQL语句的日志:

logging.level.com.secbro.mapper= debug

注意:level后面的包名需要替换成mapper所在的package路径。

此时执行单元测试,会发现只有第一次查询了数据库,后面两次都未查询。image.png同时,在日志中只打印了一次查询数据库的SQL语句。

此时我们执行如下单元测试:











@Resourceprivate OrderMapper orderMapper;
@Testvoid userFirstCache1() {  for (int i = 0; i < 3; i++) {    Order order = orderMapper.findById(1);    log.info("订单信息:{}", order);  }}

会发现三次都查询了数据库,为什么呢?这是因为每次Mapper调用findById方法都会创建一个session,并且在执行完毕后关闭session。所以三次调用并不在一个session中,一级缓存并没有起作用。

而此时,如果将该方法放在一个事务当中,修改如下:












@Resourceprivate OrderMapper orderMapper;
@Transactional@Testvoid userFirstCache1() {  for (int i = 0; i < 3; i++) {    Order order = orderMapper.findById(1);    log.info("订单信息:{}", order);  }}

此时,我们发现一级缓存又生效了。而前文提到的乐观锁重试的Bug就是由于在此场景下使用了一级缓存,查询不到最新的数据库数据导致的。此处也是大家在使用的过程中需要留意的。

实践中,将Mybatis和Spring进行整合开发,事务控制在service中。如果是执行两次service调用查询相同的用户信息,不走一级缓存,因为Service方法结束,SqlSession就关闭,一级缓存就清空。

二级缓存

二级缓存是针对不同SqlSession直接的缓存,可以理解为mapper级别。这些SqlSession需要是同一个namespace。那namespace在哪里体现呢?

就是我们在xxMapper.xml文件中配置的namespace:

<mapper namespace="com.secbro.mapper.OrderMapper" >

下面看一下二级缓存的示意图。image.pngsqlSession1去查询用户id为1的订单信息,查询到用户信息会将查询数据存储到二级缓存中。sqlSession2去查询时便会直接通过二级缓存进行查询。

二级缓存与一级缓存区别,二级缓存的范围更大,多个sqlSession可以共享一个OrderMapper的二级缓存区域。数据类型仍然为HashMap。每一个namespace的mapper都有一个二缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。

二级缓存的开启

在上面的Configuration类中我们已经看到默认开启了二级缓存,此开启操作可以通过在application中进行开启或关闭(false):

mybatis.configuration.cache-enabled=true

当然,也可以在SqlMapConfig.xml中加入:

<setting name="cacheEnabled"value="true"/>

来开启。

此时只是完成了二级缓存的全局开关,但并没有针对具体的Mapper生效。如果需要对指定的Mapper使用二级缓存,还需要在对应的xml文件中配置如下内容:

<mapper namespace="com.secbro.mapper.OrderMapper" >
    <cache/>
    <!--省略其他内容-->
</mapper>

此时,该namespace下的Mapper便开启了二级缓存。

二级缓存实例

二级缓存需要查询结果映射的pojo对象实现java.io.Serializable接口。如果存在父类、成员pojo都需要实现序列化接口。否则,执行的过程中会直接报错。

此时,Order类实现如下:










@Datapublic class Order implements Serializable {
  private int id;
  private String orderNo;
  private int amount;}

由于二级缓存数据存储介质多种多样,不一定在内存有可能是硬盘或者远程服务器。所以,pojo类实现序列化接口是为了将缓存数据取出执行反序列化操作。

下面看一下具体的单元测试:








@Testvoid userSecondCache() {  for (int i = 0; i < 3; i++) {    Order order = orderService.findById(1);    log.info("订单信息:{}", order);  }}

由于开启了二级缓存,我们直接使用service进行查询,就可以发现缓存已经生效了。image.png在图中我们可以看到,还打印出了命中缓存的概率为:0.5。

禁用指定方法的二级缓存

由于cache是针对整个Mapper中的查询方法的,因此当某个方法不需要缓存时,可在对应的select标签中添加useCache值为false来禁用二级缓存。

<select id="findById" parameterType="int" resultMap="BaseResultMap" useCache="false">

小结

查询结果实时性要求不高的情况下可采用mybatis二级缓存降低数据库访问量,提高访问速度,同时配合设置缓存刷新间隔flushInterval来根据需要改变刷新缓存的频次。

通常情况下,如果同时设置了一级缓存和二级缓存,会先使用二级缓存的数据,然后再使用一级缓存的数据,最后才会访问数据库。

关于Mybatis缓存本篇文章就讲这么多,当大家心中对Mybatis的缓存有一个基础的印象之后,后面遇到类似的问题或bug时便有了思考的方向。

目录
相关文章
|
4月前
|
缓存 Java 数据库连接
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
文章介绍了MyBatis的缓存机制,包括一级缓存和二级缓存的配置和使用,以及如何整合第三方缓存EHCache。详细解释了一级缓存的生命周期、二级缓存的开启条件和配置属性,以及如何通过ehcache.xml配置文件和logback.xml日志配置文件来实现EHCache的整合。
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
|
5月前
|
缓存 应用服务中间件 nginx
Web服务器的缓存机制与内容分发网络(CDN)
【8月更文第28天】随着互联网应用的发展,用户对网站响应速度的要求越来越高。为了提升用户体验,Web服务器通常会采用多种技术手段来优化页面加载速度,其中最重要的两种技术就是缓存机制和内容分发网络(CDN)。本文将深入探讨这两种技术的工作原理及其实现方法,并通过具体的代码示例加以说明。
505 1
|
2月前
|
SQL Java 数据库连接
Mybatis架构原理和机制,图文详解版,超详细!
MyBatis 是 Java 生态中非常著名的一款 ORM 框架,在一线互联网大厂中应用广泛,Mybatis已经成为了一个必会框架。本文详细解析了MyBatis的架构原理与机制,帮助读者全面提升对MyBatis的理解和应用能力。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Mybatis架构原理和机制,图文详解版,超详细!
|
2月前
|
存储 缓存 监控
后端开发中的缓存机制:深度解析与最佳实践####
本文深入探讨了后端开发中不可或缺的一环——缓存机制,旨在为读者提供一份详尽的指南,涵盖缓存的基本原理、常见类型(如内存缓存、磁盘缓存、分布式缓存等)、主流技术选型(Redis、Memcached、Ehcache等),以及在实际项目中如何根据业务需求设计并实施高效的缓存策略。不同于常规摘要的概述性质,本摘要直接点明文章将围绕“深度解析”与“最佳实践”两大核心展开,既适合初学者构建基础认知框架,也为有经验的开发者提供优化建议与实战技巧。 ####
|
1月前
|
缓存 Java 数据库连接
MyBatis缓存机制
MyBatis提供两级缓存机制:一级缓存(Local Cache)默认开启,作用范围为SqlSession,重复查询时直接从缓存读取;二级缓存(Second Level Cache)需手动开启,作用于Mapper级别,支持跨SqlSession共享数据,减少数据库访问,提升性能。
32 1
|
1月前
|
缓存 Java 数据库连接
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
63 4
|
2月前
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
2月前
|
SQL 缓存 Java
MyBatis如何关闭一级缓存(分注解和xml两种方式)
MyBatis如何关闭一级缓存(分注解和xml两种方式)
86 5
|
3月前
|
缓存 Java 数据库连接
使用MyBatis缓存的简单案例
MyBatis 是一种流行的持久层框架,支持自定义 SQL 执行、映射及复杂查询。本文介绍了如何在 Spring Boot 项目中集成 MyBatis 并实现一级和二级缓存,以提高查询性能,减少数据库访问。通过具体的电商系统案例,详细讲解了项目搭建、缓存配置、实体类创建、Mapper 编写、Service 层实现及缓存测试等步骤。
|
3月前
|
存储 缓存 负载均衡
Nginx代理缓存机制
【10月更文挑战第2天】
114 4