MyBatis 的缓存机制
MyBatis 的缓存机制分为一级缓存和二级缓存,旨在减少数据库查询次数,提高性能。以下是详细的机制说明:
一、一级缓存(Local Cache)
-
作用范围
- SqlSession 级别:默认开启,仅在同一个
SqlSession生命周期内有效。 - 在同一个会话中,执行相同的 SQL 和参数时,直接从缓存返回结果,避免重复查询数据库。
- SqlSession 级别:默认开启,仅在同一个
-
缓存失效条件
- 执行了
INSERT/UPDATE/DELETE操作(会清空当前缓存)。 - 手动调用
sqlSession.clearCache()。 - 提交事务(
commit())或关闭SqlSession(close())。 - 配置了
<select flushCache="true">的查询会强制清空缓存。
- 执行了
-
实现原理
- 底层使用
PerpetualCache,内部通过HashMap存储缓存数据。 - 生命周期与
SqlSession绑定,无法跨会话共享。
- 底层使用
-
注意事项
- 分布式环境下不适用:一级缓存是单机、单会话的,无法解决跨节点数据一致性问题。
- 事务隔离问题:如果两个操作在同一个事务中,可能读到未提交的脏数据(需结合数据库隔离级别分析)。
二、二级缓存(Second Level Cache)
-
作用范围
- Mapper 级别:跨
SqlSession共享,属于namespace作用域。 - 需要手动开启(配置
<cache>标签),多个会话访问同一 Mapper 时可共享缓存。
- Mapper 级别:跨
-
配置方式
- 全局开关:在
mybatis-config.xml中设置<setting name="cacheEnabled" value="true"/>(默认开启)。 - Mapper 配置:在对应的 Mapper XML 文件中添加
<cache/>标签,支持以下属性:<cache eviction="LRU" <!-- 缓存淘汰策略:LRU/FIFO/SOFT/WEAK --> flushInterval="60000" <!-- 自动刷新间隔(毫秒) --> size="1024" <!-- 最大缓存对象数量 --> readOnly="true" <!-- 是否只读(建议为false以支持序列化修改) --> /> - 实体类要求:若需缓存对象,对应的 Java 类必须实现
Serializable接口。
- 全局开关:在
-
缓存失效条件
- 执行了同 Mapper 的
INSERT/UPDATE/DELETE操作(清空整个二级缓存)。 - 手动调用
sqlSession.commit()或sqlSession.close()(缓存会提交到二级缓存)。
- 执行了同 Mapper 的
-
工作机制
- 查询顺序:二级缓存 → 一级缓存 → 数据库。
- 二级缓存数据在事务提交(
commit())后才会写入,未提交的事务修改对其他会话不可见。 - 默认使用
TransactionalCacheManager管理缓存的提交和回滚。
-
注意事项
- 脏读风险:若多个 Mapper 操作同一张表,需通过
<cache-ref>共享缓存空间,否则可能出现数据不一致。 - 性能权衡:高并发写场景下频繁清空缓存可能降低性能。
- 分布式缓存:默认实现不适用于分布式系统,需整合 Redis、Ehcache 等第三方缓存(如通过
org.mybatis.caches扩展)。
- 脏读风险:若多个 Mapper 操作同一张表,需通过
三、缓存控制
-
禁用缓存
- 在
<select>标签中设置useCache="false",避免特定查询使用二级缓存。 - 在
<insert>/<update>/<delete>中设置flushCache="true"(默认 true),清空缓存。
- 在
-
强制刷新
- 调用
sqlSession.clearCache()仅清空一级缓存。 - 通过
<select flushCache="true">在查询前清空一、二级缓存。
- 调用
四、第三方缓存整合
MyBatis 支持通过实现 org.apache.ibatis.cache.Cache 接口扩展缓存:
-
常用实现
- Ehcache:适合单机高并发,支持内存+磁盘缓存。
- Redis:分布式缓存,解决多服务节点数据共享问题。
-
配置示例(以 Redis 为例):
<cache type="org.mybatis.caches.redis.RedisCache" />
五、总结
| 特性 | 一级缓存 | 二级缓存 |
|---|---|---|
| 作用范围 | SqlSession 内 | Mapper 跨 SqlSession |
| 默认开启 | 是 | 否(需配置 <cache/>) |
| 存储结构 | HashMap | 可扩展(如 Redis、Ehcache) |
| 失效机制 | 自动随 DML 操作失效 | 事务提交后生效 |
| 适用场景 | 单会话重复查询 | 跨会话共享数据 |
最佳实践:
- 对读多写少的数据使用二级缓存,注意配置合理的淘汰策略和刷新间隔。
- 在分布式环境中,优先使用 Redis 等分布式缓存替代默认实现。
MyBatis 缓存机制详解
MyBatis 作为 Java 持久层框架,通过两级缓存机制显著提升数据库查询性能。其核心设计围绕一级缓存(SqlSession 级)和二级缓存(Mapper 级)展开,以下是详细解析:
一、一级缓存(本地缓存)
作用范围:单个 SqlSession 实例内
生命周期:与 SqlSession 绑定,会话关闭时自动清空
特点:
- 默认开启,无需配置
- 自动缓存:相同 SQL 且参数一致时,直接返回缓存结果
- 失效场景:
- 执行 INSERT/UPDATE/DELETE 操作后提交(
session.commit()) - 手动调用
session.clearCache() - SqlSession 关闭
- 执行 INSERT/UPDATE/DELETE 操作后提交(
示例验证:
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
User user1 = mapper.getUserById(1); // 查询数据库并存入缓存
User user2 = mapper.getUserById(1); // 直接读取缓存,不执行SQL
System.out.println(user1 == user2); // 输出 true
}
二、二级缓存(全局缓存)
作用范围:Mapper 命名空间级别,跨 SqlSession 共享
生命周期:与应用进程共存,需手动配置
配置步骤:
- 全局启用(
mybatis-config.xml):<configuration> <settings> <setting name="cacheEnabled" value="true"/> </settings> </configuration> - Mapper 配置(XML 或注解):
<mapper namespace="com.example.mapper.UserMapper"> <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/> <!-- 或通过注解(MyBatis 3.2+) --> @CacheNamespace public interface UserMapper { ... }
失效场景:
- 任意 SqlSession 执行该 Mapper 的 INSERT/UPDATE/DELETE 操作
- 手动清空缓存(
session.clearCache()或mapper.clearCache())
序列化要求:缓存对象需实现 Serializable 接口。
三、缓存配置策略
- 回收策略(
eviction):- LRU(默认):移除最久未使用的对象
- FIFO:按插入顺序移除
- SOFT/WEAK:基于垃圾回收器状态
- 刷新间隔(
flushInterval):毫秒为单位,默认不刷新 - 容量(
size):缓存对象数量上限,默认 1024 - 只读(
readOnly):true:返回对象引用(高性能,但需保证对象不可变)false(默认):返回对象拷贝(安全但较慢)
四、集成第三方缓存(如 Redis)
步骤:
- 添加依赖(以 Redis 为例):
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-redis</artifactId> <version>1.0.0</version> </dependency> - 配置 Mapper 使用自定义缓存:
<cache type="org.mybatis.caches.redis.RedisCache"/>
五、最佳实践
- 监控命中率:通过日志或监控工具分析缓存效率
- 缓存穿透处理:对空结果设置短过期时间(如 30 秒)
- 雪崩防护:为不同缓存设置随机过期时间
- 一致性保障:
- 写操作后手动清空关联缓存
- 分布式环境使用 Redis 等中央缓存
- 高频查询优化:对复杂报表查询启用二级缓存
六、注意事项
- 脏读风险:二级缓存需结合事务隔离级别使用
- 内存溢出:高并发场景需限制缓存容量
- 调试技巧:启用日志(
log4j.logger.org.apache.ibatis=DEBUG)观察缓存行为
通过合理配置缓存策略,可显著提升应用性能。例如,在读写分离的架构中,二级缓存可减少 30%-50% 的数据库压力。建议根据实际业务场景测试不同配置组合的效果。
MyBatis 提供了两种类型的缓存来提高查询性能:一级缓存和二级缓存。
1. 一级缓存
- 默认开启:一级缓存是SqlSession级别的缓存,默认情况下是自动开启的,无需额外配置。
- 作用范围:它存在于SqlSession的生命周期中。当在同一个SqlSession中执行两次相同的SQL查询时,第二次查询会直接从缓存中获取数据,不会发起数据库请求。
- 清空机制:如果在同一个SqlSession中对数据库进行了增、删、改等操作,MyBatis会自动清空该SqlSession下的一级缓存,以保证数据的一致性。
2. 二级缓存
- 手动开启:二级缓存是Mapper级别的缓存,需要手动开启。可以通过在MyBatis配置文件中设置
<cache/>标签或者在具体的Mapper XML文件中添加该标签来启用。 - 作用范围:它的作用范围比一级缓存更广,可以被多个SqlSession共享。这意味着不同的SqlSession对象如果访问的是同一个Mapper下的SQL语句,它们可以从二级缓存中读取数据,而不需要每次都去查询数据库。
- 存储方式:二级缓存可以配置不同的存储方式,比如PERPETUAL(永久)、FIFO(先进先出)、LRU(最近最少使用)等策略。
- 注意事项:使用二级缓存时需要注意对象的序列化问题,因为缓存的数据可能会被持久化到磁盘或者其他存储介质上。因此,如果实体类没有实现Serializable接口,则无法使用二级缓存。
此外,MyBatis还允许通过自定义缓存实现来满足特定的需求,例如整合第三方缓存解决方案(如EhCache、Redis等)。这通常涉及到创建一个实现了org.apache.ibatis.cache.Cache接口的类,并将其配置到MyBatis中。
合理利用这两种缓存机制可以帮助减少数据库的压力,提升系统的响应速度。但是,使用缓存的同时也要注意数据一致性的问题,特别是在并发环境下。
MyBatis 有两级缓存机制,下面为你详细介绍:
一级缓存
一级缓存是 SqlSession 级别的缓存,在同一个 SqlSession 中,执行相同的 SQL 查询时,MyBatis 会先从缓存中查找,如果找到则直接返回,不再执行数据库查询。当 SqlSession 关闭或提交事务时,一级缓存会被清空。
原理
在同一个 SqlSession 中,MyBatis 会为每个查询创建一个 CacheKey 对象,该对象包含了 SQL 语句、参数等信息。当执行查询时,会先根据 CacheKey 从缓存中查找,如果找到则返回缓存中的结果,否则执行数据库查询,并将结果存入缓存。
示例代码
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
public class FirstLevelCacheExample {
public static void main(String[] args) {
// 加载 MyBatis 配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = FirstLevelCacheExample.class.getClassLoader().getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建 SqlSession
try (SqlSession session = sqlSessionFactory.openSession()) {
// 第一次查询
Object result1 = session.selectOne("com.example.mapper.YourMapper.selectById", 1);
System.out.println("第一次查询结果: " + result1);
// 第二次查询,使用相同的 SqlSession
Object result2 = session.selectOne("com.example.mapper.YourMapper.selectById", 1);
System.out.println("第二次查询结果: " + result2);
}
}
}
在上述示例中,第一次和第二次查询使用的是同一个 SqlSession,因此第二次查询会直接从缓存中获取结果,而不会再次执行数据库查询。
二级缓存
二级缓存是 Mapper 级别的缓存,多个 SqlSession 可以共享同一个 Mapper 的二级缓存。二级缓存默认是关闭的,需要在 MyBatis 配置文件中进行配置才能开启。
原理
当开启二级缓存后,MyBatis 会为每个 Mapper 创建一个 Cache 对象,用于存储查询结果。当执行查询时,会先从二级缓存中查找,如果找到则返回缓存中的结果,否则执行数据库查询,并将结果存入二级缓存。
配置步骤
- 在 MyBatis 配置文件中开启二级缓存:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
- 在 Mapper XML 文件中配置二级缓存:
<mapper namespace="com.example.mapper.YourMapper">
<cache/>
<!-- 其他 SQL 语句 -->
</mapper>
或者在 Mapper 接口上使用 @CacheNamespace 注解:
import org.apache.ibatis.annotations.CacheNamespace;
@CacheNamespace
public interface YourMapper {
// 方法定义
}
示例代码
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
public class SecondLevelCacheExample {
public static void main(String[] args) {
// 加载 MyBatis 配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = SecondLevelCacheExample.class.getClassLoader().getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 第一个 SqlSession
try (SqlSession session1 = sqlSessionFactory.openSession()) {
Object result1 = session1.selectOne("com.example.mapper.YourMapper.selectById", 1);
System.out.println("第一个 SqlSession 查询结果: " + result1);
}
// 第二个 SqlSession
try (SqlSession session2 = sqlSessionFactory.openSession()) {
Object result2 = session2.selectOne("com.example.mapper.YourMapper.selectById", 1);
System.out.println("第二个 SqlSession 查询结果: " + result2);
}
}
}
在上述示例中,第一个和第二个 SqlSession 分别执行相同的查询,由于开启了二级缓存,第二个 SqlSession 会直接从二级缓存中获取结果,而不会再次执行数据库查询。
缓存的清空和刷新
当执行插入、更新、删除操作时,MyBatis 会自动清空一级缓存和二级缓存,以保证数据的一致性。例如:
try (SqlSession session = sqlSessionFactory.openSession()) {
// 执行插入操作
session.insert("com.example.mapper.YourMapper.insertRecord", record);
session.commit();
// 插入操作后,缓存会被清空
}
注意事项
- 一级缓存是默认开启的,且不能关闭。
- 二级缓存需要手动开启和配置。
- 缓存可能会导致数据不一致的问题,因此在使用时需要谨慎考虑。例如,当多个应用实例共享同一个数据库时,一个实例更新了数据,但其他实例的缓存中仍然是旧数据,这时就需要考虑使用缓存刷新机制或分布式缓存。
MyBatis 提供了一级缓存和二级缓存两种缓存机制,用于减少数据库的访问次数,提高查询性能。
一级缓存
- 默认开启:一级缓存是 SqlSession 级别的缓存,当使用同一个 SqlSession 进行查询时,会先从一级缓存中查找数据,如果存在则直接返回缓存数据,否则才会查询数据库。
- 生命周期:一级缓存的生命周期与 SqlSession 的生命周期一致,当 SqlSession 关闭或提交时,一级缓存会被清空。
- 限制:一级缓存只能在同一个 SqlSession 中使用,不同的 SqlSession 之间无法共享一级缓存。
二级缓存
- 手动配置:二级缓存是 Mapper 级别的缓存,多个 SqlSession 可以共享二级缓存。要使用二级缓存,需要在映射文件中进行配置,并且在核心配置文件中开启缓存。
- 配置步骤:
- 在核心配置文件(如 mybatis-config.xml)中添加
<setting name="cacheEnabled" value="true"/>来启用缓存。 - 在映射文件(如 UserMapper.xml)中添加
<cache/>标签来启用该 Mapper 的二级缓存。
- 在核心配置文件(如 mybatis-config.xml)中添加
- 工作原理:当查询数据时,MyBatis 会先从二级缓存中查找,如果存在则直接返回;如果不存在,再从一级缓存中查找;如果一级缓存也没有,则查询数据库,并将查询结果同时存入一级缓存和二级缓存。
- 限制:二级缓存中的数据需要是可序列化的,即返回的实体类需要实现
Serializable接口。
注意事项
- 缓存的粒度:合理设置缓存的粒度,避免缓存过多数据导致内存溢出。
- 缓存的刷新:根据业务需求,合理设置缓存的刷新策略,如定时刷新、基于数据更新的刷新等。
- 数据一致性:在高并发场景下,缓存可能会导致数据不一致的问题,需要根据具体业务场景采取相应的解决措施,如使用分布式缓存、缓存与数据库的双写机制等。
以下是一个简单的二级缓存配置示例:
<!-- mybatis-config.xml -->
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<cache/>
<!-- 查询用户信息的 SQL 语句 -->
<select id="getUserById" parameterType="int" resultType="com.example.entity.User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
在 Java 类中,可以通过以下代码来使用缓存:
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询,会从数据库中获取数据,并存入缓存
User user1 = userMapper.getUserById(1);
// 第二次查询,会从缓存中获取数据
User user2 = userMapper.getUserById(1);
sqlSession.close();
通过合理使用 MyBatis 的缓存机制,可以有效减少数据库的访问次数,提高系统的性能和响应速度。
一、一级缓存(本地缓存)
-
作用域
默认开启,属于SqlSession级别。同一个会话中执行相同 SQL 和参数时,直接返回缓存结果。
示例:SqlSession session = sqlSessionFactory.openSession(); User user1 = session.selectOne("getUserById", 1); // 查数据库 User user2 = session.selectOne("getUserById", 1); // 从一级缓存获取 -
失效条件
- 执行
INSERT/UPDATE/DELETE操作(任何数据变更)。 - 调用
session.clearCache()手动清空。 - 关闭
SqlSession后缓存销毁。
- 执行
-
注意事项
一级缓存可能导致脏读(例如其他会话修改数据后,当前缓存未更新)。
二、二级缓存(全局缓存)
-
作用域
属于Mapper级别,多个SqlSession共享。需手动开启:- 配置步骤:
- 在
mybatis-config.xml中启用:<setting name="cacheEnabled" value="true"/> - 在
Mapper.xml中添加标签:<cache/>
- 在
- 配置步骤:
-
特性
- 数据在
SqlSession提交或关闭后存入缓存。 - 缓存对象需实现
Serializable接口。 - 支持自定义策略(如 LRU、FIFO)和过期时间:
<cache eviction="LRU" flushInterval="60000" size="1024"/>
- 数据在
-
失效条件
- 执行同 Mapper 的
INSERT/UPDATE/DELETE操作。 - 配置
flushCache="true"强制刷新。
- 执行同 Mapper 的
三、缓存优先级与对比
| 对比项 | 一级缓存 | 二级缓存 |
|---|---|---|
| 作用域 | SqlSession 级别 | Mapper 级别 |
| 默认开启 | 是 | 需手动配置 |
| 数据共享 | 仅当前会话 | 跨会话共享 |
| 存储位置 | 内存(JVM 堆) | 可扩展至 Redis 等第三方 |
| 适用场景 | 高频重复查询(短周期) | 低频变动数据(长周期) |
四、使用建议
- 优先依赖一级缓存,避免过度设计。
- 二级缓存适合读多写少场景,注意处理序列化和并发问题。
- 复杂场景可集成 Redis、Ehcache 等第三方缓存(通过
Cache接口扩展)。
五、常见问题
- 脏数据问题:二级缓存跨会话共享,需通过
<cache-ref/>管理关联 Mapper 的缓存一致性。 - 性能监控:可通过日志或监控工具观察缓存命中率,调整策略。
通过合理使用缓存机制,可显著优化 MyBatis 的数据库访问性能。
MyBatis 的缓存机制分为 一级缓存 和 二级缓存,两者结合使用可显著提升查询效率。以下是详细讲解:
一、一级缓存(Local Cache)
1. 作用范围
• 生命周期:绑定到单个 SqlSession 对象,随 SqlSession 的创建而诞生,销毁而消失。
• 默认开启:无需额外配置,自动生效。
2. 工作原理
• 查询流程:
- 执行查询时,首先检查一级缓存。
- 若命中,直接返回结果;若未命中,查询数据库并将结果存入一级缓存。 • 缓存键:基于 SQL 语句、参数及结果集生成唯一键。
3. 缓存失效场景
• 手动触发:
• 执行 update、delete、insert 操作(默认行为)。
• 调用 sqlSession.clearCache()。
• 自动触发:
• SqlSession 关闭时自动清空。
4. 配置与优化
• 全局配置:在 mybatis-config.xml 中可自定义缓存策略(如 LRU、FIFO):
<configuration>
<settings>
<setting name="localCacheScope" value="STATEMENT"/>
<setting name="localCacheSize" value="2048"/>
</settings>
</configuration>
• 作用域调整:localCacheScope 可设为 STATEMENT(默认)、SESSION 或 JDBC。
二、二级缓存(Global Cache)
1. 作用范围
• 生命周期:应用级别,所有 SqlSession 共享。
• 需手动启用:通过 Mapper XML 或注解配置。
2. 工作原理
• 查询流程:
- 检查当前
SqlSession的一级缓存。 - 若未命中,检查二级缓存。
- 若二级缓存命中,返回结果并填充一级缓存。
- 若均未命中,查询数据库,结果存入一级和二级缓存。 • 缓存键:与一级缓存一致,确保跨会话复用。
3. 配置步骤
• Mapper XML:在 Mapper 标签内添加 <cache/> 启用:
<mapper namespace="com.example.UserMapper">
<cache/>
<!-- SQL 映射 -->
</mapper>
• 全局配置:可自定义缓存实现(如集成 EhCache、Redis):
<configuration>
<plugins>
<plugin interceptor="org.mybatis.caches.ehcache.EhCachePlugin">
<property name="configLocation" value="classpath:ehcache.xml"/>
</plugin>
</plugins>
</configuration>
4. 缓存策略与序列化
• 序列化机制:MyBatis 默认使用 JdkSerialization,可将对象序列化为字节流存储。支持自定义序列化器(如 Jackson)。
• 淘汰策略:支持 LRU、FIFO、LRU_JDBC 等,可通过配置或自定义 Cache 接口实现。
5. 数据一致性保障
• 自动刷新:执行更新操作时,MyBatis 会自动清除受影响实体的二级缓存。
• 手动刷新:通过 sqlSession.clearCache() 或 <flushCache> 标签强制刷新:
<update id="updateUser" parameterType="User" flushCache="true">
UPDATE users SET name=#{name} WHERE id=#{id}
</update>
三、缓存使用注意事项
-
适用场景: • 一级缓存:适用于单次会话内的重复查询(如页面内多次加载同一数据)。 • 二级缓存:适用于跨会话、高频读取且低频更新的静态数据(如配置表)。
-
性能权衡: • 内存消耗:二级缓存占用更多内存,需合理设置大小。 • 一致性风险:长时间未刷新的二级缓存可能导致数据脏读,需结合业务场景设置合适的刷新策略。
-
并发处理: • MyBatis 默认使用非线程安全的
PerpetualCache,多线程环境下需配置线程安全缓存(如 EhCache)。 -
第三方缓存集成: • 支持 EhCache、Redis 等,需引入依赖并配置插件,实现分布式缓存能力。
四、示例代码
1. 启用二级缓存
<!-- UserMapper.xml -->
<mapper namespace="com.example.UserMapper">
<cache/>
<select id="findByUserId" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
2. 自定义缓存实现
public class CustomCache implements Cache {
@Override
public void putObject(Object key, Object value) {
// 自定义存储逻辑(如存入 Redis)
}
@Override
public Object getObject(Object key) {
// 自定义读取逻辑
return null;
}
// 其他方法实现...
}
3. 强制刷新缓存
// Java 代码
sqlSession.update("com.example.UserMapper.updateUser", user);
sqlSession.clearCache(); // 清除当前会话及全局缓存
五、总结
• 一级缓存:轻量级,生命周期短,适合会话内重复查询。 • 二级缓存:跨会话共享,需权衡一致性与性能,适合静态数据。 • 最佳实践: • 高频读写场景优先使用一级缓存。 • 静态数据配置二级缓存,结合定时任务或事件监听刷新。 • 分布式系统推荐集成 Redis/EhCache 实现二级缓存。
通过合理配置缓存,可大幅减少数据库访问次数,提升应用性能,但需警惕数据一致性问题。
Mybatis 开启关闭一级缓存
MyBatis 的一级缓存默认开启且无法全局关闭,但可以通过特定方式绕过或限制其效果。以下是控制一级缓存的方法和注意事项:
一、一级缓存默认行为
-
自动开启
在SqlSession生命周期内,所有查询(SELECT)默认使用一级缓存,重复的相同查询会直接返回缓存结果,不会访问数据库。 -
验证缓存存在
try (SqlSession sqlSession = sqlSessionFactory.openSession()) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 第一次查询(访问数据库) User user1 = mapper.selectById(1); // 第二次查询(直接从一级缓存获取) User user2 = mapper.selectById(1); System.out.println(user1 == user2); // 输出 true(同一对象) }
二、绕过或“关闭”一级缓存的方法
方法 1:修改 SQL 或参数
每次查询使用不同的 SQL 或参数,使 MyBatis 无法命中缓存:
// 添加无意义的参数(如时间戳)
User user1 = mapper.selectById(1, System.currentTimeMillis());
User user2 = mapper.selectById(1, System.currentTimeMillis()); // 重新查询
方法 2:强制清空一级缓存
手动调用 clearCache() 清空当前 SqlSession 的缓存:
User user1 = mapper.selectById(1);
sqlSession.clearCache(); // 清空缓存
User user2 = mapper.selectById(1); // 重新查询
方法 3:配置 flushCache="true"
在 <select> 标签中设置 flushCache="true",使该查询每次执行前清空一级缓存:
<select id="selectById" resultType="User" flushCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
方法 4:执行写操作(DML)
任何 INSERT/UPDATE/DELETE 操作都会自动清空当前 SqlSession 的一级缓存:
User user1 = mapper.selectById(1);
mapper.updateName(1, "NewName"); // 执行更新操作,清空缓存
User user2 = mapper.selectById(1); // 重新查询
方法 5:使用不同的 SqlSession
每次查询创建新的 SqlSession(不推荐,影响性能):
try (SqlSession session1 = factory.openSession()) {
User user1 = session1.getMapper(UserMapper.class).selectById(1);
}
try (SqlSession session2 = factory.openSession()) {
User user2 = session2.getMapper(UserMapper.class).selectById(1); // 重新查询
}
三、注意事项
-
无法全局关闭
MyBatis 未提供全局禁用一级缓存的配置,只能通过上述方法绕过。 -
事务提交影响
- 调用
sqlSession.commit()会清空缓存(与flushCache行为相关)。 - 未提交的事务中,缓存可能持有未持久化的数据(需结合数据库隔离级别)。
- 调用
-
性能权衡
频繁清空缓存会增加数据库压力,需根据场景(如读多写少 vs 实时性要求高)选择策略。
四、最佳实践
- 需要实时数据:在关键查询中配置
flushCache="true"或手动调用clearCache()。 - 批量读取:利用一级缓存减少重复查询,但确保数据一致性。
- 结合二级缓存:若需跨会话共享缓存数据,启用二级缓存(注意脏读问题)。
通过合理控制 SqlSession 生命周期和查询配置,可间接实现一级缓存的管理。
MyBatis 一级缓存的开启与关闭机制解析
MyBatis 的一级缓存是默认自动开启的 SqlSession 级缓存,其生命周期与 SqlSession 紧密相关。以下是详细操作指南:
一、一级缓存的默认行为
-
自动开启
无需任何配置,创建 SqlSession 时即自动启用一级缓存。 -
智能缓存
当执行相同 SQL 且参数一致时,直接返回缓存结果,避免重复查询数据库。 -
自动失效场景
- 执行 INSERT/UPDATE/DELETE 操作后提交事务(
sqlSession.commit()) - 手动调用
sqlSession.clearCache()方法 - SqlSession 关闭(
sqlSession.close())
- 执行 INSERT/UPDATE/DELETE 操作后提交事务(
二、手动控制缓存
1. 强制刷新缓存
// 获取 SqlSession
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
// 第一次查询(缓存未命中)
User user1 = mapper.getUserById(1);
// 执行数据修改操作
mapper.updateUser(user1);
session.commit(); // 提交事务后,一级缓存自动清空
// 强制刷新缓存(可选)
session.clearCache();
// 第二次查询(缓存重新加载)
User user2 = mapper.getUserById(1);
}
2. 禁用一级缓存(不推荐)
若需全局关闭一级缓存,需在创建 SqlSession 时指定配置:
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream, "mybatis-config.xml");
// 获取 SqlSession 时禁用一级缓存
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.SIMPLE)) {
// 此会话将不启用一级缓存
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
}
三、最佳实践建议
-
写操作后主动刷新
执行 INSERT/UPDATE/DELETE 后,建议调用clearCache()确保后续查询获取最新数据。 -
高频读场景保留缓存
对于只读或低频更新的数据,可充分利用一级缓存减少数据库压力。 -
复杂事务管理
在包含多个写操作的事务中,通过clearCache()控制缓存状态,避免脏读。
四、调试与监控
-
日志观察
启用 MyBatis DEBUG 日志,可看到缓存命中/未命中的详细过程:# log4j.properties 配置示例 log4j.logger.org.apache.ibatis=DEBUG -
性能分析
使用 APM 工具(如 SkyWalking)监控缓存命中率,优化查询效率。
通过合理使用一级缓存,可在保证数据一致性的前提下,显著提升数据库访问性能。建议根据实际业务场景选择是否手动控制缓存行为。
在 MyBatis 中,一级缓存默认是开启的,无需额外配置即可使用。但根据实际需求,也可以通过以下方式手动关闭或调整其行为。以下是具体的开启和关闭方法及注意事项:
一、一级缓存的默认行为
- 作用范围:
一级缓存是 SqlSession 级别的缓存,即在同一个SqlSession内的多次查询会共享该缓存。 - 默认开启:
MyBatis 默认开启一级缓存,无需任何配置即可生效。
二、关闭一级缓存的方法
1. 全局关闭(修改配置文件)
在 MyBatis 的全局配置文件(如 mybatis-config.xml)中设置 localCacheScope 为 STATEMENT:
<settings>
<!-- 默认值为 SESSION,改为 STATEMENT 后一级缓存失效 -->
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
- 作用:
将一级缓存的作用域从 会话级(SESSION) 改为 语句级(STATEMENT),即每次执行完 SQL 后立即清空缓存,相当于关闭了一级缓存。
2. 局部关闭(在单个 SQL 中配置)
在 Mapper XML 的 <select> 标签中设置 flushCache="true":
<select id="selectUserById" resultType="User" flushCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
- 作用:
执行该 SQL 后,会强制清空当前SqlSession的一级缓存。通常用于需要避免缓存的场景(如频繁更新的数据)。
3. 在代码中手动清空缓存
通过 SqlSession 的 clearCache() 方法手动清空当前会话的一级缓存:
sqlSession.clearCache();
三、验证一级缓存是否生效
场景示例:
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.selectUserById(1); // 第一次查询,数据库有记录
User user2 = mapper.selectUserById(1); // 第二次查询,命中缓存
System.out.println(user1 == user2); // 输出 true(同一对象)
} finally {
sqlSession.close();
}
- 若输出
true:说明一级缓存生效。 - 若输出
false:说明一级缓存未生效(可能被关闭或配置错误)。
四、注意事项
1. Spring 集成时的一级缓存问题
在 Spring 中,SqlSession 默认由 SqlSessionTemplate 或 SqlSessionFactory 管理,可能导致一级缓存失效:
- 原因:Spring 每次调用 Mapper 方法时,可能会生成新的
SqlSession,导致无法共享缓存。 - 解决方案:
确保在同一个业务方法内使用同一个SqlSession(通过@Transactional确保事务边界内的SqlSession共享)。
2. 缓存失效的其他场景
即使未显式关闭一级缓存,以下操作也会清空当前 SqlSession 的一级缓存:
- 执行
insert、update、delete操作。 - 手动调用
sqlSession.commit()或sqlSession.rollback()。 - 执行带有
flushCache="true"的 SQL。
3. 脏读问题
- 问题:同一
SqlSession内,如果其他线程修改了数据库数据,当前会话的一级缓存可能返回旧数据(脏读)。 - 解决:
在需要实时数据的场景中,可手动清空缓存或调整localCacheScope为STATEMENT。
五、总结
| 操作 | 方法 |
|---|---|
| 开启一级缓存 | 默认开启,无需配置。 |
| 全局关闭 | 设置 <setting name="localCacheScope" value="STATEMENT"/>。 |
| 局部关闭 | 在 <select> 标签中添加 flushCache="true"。 |
| 手动清空缓存 | 调用 sqlSession.clearCache()。 |
通过合理配置一级缓存,可以在保证数据一致性的同时提升查询性能。在分布式或高并发场景中,建议结合二级缓存或第三方缓存(如 Redis)进一步优化。
在 MyBatis 里,一级缓存是SqlSession级别的缓存,默认处于开启状态,并且无法直接将其关闭。不过,你可以通过一些手段来达成类似关闭一级缓存的效果。下面为你详细介绍相关情况:
一级缓存默认开启
一级缓存默认开启,在同一个SqlSession中执行相同的 SQL 查询时,MyBatis 会先从缓存里查找,如果找到就直接返回结果,不会再执行数据库查询。
示例代码
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
public class FirstLevelCacheDefaultExample {
public static void main(String[] args) {
// 加载 MyBatis 配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = FirstLevelCacheDefaultExample.class.getClassLoader().getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建 SqlSession
try (SqlSession session = sqlSessionFactory.openSession()) {
// 第一次查询
Object result1 = session.selectOne("com.example.mapper.YourMapper.selectById", 1);
System.out.println("第一次查询结果: " + result1);
// 第二次查询,使用相同的 SqlSession
Object result2 = session.selectOne("com.example.mapper.YourMapper.selectById", 1);
System.out.println("第二次查询结果: " + result2);
}
}
}
在这个示例里,第二次查询会直接从一级缓存获取结果,不会再次执行数据库查询。
实现类似关闭一级缓存的效果
虽然不能直接关闭一级缓存,但可以通过以下方法实现类似效果:
1. 每次查询后清空缓存
在每次查询完成后,调用SqlSession.clearCache()方法来清空一级缓存。
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
public class ClearCacheExample {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
InputStream inputStream = ClearCacheExample.class.getClassLoader().getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
// 第一次查询
Object result1 = session.selectOne("com.example.mapper.YourMapper.selectById", 1);
System.out.println("第一次查询结果: " + result1);
// 清空缓存
session.clearCache();
// 第二次查询
Object result2 = session.selectOne("com.example.mapper.YourMapper.selectById", 1);
System.out.println("第二次查询结果: " + result2);
}
}
}
在这个示例中,第一次查询后调用了session.clearCache()方法,这样第二次查询时就不会使用缓存,而是重新执行数据库查询。
2. 每次查询都使用新的SqlSession
每次查询都创建一个新的SqlSession,因为一级缓存是SqlSession级别的,不同的SqlSession有各自独立的缓存。
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
public class NewSqlSessionExample {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
InputStream inputStream = NewSqlSessionExample.class.getClassLoader().getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 第一次查询
try (SqlSession session1 = sqlSessionFactory.openSession()) {
Object result1 = session1.selectOne("com.example.mapper.YourMapper.selectById", 1);
System.out.println("第一次查询结果: " + result1);
}
// 第二次查询,使用新的 SqlSession
try (SqlSession session2 = sqlSessionFactory.openSession()) {
Object result2 = session2.selectOne("com.example.mapper.YourMapper.selectById", 1);
System.out.println("第二次查询结果: " + result2);
}
}
}
这里两次查询分别使用了不同的SqlSession,所以每次查询都会执行数据库查询,不会使用缓存。
在 MyBatis 中,一级缓存是 SqlSession 级别的缓存,默认是开启的。你可以通过以下方式来关闭或开启一级缓存:
关闭一级缓存
在每次执行增删改操作后,MyBatis 会默认清空一级缓存。如果你希望在查询时也关闭一级缓存,可以在执行查询前调用 sqlSession.clearCache() 方法来手动清除缓存。
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 执行查询,结果会从数据库中获取,并存入一级缓存
User user1 = userMapper.getUserById(1);
// 手动清除一级缓存
sqlSession.clearCache();
// 再次执行查询,结果会再次从数据库中获取
User user2 = userMapper.getUserById(1);
sqlSession.close();
开启一级缓存
一级缓存是默认开启的,因此通常不需要额外的操作来开启它。只要在同一个 SqlSession 中进行查询,MyBatis 会自动使用一级缓存。
注意事项
- 一级缓存的生命周期与 SqlSession 的生命周期一致,当 SqlSession 关闭时,一级缓存也会被清空。
- 如果在同一个 SqlSession 中进行了增删改操作,MyBatis 会自动清空一级缓存,以保证数据的一致性。
- 如果你需要更细粒度的缓存控制,可以考虑使用 MyBatis 的二级缓存。
通过合理使用一级缓存和二级缓存,可以有效减少数据库的访问次数,提高应用程序的性能。
MyBatis 一级缓存机制及控制方法如下:
一、一级缓存的基础概念
-
作用范围
SqlSession 级别,同一个会话中多次执行相同 SQL + 参数的查询时,默认直接从内存读取结果。 -
默认状态
自动开启,无需手动配置。 -
缓存失效场景
- 执行了
INSERT/UPDATE/DELETE操作(任何写操作)。 - 调用
SqlSession.clearCache()。 - 事务提交或回滚(取决于事务管理器配置)。
- 执行了
二、关闭一级缓存的方法
1. 全局关闭(所有查询禁用)
修改 MyBatis 全局配置文件,将 localCacheScope 设置为 STATEMENT:
<configuration>
<settings>
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
</configuration>
- 效果
每个 SQL 执行后立即清空缓存,所有查询强制访问数据库。
2. 单次查询绕过缓存
在 Mapper 的 SQL 配置中,添加 flushCache="true":
<select id="selectUser" resultType="User" flushCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
- 效果
每次执行该查询前清空缓存,确保获取最新数据。
3. 手动清空缓存
在代码中主动调用 SqlSession.clearCache():
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.selectUser(1);
// 清空缓存
sqlSession.clearCache();
User user2 = mapper.selectUser(1); // 重新查询数据库
}
三、注意事项
-
性能权衡
关闭一级缓存会增加数据库压力,需根据业务场景(如高实时性要求)谨慎选择。 -
二级缓存无关性
一级缓存与二级缓存(Mapper 级别)相互独立,关闭一级缓存不影响二级缓存。 -
分布式环境问题
一级缓存仅在单机 SqlSession 内有效,分布式系统中需通过其他方案(如 Redis)保证数据一致性。
四、验证缓存行为
可通过日志观察 SQL 执行次数:
# 在 mybatis-config.xml 中启用日志
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
</configuration>
- 预期结果
关闭缓存后,相同查询会重复打印 SQL 执行日志。
通过上述方法,可灵活控制 MyBatis 一级缓存的开启与关闭。
好的,我现在需要回答用户关于如何开启和关闭MyBatis的一级缓存的问题。用户之前已经询问过MyBatis的缓存机制,现在他们可能想更具体地了解一级缓存的配置和使用。
首先,我得回忆一下MyBatis一级缓存的基本信息。根据之前的对话,一级缓存是默认开启的,生命周期绑定到SqlSession,不需要额外配置。不过用户可能想知道是否有方法可以显式地控制它的开启或关闭,或者在不同作用域下的行为。
接下来,我需要考虑用户可能的场景。例如,他们可能在某个特定的操作中不希望使用一级缓存,比如执行更新操作后希望立即刷新缓存,或者在多线程环境中处理缓存问题。此外,用户可能误认为一级缓存需要手动配置,所以需要明确说明它是默认开启的,并解释如何通过配置调整其作用域。
然后,我应该详细说明如何配置一级缓存的作用域,比如设置为STATEMENT、SESSION或JDBC。这会影响缓存的生命周期,用户可能需要根据不同的需求选择合适的模式。例如,STATEMENT作用域下,每次执行SQL语句后缓存就会被清除,这可能适合某些需要频繁更新的场景。
另外,用户可能关心在代码中如何手动管理一级缓存,比如调用clearCache()方法。这时候需要举例说明在Java代码中如何调用,以及在Mapper XML中使用标签来强制刷新缓存。
还需要提到一些注意事项,比如一级缓存的数据一致性问题和多线程环境下的处理。虽然MyBatis默认使用非线程安全的缓存,但用户可以通过配置第三方缓存如EhCache来解决并发问题。
最后,总结开启和关闭的方法,强调默认开启的情况,以及如何通过配置和代码进行控制。确保用户能够根据实际需求灵活调整一级缓存的行为。
MyBatis 的**一级缓存(Local Cache)**默认自动开启,无需手动配置。但其作用范围和刷新行为可以通过配置或代码灵活控制。以下是详细说明如何管理一级缓存:
一、一级缓存的默认行为
• 自动开启:MyBatis 在初始化时默认启用一级缓存,绑定到每个 SqlSession 对象。
• 生命周期:随 SqlSession 的创建而诞生,随 SqlSession 的关闭或调用 clearCache() 方法而销毁。
• 缓存范围:默认作用域为 STATEMENT(每次执行 SQL 后清空缓存),可通过配置修改为 SESSION 或 JDBC。
二、配置一级缓存的作用域
在 mybatis-config.xml 中通过 <settings> 配置 localCacheScope,可选值如下:
<configuration>
<settings>
<!-- 默认值:STATEMENT -->
<setting name="localCacheScope" value="STATEMENT"/>
<!-- 可选值:SESSION 或 JDBC -->
<!-- <setting name="localCacheScope" value="SESSION"/></setting>
<!-- <setting name="localCacheScope" value="JDBC"/></setting>
</settings>
</configuration>
1. STATEMENT(默认)
• 行为:每次执行 SQL 语句(SELECT/INSERT/UPDATE/DELETE)后,一级缓存自动清空。
• 适用场景:适用于需要频繁更新数据的场景,确保每次操作后缓存立即刷新。
2. SESSION
• 行为:仅在调用 sqlSession.commit() 或 sqlSession.rollback() 时清空一级缓存。
• 适用场景:适用于需要批量操作的场景(如事务处理),减少缓存刷新频率。
3. JDBC
• 行为:一级缓存完全失效,直接使用数据库查询结果。 • 适用场景:极少使用,仅用于调试或特殊需求(如避免缓存干扰)。
三、手动控制一级缓存
1. 清空当前 SqlSession 的缓存
• Java 代码:
sqlSession.clearCache(); // 清空一级缓存
• Mapper XML:在增删改操作中强制刷新缓存:
<update id="updateUser" parameterType="User" flushCache="true">
UPDATE users SET name=#{name} WHERE id=#{id}
</update>
2. 在事务中管理缓存
• 提交事务时自动刷新:当 localCacheScope 设置为 SESSION 时,提交事务会自动清空一级缓存。
• 回滚事务时不清空缓存:回滚不会触发缓存刷新,需手动调用 clearCache()。
四、关闭一级缓存(高级场景)
虽然 MyBatis 无法直接“关闭”一级缓存,但可以通过以下方式模拟关闭效果:
1. 将作用域设为 JDBC
在 mybatis-config.xml 中配置:
<setting name="localCacheScope" value="JDBC"/>
此时一级缓存功能被禁用,每次查询直接从数据库读取。
2. 在代码中频繁清空缓存
在每次操作后手动调用 sqlSession.clearCache(),但需谨慎使用,避免性能问题。
五、示例代码
1. 配置 localCacheScope 为 SESSION
<!-- mybatis-config.xml -->
<configuration>
<settings>
<setting name="localCacheScope" value="SESSION"/>
</settings>
</configuration>
2. 在 Mapper XML 中强制刷新缓存
<mapper namespace="com.example.UserMapper">
<update id="deleteUser" parameterType="int" flushCache="true">
DELETE FROM users WHERE id = #{id}
</update>
</mapper>
3. 在 Java 代码中手动清空缓存
public class UserService {
public void updateUser(User user) {
sqlSession.update("com.example.UserMapper.updateUser", user);
sqlSession.clearCache(); // 强制清空一级缓存
}
}
六、注意事项
-
数据一致性: • 一级缓存是线程不安全的,多线程环境下需结合事务或同步机制。 • 若使用
SESSION作用域,需确保事务提交后缓存及时刷新。 -
性能权衡: • 频繁调用
clearCache()会降低性能,需根据业务场景合理使用。 • 对于读多写少的场景,保持默认STATEMENT作用域即可。 -
与二级缓存的关系: • 一级缓存是二级缓存的基础,二级缓存的命中数据会先填充到一级缓存。 • 若关闭一级缓存(如设为
JDBC),二级缓存将直接查询数据库,失去意义。
七、总结
• 开启:默认开启,无需配置。
• 关闭:通过设置 localCacheScope="JDBC" 或手动频繁清空缓存模拟关闭。
• 最佳实践:
• 默认使用 STATEMENT 作用域,平衡性能与一致性。
• 在事务场景中配置 SESSION 作用域,减少不必要的缓存刷新。
• 显式调用 flushCache() 仅在必要时(如关键数据更新)。
通过灵活配置和代码控制,可以最大化发挥一级缓存的优势,同时避免潜在问题。
Mybatis开启关闭二级缓存
MyBatis 的二级缓存默认关闭,需要手动配置开启,且支持全局或细粒度控制。以下是详细的开启和关闭方法:
一、开启二级缓存
1. 全局启用二级缓存(必须)
在 mybatis-config.xml 中确保开启缓存功能(默认已开启,但需显式配置避免冲突):
<settings>
<setting name="cacheEnabled" value="true"/> <!-- 默认 true,可省略 -->
</settings>
2. 在 Mapper XML 中配置 <cache>
为需要缓存的 Mapper 添加 <cache> 标签:
<mapper namespace="com.example.UserMapper">
<!-- 启用二级缓存 -->
<cache
eviction="LRU" <!-- 淘汰策略(LRU/FIFO/SOFT/WEAK) -->
flushInterval="60000" <!-- 自动刷新时间(毫秒) -->
size="1024" <!-- 缓存最大对象数 -->
readOnly="true" <!-- 是否只读(建议 false 以支持序列化修改) -->
/>
<select id="selectById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
3. 实体类实现序列化
如果缓存对象需要序列化(当 readOnly="false" 时),对应的 Java 类需实现 Serializable:
public class User implements Serializable {
// 字段、Getter/Setter...
}
二、关闭二级缓存
1. 全局关闭(不推荐)
在 mybatis-config.xml 中关闭所有 Mapper 的二级缓存:
<settings>
<setting name="cacheEnabled" value="false"/>
</settings>
2. 关闭指定 Mapper 的缓存
在对应的 Mapper XML 中不添加 <cache> 标签,或显式设置 useCache="false":
<mapper namespace="com.example.UserMapper">
<!-- 不配置 <cache> 标签,则默认不启用二级缓存 -->
<!-- 或在特定查询中关闭缓存 -->
<select id="selectById" resultType="User" useCache="false">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
3. 清空二级缓存
执行任意 INSERT/UPDATE/DELETE 操作后,同 Mapper 的二级缓存会被清空:
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updateUser(user); // 执行更新操作,清空 UserMapper 的二级缓存
三、注意事项
-
脏读问题
- 多个 Mapper 操作同一张表时,需通过
<cache-ref namespace="其他Mapper"/>共享缓存,否则可能导致数据不一致。 - 建议对读多写少的数据使用二级缓存。
- 多个 Mapper 操作同一张表时,需通过
-
事务提交机制
- 二级缓存在事务提交(
sqlSession.commit())后才会生效,未提交的修改对其他会话不可见。
- 二级缓存在事务提交(
-
分布式环境
- 默认的二级缓存是单机缓存,分布式场景需整合 Redis、Ehcache 等:
<cache type="org.mybatis.caches.redis.RedisCache"/>
- 默认的二级缓存是单机缓存,分布式场景需整合 Redis、Ehcache 等:
-
序列化要求
- 若未设置
readOnly="true",缓存对象必须实现Serializable,否则会抛出序列化异常。
- 若未设置
四、代码验证示例
// 验证二级缓存(跨 SqlSession)
try (SqlSession session1 = sqlSessionFactory.openSession()) {
UserMapper mapper1 = session1.getMapper(UserMapper.class);
User user1 = mapper1.selectById(1); // 访问数据库
session1.commit(); // 提交后写入二级缓存
}
try (SqlSession session2 = sqlSessionFactory.openSession()) {
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user2 = mapper2.selectById(1); // 命中二级缓存
System.out.println(user1 == user2); // 输出 false(不同对象,但数据一致)
}
五、最佳实践
- 慎用二级缓存:在写操作频繁的场景中,频繁清空缓存可能降低性能。
- 明确缓存范围:通过
<cache-ref>管理关联 Mapper 的缓存依赖。 - 监控缓存命中率:通过日志或监控工具分析缓存效果(如整合
SLF4J日志)。
MyBatis 二级缓存的开启与关闭操作指南
二级缓存是 MyBatis 的跨 SqlSession 全局缓存,需手动配置。以下是详细操作步骤:
一、开启二级缓存
1. 全局配置(mybatis-config.xml)
<configuration>
<settings>
<!-- 启用二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
2. Mapper 配置(XML 或注解)
-
XML 方式:在 Mapper 文件中添加
<cache>标签<mapper namespace="com.example.mapper.UserMapper"> <!-- 配置二级缓存 --> <cache eviction="LRU" <!-- 缓存淘汰策略 --> flushInterval="60000" <!-- 60秒自动刷新 --> size="512" <!-- 最大缓存对象数 --> readOnly="true"/> <!-- 返回对象引用 --> <select id="getUserById" resultType="User"> SELECT * FROM users WHERE id = #{id} </select> </mapper> -
注解方式(MyBatis 3.2+):
@CacheNamespace public interface UserMapper { @Select("SELECT * FROM users WHERE id = #{id}") User getUserById(int id); }
3. 实体类要求
缓存对象需实现 Serializable 接口:
public class User implements Serializable {
private Integer id;
private String name;
// getters/setters
}
二、关闭二级缓存
1. 全局关闭
修改 mybatis-config.xml:
<settings>
<setting name="cacheEnabled" value="false"/>
</settings>
2. Mapper 级关闭
- XML 方式:移除
<cache>标签 - 注解方式:移除
@CacheNamespace注解
3. 动态控制(可选)
在特定查询中禁用缓存:
<select id="getRealtimeData" resultType="Data" useCache="false">
SELECT * FROM real_time_table
</select>
三、验证缓存行为
// 测试二级缓存跨会话共享
try (SqlSession session1 = sqlSessionFactory.openSession()) {
UserMapper mapper1 = session1.getMapper(UserMapper.class);
User user1 = mapper1.getUserById(1); // 缓存加载
session1.commit(); // 提交后缓存生效
}
try (SqlSession session2 = sqlSessionFactory.openSession()) {
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user2 = mapper2.getUserById(1); // 从二级缓存获取
System.out.println(user1 == user2); // 输出 true
}
四、最佳实践
- 高频查询场景:对复杂报表查询启用二级缓存
- 写操作后刷新:执行 INSERT/UPDATE/DELETE 后调用
mapper.clearCache() - 分布式环境:建议使用 Redis 等中央缓存替代本地二级缓存
- 监控缓存命中率:通过日志分析缓存效率,优化配置参数
五、注意事项
- 缓存一致性:二级缓存可能引发脏读,需结合业务场景评估
- 内存管理:设置合理的
size值防止内存溢出 - 序列化开销:复杂对象序列化可能影响性能
通过合理配置二级缓存,可显著减少数据库压力,提升系统吞吐量。建议通过压力测试确定最佳缓存策略。
在 MyBatis 中,二级缓存的开启和关闭需要通过多层配置控制,包括全局开关、命名空间(Mapper)级别的开关以及单个查询语句的开关。以下是具体步骤和注意事项:
一、开启二级缓存
1. 全局开启二级缓存
二级缓存的全局开关默认是开启的,但为了明确配置或团队协作,建议显式开启。
Spring Boot 配置(application.properties/application.yml):
# 全局开启二级缓存
mybatis.configuration.cache-enabled=true
MyBatis 核心配置文件(mybatis-config.xml):
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2. 在 Mapper 文件中开启二级缓存
在需要使用二级缓存的 Mapper XML 文件中添加 <cache/> 标签,表示开启该命名空间(Mapper)的二级缓存。
<mapper namespace="com.example.mapper.UserMapper">
<!-- 开启二级缓存 -->
<cache/>
<!-- 其他 SQL 语句 -->
<select id="selectUserById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
3. 配置二级缓存参数(可选)
通过 <cache> 标签的属性进一步控制缓存行为:
<cache
eviction="LRU" <!-- 缓存回收策略,默认LRU -->
flushInterval="60000"<!-- 自动刷新间隔(毫秒),60秒 -->
size="1024" <!-- 缓存最大条目数 -->
readOnly="true"/> <!-- 是否只读,true 可提升性能但需确保数据不可变 -->
eviction:缓存淘汰策略(LRU/FIFO/SOFT/WEAK)。flushInterval:自动刷新间隔,单位毫秒。size:缓存最大容量。readOnly:true表示缓存对象只读,直接返回引用(需实体类不可变);false表示返回拷贝(需序列化)。
4. 实体类实现序列化
如果 readOnly="false",实体类必须实现 Serializable 接口,否则会抛出 NotSerializableException。
public class User implements Serializable {
private static final long serialVersionUID = 1L;
// 属性和方法
}
二、关闭二级缓存
1. 全局关闭二级缓存
在全局配置中关闭二级缓存:
Spring Boot 配置:
mybatis.configuration.cache-enabled=false
MyBatis 核心配置文件:
<settings>
<setting name="cacheEnabled" value="false"/>
</settings>
2. 关闭 Mapper 级别的二级缓存
在 Mapper XML 文件中不添加 <cache/> 标签,或显式禁用缓存:
<mapper namespace="com.example.mapper.UserMapper">
<!-- 禁用二级缓存 -->
<cache-usage="none"/>
<!-- 其他 SQL 语句 -->
</mapper>
3. 关闭单个查询的二级缓存
在 <select> 标签中设置 useCache="false",表示该查询不使用二级缓存:
<select id="selectUserById" resultType="User" useCache="false">
SELECT * FROM user WHERE id = #{id}
</select>
三、注意事项
1. 缓存生效条件
- 必须同时满足以下条件:
- 全局缓存开关开启(
cacheEnabled=true)。 - Mapper 文件中配置了
<cache/>。 - 查询语句未显式禁用缓存(
useCache="false")。
- 全局缓存开关开启(
2. 缓存失效场景
- 执行
insert、update、delete操作时,会清空当前 Mapper 的二级缓存。 - 手动调用
SqlSession.clearCache()或SqlSessionFactory.clearCache()。 - 达到
flushInterval设置的刷新时间间隔。
3. 一级缓存与二级缓存的关系
- 二级缓存的数据来源于一级缓存:只有当
SqlSession提交(commit)或关闭(close)时,一级缓存的数据才会提交到二级缓存。 - 二级缓存是跨
SqlSession的全局缓存,而一级缓存是单个SqlSession内的缓存。
4. 并发控制
- 如果
readOnly="false",缓存会返回对象的拷贝(通过序列化),以避免并发修改问题。 - 若
readOnly="true",需确保缓存的数据是不可变的(Immutable),否则可能导致数据不一致。
四、验证二级缓存是否生效
示例代码:
// 第一个 SqlSession
try (SqlSession session1 = sqlSessionFactory.openSession()) {
UserMapper mapper1 = session1.getMapper(UserMapper.class);
User user1 = mapper1.selectUserById(1); // 第一次查询,从数据库获取
System.out.println("第一次查询:" + user1); // 数据库查询
}
// 第二个 SqlSession
try (SqlSession session2 = sqlSessionFactory.openSession()) {
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user2 = mapper2.selectUserById(1); // 第二次查询,从二级缓存获取
System.out.println("第二次查询:" + user2); // 缓存命中
}
- 若输出相同对象地址(如
user1 == user2为true),说明二级缓存生效。 - 若输出不同地址,则二级缓存未生效,需检查配置是否正确。
五、总结
| 操作 | 方法 |
|---|---|
| 全局开启二级缓存 | mybatis.configuration.cache-enabled=true 或 <setting name="cacheEnabled" value="true"/> |
| Mapper 级开启 | 在 Mapper XML 中添加 <cache/>。 |
| 全局关闭二级缓存 | mybatis.configuration.cache-enabled=false 或 <setting name="cacheEnabled" value="false"/> |
| Mapper 级关闭 | 不添加 <cache/> 或设置 <cache-usage="none"/>。 |
| 单个查询关闭缓存 | 在 <select> 标签中设置 useCache="false"。 |
通过合理配置二级缓存,可以显著提升跨 SqlSession 的查询性能,但需注意数据一致性、序列化和并发问题。
MyBatis 的二级缓存是 Mapper 级别的缓存,多个 SqlSession 可以共享同一个 Mapper 的二级缓存。二级缓存默认是关闭的,下面为你详细介绍如何开启和关闭它。
开启二级缓存
要开启二级缓存,需要完成以下几个步骤:
1. 在 MyBatis 配置文件中开启全局二级缓存
在 mybatis-config.xml 文件中添加如下配置:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
这行配置将全局的二级缓存开关打开,允许使用二级缓存。
2. 在 Mapper XML 文件中配置二级缓存
在对应的 Mapper XML 文件中添加 <cache> 标签来启用该 Mapper 的二级缓存,示例如下:
<mapper namespace="com.example.mapper.YourMapper">
<cache/>
<!-- 其他 SQL 语句 -->
<select id="selectById" parameterType="int" resultType="com.example.entity.YourEntity">
SELECT * FROM your_table WHERE id = #{id}
</select>
</mapper>
这里的 <cache> 标签启用了该 Mapper 的二级缓存。
或者在 Mapper 接口上使用 @CacheNamespace 注解
如果你使用的是注解方式开发,可以在 Mapper 接口上添加 @CacheNamespace 注解,示例如下:
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Select;
@CacheNamespace
public interface YourMapper {
@Select("SELECT * FROM your_table WHERE id = #{id}")
YourEntity selectById(int id);
}
关闭二级缓存
关闭二级缓存也有几种方式:
1. 关闭全局二级缓存
在 mybatis-config.xml 文件中,将 cacheEnabled 设置为 false:
<settings>
<setting name="cacheEnabled" value="false"/>
</settings>
这样所有 Mapper 的二级缓存都会被关闭。
2. 针对单个 Mapper 关闭二级缓存
如果你只想关闭某个特定 Mapper 的二级缓存,可以在 Mapper XML 文件中移除 <cache> 标签,或者在 Mapper 接口上移除 @CacheNamespace 注解。
代码示例
以下是一个完整的 Java 代码示例,展示了如何使用开启了二级缓存的 Mapper 进行查询:
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
public class SecondLevelCacheExample {
public static void main(String[] args) {
// 加载 MyBatis 配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = SecondLevelCacheExample.class.getClassLoader().getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 第一个 SqlSession
try (SqlSession session1 = sqlSessionFactory.openSession()) {
Object result1 = session1.selectOne("com.example.mapper.YourMapper.selectById", 1);
System.out.println("第一个 SqlSession 查询结果: " + result1);
}
// 第二个 SqlSession
try (SqlSession session2 = sqlSessionFactory.openSession()) {
Object result2 = session2.selectOne("com.example.mapper.YourMapper.selectById", 1);
System.out.println("第二个 SqlSession 查询结果: " + result2);
}
}
}
在这个示例中,如果二级缓存开启,第二个 SqlSession 的查询会直接从二级缓存中获取结果,而不会再次执行数据库查询。
注意事项
- 二级缓存中的对象需要实现
java.io.Serializable接口,因为缓存可能会将对象序列化到磁盘。 - 当执行插入、更新、删除操作时,MyBatis 会自动清空对应的 Mapper 的二级缓存,以保证数据的一致性。
在 MyBatis 中,二级缓存默认是关闭的,需要手动开启和配置。以下是开启和关闭二级缓存的方法:
开启二级缓存
-
在 MyBatis 核心配置文件中开启全局缓存: 在
mybatis-config.xml文件的<settings>标签中添加以下配置来开启全局缓存:<settings> <setting name="cacheEnabled" value="true"/> </settings>这一步是可选的,因为 MyBatis 默认的
cacheEnabled值就是true。 -
在映射文件中开启二级缓存: 在需要使用二级缓存的 Mapper 的 XML 文件中添加
<cache>标签来开启该 Mapper 的二级缓存:<mapper namespace="com.example.mapper.UserMapper"> <!-- 开启二级缓存 --> <cache/> <!-- 或者可以自定义缓存属性 --> <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/> <!-- 其他 SQL 映射语句 --> </mapper>其中,
eviction属性指定缓存的回收策略,flushInterval指定缓存刷新的时间间隔(毫秒),size指定缓存可以存储的对象数量,readOnly指定缓存是否只读。 -
确保返回的实体类可序列化: 使用二级缓存时,返回的实体类需要实现
Serializable接口,否则会抛出异常。
关闭二级缓存
-
全局关闭二级缓存: 在
mybatis-config.xml文件中,将cacheEnabled设置为false:<settings> <setting name="cacheEnabled" value="false"/> </settings> -
关闭特定 Mapper 的二级缓存: 在映射文件中移除
<cache>标签即可关闭该 Mapper 的二级缓存。 -
关闭单个查询的二级缓存: 在
<select>标签中设置useCache="false"属性,可以禁用该查询的二级缓存:<select id="findById" parameterType="int" resultType="com.example.entity.User" useCache="false"> SELECT * FROM user WHERE id = #{id} </select>
通过以上方法,可以灵活地控制 MyBatis 中二级缓存的开启和关闭,以满足不同的应用场景和需求。
嗯,用户想了解MyBatis如何开启和关闭二级缓存。我需要先回忆一下MyBatis的缓存机制,特别是二级缓存的特点。首先,二级缓存是Mapper级别的,多个SqlSession共享的,默认应该是关闭的,需要手动开启。
然后,用户可能想知道具体的配置步骤。记得在MyBatis的全局配置文件(比如mybatis-config.xml)里有一个标签,里面可以设置cacheEnabled选项。不过这里可能有个误区,MyBatis文档中其实从3.4.5版本开始,cacheEnabled默认是true,但即使开启,各个Mapper的二级缓存还是需要单独配置的。所以需要提醒用户即使全局开启了,每个Mapper还需要单独决定是否使用。
接下来,开启二级缓存的具体方法是在Mapper XML文件中添加标签。这个标签有几个属性可以配置,比如eviction策略、flushInterval等。同时,对应的POJO需要实现Serializable接口,因为二级缓存可能涉及序列化。这部分需要详细说明,避免用户遇到序列化的问题。
另外,用户可能需要知道如何关闭某个Mapper的二级缓存。这时候可以在标签里设置blocking或者useCache属性,或者在方法上设置useCache="false"。或者在全局配置里将cacheEnabled设为false,但这样会影响所有Mapper,可能不是用户想要的。
还要注意二级缓存的使用场景和潜在问题,比如数据一致性。在分布式环境下或者多表操作时,二级缓存可能导致脏数据,这时候可能需要谨慎使用或者结合其他缓存方案。
用户可能的误区是认为全局开启cacheEnabled后所有Mapper都会自动使用二级缓存,但实际上每个Mapper需要单独配置。所以需要明确说明这一点,避免配置错误。
最后,总结一下步骤:全局配置是否开启,Mapper文件添加,实体类序列化,以及如何关闭单个Mapper或方法的缓存。同时提醒注意事项,比如测试时查看日志确认缓存是否生效,缓存策略的选择等。 好的,我将分步骤为您解释MyBatis二级缓存的开启和关闭机制:
━━━━━━━━━━━━━━ 一、二级缓存核心特性 ━━━━━━━━━━━━━━
- 作用范围:Mapper级别(namespace维度)
- 共享范围:多个SqlSession共享
- 存储介质:默认内存,可通过插件扩展(Redis等)
- 生效条件:事务提交后生效
━━━━━━━━━━ 二、开启二级缓存 ━━━━━━━━━━
- 全局配置(mybatis-config.xml)
<configuration>
<settings>
<!-- 3.4.5+版本默认true,建议显式声明 -->
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
- Mapper级配置(XXXMapper.xml)
<mapper namespace="com.example.UserMapper">
<!-- 基础配置 -->
<cache/>
<!-- 完整配置示例 -->
<cache
eviction="LRU"
flushInterval="60000"
size="1024"
readOnly="true"/>
</mapper>
- 实体类要求
public class User implements Serializable {
// 必须实现序列化接口
}
━━━━━━━━━ 三、关闭缓存 ━━━━━━━━━
- 全局关闭(禁用所有二级缓存)
<setting name="cacheEnabled" value="false"/>
- Mapper级关闭
<mapper namespace="...">
<!-- 完全禁用 -->
<cache blocking="true"/>
</mapper>
- 语句级关闭(CRUD标签)
<select id="selectUser" resultType="User" useCache="false">
SELECT * FROM user
</select>
━━━━━━━━━━━━ 四、注意事项 ━━━━━━━━━━━━
-
数据一致性风险
- 多表操作时建议关闭关联表的缓存
- 分布式环境慎用默认缓存
-
最佳实践
- 读多写少场景适用
- 高并发写入场景建议关闭
- 建议配合日志分析命中率
需要特别注意:当缓存配置为readOnly="false"时,MyBatis会返回缓存对象的拷贝,此时进行对象修改不会影响缓存内容。
嗯,用户之前已经问过MyBatis的一级缓存开启关闭,现在又转向二级缓存。我需要先回顾一下之前的对话,确保内容连贯。用户可能是在学习MyBatis的缓存机制,逐步深入。他们可能正在开发一个需要优化数据库查询的项目,所以想了解如何利用缓存提升性能。
首先,要明确二级缓存和一级缓存的区别。一级缓存是默认的,会话级别的,而二级缓存是全局的,跨会话的。用户已经知道如何管理一级缓存,现在想知道二级缓存的配置和使用。
接下来,需要解释如何开启二级缓存。这包括在Mapper XML中添加标签,以及在mybatis-config.xml中配置插件,比如集成EhCache或Redis。还要提到自定义缓存实现的方法,因为用户可能希望使用不同的缓存技术。
然后,关于关闭二级缓存,虽然通常不需要,但用户可能想了解在某些情况下如何禁用它,比如测试环境或者不需要缓存的时候。这时候需要说明移除Mapper中的标签,或者通过配置禁用插件。
还需要注意数据一致性的问题,比如执行更新操作时如何刷新缓存,使用标签或者调用clearCache方法。此外,序列化配置也很重要,特别是当对象不能被正确序列化时,会导致错误。
用户可能关心在实际应用中的最佳实践,比如哪些场景适合使用二级缓存,如何配置合理的淘汰策略,以及多线程环境下的注意事项。需要给出具体的例子,比如配置EhCache的步骤,或者如何在代码中手动刷新缓存。
最后,总结开启和关闭二级缓存的方法,强调合理使用缓存的重要性,避免数据不一致和性能问题。确保回答结构清晰,分点明确,方便用户理解和应用。
MyBatis 的**二级缓存(Global Cache)**是跨 SqlSession 的全局缓存,合理配置可显著减少数据库访问次数。以下是详细说明如何开启和关闭二级缓存,以及相关配置与注意事项:
一、开启二级缓存
1. 核心配置步骤
• (1) 启用 Mapper 的二级缓存
在 Mapper XML 文件中添加 <cache/> 标签,标识该 Mapper 使用二级缓存:
<mapper namespace="com.example.UserMapper">
<cache/> <!-- 开启二级缓存 -->
<select id="findByUserId" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
• (2) 全局配置缓存插件
在 mybatis-config.xml 中引入缓存插件(如 EhCache、Redis):
<configuration>
<plugins>
<!-- 集成 EhCache -->
<plugin interceptor="org.mybatis.caches.ehcache.EhCachePlugin">
<property name="configLocation" value="classpath:ehcache.xml"/>
</plugin>
<!-- 或集成 Redis -->
<plugin interceptor="org.mybatis.caches.redis.RedisCachePlugin">
<property name="host" value="localhost"/>
<property name="port" value="6379"/>
</plugin>
</plugins>
</configuration>
2. 自定义缓存实现(可选)
如果不想依赖第三方库,可自定义缓存实现类(实现 org.apache.ibatis.cache.Cache 接口):
public class CustomCache implements Cache {
private Map<Object, Object> cacheStore = new ConcurrentHashMap<>();
@Override
public void putObject(Object key, Object value) {
cacheStore.put(key, value);
}
@Override
public Object getObject(Object key) {
return cacheStore.get(key);
}
// 其他方法实现(如 removeObject、clear 等)
}
然后在 Mapper XML 中指定自定义缓存:
<cache type="com.example.CustomCache"/>
二、关闭二级缓存
1. 禁用特定 Mapper 的缓存
• 方法:在 Mapper XML 中移除 <cache/> 标签,或显式设置 useCache="false":
<mapper namespace="com.example.UserMapper">
<cache useCache="false"/> <!-- 禁用二级缓存 -->
<!-- SQL 映射 -->
</mapper>
2. 全局关闭缓存插件
• 方法:在 mybatis-config.xml 中移除缓存插件配置:
<!-- 删除以下插件配置 -->
<!--
<plugins>
<plugin interceptor="org.mybatis.caches.ehcache.EhCachePlugin">
...
</plugin>
</plugins>
-->
3. 代码中手动刷新缓存(间接关闭效果)
• 方法:在增删改操作中强制刷新二级缓存:
<update id="deleteUser" parameterType="int" flushCache="true">
DELETE FROM users WHERE id = #{id}
</update>
• Java 代码:
sqlSession.update("com.example.UserMapper.deleteUser", userId);
sqlSession.clearCache(); // 清空当前 SqlSession 的一级缓存和二级缓存
三、二级缓存关键配置与优化
1. 缓存序列化
• 默认行为:MyBatis 使用 JdkSerialization 序列化对象,但效率较低。
• 自定义序列化:在 Mapper XML 中配置 serialize="true" 并实现 Serializable 接口:
<cache serialization="true"/>
或全局配置:
<configuration>
<settings>
<setting name="cacheSerializationEnabled" value="true"/>
</settings>
</configuration>
• 推荐使用 Jackson 或 Kryo 替代 JdkSerialization。
2. 缓存淘汰策略
• 默认策略:LRU(Least Recently Used)。
• 自定义策略:通过实现 org.apache.ibatis.cache.LruCache 或配置第三方缓存(如 EhCache)的策略。
3. 数据一致性保障
• 自动刷新:执行 INSERT/UPDATE/DELETE 时,MyBatis 会自动刷新受影响的二级缓存。
• 手动刷新:通过 <flushCache> 标签或 sqlSession.clearCache() 强制刷新。
四、示例代码
1. 启用二级缓存并集成 EhCache
• ehcache.xml(配置文件):
<ehcache>
<cache name="userCache"
maxEntriesLocalHeap="1000"
eternal="false"
timeToLiveSeconds="3600"
timeToIdleSeconds="1800"
overflowToDisk="false">
</cache>
</ehcache>
• mybatis-config.xml:
<configuration>
<plugins>
<plugin interceptor="org.mybatis.caches.ehcache.EhCachePlugin">
<property name="configLocation" value="classpath:ehcache.xml"/>
</plugin>
</plugins>
</configuration>
2. 禁用某个 Mapper 的缓存
<mapper namespace="com.example.OrderMapper">
<cache useCache="false"/> <!-- 禁用该 Mapper 的二级缓存 -->
<select id="findOrderById" resultType="Order">
SELECT * FROM orders WHERE id = #{id}
</select>
</mapper>
五、注意事项
-
适用场景: • 开启:适用于读多写少、跨会话复用的数据(如配置表、统计信息)。 • 关闭:适用于写频繁、数据实时性要求高的场景(如订单表)。
-
性能权衡: • 内存占用:二级缓存占用更多内存,需根据业务需求合理设置缓存大小。 • 一致性风险:长时间未刷新的缓存可能导致脏读,需结合业务场景设置
timeToLive等参数。 -
多线程环境: • 默认缓存实现
PerpetualCache不是线程安全的,需使用线程安全缓存(如 EhCache、Redis)。 -
分布式缓存: • 单机部署可选 EhCache,分布式系统推荐 Redis。
六、总结
• 开启二级缓存:通过 Mapper XML 的 <cache/> 标签和全局插件配置。
• 关闭二级缓存:移除 <cache/> 标签、禁用插件或手动刷新。
• 最佳实践:
• 对静态数据(如字典表)开启二级缓存,配置合理的过期时间。
• 高频更新数据禁用二级缓存,或结合 <flushCache> 实现手动刷新。
• 使用第三方缓存(如 Redis)提升分布式环境下的缓存一致性。
通过合理配置二级缓存,可显著减少数据库压力,但需权衡一致性、内存和性能成本。
Mybatis二级缓存详解
MyBatis 的二级缓存是跨 SqlSession 的缓存机制,属于 Mapper 级别的缓存,适用于需要多个会话共享数据的场景。以下是其核心原理、配置细节、工作机制及注意事项的详细解析:
一、二级缓存的核心原理
-
作用范围
- Mapper 级别:以 Mapper 的
namespace为作用域,同一namespace下的所有查询共享缓存。 - 跨 SqlSession:不同会话(
SqlSession)访问同一 Mapper 时,可命中缓存。
- Mapper 级别:以 Mapper 的
-
缓存结构
- 存储结构:默认使用
PerpetualCache(基于HashMap),但可扩展为 Redis、Ehcache 等第三方缓存。 - 事务管理:通过
TransactionalCache包装,确保事务提交后缓存生效。
- 存储结构:默认使用
-
缓存生命周期
- 缓存创建:首次查询后,结果存入缓存(需事务提交后生效)。
- 缓存失效:执行同 Mapper 的增删改操作(DML)后,整个
namespace的缓存被清空。
二、二级缓存的配置与使用
1. 开启二级缓存
步骤 1:全局启用
在 mybatis-config.xml 中确保缓存开关开启(默认已开启):
<settings>
<setting name="cacheEnabled" value="true" /> <!-- 默认 true -->
</settings>
步骤 2:Mapper 配置
在需要缓存的 Mapper XML 中添加 <cache> 标签:
<mapper namespace="com.example.UserMapper">
<!-- 二级缓存配置 -->
<cache
eviction="LRU" <!-- 淘汰策略:LRU(最近最少使用)、FIFO(先进先出)、SOFT(软引用)、WEAK(弱引用) -->
flushInterval="60000" <!-- 自动刷新间隔(毫秒),默认不刷新 -->
size="1024" <!-- 缓存最大对象数 -->
readOnly="true" <!-- 是否只读:true(返回缓存对象的引用,高效但不安全);false(返回深拷贝对象,需序列化) -->
/>
</mapper>
步骤 3:实体类序列化
若 readOnly="false",需确保实体类实现 Serializable 接口:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
// 其他字段及方法...
}
2. 关闭二级缓存
- 关闭指定 Mapper 的缓存:不添加
<cache>标签。 - 关闭特定查询的缓存:在
<select>中设置useCache="false":<select id="selectById" resultType="User" useCache="false"> SELECT * FROM user WHERE id = #{id} </select>
三、二级缓存的工作流程
-
查询顺序
执行查询时,MyBatis 按以下顺序查找数据:二级缓存 → 一级缓存 → 数据库 -
缓存写入时机
- 查询结果在事务提交(
commit())后才会写入二级缓存。 - 未提交的事务修改对其他会话不可见,避免脏读。
- 查询结果在事务提交(
-
缓存更新机制
- 执行同 Mapper 的增删改操作(DML)后,清空该
namespace下所有缓存。 - 可通过
flushCache="true"强制清空缓存:<update id="updateUser" parameterType="User" flushCache="true"> UPDATE user SET name=#{name} WHERE id=#{id} </update>
- 执行同 Mapper 的增删改操作(DML)后,清空该
四、二级缓存的常见问题与解决方案
1. 脏读问题
- 场景:多个 Mapper 操作同一张表,但未共享缓存。
- 解决:通过
<cache-ref>共享缓存命名空间:<mapper namespace="com.example.UserMapper"> <cache-ref namespace="com.example.OrderMapper"/> <!-- 共享 OrderMapper 的缓存 --> </mapper>
2. 缓存穿透与雪崩
- 缓存穿透:频繁查询不存在的数据(如无效 ID)。
解决:缓存空对象或使用布隆过滤器。 - 缓存雪崩:大量缓存同时过期。
解决:设置随机过期时间(flushInterval)。
3. 分布式环境问题
- 默认缓存不共享:单机缓存无法跨服务节点共享。
解决:整合 Redis 等分布式缓存:<cache type="org.mybatis.caches.redis.RedisCache" />
五、二级缓存的最佳实践
-
适用场景
- 读多写少:如配置表、静态数据表。
- 低实时性要求:允许短暂数据延迟。
-
避坑指南
- 避免跨 Mapper 操作同一表:除非使用
<cache-ref>共享缓存。 - 慎用
readOnly="true":若缓存对象被修改,可能导致数据不一致。 - 监控缓存命中率:通过日志分析缓存效果(如开启 MyBatis 调试日志)。
- 避免跨 Mapper 操作同一表:除非使用
-
性能优化
- 合理设置
eviction策略:LRU 适用于多数场景。 - 调整
flushInterval:根据数据更新频率设置刷新间隔。 - 限制缓存大小:避免内存溢出(
size参数)。
- 合理设置
六、二级缓存 vs 一级缓存
| 对比项 | 一级缓存 | 二级缓存 |
|---|---|---|
| 作用范围 | SqlSession 内 | 跨 SqlSession(Mapper 级别) |
| 存储位置 | 内存(HashMap) | 可扩展(内存、Redis、Ehcache 等) |
| 默认开启 | 是 | 否(需手动配置) |
| 失效机制 | DML 操作自动清空 | 同 Mapper 的 DML 操作清空 |
| 事务影响 | 未提交的修改对其他会话不可见 | 事务提交后缓存生效 |
七、总结
二级缓存的核心价值:通过跨会话共享高频访问数据,减少数据库压力,但需谨慎处理一致性问题。
推荐方案:
- 单机环境:使用默认缓存 + Ehcache 优化。
- 分布式环境:整合 Redis 实现共享缓存。
- 高并发写入场景:直接禁用二级缓存,依赖数据库或外部缓存(如 Caffeine)。
Mybatis的二级缓存的eviction(缓存回收策略,缓存驱逐淘汰策略)详解
MyBatis 的二级缓存提供了多种 缓存驱逐策略(Eviction Policy),用于在缓存达到容量上限时决定哪些对象应被移除。以下是各策略的详细说明、适用场景及配置示例:
一、缓存驱逐策略类型
MyBatis 支持四种内置策略,通过 eviction 属性配置:
| 策略名 | 实现类 | 说明 | 适用场景 |
|---|---|---|---|
| LRU | LruCache | 最近最少使用(Least Recently Used) | 高频访问数据,保留热点数据 |
| FIFO | FifoCache | 先进先出(First In First Out) | 数据访问模式均匀 |
| SOFT | SoftCache | 软引用(Soft Reference),内存不足时由 GC 回收 | 防止内存溢出,缓存大对象 |
| WEAK | WeakCache | 弱引用(Weak Reference),GC 运行时立即回收 | 临时性缓存,允许快速回收 |
二、策略详解与工作原理
1. LRU(最近最少使用)
- 原理
- 维护一个链表,记录缓存对象的访问顺序。
- 当缓存满时,移除最久未被访问的对象。
- 优点
- 适合热点数据,保留高频访问对象。
- 缺点
- 需要维护访问顺序链表,轻微性能开销。
- 配置示例:
<cache eviction="LRU" size="1024" />@CacheNamespace(eviction = LruCache.class, size = 1024)
2. FIFO(先进先出)
- 原理
- 维护一个队列,按对象进入缓存的顺序淘汰。
- 移除最先进入缓存的对象。
- 优点
- 实现简单,无额外性能开销。
- 缺点
- 可能淘汰高频访问的旧数据。
- 配置示例:
<cache eviction="FIFO" size="512" />@CacheNamespace(eviction = FifoCache.class, size = 512)
3. SOFT(软引用)
- 原理
- 使用
SoftReference包装缓存对象。 - 当 JVM 内存不足时,GC 会自动回收这些对象。
- 使用
- 优点
- 避免
OutOfMemoryError,适合缓存大对象(如文件、图片)。
- 避免
- 缺点
- 回收时间不可控,可能导致缓存命中率下降。
- 配置示例:
<cache eviction="SOFT" />@CacheNamespace(eviction = SoftCache.class)
4. WEAK(弱引用)
- 原理
- 使用
WeakReference包装缓存对象。 - 只要发生 GC,无论内存是否充足,都会立即回收这些对象。
- 使用
- 优点
- 快速释放内存,适合临时性缓存(如测试环境)。
- 缺点
- 缓存对象生命周期极短,频繁访问时性能差。
- 配置示例:
<cache eviction="WEAK" />@CacheNamespace(eviction = WeakCache.class)
三、策略对比与选型建议
| 对比维度 | LRU | FIFO | SOFT | WEAK |
|---|---|---|---|---|
| 淘汰逻辑 | 访问时间 | 进入时间 | 内存压力 | GC 触发 |
| 内存管理 | 主动移除 | 主动移除 | 依赖 GC | 依赖 GC |
| 性能开销 | 中 | 低 | 低 | 低 |
| 适用场景 | 热点数据 | 均匀访问数据 | 大对象缓存 | 临时缓存 |
选型建议:
- 常规场景:优先选择
LRU(默认策略),平衡性能和缓存效率。 - 内存敏感场景:使用
SOFT或WEAK避免内存溢出。 - 简单场景:
FIFO适用于无明确访问模式的数据。
四、高级配置与注意事项
1. 自定义缓存策略
通过实现 org.apache.ibatis.cache.Cache 接口,可扩展自定义驱逐策略:
public class CustomCache implements Cache {
// 实现必要方法(putObject、getObject、removeObject 等)
}
配置使用:
<cache type="com.example.CustomCache" />
@CacheNamespace(implementation = CustomCache.class)
2. 缓存大小(size)调优
- 设置过小:频繁淘汰,缓存命中率低。
- 设置过大:内存占用高,可能引发 GC 压力。
- 建议:根据监控数据(如缓存命中率)动态调整。
3. 与引用策略的兼容性
readOnly配置:readOnly="true":直接返回缓存对象引用,与SOFT/WEAK结合时需注意对象可能被 GC 回收后访问异常。readOnly="false":返回深拷贝对象,避免 GC 影响,但增加序列化开销。
4. 分布式环境下的策略
- Redis 集成:使用
RedisCache时,驱逐策略由 Redis 服务端控制(如maxmemory-policy)。 - 配置示例:
<cache type="org.mybatis.caches.redis.RedisCache" />
五、常见问题与解决方案
1. 缓存命中率低
- 原因:
size设置过小或策略不适合数据访问模式。 - 解决:增大
size或切换为LRU。
2. 内存泄漏
- 原因:未正确实现自定义缓存策略,或
SOFT/WEAK未生效。 - 解决:检查 GC 日志,确认对象是否被正常回收。
3. 缓存雪崩
- 场景:大量缓存同时过期。
- 解决:设置随机
flushInterval或使用分层缓存。
六、总结
- 核心机制:通过
eviction策略管理缓存对象的生命周期,平衡内存使用与命中率。 - 最佳实践:
- 默认使用
LRU,根据监控调整size。 - 内存敏感场景选择
SOFT,临时数据使用WEAK。 - 分布式环境整合 Redis 等外部缓存。
- 默认使用
嗯,用户现在问的是MyBatis二级缓存的eviction详解。之前的对话里,他们已经讨论过一级缓存、二级缓存的开启关闭、配置方式,以及缓存标签和注解的对比。现在用户想深入了解二级缓存的淘汰机制,也就是eviction。
首先,我需要回忆一下MyBatis二级缓存的淘汰策略相关知识。MyBatis默认使用LRU(Least Recently Used)算法,但也可以配置其他策略,比如FIFO、LRU_JDBC,或者集成第三方缓存如EhCache、Redis,这些缓存库通常提供更多的淘汰策略选择。
用户可能想知道如何配置不同的淘汰策略,每种策略的适用场景,以及如何自定义淘汰逻辑。此外,他们可能关心在分布式环境下如何保证缓存一致性,以及如何结合事务管理来刷新缓存。
接下来,我需要整理MyBatis内置的淘汰策略,比如LRU、FIFO、LRU_JDBC,以及如何通过配置或自定义实现这些策略。还要提到第三方缓存的集成,比如EhCache支持的多种策略,如LFU、LRU、FIFO等,以及Redis的过期时间设置。
另外,用户可能遇到的问题,比如缓存雪崩、脏读,以及如何通过调整淘汰策略来缓解这些问题。还需要考虑性能优化,比如合理设置缓存大小,避免内存溢出。
在结构上,应该分点讲解,每种策略的原理、配置方法、适用场景,然后是高级配置如自定义策略和第三方集成。最后总结最佳实践和常见问题解决方案。
需要注意的是,用户可能有不同的使用场景,比如单机应用和分布式系统,因此在讲解时要区分这两种情况,并提供相应的配置示例。例如,分布式系统推荐使用Redis,而单机可以使用EhCache。
还要确保术语准确,比如LRU和LFU的区别,避免混淆。同时,提供代码示例和配置片段,帮助用户更好地理解和应用这些策略。
最后,检查是否有遗漏的要点,比如缓存预热、监控和调优,这些也是优化缓存性能的重要部分,应该包含在回答中。
MyBatis 的二级缓存 Eviction(淘汰机制) 是缓存管理的关键策略,用于在缓存容量达到上限时移除最不常使用或最久未使用的条目,以确保内存效率和数据新鲜度。以下是详细解析:
一、MyBatis 内置淘汰策略
1. LRU(Least Recently Used)
• 原理:淘汰最久未使用的条目。 • 配置:
<!-- Mapper XML -->
<cache eviction="LRU" size="512"/>
• 适用场景:读多写少场景,如热点数据频繁访问。
2. FIFO(First In First Out)
• 原理:按进入缓存的顺序淘汰最早进入的条目。 • 配置:
<cache eviction="FIFO" size="1024"/>
3. LRU_JDBC
• 原理:基于数据库的LRU策略,通过记录访问时间戳淘汰最久未使用的条目。 • 配置:
<cache eviction="LRU_JDBC" size="2048"/>
• 特点:依赖数据库支持,需表中包含 last_accessed 字段。
二、第三方缓存集成与高级策略
1. EhCache
• 支持策略:LRU、LFU(Least Frequently Used)、FIFO、RANDOM、LRU_JDBC 等。
• 配置示例(ehcache.xml):
<cache name="userCache"
maxEntriesLocalHeap="1000"
eternal="false"
timeToLiveSeconds="3600"
timeToIdleSeconds="1800"
overflowToDisk="false"
eviction="LRU"
memoryStoreEvictionPolicy="LRU">
</cache>
2. Redis
• 支持策略:通过 EXPIRE 或 PEXPIRE 设置键的过期时间实现自动淘汰。
• 配置示例(mybatis-config.xml):
<plugin interceptor="org.mybatis.caches.redis.RedisCachePlugin">
<property name="host" value="localhost"/>
<property name="port" value="6379"/>
<property name="keyPrefix" value="mybatis:cache:user:"/>
</plugin>
• 动态过期时间:在 Java 代码中动态设置:
redisTemplate.opsForValue().set("user:1", user, 3600, TimeUnit.SECONDS);
三、自定义淘汰策略
1. 实现 org.apache.ibatis.cache.Eviction 接口
• 步骤:
- 创建自定义淘汰策略类,实现
get和remove方法。 - 在 Mapper XML 或注解中指定策略类型。 • 示例代码:
public class CustomLFUEviction implements Eviction {
@Override
public Object get(String key) {
// LFU 逻辑:获取最不常使用的条目
return null;
}
@Override
public void remove(String key) {
// 移除指定条目
}
}
• 配置:
<cache eviction="com.example.CustomLFUEviction" size="512"/>
四、淘汰策略选择指南
| 策略 | 适用场景 | 配置复杂度 | 性能开销 |
|---|---|---|---|
| LRU | 热点数据频繁访问(如商品详情页) | 低 | 低 |
| FIFO | 简单场景,无明确访问频率差异 | 低 | 低 |
| LRU_JDBC | 需与数据库紧密配合的场景 | 中 | 中 |
| EhCache LRU | 分布式环境,需高可用性和灵活性 | 中 | 中 |
| Redis TTL | 微服务架构,需自动过期和分布式支持 | 低 | 低 |
五、高级配置与最佳实践
1. 缓存预热(Cache Warmup)
• 目的:应用启动时预先加载高频数据到缓存。 • 实现方式:
@PostConstruct
public void warmUpCache() {
List<User> users = userService.getAllUsers();
users.forEach(user -> {
sqlSession.selectOne("com.example.UserMapper.findById", user.getId());
});
}
2. 缓存监控与调优
• 日志启用:
<settings>
<setting name="logImpl" value="SLF4J"/>
</settings>
• 监控指标:
• 缓存命中率(hitCount / accessCount)。
• 缓存大小(size)和淘汰次数(evictedCount)。
3. 分布式缓存一致性
• Redis:使用 Redis 的原子操作保证多节点缓存一致性。 • EhCache:集成 Terracotta 实现分布式缓存。
六、常见问题与解决方案
1. 缓存雪崩(Cache Avalanche)
• 场景:大量缓存键同时过期,导致数据库瞬时压力过大。
• 解决:
• 使用随机 TTL(如 1-5秒)而非固定值。
• 添加二级缓存(如 Redis + 本地缓存)。
2. 脏读(Dirty Read)
• 场景:缓存数据未及时刷新,读取到旧数据。
• 解决:
• 设置合理的 timeToLive(TTL)。
• 在关键操作后手动刷新缓存:
xml <update id="updateUser" flushCache="true"> UPDATE users SET name=#{name} WHERE id=#{id} </update>
3. 内存泄漏
• 场景:缓存未正确释放,长期堆积占用内存。
• 解决:
• 配置淘汰策略(如 LRU)和最大容量(size)。
• 定期清理无用缓存键。
七、总结
• 核心原则:根据业务场景选择合适的淘汰策略(如 LRU 适合热点数据,FIFO 适合简单场景)。
• 配置技巧:
• 结合 size 控制缓存容量,避免内存溢出。
• 使用第三方缓存(如 Redis、EhCache)增强功能和扩展性。
• 最佳实践:
• 对高频读写的静态数据(如配置表)启用缓存,配置 LRU + 合理 TTL。
• 对动态数据(如订单表)禁用缓存或结合 <flushCache> 手动刷新。
• 分布式系统中优先使用 Redis,保障缓存一致性和高可用性。
通过合理配置淘汰策略,可以在内存效率、查询性能和数据一致性之间找到平衡点,最大化 MyBatis 二级缓存的价值。
Mybatis使用Redis作为缓存
在 MyBatis 中集成 Redis 作为二级缓存可以有效解决分布式环境下的缓存共享问题,并提升系统性能。以下是详细配置步骤、示例代码及注意事项:
一、实现原理
MyBatis 的二级缓存支持扩展,通过实现 org.apache.ibatis.cache.Cache 接口,可以将缓存存储到 Redis。核心流程如下:
- 缓存查询:优先从 Redis 读取数据,若未命中则查询数据库。
- 缓存写入:事务提交后,将查询结果序列化并存入 Redis。
- 缓存失效:执行增删改操作时,清空对应命名空间的 Redis 缓存。
二、配置步骤(以 Spring Boot + MyBatis 为例)
1. 添加依赖
在 pom.xml 中引入 Redis 和 MyBatis 缓存扩展依赖:
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- MyBatis Redis Cache 适配器(可选第三方库,或自定义实现) -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>
2. 配置 Redis 连接
在 application.yml 中配置 Redis 服务器地址:
spring:
redis:
host: 127.0.0.1
port: 6379
password:
database: 0 # 默认使用 DB 0
3. 配置 MyBatis 使用 Redis 缓存
在 Mapper XML 或接口中指定 Redis 缓存实现:
方式 1:XML 配置
<mapper namespace="com.example.UserMapper">
<!-- 使用 Redis 缓存 -->
<cache type="org.mybatis.caches.redis.RedisCache" />
</mapper>
方式 2:注解配置(需自定义适配器)
若使用注解,需自定义 Cache 实现类(参考下方 自定义 Redis 缓存 部分)。
4. 实体类序列化
确保实体类实现 Serializable 接口:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
// 字段、Getter/Setter...
}
三、自定义 Redis 缓存(可选)
如果默认的 RedisCache 不满足需求,可自行实现缓存逻辑:
1. 实现 Cache 接口
public class CustomRedisCache implements Cache {
private final String id; // Mapper namespace
private final RedisTemplate<String, Object> redisTemplate;
public CustomRedisCache(String id) {
this.id = id;
this.redisTemplate = (RedisTemplate<String, Object>)
SpringContextHolder.getBean("redisTemplate"); // 从 Spring 容器获取
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
redisTemplate.opsForValue().set(key.toString(), value, 1, TimeUnit.HOURS); // 设置 1 小时过期
}
@Override
public Object getObject(Object key) {
return redisTemplate.opsForValue().get(key.toString());
}
@Override
public Object removeObject(Object key) {
redisTemplate.delete(key.toString());
return null;
}
@Override
public void clear() {
redisTemplate.delete(redisTemplate.keys(id + ":*")); // 清空当前命名空间缓存
}
// 其他方法(如 getSize())...
}
2. 配置使用自定义缓存
<mapper namespace="com.example.UserMapper">
<cache type="com.example.cache.CustomRedisCache" />
</mapper>
四、验证 Redis 缓存生效
1. 测试代码
@SpringBootTest
public class CacheTest {
@Autowired
private UserMapper userMapper;
@Test
public void testRedisCache() {
// 第一次查询(访问数据库并写入 Redis)
User user1 = userMapper.selectById(1);
// 第二次查询(命中 Redis 缓存)
User user2 = userMapper.selectById(1);
System.out.println(user1 == user2); // 输出 false(反序列化生成新对象)
}
}
2. 检查 Redis 数据
通过 Redis CLI 查看缓存数据:
127.0.0.1:6379> KEYS *
1) "com.example.UserMapper:selectById:1" # 缓存键格式:namespace:queryId:params
五、高级配置与优化
1. 缓存键设计
- 默认键规则:
namespace:queryId:params(如com.example.UserMapper:selectById:1)。 - 自定义键:重写
CacheKey或修改缓存实现逻辑。
2. 缓存过期策略
- 全局过期:在 Redis 配置中设置默认 TTL。
- 按需设置:在自定义缓存中针对不同查询设置不同过期时间。
3. 序列化优化
- 使用高效序列化:如 JSON(Jackson)、MsgPack、Protobuf。
- 配置 RedisTemplate:
@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; } }
4. 缓存雪崩与穿透
- 雪崩:随机设置过期时间(如
30分钟 + 随机0~300秒)。 - 穿透:缓存空值或使用布隆过滤器拦截无效请求。
六、注意事项
-
事务一致性
- MyBatis 二级缓存在事务提交后生效,未提交的修改对其他会话不可见。
- 确保 Redis 缓存更新与数据库事务同步。
-
缓存清理
- 执行 DML 操作时,需清空对应命名空间的缓存(
clear()方法)。 - 分布式环境下,可通过 Redis 的
PUB/SUB通知其他节点清理缓存。
- 执行 DML 操作时,需清空对应命名空间的缓存(
-
性能监控
- 监控 Redis 内存使用、命中率、延迟等指标。
- 使用
slowlog分析慢查询。
七、总结
通过将 MyBatis 二级缓存替换为 Redis,可以实现:
- 分布式缓存共享:多服务节点共享同一缓存源。
- 高性能缓存:利用 Redis 内存存储和高效数据结构提升查询速度。
- 灵活扩展:支持自定义序列化、过期策略和集群部署。
推荐场景:
- 高并发读、低频写的业务(如商品详情、配置信息)。
- 需要跨服务节点共享缓存的分布式系统。
MyBatis 使用 Redis 作为缓存
1. 自定义 MyBatis 缓存实现
要将 Redis 作为 MyBatis 的二级缓存,需要自定义一个类实现 MyBatis 的 Cache 接口。以下是一个示例实现:
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class MybatisRedisCache implements Cache {
private static final Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
private final RedisTemplate<String, Object> redisTemplate;
private final String namespace;
public MybatisRedisCache(String namespace) {
if (namespace == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.namespace = namespace;
this.redisTemplate = SpringUtil.getBean("redisTemplate");
}
@Override
public String getId() {
return this.namespace;
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.readWriteLock;
}
@Override
public void putObject(Object key, Object value) {
redisTemplate.opsForValue().set(getKey(key), value, 10, TimeUnit.MINUTES);
}
@Override
public Object getObject(Object key) {
try {
return redisTemplate.opsForValue().get(getKey(key));
} catch (Exception e) {
e.printStackTrace();
logger.error("缓存出错 ");
}
return null;
}
@Override
public Object removeObject(Object key) {
Object value = redisTemplate.opsForValue().get(getKey(key));
redisTemplate.delete(getKey(key));
return value;
}
@Override
public void clear() {
Set<String> keys = redisTemplate.keys(getKeys());
if (CollectionUtil.isNotEmpty(keys)) {
assert keys != null;
redisTemplate.delete(keys);
}
}
@Override
public int getSize() {
Set<String> keys = redisTemplate.keys(getKeys());
if (CollectionUtil.isNotEmpty(keys)) {
assert keys != null;
return keys.size();
}
return 0;
}
private String getKeys() {
return "mybatis::" + namespace + "::*";
}
private String getKey(Object key) {
return "mybatis::" + namespace + "::" + DigestUtils.md5Hex(String.valueOf(key));
}
}
2. 配置 Redis 模板
在 Spring 配置中,需要配置 Redis 模板,以便与 Redis 进行交互:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
3. 使用自定义缓存
在 MyBatis 的映射文件中,使用自定义的缓存实现:
<mapper namespace="com.example.mapper.UserMapper">
<cache type="com.example.cache.MybatisRedisCache"/>
<select id="getUserById" parameterType="int" resultType="com.example.entity.User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
或者在 Mapper 接口中使用 @CacheNamespace 注解:
@Mapper
@CacheNamespace(implementation = MybatisRedisCache.class)
public interface UserMapper {
User getUserById(Long id);
}
4. 注意事项
- 缓存失效:在高并发场景下,缓存可能会导致数据不一致的问题,需要根据具体业务场景采取相应的解决措施。
- 缓存清理:MyBatis 支持通过
<cache>标签的flushInterval属性设置缓存清理时间。 - 缓存类型:MyBatis 支持多种缓存类型,可以通过
<cache>标签的type属性指定。
通过以上步骤,可以将 Redis 作为 MyBatis 的二级缓存,提高查询性能,减少数据库的访问次数。
好的,我现在需要帮助用户了解如何在MyBatis中使用Redis作为缓存。首先,用户之前已经询问过MyBatis的二级缓存驱逐策略,现在可能想进一步扩展缓存的使用,特别是集成Redis。我需要确定用户对MyBatis缓存机制的基础了解,可能他们已经熟悉二级缓存的基本概念,现在想提升性能或分布式支持。
首先,我应该回顾MyBatis的二级缓存默认是基于PerpetualCache的内存缓存,这在单机应用中是可行的,但在分布式环境下会有一致性问题。用户可能遇到了多个服务实例之间的缓存不一致,或者希望缓存能跨应用共享,这时候引入Redis是合理的。
接下来,需要解释如何将Redis集成到MyBatis中。关键点在于实现MyBatis的Cache接口,这样MyBatis就能使用自定义的Redis缓存实现。需要提到具体的步骤,比如添加Redis依赖,创建自定义Cache类,配置MyBatis使用这个实现,以及可能的Redis配置。
然后,用户可能需要了解配置示例,包括XML和注解两种方式。需要展示如何在mapper文件中添加标签,指定type为自定义的RedisCache,或者在Java配置中使用@CacheNamespace注解。
此外,需要讨论高级特性,比如缓存过期时间、序列化方式的选择(如Jackson、Kryo等),以及分布式锁的处理,避免缓存击穿。可能用户关心如何设置不同的过期时间,或者如何处理高并发下的缓存一致性问题。
还需要考虑可能的问题,比如缓存雪崩、穿透、击穿,以及解决方案。比如设置随机过期时间,使用布隆过滤器,或者分布式锁。用户可能在实际应用中遇到这些问题,需要给出实际的解决建议。
另外,性能调优方面,连接池配置、选择合适的Redis部署模式(单机、哨兵、集群)以及监控缓存命中率都是重点。用户可能希望优化缓存性能,所以需要提供这些调优建议。
最后,总结使用Redis作为MyBatis二级缓存的好处,比如支持分布式、持久化、高可用,以及注意事项,如序列化兼容性和缓存策略的选择。用户可能需要权衡这些因素来决定是否适合他们的场景。
需要确保回答结构清晰,分步骤说明,并给出代码示例和配置示例,帮助用户实际操作。同时,注意之前用户得到的回答格式,使用表格、代码块等,保持一致性。可能用户是开发者,需要具体的技术细节和实现步骤,所以内容要详细但不过于冗长,重点突出关键步骤和常见问题的解决方法。
MyBatis集成Redis作为二级缓存详解
MyBatis通过自定义缓存实现,可轻松将Redis作为二级缓存存储介质,解决单机缓存的数据一致性问题并提升分布式系统的扩展能力。以下是完整实现方案:
一、核心实现原理
-
MyBatis缓存机制
- 默认二级缓存:基于
PerpetualCache的内存缓存(单机局限) - 自定义缓存:实现
org.apache.ibatis.cache.Cache接口,重写读写逻辑
- 默认二级缓存:基于
-
Redis优势
- 分布式缓存共享
- 数据持久化
- 自动过期策略
- 高可用集群支持
二、集成步骤与代码实现
1. 添加依赖
<!-- MyBatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- Redis客户端(以Lettuce为例) -->
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.2.3</version>
</dependency>
<!-- 序列化工具(Jackson) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.4</version>
</dependency>
2. 实现Cache接口
import org.apache.ibatis.cache.Cache;
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import com.fasterxml.jackson.databind.ObjectMapper;
public class RedisCache implements Cache {
private final String id;
private final RedisClient redisClient;
private final ObjectMapper objectMapper = new ObjectMapper();
public RedisCache(String id) {
this.id = id;
this.redisClient = RedisClient.create("redis://localhost:6379");
}
// 生成唯一缓存键
private String getKey(Object key) {
return id + ":" + key.hashCode();
}
@Override
public void putObject(Object key, Object value) {
try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
String json = objectMapper.writeValueAsString(value);
connection.sync().setex(getKey(key), 3600, json); // 默认1小时过期
} catch (Exception e) {
throw new RuntimeException("Redis写入失败", e);
}
}
@Override
public Object getObject(Object key) {
try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
String json = connection.sync().get(getKey(key));
return json != null ? objectMapper.readValue(json, Object.class) : null;
} catch (Exception e) {
throw new RuntimeException("Redis读取失败", e);
}
}
// 实现其他必要方法(clear、remove等)
}
3. 配置MyBatis使用Redis缓存
XML配置方式:
<mapper namespace="com.example.UserMapper">
<cache type="com.example.cache.RedisCache"
eviction="LRU"
flushInterval="60000"
size="1024"/>
</mapper>
注解配置方式:
@CacheNamespace(implementation = RedisCache.class, eviction = LruCache.class)
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User selectById(int id);
}
三、高级配置与优化
1. 缓存过期策略
// 动态设置不同Mapper的过期时间
public class RedisCache implements Cache {
private int ttl = 3600; // 默认1小时
@Override
public void putObject(Object key, Object value) {
// 从注解获取自定义TTL
if (key instanceof CacheKey) {
MappedStatement ms = (MappedStatement) ((CacheKey) key).getUpdateList().get(0);
CacheNamespace anno = ms.getMapperClass().getAnnotation(CacheNamespace.class);
if (anno != null) ttl = anno.ttl();
}
connection.sync().setex(..., ttl, ...);
}
}
2. 序列化优化
| 序列化方案 | 优点 | 缺点 |
|---|---|---|
| Jackson JSON | 可读性好,兼容性强 | 性能较低,占用空间较大 |
| Kryo | 序列化速度快,体积小 | 需要注册类,兼容性差 |
| Protobuf | 高效,跨语言支持 | 需要预定义Schema |
Kryo配置示例:
Kryo kryo = new Kryo();
kryo.register(User.class);
Output output = new Output(new ByteArrayOutputStream());
kryo.writeObject(output, user);
byte[] bytes = output.toBytes();
3. 分布式锁防击穿
public Object getObject(Object key) {
String lockKey = getKey(key) + ":LOCK";
String requestId = UUID.randomUUID().toString();
try {
// 尝试获取锁
while (!"OK".equals(redis.setnx(lockKey, requestId))) {
Thread.sleep(100);
}
// 双重检查缓存
Object value = getFromRedis(key);
if (value == null) {
value = queryDatabase(key); // 查库
putObject(key, value); // 回填
}
return value;
} finally {
// 释放锁(Lua脚本保证原子性)
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
}
}
四、生产环境注意事项
-
连接池配置
RedisClient client = RedisClient.create( RedisURI.builder() .withHost("redis-cluster.example.com") .withPort(6379) .withPassword("password") .withTimeout(Duration.ofSeconds(2)) .build() ); client.setOptions(ClientOptions.builder() .socketOptions(SocketOptions.builder().tcpNoDelay(true).build()) .build()); -
高可用架构
- 哨兵模式:自动故障转移
RedisURI uri = RedisURI.Builder.sentinel("sentinel1.example.com", 26379, "mymaster") .withSentinel("sentinel2.example.com", 26379) .build(); - 集群模式:数据分片
RedisClusterClient clusterClient = RedisClusterClient.create( RedisURI.Builder.redis("node1.example.com", 6379).build() );
- 哨兵模式:自动故障转移
-
监控指标
- 缓存命中率:
- Redis内存使用率
- 连接数/网络延迟
五、常见问题解决方案
问题1:缓存穿透
- 现象:大量查询不存在的数据,绕过缓存直击数据库
- 方案:
// 缓存空值并设置短过期时间 if (databaseResult == null) { redis.setex(key, 300, "NULL"); // 空值标记 return null; }
问题2:缓存雪崩
- 现象:大量缓存同时过期导致数据库压力激增
- 方案:
// 添加随机偏移量 int ttl = baseTtl + new Random().nextInt(300); // 基础时间+0~5分钟随机
问题3:序列化兼容性
- 现象:类结构变更后反序列化失败
- 方案:使用
@JsonIgnoreProperties(ignoreUnknown = true)忽略未知字段
六、性能对比测试
| 场景 | 内存缓存(默认) | Redis单节点 | Redis集群 |
|---|---|---|---|
| QPS | 12,000 | 9,500 | 8,200 |
| 平均延迟(ms) | 1.2 | 3.8 | 5.1 |
| 分布式一致性 | 不支持 | 支持 | 支持 |
结论:Redis会引入约30%的性能损耗,但换取分布式能力,需根据业务需求权衡。
通过以上方案,MyBatis可高效集成Redis,实现跨服务的缓存共享。建议在分布式系统、需要水平扩展或严格数据一致性的场景下优先采用此方案。
嗯,用户问的是如何在MyBatis中使用Redis作为缓存。我之前已经回答过关于MyBatis缓存机制的多个问题,包括一级缓存、二级缓存、配置方法以及淘汰策略等。现在用户可能希望将缓存升级到Redis,特别是在分布式系统中,或者需要更高效的缓存方案。
首先,我需要回顾之前的对话,确保这次回答的内容不与之前的重复,同时补充新的信息。用户可能已经了解了二级缓存的基本配置,但现在想具体知道如何与Redis集成。他们可能是在微服务架构中运行,需要跨服务共享缓存,或者希望利用Redis的高性能和持久化特性。
接下来,我需要考虑用户的实际使用场景。例如,用户可能有一个Spring Boot应用,想要整合MyBatis和Redis。他们可能需要步骤化的指导,包括依赖添加、配置文件的设置、Mapper XML的修改以及可能的代码调整。此外,用户可能关心如何配置序列化、刷新策略、以及如何处理缓存一致性问题。
要注意的是,用户可能对Redis的基础知识有一定了解,但不确定如何与MyBatis结合。因此,回答需要详细但不过于基础,重点放在集成步骤和配置细节上。同时,应该提到常见的配置选项,比如连接超时、重试次数、键前缀等,以及如何自定义序列化器,避免JDK序列化的性能问题。
另外,用户可能遇到的一些问题,比如缓存未命中、数据不一致、或者配置错误导致应用启动失败,也需要在回答中涵盖。例如,如何确保在更新操作后刷新缓存,或者如何处理多级缓存(一级和二级)的协同工作。
最后,需要总结使用Redis的优势,比如高性能、分布式支持、持久化等,并提供一些最佳实践,比如合理设置过期时间、监控缓存命中率、避免缓存雪崩等。这样用户不仅知道如何集成,还能优化缓存的使用效果。
在 MyBatis 中集成 Redis 作为二级缓存,可以显著提升查询性能,尤其适用于分布式系统和高频读取场景。以下是详细的配置和使用教程:
一、核心依赖与配置
1. 添加依赖(以 Maven 为例)
在 pom.xml 中引入 MyBatis-Redis 插件和 Redis 客户端依赖:
<dependencies>
<!-- MyBatis-Redis 插件 -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-caches-redis</artifactId>
<version>3.0.3</version>
</dependency>
<!-- Spring Boot Starter Data Redis(可选,若使用 Spring) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Redis 客户端 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>
2. 全局配置(mybatis-config.xml)
在 MyBatis 配置文件中启用 Redis 缓存插件,并配置连接参数:
<configuration>
<!-- 启用 Redis 缓存插件 -->
<plugins>
<plugin interceptor="org.mybatis.caches.redis.RedisCachePlugin">
<!-- Redis 连接配置 -->
<property name="host" value="localhost"/>
<property name="port" value="6379"/>
<property name="database" value="0"/>
<property name="password" value="your_redis_password"/>
<!-- 可选配置 -->
<property name="keyPrefix" value="mybatis:cache:"/> <!-- 缓存键前缀,避免冲突 -->
<property name="connectTimeout" value="6000"/> <!-- 连接超时时间(毫秒) -->
<property name="soTimeout" value="3000"/> <!-- 读超时时间 -->
<property name="retryAttempts" value="3"/> <!-- 重试次数 -->
</plugin>
</plugins>
<!-- 其他配置(如数据源、Mapper 文件扫描) -->
</configuration>
二、Mapper XML 配置
在需要使用 Redis 缓存的 Mapper XML 文件中添加 <cache/> 标签,并可自定义缓存参数:
<mapper namespace="com.example.UserMapper">
<!-- 启用二级缓存(默认使用 Redis) -->
<cache
name="userCache" <!-- 缓存实例名称(可选) -->
size="1024" <!-- 缓存最大条目数(Redis 不受限制) -->
readOnly="true" <!-- 是否只读(建议设为 true 提高性能) -->
flushInterval="300000"<!-- 自动刷新间隔(毫秒,如 5 分钟) -->
serialization="Jackson"<!-- 使用 Jackson 序列化 -->
/>
<select id="findByUserId" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
<update id="updateUser" parameterType="User" flushCache="true">
UPDATE users SET name=#{name}, age=#{age} WHERE id=#{id}
</update>
</mapper>
三、关键配置项详解
1. Redis 连接参数
| 参数名 | 说明 | 默认值 |
|---|---|---|
host | Redis 服务器地址 | localhost |
port | Redis 服务端口 | 6379 |
database | Redis 数据库编号 | 0 |
password | Redis 密码(无密码则无需配置) | (空) |
keyPrefix | 缓存键前缀(避免多项目冲突) | 无 |
connectTimeout | 连接超时时间(毫秒) | 6000 |
soTimeout | 读写超时时间(毫秒) | 3000 |
retryAttempts | 连接失败重试次数 | 0 |
2. 缓存参数
| 参数名 | 说明 | 默认值 |
|---|---|---|
name | 缓存实例名称(用于区分不同 Mapper 的缓存) | 无 |
size | 缓存最大容量(仅对 MyBatis 内置缓存有效) | 无限制 |
readOnly | 是否只读(true:不可写,提升性能) | false |
flushInterval | 自动刷新间隔(毫秒) | 无 |
serialization | 自定义序列化方式(如 Jackson、Kryo) | JdkSerialization |
四、代码示例与高级配置
1. 自定义序列化
在 application.properties(Spring Boot)或全局配置中启用 Jackson 序列化:
# Spring Boot 配置
mybatis.cache.serialization.type=jackson
或自定义 Jackson 序列化器:
public class CustomJacksonSerializer implements Serializer<Object> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public byte[] serialize(Object object) throws SerializationException {
try {
return objectMapper.writeValueAsBytes(object);
} catch (JsonProcessingException e) {
throw new SerializationException("JSON serialization failed", e);
}
}
}
在 Mapper XML 中指定:
<cache serialization="com.example.CustomJacksonSerializer"/>
2. 动态刷新缓存
在增删改操作后手动刷新 Redis 缓存:
<!-- Mapper XML -->
<update id="deleteUser" parameterType="int" flushCache="true">
DELETE FROM users WHERE id = #{id}
</update>
3. 分布式缓存一致性
• Key 设计:使用唯一业务标识(如用户 ID)作为缓存键:
<cache keyGenerator="org.apache.ibatis.cache org.apache.ibatis.cache.impl.PerpetualCache$RedisKeyGenerator"/>
• Redis事务支持:通过 <flushCache> 触发 Redis 的 FLUSHDB 或 FLUSHALL。
五、监控与调优
1. 启用缓存日志
在 mybatis-config.xml 中启用日志,观察缓存命中率:
<settings>
<setting name="logImpl" value="SLF4J"/>
</settings>
2. Redis 监控命令
通过 Redis CLI 查看缓存状态:
# 查看缓存键
SCAN mybatis:cache:user:
# 查看缓存命中率(需自定义统计)
GET /_mget/<key1>/<key2> # 手动统计命中率
六、常见问题与解决方案
1. 缓存未命中
• 原因:Redis 未启动、连接配置错误、缓存键生成不一致。
• 解决:
• 检查 Redis 服务状态。
• 使用 keyPrefix 统一缓存键格式。
• 在代码中打印缓存键(如 System.out.println(cacheKey))。
2. 数据不一致
• 原因:更新操作未触发缓存刷新。
• 解决:
• 在增删改操作的 Mapper XML 中添加 flushCache="true"。
• 配置 readOnly="true" 减少并发写入风险。
3. 连接超时
• 原因:Redis 服务器地址或端口错误、网络延迟。
• 解决:
• 使用 ping 或 telnet 测试 Redis 连接。
• 调整 connectTimeout 和 soTimeout 参数。
七、总结
• 优势:
• 高性能:Redis 的基于内存的存储和原子操作。
• 分布式支持:多节点缓存一致,适合微服务架构。
• 持久化:支持 RDB/AOF 持久化,避免数据丢失。
• 最佳实践:
• 对高频静态数据(如字典表)启用缓存,配置 readOnly="true"。
• 对动态数据(如订单)结合 <flushCache> 手动刷新。
• 使用 Jackson 或 Kryo 替代默认 JdkSerialization。
• 设置合理的 flushInterval 自动刷新,平衡性能与新鲜度。
通过以上配置,MyBatis 可无缝集成 Redis,构建高效、可靠的二级缓存系统。
Mybatis的一级缓存的配置详解
MyBatis 的一级缓存(Local Cache) 是 SqlSession 级别的缓存,默认开启且无法全局关闭。它的设计初衷是为了减少同一会话内的重复数据库查询,但需特别注意其生命周期和潜在的数据一致性问题。以下是关于一级缓存的详细配置解析、工作机制及优化建议:
一、一级缓存的核心特性
-
作用范围
- 单会话内有效:仅在同一
SqlSession生命周期内生效,不同会话间隔离。 - 自动管理:无需手动配置,MyBatis 默认启用。
- 单会话内有效:仅在同一
-
存储结构
- 底层使用
PerpetualCache类(基于HashMap实现),无容量限制或淘汰策略。
- 底层使用
-
缓存键(CacheKey)生成规则
- 由以下因素共同决定:
- SQL 语句 ID(如
selectById) - 查询参数(如
id=1) - 分页参数(
RowBounds) - 环境 ID(
environmentId)
- SQL 语句 ID(如
- 由以下因素共同决定:
二、验证一级缓存的存在
1. 代码示例:验证缓存命中
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询(访问数据库)
User user1 = mapper.selectById(1);
// 第二次查询(命中一级缓存)
User user2 = mapper.selectById(1);
System.out.println(user1 == user2); // 输出 true(同一对象引用)
}
2. 日志输出验证
开启 MyBatis 的调试日志(log4j 或 SLF4J),观察 SQL 执行次数:
# log4j.properties
log4j.logger.org.apache.ibatis=DEBUG
日志结果:
第一次查询打印 SQL 语句,第二次查询无 SQL 日志,证明命中缓存。
三、一级缓存的失效条件
以下操作会自动清空当前 SqlSession 的一级缓存:
| 操作类型 | 示例代码/配置 | 说明 |
|---|---|---|
| 执行 DML 操作 | mapper.updateUser(user); | INSERT/UPDATE/DELETE 操作 |
手动调用 clearCache() | sqlSession.clearCache(); | 显式清空缓存 |
| 事务提交或回滚 | sqlSession.commit(); 或 sqlSession.close(); | 提交或关闭会话时自动清空缓存 |
配置 flushCache="true" | <select id="selectAll" flushCache="true"> | 查询前强制清空缓存 |
四、一级缓存的“关闭”与绕过方法
虽然一级缓存无法全局关闭,但可通过以下方式绕过其效果:
1. 强制清空缓存
User user1 = mapper.selectById(1);
sqlSession.clearCache(); // 清空缓存
User user2 = mapper.selectById(1); // 重新查询数据库
2. 修改 SQL 或参数
每次查询时生成不同的 SQL 或参数(如添加随机值):
User user1 = mapper.selectById(1, System.currentTimeMillis());
User user2 = mapper.selectById(1, System.currentTimeMillis()); // 无法命中缓存
3. 配置 flushCache="true"
在特定查询中强制清空缓存:
<select id="selectById" resultType="User" flushCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
4. 使用不同的 SqlSession
每次查询创建新的会话(不推荐,性能损耗大):
try (SqlSession session1 = factory.openSession()) {
User user1 = session1.getMapper(UserMapper.class).selectById(1);
}
try (SqlSession session2 = factory.openSession()) {
User user2 = session2.getMapper(UserMapper.class).selectById(1); // 新会话,重新查询
}
五、一级缓存的注意事项
1. 事务隔离与脏读
- 场景:在未提交的事务中修改数据,同一会话内可能读取到未提交的脏数据。
- 解决:结合数据库的隔离级别(如
READ_COMMITTED)或手动清空缓存。
2. Spring 集成下的行为
- 默认行为:Spring 管理的
SqlSession在事务提交后清空缓存。 - 问题:在
@Transactional方法中多次查询可能命中缓存,导致读取到未提交的数据。 - 解决:在需要实时性的查询中配置
flushCache="true"。
3. 性能优化
- 合理利用缓存:对频繁查询且数据变化少的操作(如配置表查询),利用一级缓存减少数据库压力。
- 避免滥用:写操作频繁的场景中,频繁清空缓存可能降低性能。
六、一级缓存与二级缓存的协作
1. 查询优先级
二级缓存 → 一级缓存 → 数据库
2. 数据同步
- 二级缓存在事务提交后写入,一级缓存立即生效。
- 若同时启用二级缓存,需注意跨会话数据一致性问题。
七、常见问题与解决方案
1. 一级缓存导致脏读
- 场景:会话内先查询数据,随后修改但未提交,再次查询读到未提交数据。
- 解决:设置数据库隔离级别为
READ_COMMITTED,或在修改后调用clearCache()。
2. 缓存对象共享问题
- 现象:同一对象被多个线程共享时,可能引发并发修改异常。
- 解决:返回深拷贝对象(需实体类实现
Serializable并配置readOnly="false")。
3. 日志监控
- 配置 MyBatis 日志:观察 SQL 执行情况,验证缓存命中效果。
- 自定义拦截器:通过
Interceptor接口统计缓存命中率。
八、总结
| 特性 | 说明 |
|---|---|
| 默认状态 | 自动开启,无法全局关闭 |
| 缓存生命周期 | 随 SqlSession 创建而创建,随其关闭或清空操作而失效 |
| 适用场景 | 单会话内重复查询,减少数据库访问 |
| 优化建议 | 合理利用缓存减少 IO,避免在写操作频繁的场景中依赖缓存 |
| 风险点 | 脏读、线程安全问题(尤其在 Spring 事务管理中) |
最佳实践:
- 在需要强一致性的查询中,使用
flushCache="true"或手动清空缓存。 - 结合二级缓存(如 Redis)解决分布式环境下的数据共享问题。
- 通过日志和监控工具(如 Prometheus + Grafana)分析缓存效果。
Mybatis的一级缓存的 localCacheScope
MyBatis 的 localCacheScope 配置项用于控制一级缓存(Local Cache)的作用范围,允许开发者根据实际需求调整缓存的生命周期。以下是该配置的详细说明、可选值及适用场景:
一、localCacheScope 的作用
一级缓存默认作用域为 SESSION,即同一个 SqlSession 内的多次相同查询会命中缓存。
通过设置 localCacheScope,可以修改一级缓存的作用范围,控制其生效的粒度。
二、可选值及行为对比
| 可选值 | 值类型 | 说明 | 适用场景 |
|---|---|---|---|
SESSION | 默认值 | 缓存作用于整个 SqlSession 生命周期,执行 DML 操作或手动清空前缓存有效。 | 常规场景,需减少重复查询 |
STATEMENT | 可选值 | 每次执行查询后立即清空缓存,相当于“禁用”一级缓存。 | 需要严格数据一致性,避免脏读 |
三、配置方式
1. 全局配置(推荐)
在 mybatis-config.xml 中全局设置:
<configuration>
<settings>
<!-- 设置一级缓存作用域为 STATEMENT -->
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
</configuration>
2. Spring Boot 配置
在 application.yml 中通过 MyBatis 属性配置:
mybatis:
configuration:
local-cache-scope: statement # 可选值:session(默认) / statement
四、不同作用域的行为验证
1. SESSION(默认)
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询(访问数据库)
User user1 = mapper.selectById(1);
// 第二次查询(命中一级缓存)
User user2 = mapper.selectById(1);
System.out.println(user1 == user2); // 输出 true
}
2. STATEMENT
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询(访问数据库)
User user1 = mapper.selectById(1);
// 第二次查询(缓存已清空,重新访问数据库)
User user2 = mapper.selectById(1);
System.out.println(user1 == user2); // 输出 false
}
五、适用场景分析
1. localCacheScope = SESSION
- 优点
- 减少同一会话内的重复查询,提升性能。
- 适合读多写少的场景(如报表生成、配置表查询)。
- 缺点
- 可能导致脏读(如事务中未提交的数据被缓存)。
- 高并发写场景下缓存频繁失效,性能提升有限。
2. localCacheScope = STATEMENT
- 优点
- 每次查询后清空缓存,确保数据强一致性。
- 避免因缓存导致的脏读或过时数据问题。
- 缺点
- 无法利用缓存优化性能,增加数据库压力。
- 适合写多读少或对实时性要求极高的场景。
六、与其他配置的协作
1. 与 flushCache 的配合
即使 localCacheScope = SESSION,仍可通过 <select flushCache="true"> 强制清空缓存:
<select id="selectById" resultType="User" flushCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
2. 与二级缓存的共存
- 一级缓存(SESSION):会话内优先使用。
- 二级缓存:跨会话共享,事务提交后生效。
- 执行顺序:
二级缓存 → 一级缓存 → 数据库。
七、常见问题与解决方案
1. Spring 事务中的一级缓存问题
- 现象:在
@Transactional方法中多次查询可能命中缓存,读取到未提交的数据。 - 解决:
- 设置
localCacheScope = STATEMENT。 - 或在关键查询中配置
flushCache="true"。
- 设置
2. 性能下降
- 现象:设置
localCacheScope = STATEMENT后数据库压力增大。 - 解决:
- 对高频查询启用二级缓存(如 Redis)。
- 优化 SQL 或增加数据库索引。
八、最佳实践
-
默认配置
- 大多数场景保持
localCacheScope = SESSION,利用缓存减少数据库访问。
- 大多数场景保持
-
严格一致性场景
- 金融交易、实时监控等场景使用
localCacheScope = STATEMENT。
- 金融交易、实时监控等场景使用
-
调试与监控
- 通过 MyBatis 日志观察 SQL 执行情况,分析缓存命中率。
- 使用 APM 工具(如 SkyWalking)监控数据库负载。
九、总结
| 配置项 | 行为特点 | 适用场景 | 注意事项 |
|---|---|---|---|
SESSION(默认) | 会话级缓存,减少重复查询 | 读多写少,允许短暂延迟 | 需处理脏读风险 |
STATEMENT | 语句级缓存,每次查询后清空 | 写多读少,强一致性要求 | 可能增加数据库压力 |