在MyBatis开发中,缓存机制是提升查询性能的核心手段,也是面试中高频考察的知识点。很多开发者只知道MyBatis有缓存,却分不清一级缓存和二级缓存的作用范围、配置方式及使用场景,面试时容易答不全面。本文将从核心定义、两级缓存的详细解析、代码示例、使用注意事项等方面,清晰拆解MyBatis缓存机制,帮你快速掌握核心考点,规避使用误区。
一、一句话吃透MyBatis缓存核心
MyBatis采用两级缓存机制,一级缓存是SqlSession级别,默认开启,仅同一会话内共享,更新操作会自动清空;二级缓存是Mapper级别,需手动开启,支持多SqlSession共享,依赖序列化实现,可配置淘汰策略,两者均通过装饰器模式实现,也可集成第三方缓存扩展。
二、详细解析:一级缓存与二级缓存的核心差异
1. 一级缓存(Local Cache,本地缓存)
一级缓存是MyBatis默认开启的缓存,无需任何额外配置,其作用范围仅限于当前SqlSession,是最基础、最常用的缓存方式。
核心特性
-
作用范围:SqlSession级别,即同一个数据库会话(同一个SqlSession实例)内有效,不同SqlSession之间的缓存相互独立,无法共享。
-
开启方式:默认开启,无需在配置文件中添加任何配置,创建SqlSession后自动生效。
-
生命周期:与SqlSession绑定,SqlSession关闭、提交事务、回滚事务,或执行增删改操作时,一级缓存会自动清空,避免缓存数据与数据库数据不一致。
-
共享性:仅对当前SqlSession可见,其他SqlSession无法访问当前SqlSession的缓存数据。
工作原理与代码示例
一级缓存的核心逻辑是:同一SqlSession内,多次执行相同的SQL语句(参数也完全一致),第一次会查询数据库,将查询结果存入一级缓存;后续查询会直接从缓存中获取结果,无需再次访问数据库,从而提升查询效率。当执行更新操作(INSERT、UPDATE、DELETE)时,MyBatis会自动清空当前SqlSession的一级缓存,确保缓存数据与数据库数据同步。
// 1. 获取SqlSessionFactory实例(省略配置加载过程)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 开启第一个SqlSession(会话1)
SqlSession sqlSession1 = sqlSessionFactory.openSession();
// 获取Mapper接口实例
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
// 第一次查询:id=1,无缓存,访问数据库,结果存入一级缓存
User user1 = userMapper1.selectUserById(1);
System.out.println("第一次查询结果:" + user1);
// 第二次查询:id=1,与第一次SQL和参数完全一致,命中一级缓存,不访问数据库
User user2 = userMapper1.selectUserById(1);
System.out.println("第二次查询结果(命中一级缓存):" + user2);
// 执行更新操作:修改id=1的用户信息,自动清空当前SqlSession的一级缓存
user1.setAge(26);
userMapper1.updateUser(user1);
// 提交事务,也会触发一级缓存清空
sqlSession1.commit();
// 第三次查询:id=1,缓存已被清空,重新访问数据库
User user3 = userMapper1.selectUserById(1);
System.out.println("第三次查询结果(缓存失效):" + user3);
// 关闭SqlSession,一级缓存随之销毁
sqlSession1.close();
// 3. 开启第二个SqlSession(会话2)
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
// 第四次查询:id=1,属于新的SqlSession,无缓存,访问数据库
User user4 = userMapper2.selectUserById(1);
System.out.println("新会话查询结果:" + user4);
sqlSession2.close();
从示例可以看出,同一个SqlSession内的重复查询会命中缓存,而更新操作、事务提交或会话关闭后,缓存会失效;新的SqlSession无法复用之前会话的缓存,这就是一级缓存的SqlSession级别特性。
缓存失效场景总结
以下几种情况会导致一级缓存失效,需重点注意:
-
执行INSERT、UPDATE、DELETE任意一种更新操作,无论是否提交事务,都会清空当前SqlSession的一级缓存;
-
手动调用sqlSession.clearCache()方法,主动清空一级缓存;
-
提交事务(sqlSession.commit())或回滚事务(sqlSession.rollback());
-
关闭SqlSession(sqlSession.close()),一级缓存随会话销毁。
2. 二级缓存(Second Level Cache,全局缓存)
二级缓存是Mapper级别(也叫命名空间级别)的缓存,默认不开启,需要通过显式配置启用。它的作用范围比一级缓存更广,多个SqlSession可以共享同一个Mapper的缓存数据,进一步提升查询性能。
核心特性
-
作用范围:Mapper级别,即同一个Mapper接口(同一个命名空间)下的所有SqlSession共享缓存,跨SqlSession有效。
-
开启方式:需手动配置,分为全局开启和Mapper级开启两步,缺一不可。
-
生命周期:与应用生命周期一致,只要应用不停止,二级缓存就会一直存在(除非被主动清除或配置了自动刷新)。
-
共享性:多个SqlSession可以共享同一Mapper的缓存数据,无需重复查询数据库。
配置方式(核心步骤)
二级缓存的开启需要两步配置,先全局启用,再针对具体Mapper配置缓存,具体如下:
第一步:全局开启二级缓存
在MyBatis的核心配置文件(mybatis-config.xml)中,通过settings标签开启二级缓存,默认值为true,也可显式配置:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 全局开启二级缓存 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 其他配置(数据源、Mapper扫描等)省略 -->
</configuration>
第二步:Mapper级配置缓存
在需要启用二级缓存的Mapper XML文件中,添加标签,即可为该Mapper开启二级缓存,同时可配置缓存参数:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- Mapper命名空间,二级缓存的作用范围就是该命名空间 -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 开启二级缓存,并配置参数 -->
<cache
eviction="LRU" <!-- 缓存淘汰策略,默认LRU -->
flushInterval="60000" <!-- 自动刷新间隔,单位毫秒,60秒刷新一次 -->
size="1024" <!-- 缓存最大对象数量,默认1024 -->
readOnly="false" <!-- 是否只读,false表示可读写 -->
/>
<!-- 查询语句,需设置useCache="true"(默认true,可省略) -->
<select id="selectUserById" parameterType="Integer" resultType="com.example.entity.User" useCache="true">
SELECT id, username, age, email FROM t_user WHERE id = #{id}
</select><!-- 更新语句,需设置flushCache="true"(默认true,可省略),执行后清空当前Mapper的二级缓存 -->
<update id="updateUser" parameterType="com.example.entity.User" flushCache="true">
UPDATE t_user SET username = #{username}, age = #{age} WHERE id = #{id}
</update>
</mapper>
缓存参数说明(核心):
-
eviction:缓存淘汰策略,当缓存达到最大容量时,删除过期或不常用的缓存数据,可选值:
-
LRU(默认):最近最少使用,删除长时间未被访问的缓存;
-
FIFO:先进先出,按缓存存入顺序删除最早的缓存;
-
SOFT:软引用,当内存不足时,删除软引用关联的缓存;
-
WEAK:弱引用,一旦垃圾回收机制触发,立即删除弱引用关联的缓存。
-
flushInterval:自动刷新缓存的时间间隔(毫秒),超过该时间,缓存会自动清空,默认无间隔(不自动刷新)。
-
size:缓存可存储的最大对象数量,默认1024,超过后按淘汰策略删除缓存。
工作原理与代码示例
二级缓存的工作逻辑是:当多个SqlSession访问同一个Mapper的相同SQL(参数一致)时,第一个SqlSession查询数据库后,会将结果存入该Mapper的二级缓存;后续其他SqlSession查询相同SQL时,会先检查二级缓存,若存在数据则直接返回,无需访问数据库。当执行该Mapper的更新操作时,会自动清空该Mapper的二级缓存,确保数据一致性。
// 1. 获取SqlSessionFactory实例(省略配置加载)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 开启第一个SqlSession(会话1)
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
// 第一次查询:id=2,无二级缓存,访问数据库,结果存入二级缓存和当前会话的一级缓存
User user1 = userMapper1.selectUserById(2);
System.out.println("会话1第一次查询结果:" + user1);
// 关闭会话1,一级缓存销毁,但二级缓存数据保留
sqlSession1.close();
// 3. 开启第二个SqlSession(会话2)
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
// 第二次查询:id=2,一级缓存(会话2)无数据,检查二级缓存,命中缓存,不访问数据库
User user2 = userMapper2.selectUserById(2);
System.out.println("会话2查询结果(命中二级缓存):" + user2);
// 执行更新操作:修改id=2的用户信息,清空当前Mapper的二级缓存
user2.setEmail("test@163.com");
userMapper2.updateUser(user2);
sqlSession2.commit();
// 第三次查询:id=2,二级缓存已被清空,访问数据库
User user3 = userMapper2.selectUserById(2);
System.out.println("会话2更新后查询结果(缓存失效):" + user3);
sqlSession2.close();
// 4. 开启第三个SqlSession(会话3)
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
// 第四次查询:id=2,二级缓存无数据,访问数据库
User user4 = userMapper3.selectUserById(2);
System.out.println("会话3查询结果:" + user4);
sqlSession3.close();
注意:二级缓存默认采用序列化方式存储缓存数据,因此缓存的实体类(如User)必须实现Serializable接口,否则会抛出序列化异常。
// 实体类实现Serializable接口,支持二级缓存序列化存储
public class User implements Serializable {
private Integer id;
private String username;
private Integer age;
private String email;
// getter、setter、toString方法省略
}
3. 两级缓存的执行优先级与流程
当MyBatis同时启用一级缓存和二级缓存时,查询数据会遵循固定的优先级顺序,确保缓存的高效利用,具体流程如下:
-
优先查询当前SqlSession的一级缓存,若命中,直接返回结果;
-
若一级缓存未命中,查询当前Mapper的二级缓存,若命中,返回结果,并将结果存入当前SqlSession的一级缓存;
-
若两级缓存均未命中,执行SQL查询数据库,将查询结果依次存入二级缓存和一级缓存,再返回结果。
三、两级缓存核心区别总结表
| 特性 | 一级缓存 | 二级缓存 |
|---|---|---|
| 作用范围 | SqlSession级别(单一会话) | Mapper级别(跨会话共享) |
| 开启方式 | 默认开启,无需配置 | 需全局+Mapper级双重配置 |
| 生命周期 | 与SqlSession绑定,会话关闭即销毁 | 与应用生命周期一致 |
| 数据存储 | 直接存储对象,无需序列化 | 需序列化存储,实体类需实现Serializable |
| 缓存失效触发 | 更新操作、会话关闭、事务提交/回滚 | 当前Mapper的更新操作、自动刷新、手动清除 |
四、缓存使用注意事项
-
一级缓存无需手动配置,但需注意:同一SqlSession内的更新操作会清空缓存,避免缓存脏读;关闭会话后,缓存数据会丢失,无法跨会话复用。
-
二级缓存启用后,实体类必须实现Serializable接口,否则会抛出序列化异常;若不需要某条查询语句使用二级缓存,可设置useCache="false"。
-
二级缓存适合读多写少、数据实时性要求不高的场景(如配置表、静态数据);若数据实时性要求高(如用户信息表),不建议开启二级缓存,避免出现脏读。
-
执行更新操作(增删改)时,MyBatis会自动清空对应Mapper的二级缓存,无需手动处理;若需手动清空二级缓存,可调用sqlSession.clearCache()方法。
-
分布式环境中,默认的二级缓存是单机缓存,无法实现多节点共享,需集成Redis、Ehcache等第三方分布式缓存框架,替换默认缓存实现。
以上就是MyBatis缓存机制的全部核心内容,重点掌握一级缓存和二级缓存的作用范围、配置方式及使用场景,面试时只需抓住“SqlSession级别vsMapper级别”“默认开启vs手动配置”“无需序列化vs需序列化”这几个关键点,就能轻松应对相关问题,同时在实际开发中也能合理利用缓存提升系统性能。