深入解析 MyBatis 缓存:一级缓存与二级缓存实战指南

目录

前言

一、为什么需要缓存?

二、一级缓存:会话级的高速便签

1.核心特性

2.工作流程示例:

3.缓存失效场景

三、二级缓存:跨会话的共享数据池

1.核心特性

2.启用与配置

2.1全局开启(mybatis-config.xml):

[2.2Mapper XML 中启用:](#2.2Mapper XML 中启用:)

[2.3.注解方式启用(实体类需实现 Serializable)](#2.3.注解方式启用(实体类需实现 Serializable))

3.工作流程:

[四、一级缓存 vs 二级缓存:核心差异](#四、一级缓存 vs 二级缓存:核心差异)

五、适用场景

总结



前言

作为 Java 开发者最常用的 ORM(Object-Relational Mapping,对象关系映射) 框架之一,MyBatis 的缓存机制是提升数据库访问性能的关键利器。理解其一级缓存和二级缓存的原理与差异,能让你在开发中做出更优化的选择。下面我们深入剖析这两级缓存的核心机制。


**一、**为什么需要缓存?

在数据库访问中,频繁执行相同 SQL 会导致:

  1. 网络开销:重复连接数据库消耗资源

  2. 数据库压力:增加数据库服务器负载

  3. 响应延迟:重复查询降低应用响应速度

MyBatis 通过两级缓存机制,将查询结果暂存于内存中,避免不必要的数据库交互,显著提升性能。

二、一级缓存:会话级的高速便签

1.核心特性

  • 作用域:单个SqlSession范围内(数据库会话期间,从创建SqlSession会话,到提关闭会话)

  • 默认开启:无需配置,自动生效

  • 存储位置:SqlSession对象内部

2.工作流程示例:

java 复制代码
try (SqlSession session = sqlSessionFactory.openSession()) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    
    // 首次查询,访问数据库并将结果存入一级缓存
    User user1 = mapper.selectUserById(1); 
    
    // 再次查询相同数据,直接从一级缓存获取
    User user2 = mapper.selectUserById(1); 
    
    System.out.println(user1 == user2); // 输出 true,同一对象!
}

3.缓存失效场景

(1)执行写操作 :**insert/update/delete**会清空当前会话缓存

java 复制代码
mapper.updateUser(user); // 执行后,一级缓存被清空
User user3 = mapper.selectUserById(1); // 重新查询数据库

(2)手动清空 :调用 session.clearCache()

java 复制代码
try (SqlSession session = sqlSessionFactory.openSession()) {
    UserMapper userMapper = session.getMapper(UserMapper.class);
    
    // 第一次查询,数据存入缓存
    User user1 = userMapper.selectUserById(1);
    System.out.println("第一次查询: " + user1.getName());
    
    // 手动清空一级缓存
    session.clearCache();
    System.out.println("手动清空缓存");
    
    // 第二次查询,缓存已清空,重新查询数据库
    User user2 = userMapper.selectUserById(1);
    System.out.println("清空缓存后查询: " + user2.getName());
}

(3)会话结束SqlSession 关闭后缓存自动销毁

java 复制代码
//第一个会话
SqlSession session1 = sqlSessionFactory.openSession();
UserMapper mapper1 = session1.getMapper(UserMapper.class);
User user1 = mapper1.selectUserById(1); // 从数据库查询
System.out.println("会话1第一次查询: " + user1.getName());

User user2 = mapper1.selectUserById(1); // 从一级缓存获取
System.out.println("会话1第二次查询: " + user2.getName());

// 关闭会话1,其缓存随之销毁
session1.close();

// 第二个新会话
SqlSession session2 = sqlSessionFactory.openSession();
UserMapper mapper2 = session2.getMapper(UserMapper.class);
// 由于是新会话,没有缓存,会从数据库查询
User user3 = mapper2.selectUserById(1);
System.out.println("新会话查询: " + user3.getName());
session2.close();

关键点 :一级缓存的生命周期与数据库会话绑定,适用于同一事务内重复查询的优化

三、二级缓存:跨会话的共享数据池

1.核心特性

  • 作用域 :Mapper 级别(跨 SqlSession)即同一个命名空间下的所有 SQL 操作共享该缓存。

  • 默认关闭:需手动在配置或映射文件中启用

  • 存储位置:可配置(默认内存,支持集成 Redis/Ehcache)

2.启用与配置

2.1全局开启(mybatis-config.xml):

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

2.2Mapper XML 中启用:

java 复制代码
<mapper namespace="com.example.UserMapper">
   <cache 
      eviction="LRU"        <!-- 淘汰策略:最近最少使用 -->
      flushInterval="60000" <!-- 自动刷新时间:1分钟 -->
      size="512"            <!-- 最大缓存对象数:512个 -->
      readOnly="false"      <!-- 缓存是否只读(false表示可读写) -->
    />
</mapper>

2.2.1 eviction:缓存淘汰策略

当缓存空间不足(达到 size 限制)时,MyBatis 会根据该策略移除部分缓存数据,释放空间。常见取值:

  • LRU(Least Recently Used,默认值) :移除最近最少使用 的缓存对象(基于访问时间,很久未被访问的数据优先淘汰)。

    适合:大部分场景,优先保留热点数据。

  • FIFO(First In First Out) :按照缓存对象的存入顺序 淘汰,先存入的先被移除。

    适合:数据访问顺序性较强的场景。

  • SOFT :基于 JVM 的软引用(Soft Reference)淘汰,当 JVM 内存不足时,才会回收软引用对象。

    适合:希望尽可能利用内存缓存,但允许在内存紧张时释放的场景。

  • WEAK :基于 JVM 的弱引用(Weak Reference)淘汰,只要发生垃圾回收,弱引用对象就会被回收。

    适合:缓存数据可以随时被回收,不占用过多内存的场景。

2.2.2 flushInterval:缓存自动刷新时间(毫秒)

设置缓存的过期时间,当缓存存在时间超过该值时,会被自动清空(无论是否被访问)。

  • 单位:毫秒(如 60000 表示 1 分钟)。

  • 默认值:null(即不自动刷新,缓存仅在执行写操作或手动清空时更新)。

  • 适用场景:数据会定期更新,但更新频率可预测的场景(如每小时更新一次的统计数据)。

2.2.3 size:最大缓存对象数量

控制缓存中最多可存储的对象数量(而非字节大小),超过该数量时会触发 eviction 策略淘汰旧数据。

  • 默认值:1024(即最多缓存 1024 个对象)。

  • 需根据实际场景调整:

    • 设太小:可能频繁触发淘汰,缓存命中率低。

    • 设太大:占用内存过多,可能影响系统性能。

2.3.注解方式启用(实体类需实现 Serializable

java 复制代码
@CacheNamespace(eviction = LruCache.class, flushInterval = 60000, size = 512)
public interface UserMapper {
    @Select("SELECT * FROM users WHERE id=#{id}")
    User selectUserById(int id);
}

3.工作流程:

java 复制代码
try (SqlSession session1 = factory.openSession()) {
    UserMapper mapper1 = session1.getMapper(UserMapper.class);
    User user1 = mapper1.selectUserById(1); // 查询数据库,结果存入二级缓存
}

try (SqlSession session2 = factory.openSession()) {
    UserMapper mapper2 = session2.getMapper(UserMapper.class);
    User user2 = mapper2.selectUserById(1); // 从二级缓存直接获取!
}

四、一级缓存 vs 二级缓存:核心差异

五、适用场景

  1. 一级缓存适用:

    • 同一事务中多次获取用户信息

    • 循环内重复查询同一数据

  2. 二级缓存适用:

    • 全局配置项(如系统参数)

    • 热点商品信息展示

    • 低频更新的基础数据(如省份列表)


总结

MyBatis 的两级缓存是提升性能的利器,但需理解其内在机制:

  • 一级缓存:轻量高效,自动管理,注意会话边界

  • 二级缓存:威力强大,需谨慎配置,严防脏数据

正确使用缓存可使应用性能提升数倍!建议在开发中根据数据特性和访问模式灵活选择缓存策略,并通过监控工具(如 VisualVM)观察缓存命中率,持续优化配置。