【MyBatis学习笔记 八】MyBatis两级缓存机制

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 【MyBatis学习笔记 八】MyBatis两级缓存机制

早在去年12月份的一篇Blog中【MySQL数据库原理 一】MySQL架构及查询语句执行流程探索MySQL的执行步骤中就提到过查询缓存这一个概念:

并且提到过其实不建议使用查询缓存,正因为如此,我们才不把缓存做到数据库,这样作为服务端的数据库缓存了各个客户端大量查询结果能用的比例却比较低,性价比不高;反之大多数应用都把缓存做到了应用逻辑层,简单的如一个map的MyBatis,由客户端自己定义策略。

缓存的基本概念

什么是缓存,缓存就是存储在内存中的临时数据。将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。使用缓存的目的就是减少和数据库的交互次数,减少系统开销,提高系统效率,但是一定要注意,缓存存储的只能是:经常查询并且不经常改变的数据

MyBatis两级缓存

MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率,MyBatis默认定义了两级缓存:一级缓存和二级缓存

  • 一级缓存,SqlSession级别的缓存,也称为本地缓存,默认情况下,只有一级缓存开启。
  • 二级缓存,基于namespace级别的缓存,也称为全局缓存,需要手动开启和配置。

其中,为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存,整体的结构图如下:

我们使用之前的Person进行相关的测试,看看一次会话期间的一级缓存是如何实现的。PersonDao查询语句如下

Person getPersonByIdResult(@Param("id")int id);

xml中的SQL配置如下:

<select id="getPersonByIdResult" resultMap="PersonBankAccountByResultMap">
        select p.id id,p.username,username,p.password password,p.age age,p.phone phone,p.email email,p.hobby hobby,ba.id bid,ba.bank_name bank_name,ba.account_name account_name,ba.person_id person_id
        from person p,bank_Account ba
        where p.id=ba.person_id and p.id=#{id}
    </select>
    <resultMap id="PersonBankAccountByResultMap" type="com.example.MyBatis.dao.model.Person">
        <!-- id为主键 -->
        <id column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="age" property="age"/>
        <result column="phone" property="phone"/>
        <!-- column写错会因为查到数据匹配不上,给默认值, type写错会报错,找不到属性-->
        <result column="email" property="email"/>
        <!-- column是数据库表的列名 , property是对应实体类的属性名 -->
        <result column="hobby" property="interests"/>
        <collection  property="bankAccounts" javaType="ArrayList" ofType="com.example.MyBatis.dao.model.BankAccount">
            <result column="bid" property="id"  />
            <result column="bank_name" property="bankName"/>
            <result column="account_name" property="accountName"/>
            <result column="person_id" property="personId"/>
        </collection>
    </resultMap>

MyBatis一级缓存

一级缓存表示会话缓存,也就是SqlSession缓存,在一次会话中的查询数据会被缓存下来。

一级缓存测试

我们编写测试类来查看下一级缓存是否生效

@Test
    public void testGetPersonByIdResultFirstCache() {
        //1.获取SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //2.执行SQL
        PersonDao personDao = sqlSession.getMapper(PersonDao.class);
        Person person = personDao.getPersonByIdResult(1);
        System.out.println(person);
        Person person2 = personDao.getPersonByIdResult(1);
        System.out.println(person2);
        //关闭sqlSession
        sqlSession.close();
    }

日志打印结果如下:

查询同样的数据,sql语句只被执行了一次,第二次是从缓存中获取的。

一级缓存失效情况

一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它。但有以下几种情况一级缓存会自动失效:

1 SqlSession不同导致缓存失效

两次请求的sqlSession不同导致缓存失效,这个很容易理解,因为一级缓存的作用域就在一次会话内嘛

@Test
    public void testGetPersonByIdResultFirstCache() {
        //1.获取SqlSession对象
        SqlSession sqlSession1 = MybatisUtils.getSqlSession();
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();
        //2.执行SQL
        PersonDao personDao1 = sqlSession1.getMapper(PersonDao.class);
        PersonDao personDao2 = sqlSession2.getMapper(PersonDao.class);
        Person person = personDao1.getPersonByIdResult(1);
        System.out.println(person);
        Person person2 = personDao2.getPersonByIdResult(1);
        System.out.println(person2);
        //关闭sqlSession
        sqlSession1.close();
        sqlSession2.close();
    }

从日志可以看到进行了两次查询

2 查询条件不同导致缓存失效

查询条件不同导致缓存失效,这个也很容易理解,因为两次查询的数据不同,第二次查询获取到的数据没有出现在第一次查询后缓存里

@Test
    public void testGetPersonByIdResultFirstCache() {
        //1.获取SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //2.执行SQL
        PersonDao personDao = sqlSession.getMapper(PersonDao.class);
        Person person = personDao.getPersonByIdResult(1);
        System.out.println(person);
        Person person2 = personDao.getPersonByIdResult(2);
        System.out.println(person2);
        //关闭sqlSession
        sqlSession.close();
    }

从日志可以看到进行了两次查询

3 两次查询间执行增删改操作导致缓存失效

同MySQL之前的查询缓存一样,只要表在两次查询之间发生了变更,那么这个变更语句会清空查询缓存,这也是MySQL弃用查询缓存的原因:

@Test
    public void testGetPersonByIdResultFirstCache() {
        //1.获取SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //2.执行SQL
        PersonDao personDao = sqlSession.getMapper(PersonDao.class);
        Person person = personDao.getPersonByIdResult(1);
        System.out.println(person);
        int result= personDao.deletePerson(0);
        System.out.println(result);
        Person person2 = personDao.getPersonByIdResult(1);
        System.out.println(person2);
        //关闭sqlSession
        sqlSession.close();
    }

从日志可以看到进行了两次查询

4 手动清除SqlSession缓存导致缓存失效

这个也很容易理解,当我们手动clear掉SqlSession缓存时,缓存自然而然也被处理掉了:

@Test
    public void testGetPersonByIdResultFirstCache() {
        //1.获取SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //2.执行SQL
        PersonDao personDao = sqlSession.getMapper(PersonDao.class);
        Person person = personDao.getPersonByIdResult(1);
        System.out.println(person);
        sqlSession.clearCache();
        Person person2 = personDao.getPersonByIdResult(1);
        System.out.println(person2);
        //关闭sqlSession
        sqlSession.close();
    }

从日志可以看到进行了两次查询

MyBatis二级缓存

二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存,基于namespace级别的缓存,一个命名空间,对应一个二级缓存,也就是一个Mapper对应一个二级缓存。工作机制:

  1. 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
  2. 如果当前会话关闭了,这个会话对应的一级缓存就没了,一级缓存中的数据被保存到二级缓存中
  3. 新的会话查询信息,可以从二级缓存中获取内容;
  4. 不同的mapper查出的数据会放在自己对应的缓存(map)

我们来看下二级缓存的实现方式:

1 开启全局缓存

首先需要在全局配置文件mybatis-config.xml中打开全局缓存,也就是二级缓存:

<settings>
        <setting name="logImpl" value="log4j"/>
        <setting name="cacheEnabled" value="true"/>  //打开全局缓存
    </settings>

2 PersonMapper中开启全局缓存

personMapper.xml的配置文件中加入:

<!--创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,
     而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突-->
    <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

3 测试二级缓存

只有在一级缓存会话关闭或者提交后,缓存数据才会被提交到二级缓存,我们模拟下这个实现:

@Test
    public void testGetPersonByIdResultSecondCache() {
        //1.获取SqlSession对象
        SqlSession sqlSession1 = MybatisUtils.getSqlSession();
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();
        //2.执行SQL
        PersonDao personDao1 = sqlSession1.getMapper(PersonDao.class);
        PersonDao personDao2 = sqlSession2.getMapper(PersonDao.class);
        Person person = personDao1.getPersonByIdResult(1);
        System.out.println(person);
        //会话1关闭,会话2执行查询
        sqlSession1.close();
        Person person2 = personDao2.getPersonByIdResult(1);
        System.out.println(person2);
        //关闭sqlSession
        sqlSession1.close();
        sqlSession2.close();
    }

查看日志发现只进行了一次查询,全局缓存生效

EhCache实现二级缓存

Ehcache是一种广泛使用的java分布式缓存,用于二级全局缓存。要在应用程序中使用Ehcache,需要引入依赖的jar包,输入如下Maven坐标引入:

<dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.2.1</version>
        </dependency>

然后在对应的Mapper中,我们这里是personMapper.xml文件中加入该缓存支持,同时注释原有二级缓存配置:

<!--创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突-->
    <!--<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>-->
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

代码测试结果同原有二级缓存测试。

总结一下

MySQL其实不建议使用查询缓存,同时8.X以后版本也移除了该模块。正因为如此,我们才不把缓存做到数据库,作为服务端的数据库缓存了各个客户端大量查询结果只要进行一次全表更新就失效,能用的比例非常低,性价比不高;反之大多数应用都把缓存做到了应用逻辑层,简单的如一个map的MyBatis,由客户端自己定义策略,缓存由框架自己实现,MySQL没有负担,客户端也能灵活定义自己的配置。

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
3月前
|
存储 缓存 NoSQL
mybatisplus一二级缓存
MyBatis-Plus 继承并优化了 MyBatis 的一级与二级缓存机制。一级缓存默认开启,作用于 SqlSession,适用于单次会话内的重复查询;二级缓存需手动开启,跨 SqlSession 共享,适合提升多用户并发性能。支持集成 Redis 等外部存储,增强缓存能力。
|
6月前
|
缓存 并行计算 PyTorch
PyTorch CUDA内存管理优化:深度理解GPU资源分配与缓存机制
本文深入探讨了PyTorch中GPU内存管理的核心机制,特别是CUDA缓存分配器的作用与优化策略。文章分析了常见的“CUDA out of memory”问题及其成因,并通过实际案例(如Llama 1B模型训练)展示了内存分配模式。PyTorch的缓存分配器通过内存池化、延迟释放和碎片化优化等技术,显著提升了内存使用效率,减少了系统调用开销。此外,文章还介绍了高级优化方法,包括混合精度训练、梯度检查点技术及自定义内存分配器配置。这些策略有助于开发者在有限硬件资源下实现更高性能的深度学习模型训练与推理。
1022 0
|
5月前
|
缓存 Java 数据库连接
Mybatis一级缓存详解
Mybatis一级缓存为开发者提供跨数据库操作的一致性保证,有效减轻数据库负担,提高系统性能。在使用过程中,需要结合实际业务场景选择性地启用一级缓存,以充分发挥其优势。同时,开发者需注意其局限性,并做好事务和并发控制,以确保系统的稳定性和数据的一致性。
150 20
|
7月前
|
缓存 Java 数据库连接
Mybatis一级缓存、二级缓存详讲
本文介绍了MyBatis中的查询缓存机制,包括一级缓存和二级缓存。一级缓存基于同一个SqlSession对象,重复查询相同数据时可直接从缓存中获取,减少数据库访问。执行`commit`操作会清空SqlSession缓存。二级缓存作用于同一namespace下的Mapper对象,支持数据共享,需手动开启并实现序列化接口。二级缓存通过将数据存储到硬盘文件中实现持久化,为优化性能,通常在关闭Session时批量写入缓存。文章还说明了缓存的使用场景及注意事项。
209 7
Mybatis一级缓存、二级缓存详讲
|
8月前
|
缓存 Java 数据库连接
十、MyBatis的缓存
十、MyBatis的缓存
133 6
|
8月前
|
存储 缓存 分布式计算
【赵渝强老师】Spark RDD的缓存机制
Spark RDD通过`persist`或`cache`方法可将计算结果缓存,但并非立即生效,而是在触发action时才缓存到内存中供重用。`cache`方法实际调用了`persist(StorageLevel.MEMORY_ONLY)`。RDD缓存可能因内存不足被删除,建议结合检查点机制保证容错。示例中,读取大文件并多次调用`count`,使用缓存后执行效率显著提升,最后一次计算仅耗时98ms。
162 0
【赵渝强老师】Spark RDD的缓存机制
|
7月前
|
SQL Java 数据库连接
MyBatis 实现分页的机制
MyBatis 的分页机制主要依赖于 `RowBounds` 对象和分页插件。`RowBounds` 实现内存分页,适合小数据量场景,通过设定偏移量和限制条数对结果集进行筛选。而针对大数据量,则推荐使用分页插件(如 PageHelper),实现物理分页。插件通过拦截 SQL 执行,动态修改语句添加分页逻辑,支持多种数据库方言。配置插件后,无需手动调整查询方法即可完成分页操作,提升性能与灵活性。
148 0
|
9月前
|
缓存 NoSQL Java
Mybatis学习:Mybatis缓存配置
MyBatis缓存配置包括一级缓存(事务级)、二级缓存(应用级)和三级缓存(如Redis,跨JVM)。一级缓存自动启用,二级缓存需在`mybatis-config.xml`中开启并配置映射文件或注解。集成Redis缓存时,需添加依赖、配置Redis参数并在映射文件中指定缓存类型。适用于查询为主的场景,减少增删改操作,适合单表操作且表间关联较少的业务。
164 6
|
11月前
|
存储 缓存 监控
后端开发中的缓存机制:深度解析与最佳实践####
本文深入探讨了后端开发中不可或缺的一环——缓存机制,旨在为读者提供一份详尽的指南,涵盖缓存的基本原理、常见类型(如内存缓存、磁盘缓存、分布式缓存等)、主流技术选型(Redis、Memcached、Ehcache等),以及在实际项目中如何根据业务需求设计并实施高效的缓存策略。不同于常规摘要的概述性质,本摘要直接点明文章将围绕“深度解析”与“最佳实践”两大核心展开,既适合初学者构建基础认知框架,也为有经验的开发者提供优化建议与实战技巧。 ####
|
10月前
|
缓存 Java 数据库连接
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
364 4