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 之间共享缓存的场景。同时,需要注意缓存的使用可能会导致数据一致性问题,需要在业务逻辑中进行相应的处理。

相关推荐
5***84641 分钟前
Spring Boot的项目结构
java·spring boot·后端
SimonKing1 分钟前
基于Netty的TCP协议的Socket客户端
java·后端·程序员
程序员飞哥2 分钟前
几年没面试,这次真的被打醒了!
java·面试
Learner12 分钟前
Python异常处理
java·前端·python
tao35566716 分钟前
VS Code登录codex,报错(os error 10013)
java·服务器·前端
oMcLin22 分钟前
如何在Oracle Linux 8.4上通过配置Oracle RAC集群,确保企业级数据库的高可用性与负载均衡?
linux·数据库·oracle
信创天地22 分钟前
核心系统去 “O” 攻坚:信创数据库迁移的双轨运行与数据一致性保障方案
java·大数据·数据库·金融·架构·政务
mjhcsp24 分钟前
C++ AC 自动机:原理、实现与应用全解析
java·开发语言·c++·ac 自动机
huihuihuanhuan.xin26 分钟前
后端八股之java并发编程
java·开发语言
茶本无香29 分钟前
设计模式之二—原型模式:灵活的对象克隆机制
java·设计模式·原型模式