Mybatis中的一级二级缓存扫盲

思维导图:

MyBatis 提供了一级缓存和二级缓存机制,用于提高数据库查询的性能,减少对数据库的访问次数。(本质上是减少IO次数)。

一级缓存

1. 概念

一级缓存也称为会话缓存,它是基于 SqlSession 的缓存。在同一个 SqlSession 中,执行相同的 SQL 查询时,MyBatis 会优先从一级缓存中获取结果,而不是再次访问数据库。

2. 工作原理

2.1缓存结构:

在 SqlSession 内部,一级缓存是一个 PerpetualCache 对象,它本质上是一个 HashMap,键是根据查询的 SQL 语句、参数、环境等信息生成的唯一标识,值是查询结果。

查询流程:当调用 SqlSession 的查询方法时,MyBatis 会先将查询的 SQL 语句、参数等信息组合成一个唯一的缓存键。然后在 PerpetualCache 这个 HashMap 中查找该键对应的值。如果找到了,就直接返回该值;如果没找到,就会执行 SQL 查询,将查询结果存入 PerpetualCache 中,下次再执行相同查询时就可以直接从缓存中获取结果。

2.2 缓存命中的条件

相同的 SqlSession:必须是在同一个 SqlSession 实例中执行相同的查询,一级缓存才会生效。

相同的 SQL 语句:查询的 SQL 语句必须完全相同,包括 SQL 中的参数占位符和参数值。

相同的环境:查询的环境(如数据库连接、事务等)也必须相同。

代码示例:

java 复制代码
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;

public class FirstLevelCacheDetailExample {
    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 创建第一个 SqlSession
        try (SqlSession sqlSession1 = sqlSessionFactory.openSession()) {
            // 第一次查询
            User user1 = sqlSession1.selectOne("com.example.UserMapper.selectUserById", 1);
            System.out.println("第一次查询结果: " + user1);

            // 第二次查询,使用相同的 SqlSession
            User user2 = sqlSession1.selectOne("com.example.UserMapper.selectUserById", 1);
            System.out.println("第二次查询结果: " + user2);

            // 两次查询结果相同,说明使用了一级缓存
            System.out.println("两次查询结果是否相同: " + (user1 == user2));

            // 执行更新操作
            sqlSession1.update("com.example.UserMapper.updateUser", new User(1, "New Name"));

            // 第三次查询
            User user3 = sqlSession1.selectOne("com.example.UserMapper.selectUserById", 1);
            System.out.println("第三次查询结果: " + user3);

            // 由于执行了更新操作,一级缓存已清空,user3 是重新查询数据库得到的结果
            System.out.println("第一次查询结果和第三次查询结果是否相同: " + (user1 == user3));
        }
    }
}

在上述代码中,前两次查询使用相同的 SqlSession 和相同的查询条件,所以第二次查询会从一级缓存中获取结果。而执行更新操作后,一级缓存被清空,第三次查询会重新访问数据库。

3. 缓存失效情况

  • 不同的 SqlSession:每个 SqlSession 都有自己独立的一级缓存,不同的 SqlSession 之间的缓存是不共享的。
  • 执行 insert、update、delete 操作:当在同一个 SqlSession 中执行这些操作时,会清空该 SqlSession 的一级缓存,以保证数据的一致

4. 优缺点

优点:

  • 提高性能:在同一个 SqlSession 中多次执行相同查询时,避免了重复的数据库访问,减少了数据库的负载,提高了查询性能。
  • 简单易用:一级缓存是 MyBatis 内置的,无需额外配置,默认开启,使用方便。

缺点:

  • 作用范围小:只在同一个 SqlSession 中有效,不同的 SqlSession 之间无法共享缓存,限制了缓存的使用范围。
  • 数据一致性问题:如果在同一个 SqlSession 中执行了 insert、update、delete 操作,会清空该 SqlSession 的一级缓存,但如果在不同的 SqlSession 中对同一数据进行了修改,一级缓存可能会返回旧数据,导致数据不一致。

二级缓存

1. 工作原理

缓存结构:

二级缓存也是基于 PerpetualCache实现的,但它是基于SqlSessionFactory 的。每个 Mapper 可以有自己独立的二级缓存,也可以多个 Mapper 共享同一个二级缓存。

查询流程:当一个 SqlSession 执行查询操作时,MyBatis 会先检查该 Mapper 对应的二级缓存中是否存在该查询结果。如果存在,则直接从二级缓存中获取结果;如果不存在,则执行 SQL 查询,并将查询结果存入二级缓存中。在多个 SqlSession 之间,只要它们是由同一个 SqlSessionFactory 创建的,就可以共享二级缓存。

2. 配置详解

全局配置:在 mybatis-config.xml 中开启二级缓存的全局开关。

XML 复制代码
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

Mapper 配置:在 Mapper 映射文件中配置缓存。

XML 复制代码
<mapper namespace="com.example.UserMapper">
    <!-- 开启二级缓存,并配置相关属性 -->
    <cache
        eviction="LRU" <!-- 缓存淘汰策略,这里使用最近最少使用策略 -->
        flushInterval="60000" <!-- 缓存刷新间隔,单位为毫秒 -->
        size="512" <!-- 缓存的最大对象数 -->
        readOnly="true" /> <!-- 是否只读 -->

    <select id="selectUserById" resultType="com.example.User">
        SELECT * FROM users WHERE id = #{id}
    </select>
</mapper>
  • eviction:缓存淘汰策略,常见的有 LRU(最近最少使用)、FIFO(先进先出)等。
  • flushInterval:缓存刷新间隔,指定多长时间清空一次缓存。
  • size:缓存的最大对象数,当缓存中的对象数量超过该值时,会根据淘汰策略淘汰部分对象。
  • readOnly:是否只读。如果设置为 true,则缓存中的对象是只读的,MyBatis 会直接返回缓存中的对象,不会进行序列化和反序列化操作,性能较高;如果设置为 false,则每次返回缓存中的对象时都会进行序列化和反序列化操作,保证返回的对象是一个新的实例,但性能相对较低。
  1. 示例代码及分析
java 复制代码
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;

public class SecondLevelCacheDetailExample {
    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 第一个 SqlSession
        try (SqlSession sqlSession1 = sqlSessionFactory.openSession()) {
            User user1 = sqlSession1.selectOne("com.example.UserMapper.selectUserById", 1);
            System.out.println("第一个 SqlSession 查询结果: " + user1);
        }

        // 第二个 SqlSession
        try (SqlSession sqlSession2 = sqlSessionFactory.openSession()) {
            User user2 = sqlSession2.selectOne("com.example.UserMapper.selectUserById", 1);
            System.out.println("第二个 SqlSession 查询结果: " + user2);

            // 两次查询结果相同,说明使用了二级缓存
            System.out.println("两次查询结果是否相同: " + (user1 == user2));
        }

        // 执行更新操作
        try (SqlSession sqlSession3 = sqlSessionFactory.openSession()) {
            sqlSession3.update("com.example.UserMapper.updateUser", new User(1, "New Name"));
            sqlSession3.commit(); // 提交事务,清空二级缓存
        }

        // 第三个 SqlSession
        try (SqlSession sqlSession4 = sqlSessionFactory.openSession()) {
            User user3 = sqlSession4.selectOne("com.example.UserMapper.selectUserById", 1);
            System.out.println("第三个 SqlSession 查询结果: " + user3);

            // 由于执行了更新操作,二级缓存已清空,user3 是重新查询数据库得到的结果
            System.out.println("第一个查询结果和第三个查询结果是否相同: " + (user1 == user3));
        }
    }
}

在上述代码中,第一个 SqlSession 执行查询后,结果会存入二级缓存。第二个 SqlSession 执行相同查询时,会从二级缓存中获取结果。执行更新操作并提交事务后,二级缓存会被清空,第三个 SqlSession 执行查询时会重新访问数据库。

3. 缓存失效情况

  • 执行 insert、update、delete 操作:当执行这些操作时,会清空该 Mapper 对应的二级缓存,以保证数据的一致性。
  • 缓存刷新策略:可以通过配置缓存的刷新策略,如 flushInterval 来定期清空缓存。

4. 优缺点

优点

  • 作用范围大:多个 SqlSession 可以共享二级缓存,减少了数据库的访问次数,提高了系统的整体性能。
  • 可配置性强:可以通过配置不同的缓存淘汰策略、刷新间隔等参数,满足不同的业务需求。

缺点

  • 配置复杂:需要在全局配置和 Mapper 映射文件中进行配置,相对一级缓存来说配置较为复杂。
  • 数据一致性问题:如果在不同的 Mapper 中对同一数据进行了修改,可能会导致二级缓存中的数据不一致,需要手动清空缓存或使用更复杂的缓存刷新策略。

5.一级缓存和二级缓存的比较

作用范围:一级缓存是基于 SqlSession 的,作用范围较小;二级缓存是基于 SqlSessionFactory 的,作用范围较大。

缓存共享:一级缓存不共享,每个 SqlSession 有自己独立的缓存;二级缓存可以在多个 SqlSession 之间共享。

开启方式 :一级缓存默认开启;二级缓存需要手动配置开启。

通过合理使用一级缓存和二级缓存,可以有效提高 MyBatis 应用的性能。但在使用缓存时,需要注意数据的一致性问题,避免出现脏数据。

总结

MyBatis 的一级缓存和二级缓存各有优缺点,在实际应用中需要根据具体的业务场景合理使用。一级缓存适用于在同一个 SqlSession 中多次执行相同查询的场景,而二级缓存适用于多个 SqlSession 之间共享缓存的场景。同时,需要注意缓存的使用可能会导致数据一致性问题,需要在业务逻辑中进行相应的处理。

相关推荐
小小不董7 分钟前
Oracle RAC ‘Metrics Global Cache Blocks Lost‘告警解决处理
linux·服务器·数据库·oracle·dba
江沉晚呤时20 分钟前
深入解析 SqlSugar 与泛型封装:实现通用数据访问层
数据结构·数据库·oracle·排序算法·.netcore
博哥爱学习39 分钟前
《Java高级编程:从原理到实战 - 进阶知识篇四》
java·开发语言
-曾牛2 小时前
Java面试:Spring及Spring Cloud技术深度剖析
java·spring·spring cloud·面试·springboot·javaee·面经
小小不董2 小时前
Oracle OCP认证考试考点详解083系列04
数据库·oracle·dba
啊喜拔牙2 小时前
如何在idea中写spark程序
java·spark·intellij-idea
橘猫云计算机设计3 小时前
springboot基于推荐算法的景点推荐系统(源码+lw+部署文档+讲解),源码可白嫖!
java·spring boot·后端·毕业设计·推荐算法
维基框架3 小时前
Spring Boot Jpa封装快速构建Specification、OrderBy、Pageable的查询条件
java·spring boot
Tanecious.4 小时前
C++--入门基础
java·开发语言·c++
等什么君!4 小时前
学习springboot-条件化配置@Conditional(条件注解)
java·spring boot·学习