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 的**一级缓存(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 中二级缓存的开启和关闭,以满足不同的应用场景和需求。
━━━━━━━━━━━━━━
一、二级缓存核心特性
━━━━━━━━━━━━━━
- 作用范围: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 的**二级缓存(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 二级缓存深度解析(含原理、配置、最佳实践)
一、二级缓存核心原理
存储层级
- 一级缓存(SqlSession级):默认开启,生命周期与 SqlSession 一致
- 二级缓存(SqlSessionFactory级):跨会话共享,需手动配置
工作流程
缓存键生成规则
MapperID + 方法ID + 参数哈希值 + SQL语句哈希值
二、高级配置详解
1. 缓存淘汰策略(eviction)
策略 | 描述 | 适用场景 |
---|---|---|
LRU | 最近最少使用(默认) | 通用场景 |
FIFO | 先进先出 | 按时间顺序访问 |
SOFT | 软引用(内存不足时回收) | 内存敏感场景 |
WEAK | 弱引用(垃圾回收时回收) | 高频对象访问 |
2. 刷新间隔(flushInterval)
- 单位:毫秒
- 建议值:高频更新表(3000-60000ms),低频表(300000ms+)
- 示例配置:
<cache eviction="LRU" flushInterval="60000" size="512"/>
3. 只读模式(readOnly)
true
:返回对象副本(线程安全,性能略低)false
:返回对象引用(需实现序列化)- 推荐:写操作频繁时设为 false
三、最佳实践方案
1. 缓存使用场景决策树
2. 分布式环境优化方案
- 方案一:Redis 中央缓存
<cache type="org.mybatis.caches.redis.RedisCache"/>
- 方案二:Ehcache 集群模式
<cache type="org.mybatis.caches.ehcache.EhcacheCache"> <property name="timeToLiveSeconds" value="3600"/> </cache>
3. 缓存一致性保障策略
- 写操作后强制刷新:
userMapper.updateUser(user);
sqlSession.commit();
userMapper.clearCache(); // 清除本Mapper缓存
sqlSessionFactory.getConfiguration().getCacheNames().forEach(
cacheName -> sqlSessionFactory.getConfiguration().getCache(cacheName).clear()
); // 全局清除
四、性能优化技巧
缓存穿透防护
对空结果也进行缓存(设置较短过期时间):<select id="findUser" resultType="User"> SELECT * FROM users WHERE id = #{id} <if test="id != null"> WHERE id = #{id} </if> </select>
缓存雪崩预防
设置随机过期时间:Random random = new Random(); int expireTime = 300000 + random.nextInt(300000); // 5-10分钟
序列化优化
使用 Kryo 序列化:<cache type="org.mybatis.caches.kryo.KryoCache"/>
五、监控与诊断
日志监控
在 log4j.properties 中添加:log4j.logger.org.apache.ibatis.cache=DEBUG
命中率统计
自定义缓存拦截器:@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class CacheInterceptor implements Interceptor { private int hitCount = 0; private int missCount = 0; @Override public Object intercept(Invocation invocation) throws Throwable { // 统计缓存命中情况 return invocation.proceed(); } public double getHitRate() { return (double) hitCount / (hitCount + missCount); } }
可视化监控
集成 Prometheus + Grafana:<dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency>
六、典型问题解决方案
缓存脏读问题
- 解决方案:对写操作使用
select ... for update
+ 缓存刷新
- 解决方案:对写操作使用
内存泄漏风险
- 预防措施:设置合理的
size
参数,监控缓存对象大小
- 预防措施:设置合理的
序列化异常
- 排查方法:检查实体类是否实现 Serializable 接口,排除非序列化字段
通过深入理解二级缓存机制,结合业务场景进行精细化配置,可以显著提升系统性能。建议通过 JMeter 等工具进行压力测试,找到最佳缓存策略参数组合。
以下是关于 MyBatis 二级缓存的详细解析,涵盖其核心概念、配置方法、使用场景及注意事项:
一、二级缓存的核心概念
作用范围:
- 跨
SqlSession
的全局缓存:二级缓存的作用域是 命名空间(即 Mapper 接口),多个SqlSession
可以共享同一个 Mapper 的缓存。 - 与一级缓存的区别:一级缓存是
SqlSession
级别的,而二级缓存是 跨SqlSession
的,适用于更广泛的场景。
- 跨
默认状态:
- 默认关闭:MyBatis 默认不开启二级缓存,需手动配置。
数据存储:
- 可读写缓存(
readOnly="false"
):缓存对象的序列化数据,每次查询需反序列化,线程安全但性能稍低。 - 只读缓存(
readOnly="true"
):缓存对象的直接引用,直接返回对象,性能高但需确保数据不可变,否则可能导致脏读。
- 可读写缓存(
二、二级缓存的配置步骤
1. 全局开启二级缓存
在 MyBatis 核心配置文件(如 mybatis-config.xml
)中设置:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
或在 Spring Boot 中通过 application.yml
配置:
mybatis:
configuration:
cache-enabled: true
2. 在 Mapper 文件中启用二级缓存
在需要使用二级缓存的 Mapper XML 文件中添加 <cache>
标签:
<mapper namespace="com.example.mapper.UserMapper">
<!-- 开启二级缓存 -->
<cache
eviction="LRU" <!-- 缓存回收策略 -->
flushInterval="60000" <!-- 自动刷新间隔(毫秒) -->
size="1024" <!-- 缓存最大条目数 -->
readOnly="true"/> <!-- 是否只读 -->
<!-- 其他 SQL 语句 -->
<select id="selectUserById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
3. 配置参数详解
eviction
:缓存回收策略(默认LRU
):LRU
(最近最少使用):淘汰最久未被使用的对象。FIFO
(先进先出):淘汰最早存入的对象。SOFT
(软引用):基于 JVM 内存需求回收。WEAK
(弱引用):对象可能被随时回收。
flushInterval
:自动刷新间隔(单位:毫秒)。例如60000
表示每 60 秒刷新一次。size
:缓存最大容量(默认1024
条)。readOnly
:true
:缓存对象的直接引用(性能高,但需确保数据不可变)。false
:缓存对象的序列化数据(线程安全,但性能稍低)。
4. 实体类实现序列化
如果 readOnly="false"
,实体类必须实现 Serializable
接口:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
// 属性和方法
}
三、二级缓存的工作流程
查询流程:
- 优先检查二级缓存:如果命中,直接返回缓存数据。
- 未命中则检查一级缓存:如果命中,返回一级缓存数据。
- 未命中则查询数据库:结果存入一级缓存和二级缓存。
数据提交到二级缓存:
- 当
SqlSession
提交(commit
)或关闭(close
)时,一级缓存的数据会提交到二级缓存。
- 当
四、二级缓存的失效场景
- 自动清空缓存:
- 执行 增、删、改 操作后,当前 Mapper 的二级缓存会被清空。
- 达到
flushInterval
设置的刷新时间间隔。
- 手动清空缓存:
- 调用
SqlSessionFactory.clearCache()
清空指定 Mapper 的二级缓存。 - 调用
SqlSession.clearCache()
清空当前SqlSession
的一级和二级缓存。
- 调用
五、注意事项与最佳实践
1. 脏读问题
- 只读缓存(
readOnly="true"
)的风险:- 多个
SqlSession
可能共享同一对象引用,若其中一个会话修改了对象,其他会话会看到脏数据。 - 解决方案:确保缓存对象是不可变的(Immutable),或使用
readOnly="false"
并接受序列化开销。
- 多个
- 并发环境建议:
- 对于频繁更新的数据,建议关闭二级缓存或使用第三方缓存(如 Redis)。
2. 缓存命中率优化
- 合理设置
size
和eviction
:根据业务场景调整缓存容量和回收策略。 - 避免复杂查询缓存:二级缓存默认缓存 完整查询结果,对
JOIN
或动态 SQL 的缓存效果较差。
3. 与一级缓存的配合
- 优先级:二级缓存的查询优先级高于一级缓存,但二级缓存的写入依赖一级缓存。
4. 日志验证
- 开启 MyBatis 日志(如
log4j
或STDOUT_LOGGING
),查看缓存命中情况:<settings> <setting name="logImpl" value="LOG4J"/> </settings>
六、验证二级缓存是否生效
示例代码:
// 第一个 SqlSession
try (SqlSession session1 = sqlSessionFactory.openSession()) {
UserMapper mapper1 = session1.getMapper(UserMapper.class);
User user1 = mapper1.selectUserById(1); // 第一次查询,数据库查询
}
// 第二个 SqlSession
try (SqlSession session2 = sqlSessionFactory.openSession()) {
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user2 = mapper2.selectUserById(1); // 第二次查询,命中二级缓存
System.out.println(user1 == user2); // 输出 true(二级缓存生效)
}
七、总结
特性 | 一级缓存 | 二级缓存 |
---|---|---|
作用范围 | SqlSession 级别 |
Mapper 级别(跨 SqlSession ) |
默认状态 | 默认开启 | 默认关闭 |
配置复杂度 | 无需配置 | 需手动配置 Mapper 和全局开关 |
适用场景 | 单次会话内的多次查询 | 多会话共享的频繁查询(数据不频繁更新) |
数据一致性风险 | 无 | 可能存在脏读(需谨慎使用 readOnly ) |
通过合理配置二级缓存,可以显著提升跨会话的查询性能,但需权衡数据一致性、序列化开销和并发场景。对于高并发写入的场景,建议结合版本控制或使用分布式缓存(如 Redis)。
MyBatis 的二级缓存是一种更为高级的缓存机制,它是 Mapper 级别的缓存,多个 SqlSession
可以共享同一个 Mapper 的二级缓存。下面将从多个方面对 MyBatis 二级缓存进行详细解析。
1. 二级缓存的基本原理
当开启二级缓存后,MyBatis 会为每个 Mapper 创建一个 Cache
对象,用于存储查询结果。在执行查询操作时,MyBatis 会先根据查询的 CacheKey
(包含 SQL 语句、参数等信息)从二级缓存中查找对应的结果。如果找到,就直接返回缓存中的结果,而不会再执行数据库查询;如果未找到,则执行数据库查询,并将查询结果存入二级缓存,以便后续相同的查询可以直接从缓存获取数据。
2. 开启二级缓存的步骤
2.1 全局开启二级缓存
在 mybatis-config.xml
配置文件中,通过 <settings>
标签开启全局的二级缓存:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
这一步是开启二级缓存的基础,只有将 cacheEnabled
设置为 true
,后续 Mapper 的二级缓存才有可能生效。
2.2 为特定 Mapper 开启二级缓存
可以通过两种方式为特定的 Mapper 开启二级缓存。
方式一:在 Mapper XML 文件中配置
在对应的 Mapper XML 文件中添加 <cache>
标签:
<mapper namespace="com.example.mapper.UserMapper">
<cache/>
<!-- 具体的 SQL 查询语句 -->
<select id="selectUserById" parameterType="int" resultType="com.example.entity.User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
<cache>
标签会为该 Mapper 启用默认配置的二级缓存。
方式二:在 Mapper 接口上使用注解
如果使用注解方式开发,可以在 Mapper 接口上添加 @CacheNamespace
注解:
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Select;
@CacheNamespace
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User selectUserById(int id);
}
3. 二级缓存的配置参数
<cache>
标签或 @CacheNamespace
注解可以配置一些参数来定制二级缓存的行为:
3.1 eviction
(缓存清除策略)
- LRU(Least Recently Used):最近最少使用,移除最长时间不被使用的对象,这是默认的清除策略。
- FIFO(First In First Out):先进先出,按对象进入缓存的顺序来移除它们。
- SOFT:软引用,基于垃圾回收器状态和软引用规则移除对象。
- WEAK:弱引用,更积极地基于垃圾回收器状态和弱引用规则移除对象。
示例(XML 配置):
<cache eviction="LRU"/>
示例(注解配置):
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.cache.decorators.LruCache;
@CacheNamespace(eviction = LruCache.class)
public interface UserMapper {
// ...
}
3.2 flushInterval
(刷新间隔)
指定缓存多长时间清空一次,单位是毫秒。可以设置一个具体的数值,如 60000
表示每分钟清空一次缓存。
示例(XML 配置):
<cache flushInterval="60000"/>
示例(注解配置):
import org.apache.ibatis.annotations.CacheNamespace;
@CacheNamespace(flushInterval = 60000)
public interface UserMapper {
// ...
}
3.3 size
(缓存大小)
指定缓存可以存储的对象数量上限。
示例(XML 配置):
<cache size="100"/>
示例(注解配置):
import org.apache.ibatis.annotations.CacheNamespace;
@CacheNamespace(size = 100)
public interface UserMapper {
// ...
}
3.4 readOnly
(只读缓存)
true
:表示缓存中的对象是只读的,MyBatis 会直接返回缓存对象的引用,这种方式性能较高,但如果多个地方修改该对象会影响数据一致性。false
:表示缓存中的对象是可读写的,MyBatis 会通过序列化和反序列化来返回对象的副本,保证数据的独立性,但性能相对较低。
示例(XML 配置):
<cache readOnly="true"/>
示例(注解配置):
import org.apache.ibatis.annotations.CacheNamespace;
@CacheNamespace(readOnly = true)
public interface UserMapper {
// ...
}
4. 二级缓存的使用注意事项
4.1 序列化要求
存储在二级缓存中的对象必须实现 java.io.Serializable
接口,因为缓存可能会将对象序列化到磁盘。例如:
import java.io.Serializable;
public class User implements Serializable {
private int id;
private String name;
// 构造函数、getter 和 setter 方法
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4.2 缓存一致性问题
当执行插入、更新、删除操作时,MyBatis 会自动清空对应的 Mapper 的二级缓存,以保证数据的一致性。但在分布式环境下,多个应用实例共享同一个数据库时,可能会出现缓存不一致的问题,此时可以考虑使用分布式缓存(如 Redis)来替代 MyBatis 自带的二级缓存。
4.3 性能影响
虽然二级缓存可以减少数据库查询次数,提高性能,但如果缓存配置不合理,如缓存大小设置过小、刷新间隔设置过短等,可能会导致频繁的缓存清除和重建,反而影响性能。
5. 代码示例展示二级缓存的效果
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()) {
UserMapper userMapper1 = session1.getMapper(UserMapper.class);
User user1 = userMapper1.selectUserById(1);
System.out.println("第一个 SqlSession 查询结果: " + user1.getName());
}
// 第二个 SqlSession
try (SqlSession session2 = sqlSessionFactory.openSession()) {
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
User user2 = userMapper2.selectUserById(1);
System.out.println("第二个 SqlSession 查询结果: " + user2.getName());
}
}
}
在这个示例中,如果二级缓存开启且配置正确,第二个 SqlSession
的查询会直接从二级缓存中获取结果,而不会再次执行数据库查询。
MyBatis 的二级缓存是基于 SqlSessionFactory
的缓存机制,适用于整个 SqlSessionFactory
实例,可以跨多个 SqlSession
共享缓存数据。以下是二级缓存的详解:
1. 开启二级缓存
全局配置:在
mybatis-config.xml
文件中,确保<settings>
标签中包含以下配置:<settings> <setting name="cacheEnabled" value="true"/> </settings>
默认情况下,
cacheEnabled
的值为true
,因此通常无需显式配置。局部配置:在对应的
Mapper.xml
文件中,添加<cache>
标签以启用二级缓存:<mapper namespace="com.example.mapper.UserMapper"> <!-- 开启二级缓存 --> <cache/> <!-- 或者可以自定义缓存属性 --> <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/> <!-- 其他 SQL 映射语句 --> </mapper>
实体类序列化:使用二级缓存的实体类必须实现
Serializable
接口。
2. 工作原理
- 查询流程:当查询数据时,MyBatis 会先从二级缓存中查找,如果存在则直接返回;如果不存在,再从一级缓存中查找;如果一级缓存也没有,则查询数据库,并将查询结果同时存入一级缓存和二级缓存。
- 数据写入:一级缓存中的数据只有在
SqlSession
关闭或提交时才会被写入二级缓存。 - 缓存失效:所有的
update
操作(insert
、delete
、update
)都会触发缓存的刷新,从而导致二级缓存失效。
3. 配置选项
eviction
:缓存回收策略,默认为LRU
(最近最少使用),其他选项包括FIFO
(先进先出)、SOFT
(软引用)、WEAK
(弱引用)。flushInterval
:缓存刷新间隔,默认情况下不会自动刷新,可以设置毫秒级的刷新间隔。size
:缓存的数量,默认没有限制。readOnly
:是否只读,默认为false
,如果设置为true
,则提高并发性能,但所有返回的对象在缓存期间不可修改。
4. 注意事项
- 缓存失效:在高并发场景下,缓存可能会导致数据不一致的问题,需要根据具体业务场景采取相应的解决措施。
- 缓存清理:MyBatis 支持通过
<cache>
标签的flushInterval
属性设置缓存清理时间。 - 缓存类型:MyBatis 支持多种缓存类型,可以通过
<cache>
标签的type
属性指定。
5. 示例
<!-- mybatis-config.xml -->
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
<select id="getUserById" parameterType="int" resultType="com.example.entity.User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
public class User implements Serializable {
private Integer id;
private String name;
// getter 和 setter 方法
}
SqlSession sqlSession1 = MyBatisUtil.getSqlSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.getUserById(1);
sqlSession1.close();
SqlSession sqlSession2 = MyBatisUtil.getSqlSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.getUserById(1); // 从二级缓存中获取数据
sqlSession2.close();
通过合理使用二级缓存,可以有效减少数据库的访问次数,提高查询性能。
MyBatis二级缓存详解(分步解析)
一、缓存层级对比
一级缓存
- 作用范围:
SqlSession
级别(默认开启) - 生命周期:随
SqlSession
的创建而生效,关闭或执行commit()
/rollback()
时清空 - 特点:同一会话内多次相同查询直接返回缓存结果
- 作用范围:
二级缓存
- 作用范围:
Mapper
级别(需手动开启) - 生命周期:跨
SqlSession
,应用运行期间持续有效(可配置过期策略) - 特点:不同会话共享缓存,数据持久性更高
- 作用范围:
二、二级缓存核心机制
存储结构
- 底层使用
PerpetualCache
作为基础存储(HashMap实现) - 通过装饰器模式添加功能(如LRU淘汰策略、序列化支持)
- 底层使用
生效条件
- 查询语句需显式声明
useCache="true"
(默认true) - 事务提交后才会缓存(未提交事务的查询结果不缓存)
- 查询顺序:二级缓存 → 一级缓存 → 数据库
- 查询语句需显式声明
失效场景
- 执行了
<insert>/<update>/<delete>
操作 - 手动调用
flushCache="true"
- 达到配置的
flushInterval
时间间隔
- 执行了
三、配置步骤(XML方式)
全局启用
在mybatis-config.xml
中配置:<settings> <setting name="cacheEnabled" value="true"/> </settings>
Mapper级配置
在对应Mapper XML中添加:<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
- 参数说明
eviction
:淘汰策略(LRU/FIFO/SOFT/WEAK)flushInterval
:自动刷新时间(毫秒)size
:最大缓存对象数readOnly
:是否只读(性能优化关键)
- 参数说明
四、高级特性
缓存引用
多个Mapper共享缓存:<cache-ref namespace="com.example.OtherMapper"/>
部分禁用
在特定语句关闭缓存:<select id="selectAll" resultType="User" useCache="false">
序列化要求
- 当
readOnly="false"
时,返回对象必须实现Serializable
接口 - 深度拷贝避免缓存对象被修改污染
- 当
五、事务同步机制
TransactionalCache
- 临时存储未提交事务的修改
- 事务提交时批量写入二级缓存
- 事务回滚时自动清空临时数据
并发控制
- 通过
ReadWriteLock
实现读写分离 - 写操作独占锁,读操作共享锁
- 通过
六、性能优化建议
合理选择策略
- 高频读:LRU + readOnly
- 写后立即读:
flushCache="true"
监控手段
- 启用日志统计缓存命中率:
<configuration> <settings> <setting name="logImpl" value="SLF4J"/> </settings> </configuration>
七、分布式场景问题
一致性挑战
- 多服务实例导致本地缓存不一致
- 解决方案:集成Redis等中央缓存(通过实现
Cache
接口)
序列化协议
- 推荐使用JSON/Protobuf替代JDK序列化
- 示例Redis集成配置:
public class RedisCache implements Cache { // 自定义实现逻辑 }
八、调试技巧
缓存命中验证
- 观察SQL日志:未打印SQL说明命中缓存
- 使用DEBUG模式查看
CacheKey
生成
常见异常处理
NotSerializableException
:检查实体类序列化CacheException
:检查淘汰策略配置
九、适用场景分析
场景 | 适用性 | 理由 |
---|---|---|
配置表查询 | ★★★★★ | 数据变更频率极低 |
实时交易系统 | ★☆☆☆☆ | 数据强一致性要求高 |
报表分析 | ★★★★☆ | 大数据量重复查询 |
高频更新业务 | ★☆☆☆☆ | 缓存频繁失效 |
缓存收益比 = 读频率 写频率 × 单次查询开销 \text{缓存收益比} = \frac{\text{读频率}}{\text{写频率}} \times \text{单次查询开销} 缓存收益比=写频率读频率×单次查询开销
建议当收益比 > 5时启用二级缓存
一、二级缓存的核心机制
1. 缓存层级关系
• 一级缓存:绑定到单个 SqlSession
,生命周期短。
• 二级缓存:全局共享,生命周期长,所有 SqlSession
可访问。
• 工作流程:
- 查询先检查一级缓存 → 未命中则查二级缓存 → 未命中则查数据库。
- 数据首次查询时,结果会存入一级缓存和二级缓存。
- 后续查询优先从一级缓存读取,一级缓存未命中时从二级缓存读取。
2. 缓存键生成
• 默认规则:基于 SQL 语句、参数、结果集元数据(如列名、类型)生成唯一键。
• 自定义键:通过实现 org.apache.ibatis.cache.CacheKey
接口自定义生成逻辑。
3. 缓存刷新机制
• 自动刷新:执行 INSERT
/UPDATE
/DELETE
时,MyBatis 会自动清除受影响实体的二级缓存。
• 手动刷新:
• XML 中标记 <flushCache>
:<update ... flushCache="true"/>
。
• Java 代码调用 sqlSession.clearCache()
。
二、启用与配置二级缓存
1. 基础配置步骤
• (1) 启用 Mapper 缓存
在 Mapper XML 中添加 <cache/>
标签:
<mapper namespace="com.example.UserMapper">
<cache/> <!-- 开启二级缓存 -->
<select id="findByUserId" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
• (2) 全局配置缓存插件
引入第三方缓存依赖(如 EhCache、Redis)并配置插件:
<!-- mybatis-config.xml -->
<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. 自定义缓存实现
• 实现 Cache
接口:自定义存储逻辑(如基于内存、文件、数据库)。
public class CustomCache implements Cache {
private Map<Object, Object> cache = new ConcurrentHashMap<>();
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
// 其他方法实现(如 remove、clear 等)
}
• 在 Mapper 中指定自定义缓存:
<cache type="com.example.CustomCache"/>
三、缓存策略与优化
1. 缓存序列化
• 默认序列化:MyBatis 使用 JdkSerialization
,但效率较低。
• 自定义序列化:
• Jackson:在 Mapper XML 中配置:
xml <cache serialization="Jackson"/>
• Kryo:全局配置 mybatis-config.xml
:
xml <settings> <setting name="cacheSerializationEnabled" value="true"/> <setting name="kryoEnabled" value="true"/> </settings>
2. 缓存淘汰策略
• 默认策略:LRU(Least Recently Used)。
• 自定义策略:
• EhCache:配置 ehcache.xml
中的 maxEntriesLocalHeap
、timeToLiveSeconds
等参数。
• Redis:通过 expire
命令设置键的过期时间。
3. 数据一致性保障
• 自动刷新:增删改操作触发二级缓存刷新。
• 手动刷新:
<!-- 在 Mapper XML 中强制刷新 -->
<update id="updateUser" parameterType="User" flushCache="true">
UPDATE users SET name=#{name} WHERE id=#{id}
</update>
• Java 代码:
sqlSession.update("com.example.UserMapper.updateUser", user);
sqlSession.clearCache(); // 清空一级和二级缓存
四、高级场景与最佳实践
1. 分布式缓存集成
• Redis:适用于微服务架构,保障多节点缓存一致性。
<!-- Redis 插件配置 -->
<plugin interceptor="org.mybatis.caches.redis.RedisCachePlugin">
<property name="host" value="redis-host"/>
<property name="database" value="0"/>
<property name="keyPrefix" value="mybatis:cache:"/>
</plugin>
• EhCache:本地缓存 + 分布式缓存(如结合 Terracotta)。
2. 缓存预热
• 应用启动时加载数据:通过定时任务或初始化逻辑预先填充缓存。
@PostConstruct
public void initCache() {
List<User> users = userService.getAllUsers();
for (User user : users) {
sqlSession.selectOne("com.example.UserMapper.findByUserId", user.getId());
}
}
3. 缓存监控与调优
• 日志输出:启用 MyBatis 日志,观察缓存命中率:
<settings>
<setting name="logImpl" value="SLF4J"/>
</settings>
• 监控工具:使用 EhCache 的 MBean
或 Redis 的 MONITOR
命令监控缓存状态。
五、常见问题与解决方案
1. 脏读问题
• 场景:缓存数据未及时刷新,读取到旧数据。
• 解决:
• 设置合理的 timeToLive
(TTL)或 timeToIdle
(TII)。
• 在关键操作后手动刷新缓存。
2. 缓存雪崩
• 场景:大量缓存键同时失效,导致数据库压力骤增。
• 解决:
• 使用随机 TTL(如 1-5秒
)而非固定值。
• 添加二级缓存(如 Redis + 本地缓存)。
3. 内存泄漏
• 场景:缓存未正确释放,长期堆积占用内存。
• 解决:
• 配置缓存淘汰策略(如 LRU、FIFO)。
• 定期清理无用缓存键。
六、总结
• 二级缓存的核心价值:减少数据库访问,提升查询性能,尤其适用于读多写少的场景(如配置表、统计信息)。
• 关键配置项:
• 缓存插件:选择合适的存储介质(EhCache、Redis)。
• 序列化机制:优先使用 Jackson 或 Kryo 替代 JdkSerialization。
• 淘汰策略:根据业务需求配置 LRU、TTL 等参数。
• 最佳实践:
• 对频繁更新的表禁用二级缓存。
• 结合事务管理控制缓存刷新时机。
• 在分布式系统中使用 Redis 保障一致性。
• 避坑指南:
• 避免在多线程环境中共享默认非线程安全缓存。
• 明确缓存失效条件,防止脏读。
通过合理配置和优化二级缓存,可以显著提升系统性能,但需权衡一致性、内存消耗和代码复杂度。建议结合具体业务场景灵活选择缓存策略。
Mybatis的二级缓存,标签 和 @CacheNamespace注解 的配置详解
MyBatis 的二级缓存可以通过两种方式配置:XML 的 <cache>
标签和注解 @CacheNamespace
。两者的核心功能一致,但适用场景不同。以下是对两者的详细配置说明及对比:
一、XML 配置方式:<cache>
标签
在 Mapper XML 文件中通过 <cache>
标签配置二级缓存,适用于 XML 映射文件的场景。
1. 基础配置
<mapper namespace="com.example.UserMapper">
<!-- 开启二级缓存 -->
<cache
eviction="LRU" <!-- 缓存淘汰策略(默认 LRU) -->
flushInterval="60000" <!-- 自动刷新间隔(毫秒,默认不刷新) -->
size="1024" <!-- 缓存最大对象数(默认 1024) -->
readOnly="true" <!-- 是否只读(默认 false) -->
/>
<select id="selectById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
2. 属性详解
属性名 | 说明 | 可选值 | 默认值 |
---|---|---|---|
eviction |
缓存淘汰策略,决定何时移除旧对象 | LRU 、FIFO 、SOFT 、WEAK |
LRU |
flushInterval |
缓存自动刷新间隔(毫秒),设置为正数时,每隔指定时间清空缓存 | 正整数(如 60000 表示 1 分钟) |
不刷新 |
size |
缓存最大对象数量,超出时根据淘汰策略移除旧数据 | 正整数 | 1024 |
readOnly |
是否返回缓存对象的引用(只读)。若为 false ,会通过序列化返回深拷贝对象,要求实体类可序列化 |
true /false |
false |
type |
指定自定义缓存实现类(如 Redis、Ehcache) | 全限定类名 | 默认实现 |
3. 注意事项
- 序列化要求:若
readOnly="false"
,实体类需实现Serializable
接口。 - 关联 Mapper:若多个 Mapper 操作同一张表,需通过
<cache-ref>
共享缓存:<mapper namespace="com.example.OrderMapper"> <cache-ref namespace="com.example.UserMapper"/> <!-- 共享 UserMapper 的缓存 --> </mapper>
二、注解配置方式:@CacheNamespace
在 Mapper 接口上使用 @CacheNamespace
注解配置二级缓存,适用于纯注解开发模式。
1. 基础配置
@CacheNamespace(
eviction = LruCache.class, // 淘汰策略实现类
flushInterval = 60000, // 自动刷新间隔(毫秒)
size = 1024, // 缓存最大对象数
readWrite = false, // 是否可读写(等效于 readOnly)
implementation = PerpetualCache.class // 缓存实现类(默认 PerpetualCache)
)
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Integer id);
}
2. 属性详解
属性名 | 说明 | 可选值/示例 | 默认值 |
---|---|---|---|
eviction |
淘汰策略实现类 | LruCache.class 、FifoCache.class |
LruCache.class |
flushInterval |
自动刷新间隔(毫秒) | 正整数 | 0(不刷新) |
size |
缓存最大对象数 | 正整数 | 1024 |
readWrite |
是否可读写,false 表示只读(等效于 XML 的 readOnly="true" ) |
true /false |
true |
implementation |
自定义缓存实现类(如集成 Redis) | RedisCache.class |
PerpetualCache |
properties |
附加属性(需配合自定义缓存实现使用) | @Property(name="key", value="value") |
无 |
3. 示例:整合 Redis 缓存
@CacheNamespace(
implementation = RedisCache.class,
properties = {
@Property(name = "host", value = "127.0.0.1"),
@Property(name = "port", value = "6379")
}
)
public interface UserMapper {
// ...
}
三、对比 <cache>
标签与 @CacheNamespace
特性 | <cache> 标签 |
@CacheNamespace 注解 |
---|---|---|
适用场景 | XML 映射文件开发 | 纯注解开发 |
配置位置 | Mapper XML 文件内 | Mapper 接口上 |
可读性 | 适合复杂配置(属性直观) | 适合简洁配置(与代码耦合) |
灵活性 | 更高(支持 <cache-ref> 等扩展配置) |
较局限(依赖注解属性) |
序列化要求 | 需显式实现 Serializable |
同左 |
第三方缓存集成 | 通过 type 属性指定 |
通过 implementation 属性指定 |
四、二级缓存的核心行为
1. 缓存生命周期
- 生效时机:事务提交(
sqlSession.commit()
)后写入二级缓存。 - 失效条件:同 Mapper 的增删改操作(DML)会清空整个命名空间的缓存。
2. 缓存范围控制
禁用某个方法的缓存:
<!-- XML 方式 --> <select id="selectAll" resultType="User" useCache="false"> SELECT * FROM user </select>
// 注解方式 @Options(useCache = false) @Select("SELECT * FROM user") List<User> selectAll();
强制清空缓存:
<update id="updateUser" flushCache="true"> UPDATE user SET name=#{name} WHERE id=#{id} </update>
五、常见问题与解决方案
1. 缓存脏读
- 场景:多个 Mapper 操作同一张表但未共享缓存。
- 解决:
- XML:通过
<cache-ref>
关联其他 Mapper。 - 注解:无直接支持,需手动同步或使用全局缓存(如 Redis)。
- XML:通过
2. 序列化异常
- 报错:
java.io.NotSerializableException
。 - 解决:实体类实现
Serializable
接口。
3. 分布式缓存一致性
- 问题:默认缓存是单机的,多服务节点数据不一致。
- 解决:集成 Redis 等分布式缓存。
六、最佳实践
选择配置方式:
- 传统项目(XML 为主):优先使用
<cache>
标签。 - 现代项目(注解驱动):使用
@CacheNamespace
。
- 传统项目(XML 为主):优先使用
性能调优:
- 设置合理的
eviction
和flushInterval
。 - 避免缓存过多大数据对象(如 BLOB 字段)。
- 设置合理的
监控与日志:
- 启用 MyBatis 日志(查看缓存命中情况):
<settings> <setting name="logImpl" value="SLF4J"/> </settings>
- 启用 MyBatis 日志(查看缓存命中情况):
慎用场景:
- 高并发写操作:二级缓存频繁清空可能降低性能。
- 实时性要求高:直接查询数据库,绕过缓存。
MyBatis 二级缓存配置详解(含 XML 标签与注解对比)
一、XML 配置方式:<cache>
标签
1. 基础配置模板
<cache
eviction="LRU"
flushInterval="60000"
size="512"
readOnly="true"
type="org.mybatis.caches.ehcache.EhcacheCache"/>
2. 关键属性解析
属性 | 可选值 | 默认值 | 说明 |
---|---|---|---|
eviction | LRU, FIFO, SOFT, WEAK | LRU | 缓存淘汰策略 |
flushInterval | 毫秒数 | 不设置 | 自动刷新间隔(0表示不刷新) |
size | 正整数 | 1024 | 最大缓存对象数 |
readOnly | true, false | true | 是否返回缓存对象副本 |
type | 全限定类名 | 默认实现 | 自定义缓存实现类 |
3. 完整配置示例
<mapper namespace="com.example.UserMapper">
<cache
eviction="FIFO"
flushInterval="300000"
size="2048"
readOnly="false"
type="com.example.RedisCacheAdapter"/>
<select id="selectUser" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
二、注解配置方式:@CacheNamespace
1. 注解参数说明
@CacheNamespace(
eviction = FifoCache.class,
flushInterval = 300000,
size = 2048,
readWrite = false,
implementation = RedisCache.class
)
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User selectUser(Long id);
}
2. 参数对应关系表
注解参数 | XML 属性 | 说明 |
---|---|---|
eviction | eviction | 淘汰策略实现类 |
flushInterval | flushInterval | 自动刷新间隔(毫秒) |
size | size | 最大缓存对象数 |
readWrite | readOnly | 是否允许写操作 |
implementation | type | 自定义缓存实现类 |
3. 混合配置示例
@CacheNamespace(
eviction = LruCache.class,
flushInterval = 60000,
readWrite = true
)
public interface OrderMapper {
@Select("SELECT * FROM orders WHERE user_id = #{userId}")
@Options(useCache = true, flushCache = Options.FlushCachePolicy.TRUE)
List<Order> getOrdersByUserId(Long userId);
}
三、配置方式对比与选择建议
配置方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
XML 标签 | 配置集中管理,易维护 | 侵入性强,需修改XML文件 | 传统XML配置项目 |
注解方式 | 代码简洁,配置与接口绑定 | 分散配置,不利于统一管理 | 注解驱动项目,快速开发场景 |
选择建议:
- 新项目推荐使用注解方式保持代码简洁
- 需要全局统一配置时优先使用XML方式
- 复杂缓存策略建议结合自定义实现类
四、高级配置技巧
- 条件化缓存:
@Select("SELECT * FROM products WHERE stock > 0")
@Options(useCache = true, flushCache = false)
@Results({
@Result(property = "id", column = "product_id"),
@Result(property = "name", column = "product_name")
})
List<Product> getAvailableProducts();
- 多Mapper共享缓存:
<!-- 在公共配置文件中定义 -->
<cache-ref namespace="com.example.BaseMapper"/>
- 缓存穿透防护:
@Select("SELECT * FROM users WHERE id = #{id}")
@Options(useCache = true)
@Results({
@Result(property = "id", column = "user_id"),
@Result(property = "name", column = "user_name")
})
User getUserById(Long id);
// 业务层处理
public User safeGetUser(Long id) {
User user = userMapper.getUserById(id);
if (user == null) {
// 缓存空对象(设置较短过期时间)
userMapper.cacheEmptyUser(id);
}
return user;
}
五、验证与调试
- 日志验证:
# log4j2.xml 配置
<Logger name="org.apache.ibatis.cache" level="TRACE"/>
- 命中率监控:
// 自定义拦截器
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class CacheMonitorInterceptor implements Interceptor {
private final AtomicInteger hitCount = new AtomicInteger();
private final AtomicInteger missCount = new AtomicInteger();
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
if (ms.getCache() != null) {
Object result = ms.getCache().getObject(invocation.getArgs()[1]);
if (result != null) {
hitCount.incrementAndGet();
} else {
missCount.incrementAndGet();
}
}
return invocation.proceed();
}
public double getHitRate() {
long total = hitCount.get() + missCount.get();
return total == 0 ? 0 : (double) hitCount.get() / total;
}
}
- 缓存内容查看:
// 通过反射查看缓存内容(调试用)
Cache cache = sqlSessionFactory.getConfiguration().getCache("com.example.UserMapper");
if (cache instanceof PerpetualCache) {
Map<Object, Object> cacheMap = ((PerpetualCache) cache).getCache();
cacheMap.forEach((key, value) -> {
System.out.println("Cache Key: " + key + ", Value: " + value);
});
}
通过灵活组合这些配置方式,可以构建出适应不同业务场景的二级缓存方案。建议在实际项目中结合压力测试,找到最佳配置参数组合。对于分布式系统,推荐使用Redis等中央缓存方案,并通过<cache-ref>
实现多Mapper共享缓存。
在 MyBatis 中,二级缓存的配置可以通过 XML 的 <cache>
标签或注解(如 @CacheNamespace
)实现。以下是两者的详细配置说明、区别及使用场景:
一、XML 配置:<cache>
标签
1. 基本用法
在 Mapper 的 XML 文件中添加 <cache>
标签,开启当前命名空间(Mapper 接口)的二级缓存:
<mapper namespace="com.example.mapper.UserMapper">
<!-- 开启二级缓存 -->
<cache
eviction="LRU" <!-- 缓存回收策略 -->
flushInterval="60000" <!-- 自动刷新间隔(毫秒) -->
size="1024" <!-- 缓存最大条目数 -->
readOnly="true"/> <!-- 是否只读 -->
<!-- 其他 SQL 语句 -->
<select id="selectUserById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
2. 核心属性说明
属性名 | 说明 | 默认值 |
---|---|---|
eviction |
缓存回收策略(LRU/FIFO/SOFT/WEAK) | LRU |
flushInterval |
自动刷新间隔(毫秒),达到时间后清空缓存 | 不设置 |
size |
缓存最大容量(条目数) | 1024 |
readOnly |
是否只读:true 返回对象引用(性能高,需确保数据不可变);false 返回拷贝 |
false |
3. 注意事项
- 实体类必须实现
Serializable
:若readOnly="false"
,实体类需实现Serializable
接口。 - 全局开关:需确保全局配置
cacheEnabled
为true
(默认开启)。
二、注解配置:@CacheNamespace
1. 基本用法
在 Mapper 接口上使用 @CacheNamespace
注解,替代 XML 的 <cache>
标签:
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Select;
@CacheNamespace(
eviction = CacheNamespaceEvictionPolicy.LRU,
flushInterval = 60000,
size = 1024,
readOnly = true
)
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User selectUserById(int id);
}
2. 核心属性说明
属性名 | 说明 | 默认值 |
---|---|---|
eviction |
缓存回收策略(LRU /FIFO /SOFT /WEAK ) |
LRU |
flushInterval |
自动刷新间隔(毫秒) | 0 (不设置) |
size |
缓存最大容量(条目数) | Integer.MAX_VALUE |
readOnly |
是否只读 | false |
3. 注意事项
- 兼容性:
@CacheNamespace
是 MyBatis 的注解配置方式,需确保 MyBatis 版本支持(MyBatis 3.4+)。 - 实体类序列化:与 XML 配置一致,
readOnly="false"
时实体类需实现Serializable
。
三、<cache>
标签与 @CacheNamespace
的对比
特性 | XML 的 <cache> 标签 |
注解 @CacheNamespace |
---|---|---|
配置位置 | Mapper XML 文件 | Mapper 接口 |
适用场景 | 传统 XML 驱动的项目 | 注解驱动的项目(如 Spring Boot) |
可读性 | 配置集中,适合复杂场景 | 代码直接配置,简洁直观 |
扩展性 | 支持自定义缓存实现(通过 type 属性) |
仅支持内置策略,需结合其他方式扩展 |
兼容性 | 全版本支持 | 需 MyBatis 3.4+ |
四、完整配置示例
1. XML 配置示例
<!-- mybatis-config.xml -->
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/> <!-- 全局开启二级缓存 -->
</settings>
</configuration>
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<select id="selectUserById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
2. 注解配置示例
// MyBatis 配置类(如 Spring Boot 中的 MyBatisConfig)
@Configuration
public class MyBatisConfig {
@Bean
public Configuration customConfiguration() {
Configuration configuration = new Configuration();
configuration.setCacheEnabled(true); // 全局开启二级缓存
return configuration;
}
}
// Mapper 接口
@CacheNamespace(
eviction = CacheNamespaceEvictionPolicy.FIFO,
flushInterval = 60000,
size = 512,
readOnly = true
)
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User selectUserById(int id);
}
五、关键注意事项
- 全局开关:
- 在
mybatis-config.xml
或配置类中确保cacheEnabled
为true
。
- 在
- 实体类序列化:
- 若
readOnly="false"
,实体类必须实现Serializable
。
- 若
- 缓存失效场景:
- 执行
insert
/update
/delete
会清空当前 Mapper 的二级缓存。 - 达到
flushInterval
设置的刷新时间间隔。
- 执行
- 查询缓存控制:
- 在
<select>
标签中设置useCache="false"
可禁用单条查询的二级缓存。 - 在注解中通过
@Options(useCache = false)
实现相同效果。
- 在
六、总结
- XML 的
<cache>
标签:适合传统 XML 配置的项目,灵活性高,支持自定义缓存实现。 - 注解
@CacheNamespace
:适合注解驱动的项目(如 Spring Boot),配置简洁,但功能稍受限。 - 选择建议:
- 若项目以 XML 配置为主,优先使用
<cache>
标签。 - 若项目以注解为主(如 Spring Boot),优先使用
@CacheNamespace
。
- 若项目以 XML 配置为主,优先使用
通过合理配置二级缓存,可以显著提升跨 SqlSession
的查询性能,但需注意数据一致性、序列化和并发问题。
在 MyBatis 里,<cache>
标签和 @CacheNamespace
注解可用于配置二级缓存。下面为你详细介绍这两种方式的配置。
<cache>
标签配置
<cache>
标签在 Mapper XML 文件里使用,以下是其详细配置参数。
基本使用
在 Mapper XML 文件中添加 <cache>
标签,即可为该 Mapper 启用默认配置的二级缓存。
<mapper namespace="com.example.mapper.UserMapper">
<cache/>
<!-- 具体的 SQL 查询语句 -->
<select id="selectUserById" parameterType="int" resultType="com.example.entity.User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
配置参数
# eviction
(缓存清除策略)
该参数规定了缓存满时移除对象的策略,支持的策略如下:
- LRU(Least Recently Used):最近最少使用,移除最长时间未被使用的对象,这是默认策略。
- FIFO(First In First Out):先进先出,按对象进入缓存的顺序移除。
- SOFT:软引用,依据垃圾回收器状态和软引用规则移除对象。
- WEAK:弱引用,更积极地根据垃圾回收器状态和弱引用规则移除对象。
示例:
<cache eviction="FIFO"/>
# flushInterval
(刷新间隔)
此参数指定了缓存自动清空的时间间隔,单位为毫秒。设置为 60000
表示每分钟清空一次缓存。
示例:
<cache flushInterval="60000"/>
# size
(缓存大小)
它用于限定缓存能够存储的对象数量上限。
示例:
<cache size="100"/>
# readOnly
(只读缓存)
true
:缓存中的对象为只读,MyBatis 直接返回缓存对象的引用,性能较高,但多个地方修改对象会影响数据一致性。false
:缓存中的对象可读写,MyBatis 通过序列化和反序列化返回对象副本,保证数据独立性,但性能相对较低。
示例:
<cache readOnly="true"/>
# type
(自定义缓存实现类)
你可以使用 type
参数指定自定义的缓存实现类。
示例:
<cache type="com.example.cache.MyCustomCache"/>
@CacheNamespace
注解配置
@CacheNamespace
注解用于 Mapper 接口,其配置参数与 <cache>
标签类似。
基本使用
在 Mapper 接口上添加 @CacheNamespace
注解,为该 Mapper 启用二级缓存。
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Select;
@CacheNamespace
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User selectUserById(int id);
}
配置参数
# eviction
(缓存清除策略)
借助 eviction
属性指定缓存清除策略,需传入对应的缓存装饰器类。
示例:
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.cache.decorators.FifoCache;
@CacheNamespace(eviction = FifoCache.class)
public interface UserMapper {
// ...
}
# flushInterval
(刷新间隔)
通过 flushInterval
属性指定缓存自动清空的时间间隔,单位为毫秒。
示例:
import org.apache.ibatis.annotations.CacheNamespace;
@CacheNamespace(flushInterval = 60000)
public interface UserMapper {
// ...
}
# size
(缓存大小)
使用 size
属性限定缓存可存储的对象数量上限。
示例:
import org.apache.ibatis.annotations.CacheNamespace;
@CacheNamespace(size = 100)
public interface UserMapper {
// ...
}
# readOnly
(只读缓存)
利用 readOnly
属性设置缓存是否为只读。
示例:
import org.apache.ibatis.annotations.CacheNamespace;
@CacheNamespace(readOnly = true)
public interface UserMapper {
// ...
}
# implementation
(自定义缓存实现类)
使用 implementation
属性指定自定义的缓存实现类。
示例:
import org.apache.ibatis.annotations.CacheNamespace;
@CacheNamespace(implementation = com.example.cache.MyCustomCache.class)
public interface UserMapper {
// ...
}
总结
<cache>
标签和 @CacheNamespace
注解功能类似,只是使用场景不同。<cache>
标签用于 Mapper XML 文件,而 @CacheNamespace
注解用于 Mapper 接口。在配置时,可根据具体需求选择合适的缓存清除策略、刷新间隔、缓存大小等参数,以优化缓存性能。
MyBatis 二级缓存详解
1. 开启二级缓存
全局配置:
在 mybatis-config.xml
文件中,确保 <settings>
标签中包含以下配置:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
默认情况下,cacheEnabled
的值为 true
,因此通常无需显式配置。
Mapper 映射文件中使用二级缓存:
在需要使用二级缓存的 Mapper 的 XML 文件中添加 <cache>
标签:
<mapper namespace="com.example.mapper.UserMapper">
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
<select id="getUserById" parameterType="int" resultType="com.example.entity.User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
其中,eviction
属性指定缓存的回收策略,flushInterval
指定缓存刷新的时间间隔(毫秒),size
指定缓存可以存储的对象数量,readOnly
指定缓存是否只读。
实体类序列化:
使用二级缓存的实体类必须实现 Serializable
接口,以确保数据可以被序列化和反序列化。
2. 使用 @CacheNamespace
注解
基本用法:
在 Mapper 接口上使用 @CacheNamespace
注解来配置二级缓存:
@Mapper
@CacheNamespace(size = 512)
public interface UserMapper {
User getUserById(Long id);
}
这将为 UserMapper
配置一个大小为 512 的缓存。
自定义缓存实现:
可以指定自定义的缓存实现类:
@Mapper
@CacheNamespace(implementation = MyCustomCache.class)
public interface UserMapper {
User getUserById(Long id);
}
其中,MyCustomCache
是一个实现了 org.apache.ibatis.cache.Cache
接口的类。
缓存回收策略:
可以指定缓存的回收策略,如 LRU(最近最少使用)、FIFO(先进先出)等:
@Mapper
@CacheNamespace(eviction = org.apache.ibatis.cache.impl.LruCache.class)
public interface UserMapper {
User getUserById(Long id);
}
缓存大小:
可以设置缓存的大小,即缓存中可以存储的对象数量:
@Mapper
@CacheNamespace(size = 1024)
public interface UserMapper {
User getUserById(Long id);
}
读写模式:
可以指定缓存是否为读写模式。如果为 true
,MyBatis 会对缓存中的对象进行序列化和反序列化,以确保缓存中的对象是线程安全的:
@Mapper
@CacheNamespace(readWrite = true)
public interface UserMapper {
User getUserById(Long id);
}
3. 注意事项
- 缓存失效:在高并发场景下,缓存可能会导致数据不一致的问题,需要根据具体业务场景采取相应的解决措施。
- 缓存清理:MyBatis 支持通过
<cache>
标签的flushInterval
属性设置缓存清理时间。 - 缓存类型:MyBatis 支持多种缓存类型,可以通过
<cache>
标签的type
属性指定。
4. 示例
XML 配置示例:
<!-- mybatis-config.xml -->
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
<select id="getUserById" parameterType="int" resultType="com.example.entity.User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
注解配置示例:
@Mapper
@CacheNamespace(
eviction = org.apache.ibatis.cache.impl.LruCache.class,
size = 512,
readWrite = true
)
public interface UserMapper {
User getUserById(Long id);
}
通过合理使用二级缓存,可以有效减少数据库的访问次数,提高查询性能。
MyBatis二级缓存配置详解(XML <cache>
与注解 @CacheNamespace
)
一、核心配置对比
配置方式 | 适用场景 | 优势 | 限制 |
---|---|---|---|
XML <cache> 标签 |
传统XML映射文件 | 集中管理配置,支持细粒度参数调优 | 需维护XML文件 |
@CacheNamespace 注解 |
注解驱动的Mapper接口 | 代码与配置一体,减少文件切换 | 部分高级配置需结合XML使用 |
二、XML <cache>
标签配置
完整语法示例:
<cache
eviction="LRU"
flushInterval="60000"
size="1024"
readOnly="true"
blocking="false"
type="com.custom.MyCache"/>
参数详解:
eviction
(淘汰策略)LRU
(默认):最近最少使用,优先淘汰最久未访问的数据
淘汰对象 = arg min ( 最后访问时间 ) \text{淘汰对象} = \arg\min(\text{最后访问时间}) 淘汰对象=argmin(最后访问时间)FIFO
:先进先出,按对象进入缓存的顺序淘汰SOFT
:软引用,基于JVM内存回收策略淘汰WEAK
:弱引用,比SOFT更激进的内存回收
flushInterval
(刷新间隔)- 单位:毫秒(例如
60000
= 1分钟) - 到期自动清空缓存(非逐出,直接全部清除)
- 单位:毫秒(例如
size
(缓存容量)- 最大缓存对象数(非内存大小)
- 超过时触发淘汰策略
readOnly
(只读模式)true
(默认):直接返回缓存对象的引用,要求对象不可变false
:通过序列化生成副本,对象必须实现Serializable
blocking
(阻塞模式)true
:缓存未命中时,阻塞其他线程直到数据加载完成(防缓存击穿)
type
(自定义缓存)- 指定实现
org.apache.ibatis.cache.Cache
接口的类 - 示例:集成Redis等分布式缓存
- 指定实现
三、@CacheNamespace
注解配置
基本用法(标注在Mapper接口上):
@CacheNamespace(
implementation = PerpetualCache.class,
eviction = LruCache.class,
flushInterval = 60000,
size = 1024,
readWrite = true, // 等效于readOnly=false
blocking = false
)
public interface UserMapper {
// 方法定义
}
参数对照表:
注解参数 | XML等效属性 | 特殊说明 |
---|---|---|
implementation |
type |
必须继承 Cache 接口 |
eviction |
eviction |
需指定淘汰策略实现类(如LruCache.class ) |
readWrite |
readOnly |
readWrite=true 对应 readOnly=false |
四、混合配置规则
优先级冲突
- 注解与XML同时存在时,注解配置优先级更高
- 例外:若XML中指定
type
属性,则覆盖注解的implementation
引用外部缓存
- XML方式:
<cache-ref namespace="com.example.OtherMapper"/>
- 注解方式:
@CacheNamespaceRef(OtherMapper.class)
- XML方式:
五、关键问题解决方案
场景1:缓存脏读
- 现象:其他系统直连数据库修改数据,导致缓存与数据库不一致
- 方案:
<!-- 设置短刷新间隔 --> <cache flushInterval="300000"/> <!-- 5分钟强制刷新 -->
场景2:大对象序列化性能差
- 优化:
@CacheNamespace(readWrite = false) // 启用只读模式(无需序列化) public interface BigDataMapper {}
场景3:缓存穿透
- 防御:
<cache blocking="true"/> <!-- 启用阻塞模式 -->
六、调试与监控
日志输出
在log4j.properties
中添加:log4j.logger.org.mybatis.caches=DEBUG
监控指标
Cache cache = sqlSession.getConfiguration().getCache("com.example.UserMapper"); System.out.println("缓存命中率: " + (cache.getHitCount()/(double)cache.getRequestCount()));
七、配置决策树
选择条件 = { 使用XML配置 需要精细控制或使用缓存引用 使用注解配置 追求代码简洁且无需跨Mapper共享缓存 \text{选择条件} = \begin{cases} \text{使用XML配置} & \text{需要精细控制或使用缓存引用} \\ \text{使用注解配置} & \text{追求代码简洁且无需跨Mapper共享缓存} \end{cases} 选择条件={使用XML配置使用注解配置需要精细控制或使用缓存引用追求代码简洁且无需跨Mapper共享缓存
通过合理组合 <cache>
标签与 @CacheNamespace
,可实现从单机到分布式缓存的灵活扩展,建议根据数据访问模式和系统架构选择最优方案。
MyBatis 的二级缓存可以通过 <cache>
标签(XML 配置)或 @CacheNamespace
注解(Java 注解)启用和配置。两者功能相似,但配置方式和适用场景略有不同。以下是详细对比和配置详解:
一、 标签(XML 配置)
1. 基本用法
在 Mapper XML 文件中通过 <cache/>
标签启用二级缓存,并支持自定义配置参数:
<mapper namespace="com.example.UserMapper">
<cache
name="userCache" <!-- 缓存名称(可选) -->
size="512" <!-- 缓存最大条目数(默认未配置) -->
readOnly="true" <!-- 是否只读(默认 false) -->
flushInterval="60000" <!-- 刷新间隔(毫秒,仅当结合第三方缓存时生效) -->
serialization="Jackson" <!-- 序列化方式(如 Jackson、Kryo) -->
/>
<select id="findByUserId" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
2. 核心配置参数
参数名 | 说明 |
---|---|
name |
缓存实例名称(用于区分不同 Mapper 的缓存,可选) |
size |
缓存最大容量(仅对 MyBatis 内置缓存有效,第三方缓存如 EhCache 不受此限制) |
readOnly |
是否为只读缓存(true :不可写,false :可读写,默认 false ) |
flushInterval |
自动刷新间隔(仅当集成第三方缓存时生效,如 EhCache、Redis) |
serialization |
自定义序列化方式(如 Jackson、Kryo,默认 JdkSerialization) |
3. 特殊场景配置
• 集成 EhCache:
<cache type="org.mybatis.caches.ehcache.EhCacheCache"
name="userCache"
properties="timeToLiveSeconds=3600, timeToIdleSeconds=1800"/>
通过 properties
指定 EhCache 的配置参数。
• 禁用缓存:
<cache useCache="false"/> <!-- 直接禁用该 Mapper 的二级缓存 -->
二、@CacheNamespace 注解(Java 注解)
1. 基本用法
在 Mapper 接口上直接添加 @CacheNamespace
注解启用二级缓存:
@CacheNamespace(
name = "userCache",
size = 512,
readOnly = true,
flushInterval = 60000,
serialization = "Jackson"
)
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User findByUserId(int id);
}
2. 核心配置参数
与 XML 的 <cache>
标签一致,支持相同属性配置:
• name
:缓存名称
• size
:缓存容量
• readOnly
:只读模式
• flushInterval
:刷新间隔
• serialization
:序列化方式
3. 高级配置
• 继承配置:可通过 @CacheNamespace
的 extends
属性继承其他缓存的配置:
@CacheNamespace(extendsConfig = "baseCacheConfig")
public interface UserMapper { ... }
需在配置文件中定义 baseCacheConfig
。
• 动态刷新:结合 @FlushCache
注解手动触发缓存刷新:
@CacheNamespace
public interface UserMapper {
@Update("UPDATE users SET name=#{name} WHERE id=#{id}")
@FlushCache
void updateUser(User user);
}
三、 标签 vs @CacheNamespace 注解
对比维度 | <cache> 标签(XML) |
@CacheNamespace 注解(Java) |
---|---|---|
配置位置 | Mapper XML 文件中 | Mapper 接口类上 |
配置灵活性 | 支持更复杂的配置(如第三方缓存参数) | 简洁,适合快速配置 |
继承性 | 不支持继承 | 支持通过 extends 属性继承配置 |
代码耦合度 | 低(配置与代码分离) | 高(配置与接口代码绑定) |
适用场景 | 需要细粒度控制缓存行为(如多 Mapper 共享缓存配置) | 追求代码简洁,配置简单的场景 |
四、配置优先级与冲突解决
优先级规则:
• 注解 > XML:若同一 Mapper 同时使用@CacheNamespace
和<cache/>
,注解配置优先级更高。
• 全局插件配置:如localCacheScope
会覆盖局部配置。冲突处理:
• 属性冲突:例如同时配置size
在注解和 XML 中,以注解值为准。
• 禁用缓存:如果在 XML 中通过<cache useCache="false"/>
禁用缓存,注解配置将失效。
五、高级配置示例
1. 使用 Jackson 序列化
XML 方式:
<cache serialization="Jackson"/>
Java 注解方式:
@CacheNamespace(serialization = "Jackson")
public interface UserMapper { ... }
2. 集成 EhCache
XML 方式:
<cache type="org.mybatis.caches.ehcache.EhCacheCache">
<property name="timeToLiveSeconds" value="3600"/>
</cache>
Java 注解方式:
@CacheNamespace(type = "org.mybatis.caches.ehcache.EhCacheCache", properties = "timeToLiveSeconds=3600")
public interface UserMapper { ... }
3. 自定义缓存实现
XML 方式:
<cache type="com.example.CustomCache" name="myCustomCache"/>
Java 注解方式:
@CacheNamespace(type = "com.example.CustomCache")
public interface UserMapper { ... }
六、最佳实践
一致性原则:
• 项目中统一使用 XML 或注解配置,避免混用导致维护困难。
• 若需复杂配置(如多级缓存、第三方集成),优先使用 XML。性能优化:
• 对高频读取的静态数据(如字典表)启用二级缓存,配置合理的size
和timeToLive
。
• 对频繁更新的动态数据(如订单表)禁用二级缓存或结合<flushCache>
手动刷新。分布式缓存:
• 使用 Redis 或 EhCache 的分布式缓存能力,避免单机缓存不一致问题。
• 配置keyPrefix
或自定义缓存键生成策略,防止键冲突。监控与调试:
• 启用 MyBatis 日志(logImpl=SLF4J
)观察缓存命中率:<settings> <setting name="logImpl" value="SLF4J"/> </settings>
• 通过缓存管理工具(如 EhCache Studio、Redis CLI)监控缓存状态。
七、总结
• <cache>
标签:适合需要细粒度控制缓存的场景,如多 Mapper 共享配置或集成复杂缓存策略。
• @CacheNamespace
注解:适合追求代码简洁、配置快速的场景,尤其适用于基于接口的 Mapper 设计。
两者均能高效启用二级缓存,根据项目需求和团队编码规范灵活选择即可。关键是通过合理配置缓存参数(如序列化、淘汰策略、刷新机制),平衡性能与一致性风险。
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
(缓存回收策略)用于管理缓存空间,当缓存达到容量上限时,通过特定的算法淘汰旧数据。以下是其核心机制和常见策略的详解:
1. 为什么需要Evictio策略?
二级缓存是Mapper级别的共享缓存,若未设置容量限制,可能导致内存溢出。通过eviction
策略可:
- 控制内存占用:避免缓存无限增长。
- 提高命中率:优先保留高频访问数据。
- 适应不同场景:根据业务特点选择淘汰算法。
2. 常见Evictio策略
MyBatis支持以下策略(通过<property name="eviction" value="..."/>
配置):
(1)LRU(Least Recently Used,最近最少使用)
- 原理:淘汰最长时间未被访问的缓存。
- 实现:通过链表维护访问顺序,命中时移至链表头部,淘汰时移除尾部。
- 适用场景:热点数据集中,符合局部性原理的业务。
- 默认策略:MyBatis默认使用LRU。
(2)FIFO(First In First Out,先进先出)
- 原理:按插入顺序淘汰最早进入缓存的数据。
- 实现:通过队列结构,淘汰队首元素。
- 适用场景:数据访问频率相近,需公平淘汰的场景。
- 缺点:可能误删高频数据。
(3)SOFT(软引用)
- 原理:基于Java软引用(
SoftReference
),当内存不足时由GC回收。 - 特点:缓存对象在内存紧张时被回收,避免OOM。
- 适用场景:内存敏感型应用,允许缓存被GC优先清理。
- 注意:需结合JVM内存参数调优。
(4)WEAK(弱引用)
- 原理:基于Java弱引用(
WeakReference
),GC时直接回收。 - 特点:缓存生命周期短,适合临时数据。
- 适用场景:缓存非关键数据,或需快速失效的场景。
- 缺点:可能被频繁回收,降低缓存效率。
3. 配置示例
在mybatis-config.xml
中配置二级缓存及策略:
<settings>
<setting name="cacheEnabled" value="true"/> <!-- 启用二级缓存 -->
</settings>
<cache
eviction="LRU"
flushInterval="60000"
size="512"
readOnly="true"/>
- 关键参数:
eviction
:淘汰策略(必填)。flushInterval
:自动刷新间隔(毫秒)。size
:缓存最大条目数。readOnly
:是否只读(建议true
提升性能)。
4. 策略选择建议
策略 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
LRU | 命中率高,适应多数场景 | 维护访问顺序有性能开销 | 通用场景,热点数据集中 |
FIFO | 实现简单,无额外开销 | 可能淘汰高频数据 | 数据访问频率均匀的场景 |
SOFT | 内存不足时自动回收 | 依赖JVM GC行为 | 内存敏感型应用 |
WEAK | 快速失效,避免内存泄漏 | 缓存可能被频繁回收 | 临时数据或低优先级缓存 |
5. 注意事项
- 监控与调优:通过日志或监控工具观察缓存命中率,调整
size
和flushInterval
。 - 序列化:若缓存对象需跨JVM,需实现
Serializable
接口。 - 一致性:二级缓存为本地缓存,分布式系统需结合Redis等中间件。
总结
MyBatis的eviction
策略通过不同的淘汰算法平衡内存与性能。LRU适合大多数场景,SOFT/WEAK适用于内存敏感场景,FIFO则提供简单公平的淘汰机制。实际选择需结合业务访问模式和系统资源情况。
以下是关于 MyBatis 二级缓存的 eviction
(缓存回收/淘汰策略) 的详细解析,涵盖其核心概念、配置方法、工作原理及使用场景:
一、eviction
属性概述
- 作用:定义当缓存容量达到
size
限制时,如何淘汰(驱逐)旧对象以腾出空间。 - 默认值:
LRU
(Least Recently Used,最近最少使用算法)。 - 可选值:
LRU
(默认)FIFO
(First In First Out,先进先出)SOFT
(Soft Reference,软引用)WEAK
(Weak Reference,弱引用)
二、各策略详解
1. LRU(Least Recently Used,最近最少使用)
- 原理:移除 最长时间未被访问 的对象。
- 适用场景:
- 数据访问频率差异大,且希望保留最近活跃的数据。
- 需要平衡缓存命中率和资源利用率的场景。
- 优缺点:
- 优点:命中率较高,适合高频访问的热点数据。
- 缺点:实现复杂,需要维护访问时间的排序结构。
- 配置示例:
<!-- XML 配置 --> <cache eviction="LRU" size="1024"/>
// 注解配置(MyBatis 3.4+) @CacheNamespace(eviction = CacheNamespaceEvictionPolicy.LRU)
2. FIFO(First In First Out,先进先出)
- 原理:移除 最早存入缓存 的对象。
- 适用场景:
- 需要简单、快速的淘汰策略,且数据访问时间分布均匀。
- 对缓存命中率要求不高,但希望实现简单。
- 优缺点:
- 优点:实现简单,无需维护复杂结构。
- 缺点:可能移除近期频繁访问的数据。
- 配置示例:
<cache eviction="FIFO" size="512"/>
@CacheNamespace(eviction = CacheNamespaceEvictionPolicy.FIFO)
3. SOFT(软引用)
- 原理:基于 JVM 的内存使用情况 自动回收对象。
- 当 JVM 内存不足时,由垃圾回收器回收软引用对象。
- 适用场景:
- 内存资源有限,希望让 JVM 自动管理缓存占用。
- 缓存数据可被动态调整,无需固定淘汰策略。
- 优缺点:
- 优点:自动适应内存压力,减少手动配置。
- 缺点:回收时间不可预测,可能影响缓存命中率。
- 配置示例:
<cache eviction="SOFT" size="2048"/>
@CacheNamespace(eviction = CacheNamespaceEvictionPolicy.SOFT)
4. WEAK(弱引用)
- 原理:更积极地回收 对象,只要垃圾回收器运行,弱引用对象可能被立即回收。
- 适用场景:
- 需要最小化内存占用,缓存数据可随时丢失。
- 缓存数据可从其他地方快速重建。
- 优缺点:
- 优点:内存占用极低,适合对缓存依赖性低的场景。
- 缺点:缓存命中率极低,可能频繁访问数据库。
- 配置示例:
<cache eviction="WEAK" size="100"/>
@CacheNamespace(eviction = CacheNamespaceEvictionPolicy.WEAK)
三、策略选择建议
策略 | 适用场景 | 注意事项 |
---|---|---|
LRU | 高频访问、热点数据,需平衡命中率和资源。 | 实现复杂,需维护访问时间记录。 |
FIFO | 简单场景,数据访问时间均匀,无需复杂逻辑。 | 可能淘汰近期活跃数据。 |
SOFT | 内存资源有限,希望依赖 JVM 自动管理。 | 回收不可预测,需接受命中率波动。 |
WEAK | 内存敏感,缓存数据可快速重建,对命中率要求低。 | 几乎等同于无缓存,慎用。 |
四、配置注意事项
- 与
size
的配合:size
定义缓存最大容量,超过后触发淘汰策略。- 例如:
size="1024"
表示最多缓存 1024 个对象。
- 实体类序列化:
- 若
readOnly="false"
,实体类必须实现Serializable
接口。
- 若
- 并发场景:
LRU
和FIFO
在高并发下需考虑线程安全,MyBatis 内置实现已处理。
- 测试验证:
- 通过日志或工具观察缓存命中率,调整策略和
size
。
- 通过日志或工具观察缓存命中率,调整策略和
五、示例配置对比
场景:电商商品详情缓存
- 需求:商品访问频率差异大,需保留热门商品数据。
- 策略选择:
LRU
- 配置:
<cache eviction="LRU" size="5000" flushInterval="1800000" <!-- 30分钟刷新 --> readOnly="true"/>
场景:日志记录缓存
- 需求:缓存日志数据,按时间顺序写入,内存敏感。
- 策略选择:
FIFO
- 配置:
@CacheNamespace( eviction = CacheNamespaceEvictionPolicy.FIFO, size = 1000, readOnly = true )
六、总结
- LRU 是最常用的策略,适合大多数场景。
- FIFO 简单易用,适合数据访问均匀的场景。
- SOFT/WEAK 依赖 JVM 内存管理,需权衡命中率和资源占用。
- 根据业务需求、数据访问模式和系统资源选择合适的策略,并通过测试验证效果。
通过合理配置 eviction
策略,可以显著优化二级缓存的性能和资源利用率。
MyBatis 二级缓存的 eviction
(缓存回收策略,也称为缓存驱逐淘汰策略)决定了当缓存空间不足时,哪些数据会被优先从缓存中移除。MyBatis 提供了多种内置的缓存回收策略,下面为你详细介绍这些策略。
1. LRU(Least Recently Used):最近最少使用策略
- 原理:该策略会移除最长时间未被使用的对象。它会维护一个访问顺序的列表,当有新的对象被访问时,将其移动到列表的头部;当缓存空间满时,从列表的尾部移除对象,因为尾部的对象是最长时间未被使用的。
- 优点:能够很好地反映出对象的使用频率,经常被使用的对象会被保留在缓存中,从而提高缓存的命中率。
- 缺点:实现相对复杂,需要维护一个访问顺序的列表,并且每次访问对象都需要更新列表的顺序,会带来一定的性能开销。
- 配置示例(XML):
<mapper namespace="com.example.mapper.UserMapper">
<cache eviction="LRU"/>
<!-- 其他 SQL 语句 -->
</mapper>
- 配置示例(注解):
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.cache.decorators.LruCache;
@CacheNamespace(eviction = LruCache.class)
public interface UserMapper {
// 方法定义
}
2. FIFO(First In First Out):先进先出策略
- 原理:按照对象进入缓存的顺序进行移除,先进入缓存的对象会先被移除。它使用一个队列来维护对象的进入顺序,当缓存空间满时,从队列的头部移除对象。
- 优点:实现简单,只需要维护一个队列,不需要记录对象的访问顺序,性能开销较小。
- 缺点:没有考虑对象的使用频率,可能会移除一些经常被使用的对象,导致缓存命中率较低。
- 配置示例(XML):
<mapper namespace="com.example.mapper.UserMapper">
<cache eviction="FIFO"/>
<!-- 其他 SQL 语句 -->
</mapper>
- 配置示例(注解):
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.cache.decorators.FifoCache;
@CacheNamespace(eviction = FifoCache.class)
public interface UserMapper {
// 方法定义
}
3. SOFT:软引用策略
- 原理:使用 Java 的软引用(
SoftReference
)来存储缓存对象。当系统内存充足时,软引用对象不会被垃圾回收;当系统内存不足时,垃圾回收器会优先回收软引用对象。 - 优点:可以根据系统内存的使用情况动态调整缓存的大小,避免内存溢出。
- 缺点:由于依赖于垃圾回收器的行为,缓存的清除时间不可控,可能会导致在内存紧张时才开始回收缓存,影响系统性能。
- 配置示例(XML):
<mapper namespace="com.example.mapper.UserMapper">
<cache eviction="SOFT"/>
<!-- 其他 SQL 语句 -->
</mapper>
- 配置示例(注解):
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.cache.decorators.SoftCache;
@CacheNamespace(eviction = SoftCache.class)
public interface UserMapper {
// 方法定义
}
4. WEAK:弱引用策略
- 原理:使用 Java 的弱引用(
WeakReference
)来存储缓存对象。只要垃圾回收器运行,弱引用对象就会被回收,无论系统内存是否充足。 - 优点:可以更及时地释放缓存占用的内存,避免内存泄漏。
- 缺点:缓存的命中率可能会比较低,因为缓存对象很容易被回收。
- 配置示例(XML):
<mapper namespace="com.example.mapper.UserMapper">
<cache eviction="WEAK"/>
<!-- 其他 SQL 语句 -->
</mapper>
- 配置示例(注解):
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.cache.decorators.WeakCache;
@CacheNamespace(eviction = WeakCache.class)
public interface UserMapper {
// 方法定义
}
选择合适的缓存回收策略
- 如果数据的访问频率差异较大:可以选择 LRU 策略,它能保留经常被访问的数据,提高缓存命中率。
- 如果对缓存的实现复杂度和性能开销有严格要求:FIFO 策略是一个不错的选择,它实现简单,性能开销小。
- 如果系统内存比较紧张:可以考虑使用 SOFT 或 WEAK 策略,它们能根据内存情况动态调整缓存大小,避免内存溢出。
通过合理选择缓存回收策略,可以提高 MyBatis 二级缓存的性能和效率,减少数据库的访问次数,从而提升整个应用系统的性能。
MyBatis 的二级缓存通过 <cache>
标签或 @CacheNamespace
注解进行配置,其中 eviction
属性用于指定缓存的回收策略,即缓存驱逐淘汰策略。以下是 MyBatis 提供的几种常见缓存回收策略及其详解:
1. LRU(Least Recently Used,最近最少使用)
- 原理:LRU 策略会移除最近最少使用的缓存项。当缓存达到其容量限制时,最长时间未被访问的缓存项将被优先淘汰。
- 适用场景:适用于大多数场景,尤其是当缓存数据的访问模式具有明显的局部性时。
- 配置示例:
或者使用注解:<cache eviction="LRU"/>
@CacheNamespace(eviction = org.apache.ibatis.cache.impl.LruCache.class)
2. FIFO(First In First Out,先进先出)
- 原理:FIFO 策略会移除最早被插入的缓存项。当缓存达到其容量限制时,最先插入的缓存项将被优先淘汰。
- 适用场景:适用于缓存数据的生命周期较短且插入顺序对淘汰策略影响不大的场景。
- 配置示例:
或者使用注解:<cache eviction="FIFO"/>
@CacheNamespace(eviction = org.apache.ibatis.cache.impl.FifoCache.class)
3. SOFT(软引用)
- 原理:SOFT 策略使用 Java 的软引用来缓存数据。当 JVM 内存不足时,软引用的缓存项可能会被垃圾回收器回收。
- 适用场景:适用于缓存数据较大且希望在内存不足时自动释放缓存的场景。
- 配置示例:
或者使用注解:<cache eviction="SOFT"/>
@CacheNamespace(eviction = org.apache.ibatis.cache.impl.SoftCache.class)
4. WEAK(弱引用)
- 原理:WEAK 策略使用 Java 的弱引用来缓存数据。弱引用的缓存项在下次垃圾回收时一定会被回收。
- 适用场景:适用于缓存数据较小且希望在垃圾回收时自动清理缓存的场景。
- 配置示例:
或者使用注解:<cache eviction="WEAK"/>
@CacheNamespace(eviction = org.apache.ibatis.cache.impl.WeakCache.class)
配置缓存回收策略的完整示例
XML 配置
<mapper namespace="com.example.mapper.UserMapper">
<!-- 配置二级缓存,使用 LRU 策略,缓存大小为 512,只读模式 -->
<cache
eviction="LRU"
size="512"
readOnly="true"
/>
<select id="getUserById" parameterType="int" resultType="com.example.entity.User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
注解配置
@Mapper
@CacheNamespace(
eviction = org.apache.ibatis.cache.impl.LruCache.class,
size = 512,
readWrite = false
)
public interface UserMapper {
User getUserById(Long id);
}
选择合适的缓存回收策略
- LRU:适用于大多数场景,能够有效利用缓存空间。
- FIFO:适用于缓存数据的生命周期较短且插入顺序对淘汰策略影响不大的场景。
- SOFT:适用于缓存数据较大且希望在内存不足时自动释放缓存的场景。
- WEAK:适用于缓存数据较小且希望在垃圾回收时自动清理缓存的场景。
通过合理选择和配置缓存回收策略,可以优化 MyBatis 的二级缓存性能,减少数据库的访问次数,提高应用程序的响应速度。
MyBatis二级缓存Eviction策略详解
MyBatis的二级缓存Eviction策略用于定义缓存空间不足时淘汰数据的规则,直接影响缓存命中率和系统性能。以下是主流策略的逐条解析:
一、核心策略分类
策略名 | 实现原理 | 适用场景 | 配置值 |
---|---|---|---|
LRU |
最近最少使用(Least Recently Used),优先淘汰最久未被访问的数据 | 通用场景,尤其适合数据访问具有时间局部性 | LRU |
FIFO |
先进先出(First In First Out),按数据进入缓存的顺序淘汰 | 数据访问模式无显著规律 | FIFO |
SOFT |
软引用(Soft Reference),基于JVM内存压力触发回收(内存不足时优先回收) | 内存敏感型应用,允许缓存非强一致性 | SOFT |
WEAK |
弱引用(Weak Reference),比SOFT 更激进,GC运行时立即回收 |
临时性缓存或允许频繁丢失数据的场景 | WEAK |
二、策略实现细节
LRU
(默认策略)- 数据结构:通过
LinkedHashMap
维护访问顺序 - 淘汰逻辑:
当缓存大小超过size
时,移除链表中最久未被访问的条目
淘汰对象 = arg min ( 最后访问时间戳 ) \text{淘汰对象} = \arg\min(\text{最后访问时间戳}) 淘汰对象=argmin(最后访问时间戳) - 示例配置:
<cache eviction="LRU" size="1024"/>
- 数据结构:通过
FIFO
- 数据结构:维护一个队列记录插入顺序
- 淘汰逻辑:
缓存满时,移除队列中最早进入缓存的条目
淘汰对象 = 队列头节点 \text{淘汰对象} = \text{队列头节点} 淘汰对象=队列头节点 - 示例配置:
@CacheNamespace(eviction = FifoCache.class, size = 500)
SOFT
- 依赖机制:使用
SoftReference
包装缓存对象 - 回收触发条件:
- JVM内存不足时(在抛出
OutOfMemoryError
前) - 依赖垃圾回收器策略,无法精确控制回收时机
- JVM内存不足时(在抛出
- 注意事项:
适合缓存非关键数据(如频繁计算的统计结果)
- 依赖机制:使用
WEAK
- 依赖机制:使用
WeakReference
包装缓存对象 - 回收触发条件:
- 垃圾回收器运行时立即回收
- 缓存对象存活周期最短,适用于临时性数据
- 配置示例:
<cache eviction="WEAK" size="2048"/>
- 依赖机制:使用
三、策略对比与选型建议
维度 | LRU |
FIFO |
SOFT |
WEAK |
---|---|---|---|---|
时间复杂度 | O ( 1 ) O(1) O(1)(链表+哈希表) | O ( 1 ) O(1) O(1)(队列) | 依赖GC | 依赖GC |
数据一致性 | 强 | 强 | 弱(可能突然失效) | 极弱(频繁失效) |
内存控制 | 精确控制对象数量 | 精确控制对象数量 | 依赖JVM内存管理 | 依赖JVM内存管理 |
典型应用 | 用户会话、热点数据 | 日志缓存、顺序访问数据 | 计算结果缓存 | 临时令牌缓存 |
选型建议:
- 优先使用
LRU
:适用于大多数查询场景,平衡命中率与性能 - 谨慎使用
SOFT/WEAK
:需充分测试GC行为对业务的影响 - 避免
FIFO
用于随机访问模式:可能导致缓存命中率骤降
四、高级调优技巧
容量与淘汰策略联动
- 设置合理的
size
值(过小导致频繁淘汰,过大会占用过多内存):<!-- 根据业务数据量调整 --> <cache eviction="LRU" size="2048"/>
- 设置合理的
混合使用刷新策略
- 配合
flushInterval
强制清空缓存,避免长期堆积陈旧数据:@CacheNamespace(eviction = LruCache.class, flushInterval = 3600000) // 1小时刷新
- 配合
监控淘汰效率
- 通过日志分析缓存淘汰频率:
# log4j配置 log4j.logger.org.apache.ibatis.cache=DEBUG
- 通过日志分析缓存淘汰频率:
五、常见问题解决方案
问题1:高并发下LRU性能瓶颈
- 现象:链表结构调整引发锁竞争
- 方案:改用分段锁缓存实现(如
ConcurrentLinkedHashMap
)或降低size
值
问题2:SOFT策略导致缓存雪崩
- 现象:内存不足时大量缓存同时失效,数据库压力骤增
- 方案:
<!-- 结合阻塞模式缓解 --> <cache eviction="SOFT" blocking="true"/>
通过合理选择Eviction策略,可显著提升MyBatis二级缓存的效率。建议结合压力测试与监控数据,针对业务特征进行精细化调参。
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作为二级缓存,可以通过自定义Cache
实现类,将Redis作为分布式缓存替代默认的本地缓存。以下是详细步骤和原理说明:
1. 实现原理
MyBatis的二级缓存通过Cache
接口定义,核心方法包括:
putObject(key, value)
:存入缓存getObject(key)
:获取缓存removeObject(key)
:删除缓存clear()
:清空缓存
通过实现该接口,将操作委托给Redis即可实现分布式缓存。
2. 实现步骤
(1)添加依赖
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- Redis客户端(以Jedis为例) -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.4.6</version>
</dependency>
(2)实现RedisCache类
import org.apache.ibatis.cache.Cache;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class RedisCache implements Cache {
private final String id;
private final JedisPool jedisPool;
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public RedisCache(String id) {
this.id = id;
this.jedisPool = new JedisPool("localhost", 6379);
}
@Override
public String getId() {
return id;
}
@Override
public void putObject(Object key, Object value) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.set(serialize(key), serialize(value));
}
}
@Override
public Object getObject(Object key) {
try (Jedis jedis = jedisPool.getResource()) {
return deserialize(jedis.get(serialize(key)));
}
}
@Override
public Object removeObject(Object key) {
try (Jedis jedis = jedisPool.getResource()) {
return deserialize(jedis.del(serialize(key)) > 0 ? "1" : null);
}
}
@Override
public void clear() {
try (Jedis jedis = jedisPool.getResource()) {
jedis.flushDB(); // 根据需求调整,如按前缀删除
}
}
@Override
public int getSize() {
try (Jedis jedis = jedisPool.getResource()) {
return Integer.parseInt(jedis.dbSize().toString());
}
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
// 序列化/反序列化方法(示例使用JSON)
private byte[] serialize(Object obj) {
return obj.toString().getBytes();
}
private Object deserialize(byte[] bytes) {
return bytes != null ? new String(bytes) : null;
}
}
(3)配置MyBatis使用RedisCache
在Mapper XML中添加:
<cache type="com.example.RedisCache">
<property name="eviction" value="LRU"/>
<property name="flushInterval" value="60000"/>
<property name="size" value="512"/>
</cache>
3. 高级优化
(1)序列化优化
- 使用高效序列化(如Kryo、Protobuf)替代JSON。
- 为不同数据类型定制序列化策略。
(2)缓存一致性
- 手动刷新:在数据变更后调用
sqlSession.clearCache()
。 - 自动过期:利用Redis的
EXPIRE
命令设置TTL。 - 监听Binlog:通过Canal等工具同步数据库变更到Redis。
(3)集群支持
- 使用Redis Cluster或哨兵模式提高可用性。
- 配置连接池参数(如
maxTotal
,maxIdle
)。
(4)性能监控
- 集成Prometheus + Grafana监控缓存命中率。
- 记录慢查询日志分析瓶颈。
4. 注意事项
- 线程安全:确保Redis操作线程安全(示例使用连接池+锁)。
- 异常处理:添加Redis连接失败的重试机制。
- 内存管理:根据Redis内存配置调整
size
参数。 - 安全加固:生产环境需配置密码和SSL连接。
5. 替代方案
- MyBatis-Redis插件:使用现成插件(如mybatis-redis-cache)简化集成。
- Spring Cache抽象:通过
@Cacheable
注解统一管理缓存。
通过这种方式,MyBatis的二级缓存将具备Redis的分布式特性,适用于高并发、多节点的应用场景。
以下是 MyBatis 使用 Redis 作为二级缓存的详细步骤和配置说明,结合知识库信息整理而成:
一、原理与目标
MyBatis 的二级缓存默认基于 PerpetualCache
(内存缓存),但其局限性在于:
- 单机缓存:无法跨 JVM 共享,分布式场景下不适用。
- 数据丢失:JVM 重启后缓存失效。
使用 Redis 的优势:
- 分布式缓存:支持多实例共享缓存。
- 持久化:数据可持久化,避免重启丢失。
- 高并发:Redis 的高性能适合高负载场景。
二、实现步骤
1. 添加依赖
根据项目技术栈选择 Redis 客户端库(Jedis 或 Spring Data Redis):
<!-- Maven 依赖示例(Spring Boot + Jedis) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
<!-- MyBatis 依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
2. 配置 MyBatis 全局二级缓存
在 mybatis-config.xml
或 Spring Boot 配置类中开启二级缓存,并指定自定义的 Redis 缓存实现类:
<!-- mybatis-config.xml -->
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/> <!-- 开启二级缓存 -->
<!-- 可选:指定默认缓存实现类(如全局使用 Redis) -->
</settings>
</configuration>
在 Spring Boot 中,通过配置类启用:
@Configuration
public class MyBatisConfig {
@Bean
public Configuration customConfiguration() {
Configuration configuration = new Configuration();
configuration.setCacheEnabled(true);
return configuration;
}
}
3. 实现自定义 Redis 缓存类
MyBatis 的 Cache
接口需要实现以下方法:
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class RedisCache implements Cache, ApplicationContextAware {
private static RedisTemplate<String, Object> redisTemplate;
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private String id;
@Override
public String getId() {
return id;
}
public RedisCache(String id) {
this.id = id;
}
@Override
public void putObject(Object key, Object value) {
String cacheKey = generateKey(key);
redisTemplate.opsForHash().put(id, cacheKey, value);
}
@Override
public Object getObject(Object key) {
String cacheKey = generateKey(key);
return redisTemplate.opsForHash().get(id, cacheKey);
}
@Override
public Object removeObject(Object key) {
String cacheKey = generateKey(key);
return redisTemplate.opsForHash().delete(id, cacheKey);
}
@Override
public void clear() {
redisTemplate.delete(id);
}
@Override
public int getSize() {
return redisTemplate.opsForHash().size(id).intValue();
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
redisTemplate = applicationContext.getBean(RedisTemplate.class);
}
private String generateKey(Object key) {
return key.toString();
}
}
4. 配置 Redis 连接
在 application.properties
中配置 Redis 信息:
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0
5. 在 Mapper 中启用 Redis 缓存
在 Mapper XML 或注解中指定 RedisCache
:
<!-- Mapper XML 配置 -->
<mapper namespace="com.example.mapper.UserMapper">
<cache type="com.example.cache.RedisCache"/>
<select id="selectUserById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
// 使用注解配置(MyBatis 3.4+)
@CacheNamespace(
implementation = RedisCache.class
)
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User selectUserById(int id);
}
6. 确保实体类可序列化
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
// getters and setters
}
三、关键配置说明
1. 缓存键生成规则
- 命名空间:
id
对应 Mapper 的namespace
,确保不同 Mapper 的缓存隔离。 - 键格式:
id + key
,例如com.example.mapper.UserMapper:1
。
2. Redis 数据存储结构
- Hash 结构:使用
id
作为 Hash 的 key,key
作为字段名,value
为序列化后的对象。 - 示例:
HSET com.example.mapper.UserMapper "1" "<serialized User object>"
3. 缓存失效策略
- 自动失效:通过 Redis 的 TTL(Time To Live)设置过期时间(需在
putObject
方法中添加)。 - 手动清除:在
insert
/update
/delete
操作后调用clear()
清空缓存。
4. Spring 集成优化
- 注入 RedisTemplate:通过
ApplicationContextAware
获取 Spring 管理的RedisTemplate
。 - 序列化配置:确保
RedisTemplate
的序列化器兼容性(如Jackson2JsonRedisSerializer
):@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; }
四、验证与测试
第一次查询:
User user1 = userMapper.selectUserById(1); // 走数据库,写入 Redis
Redis 中会存储
com.example.mapper.UserMapper:1
的键值对。第二次查询:
User user2 = userMapper.selectUserById(1); // 直接从 Redis 取值
日志应显示
Cache Hit Ratio
增加。重启验证:
- 重启应用后,再次查询仍能从 Redis 获取数据,说明缓存生效。
五、注意事项
- 并发控制:
- MyBatis 的
Cache
接口要求实现getReadWriteLock()
,需使用ReadWriteLock
避免并发问题。
- MyBatis 的
- 序列化兼容性:
- 使用
RedisTemplate
的 JSON 序列化器(如GenericJackson2JsonRedisSerializer
)可避免Serializable
的限制。
- 使用
- 数据一致性:
- 在
insert
/update
/delete
后,需手动调用clear()
或通过@CacheEvict
注解清除缓存。
- 在
- 性能监控:
- 使用 Redis 的
HLEN
或DBSIZE
命令监控缓存命中率和容量。
- 使用 Redis 的
六、完整示例代码
1. RedisCache 实现类
public class RedisCache implements Cache, ApplicationContextAware {
// ...(如上所述)
}
2. Spring Boot 配置类
@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;
}
}
3. Mapper 接口
@CacheNamespace(implementation = RedisCache.class)
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User selectUserById(int id);
}
通过以上步骤,MyBatis 可以成功集成 Redis 作为二级缓存,实现跨 JVM 的分布式缓存,提升系统性能和扩展性。
MyBatis 默认的二级缓存是基于内存的,在分布式环境下存在缓存一致性问题,而 Redis 是一个高性能的分布式缓存数据库,可用于解决该问题。下面将详细介绍如何让 MyBatis 使用 Redis 作为缓存。
1. 添加依赖
若使用 Maven 构建项目,需在 pom.xml
里添加 MyBatis 和 Redis 的相关依赖:
<dependencies>
<!-- MyBatis 核心依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<!-- Redis 客户端依赖 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
<!-- Jackson 用于对象序列化和反序列化 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.4</version>
</dependency>
</dependencies>
2. 实现 Redis 缓存类
要让 MyBatis 使用 Redis 作为缓存,需实现 org.apache.ibatis.cache.Cache
接口:
import org.apache.ibatis.cache.Cache;
import redis.clients.jedis.Jedis;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class RedisCache implements Cache {
private final String id;
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Jedis jedis = new Jedis("localhost", 6379);
private final ObjectMapper objectMapper = new ObjectMapper();
public RedisCache(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public void putObject(Object key, Object value) {
try {
String jsonValue = objectMapper.writeValueAsString(value);
jedis.set(key.toString(), jsonValue);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public Object getObject(Object key) {
String jsonValue = jedis.get(key.toString());
if (jsonValue != null) {
try {
return objectMapper.readValue(jsonValue, Object.class);
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
@Override
public Object removeObject(Object key) {
Object value = getObject(key);
jedis.del(key.toString());
return value;
}
@Override
public void clear() {
jedis.flushDB();
}
@Override
public int getSize() {
return Integer.parseInt(jedis.dbSize().toString());
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
}
3. 配置 MyBatis 使用 Redis 缓存
可通过两种方式配置 MyBatis 使用 Redis 缓存。
3.1 在 Mapper XML 文件中配置
在 Mapper XML 文件里指定使用自定义的 Redis 缓存类:
<mapper namespace="com.example.mapper.UserMapper">
<cache type="com.example.cache.RedisCache"/>
<!-- 具体的 SQL 查询语句 -->
<select id="selectUserById" parameterType="int" resultType="com.example.entity.User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
3.2 在 Mapper 接口上使用注解配置
在 Mapper 接口上使用 @CacheNamespace
注解指定使用 Redis 缓存类:
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Select;
@CacheNamespace(implementation = com.example.cache.RedisCache.class)
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User selectUserById(int id);
}
4. 注意事项
- 序列化问题:因为 Redis 存储的是字符串,所以在将对象存入 Redis 时要进行序列化,从 Redis 取出对象时要进行反序列化。上述示例使用 Jackson 库完成序列化和反序列化操作。
- Redis 连接管理:在实际应用中,要考虑 Redis 连接池的使用,以提升性能和资源利用率。
- 缓存一致性:尽管 Redis 能解决分布式环境下的缓存一致性问题,但在进行插入、更新、删除操作时,仍需确保及时更新或清除缓存。
通过以上步骤,MyBatis 就能使用 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缓存机制
- 默认二级缓存:基于
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() );
- 哨兵模式:自动故障转移
监控指标
- 缓存命中率: Hit Rate = 缓存命中次数 总请求次数 × 100 % \text{Hit Rate} = \frac{\text{缓存命中次数}}{\text{总请求次数}} \times 100\% Hit Rate=总请求次数缓存命中次数×100%
- 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 作为二级缓存,可以显著提升查询性能,尤其适用于分布式系统和高频读取场景。以下是详细的配置和使用教程:
一、核心依赖与配置
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的一级缓存是SqlSession级别的缓存,默认自动开启,无需额外配置即可使用。以下是关于一级缓存配置的详细说明和最佳实践:
1. 核心机制
- 作用域:一级缓存与SqlSession生命周期绑定,同一会话中的相同查询会复用缓存结果。
- 自动刷新:执行INSERT/UPDATE/DELETE操作后,MyBatis会自动清空一级缓存,确保数据一致性。
- 手动控制:可通过配置或语句参数调整缓存行为。
2. 配置方式
(1)全局配置(mybatis-config.xml)
<configuration>
<settings>
<!-- 控制一级缓存作用域 -->
<setting name="localCacheScope" value="SESSION"/> <!-- 默认SESSION(启用缓存) -->
<!-- 可选值:STATEMENT(禁用一级缓存) -->
</settings>
</configuration>
(2)Mapper XML配置
在单个查询语句中覆盖全局设置:
<select id="findUserById" resultType="User" flushCache="true" useCache="false">
SELECT * FROM user WHERE id = #{id}
</select>
flushCache="true"
:执行前清空一级缓存useCache="false"
:禁用该语句的一级缓存
3. 关键配置项
配置项 | 可选值 | 说明 |
---|---|---|
localCacheScope |
SESSION (默认) |
启用一级缓存,同一会话共享 |
STATEMENT |
禁用一级缓存,每次查询独立 | |
flushCache |
true /false (默认) |
执行前是否清空一级缓存 |
useCache |
true (默认) |
是否启用该语句的一级缓存 |
4. 最佳实践
(1)禁用缓存的场景
- 高频更新数据:如实时排行榜,避免脏读。
- 简单查询:执行成本低于缓存管理开销。
- 事务性操作:在事务中禁用缓存,防止中间状态被缓存。
(2)优化缓存效率
- 批量查询:合并多次查询为单次批量查询。
- 控制会话生命周期:避免长时间持有SqlSession导致内存泄漏。
- 监控缓存命中率:通过日志或插件分析缓存使用情况。
(3)与二级缓存协同
- 作用域隔离:一级缓存(SqlSession)和二级缓存(Mapper)独立,需通过
flushCache
控制交互。 - 缓存顺序:查询时先查一级缓存,再查二级缓存,最后查数据库。
5. 调试与监控
- 日志输出:启用MyBatis日志,观察缓存命中情况。
- JVM监控:使用VisualVM等工具监控堆内存变化。
- 自定义拦截器:通过
Interceptor
接口统计缓存使用频率。
6. 示例场景
<!-- 场景1:禁用特定查询的缓存 -->
<select id="getRealtimeData" resultType="Data" useCache="false">
SELECT * FROM sensor_data ORDER BY timestamp DESC LIMIT 1
</select>
<!-- 场景2:强制刷新缓存后查询 -->
<select id="getCriticalData" resultType="Data" flushCache="true">
SELECT * FROM financial_data WHERE status = 'CRITICAL'
</select>
通过合理配置一级缓存,可以在保证数据一致性的前提下,显著提升高频查询的性能。建议根据实际业务场景,结合二级缓存和Redis等分布式缓存方案构建多级缓存体系。
以下是 MyBatis 一级缓存的配置详解,结合知识库信息整理而成:
一、一级缓存的核心概念
- 作用:在同一个
SqlSession
会话中,缓存查询结果,避免重复查询相同 SQL。 - 默认状态:默认开启,不可直接关闭,但可通过配置
localCacheScope
或触发清空操作间接控制。 - 缓存结构:基于
HashMap
实现(PerpetualCache
类),简单高效。
二、核心配置项
1. localCacheScope
(一级缓存作用范围)
- 作用:定义一级缓存的有效范围。
- 可选值:
SESSION
(默认):缓存作用于整个SqlSession
会话,同一会话的所有查询共享缓存。STATEMENT
:缓存仅对当前 SQL 语句生效,语句执行后立即清空。
- 配置示例:
<!-- MyBatis全局配置文件(mybatis-config.xml) --> <settings> <setting name="localCacheScope" value="SESSION"/> <!-- 默认值 --> <!-- 或 <setting name="localCacheScope" value="STATEMENT"/> --> </settings>
2. 其他相关配置
cacheEnabled
:控制二级缓存是否开启,但不影响一级缓存。<settings> <setting name="cacheEnabled" value="true"/> <!-- 开启二级缓存 --> </settings>
logImpl
:开启 SQL 日志,便于验证缓存是否生效。<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
三、一级缓存的失效场景
即使配置正确,以下情况会导致一级缓存失效:
不同
SqlSession
:- 每个
SqlSession
有独立的一级缓存,跨会话无法共享。 - Spring 集成问题:Spring 默认每次操作会新建
SqlSession
,需确保在同一个SqlSession
中操作(如事务内)。
- 每个
查询条件不同:
- 即使 SQL 相同,但参数不同(如
id=1
和id=2
),缓存不会命中。
- 即使 SQL 相同,但参数不同(如
执行增删改操作:
- 调用
insert
、update
、delete
后,自动清空当前SqlSession
的一级缓存,避免脏读。
- 调用
手动清空缓存:
- 调用
SqlSession.clearCache()
或Executor.clearLocalCache()
。
- 调用
提交或回滚事务:
- 调用
sqlSession.commit()
或sqlSession.rollback()
前,会清空缓存。
- 调用
配置
flushCache="true"
:- 在映射文件中配置
flushCache="true"
的操作(如insert
/update
)会清空缓存。
- 在映射文件中配置
四、配置步骤与示例
1. 开启日志与基础配置
<!-- mybatis-config.xml -->
<configuration>
<settings>
<!-- 开启一级缓存(默认已开启,无需显式配置) -->
<setting name="localCacheScope" value="SESSION"/>
<!-- 开启 SQL 日志 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- 可选:关闭二级缓存(不影响一级缓存) -->
<setting name="cacheEnabled" value="false"/>
</settings>
</configuration>
2. 验证一级缓存生效
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询(命中数据库)
User user1 = mapper.selectUserById(1);
System.out.println("第一次查询:从数据库获取");
// 第二次查询(命中一级缓存)
User user2 = mapper.selectUserById(1);
System.out.println("第二次查询:从缓存获取");
System.out.println(user1 == user2); // 输出 true,引用地址相同
}
3. 触发缓存失效的场景
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 查询
User user = mapper.selectUserById(1); // 命中数据库
// 执行更新操作(自动清空缓存)
mapper.updateUser(user);
sqlSession.commit(); // 提交前再次清空缓存
// 再次查询(重新查询数据库)
User userUpdated = mapper.selectUserById(1);
// 输出日志会显示新的查询 SQL
}
五、Spring 集成时的一级缓存问题
在 Spring 中,由于 SqlSession
的生命周期由 Spring 管理,可能导致一级缓存失效:
- 问题:默认情况下,每次数据库操作会创建新的
SqlSession
,导致缓存无法共享。 - 解决方案:
- 事务控制:确保同一业务逻辑在同一个事务(
SqlSession
)中完成。@Transactional public void testCache() { User user1 = userMapper.selectUserById(1); // 第一次查询 User user2 = userMapper.selectUserById(1); // 命中缓存 }
- 手动控制
SqlSession
:@Autowired private SqlSessionFactory sqlSessionFactory; public void testCache() { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 所有操作在同一个 sqlSession 中 } }
- 事务控制:确保同一业务逻辑在同一个事务(
六、注意事项
不可直接关闭一级缓存:
- MyBatis 设计上无法直接关闭一级缓存,但可通过
localCacheScope="STATEMENT"
使缓存仅对单条 SQL 有效,等同于“临时关闭”。
- MyBatis 设计上无法直接关闭一级缓存,但可通过
缓存键(CacheKey)的生成:
- MyBatis 通过
CacheKey
确定缓存是否命中,其生成逻辑基于 SQL、参数、分页条件等。
- MyBatis 通过
性能优化:
- 对频繁增删改的场景,避免在
SqlSession
中长时间持有会话,减少缓存失效的开销。
- 对频繁增删改的场景,避免在
七、完整配置示例
<!-- mybatis-config.xml -->
<configuration>
<settings>
<!-- 设置一级缓存作用域为 SESSION -->
<setting name="localCacheScope" value="SESSION"/>
<!-- 开启 SQL 日志 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- 开启二级缓存(可选) -->
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 其他配置(如别名、类型处理器等) -->
</configuration>
总结
- 核心配置:通过
localCacheScope
控制作用范围,结合日志验证缓存效果。 - 失效场景:跨会话、数据变更、事务提交等均会导致缓存失效。
- Spring 集成:需注意
SqlSession
的生命周期,确保在同一会话中操作。
通过合理配置和理解失效条件,可以充分利用一级缓存提升查询性能。
MyBatis 的一级缓存是 SqlSession 级别的缓存,默认是开启的,且不能直接关闭,但可以通过一些操作来控制其行为。下面为你详细介绍一级缓存的配置相关内容。
一级缓存的基本原理
在同一个 SqlSession 中,MyBatis 会为每个查询创建一个 CacheKey 对象,该对象包含了 SQL 语句、参数等信息。当执行查询时,会先根据 CacheKey 从缓存中查找,如果找到则返回缓存中的结果,不再执行数据库查询;若未找到,则执行数据库查询,并将结果存入缓存。
一级缓存的默认开启
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 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,因此第二次查询会直接从缓存中获取结果,而不会再次执行数据库查询。
控制一级缓存的行为
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 ClearFirstLevelCacheExample {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
InputStream inputStream = ClearFirstLevelCacheExample.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 UseDifferentSqlSessionExample {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
InputStream inputStream = UseDifferentSqlSessionExample.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 的一级缓存,以保证数据的一致性。
- 一级缓存的作用域是 SqlSession,因此在不同的 SqlSession 中,缓存是相互独立的。
MyBatis 的一级缓存是 SqlSession 级别的缓存,默认是开启的,无法通过配置文件进行配置,但可以通过编程的方式进行控制。以下是关于 MyBatis 一级缓存的配置详解:
1. 默认行为
- 自动开启:一级缓存是 MyBatis 默认开启的,无需任何额外配置。
- 生命周期:一级缓存的生命周期与 SqlSession 的生命周期一致。当 SqlSession 关闭或提交时,一级缓存会被清空。
- 作用范围:一级缓存仅在同一个 SqlSession 中有效,不同的 SqlSession 之间无法共享一级缓存。
2. 缓存刷新
- 增删改操作:每次执行
insert
、update
或delete
操作后,MyBatis 会自动清空一级缓存。这意味着在同一个 SqlSession 中,如果进行了写操作,后续的查询操作将不会使用缓存,而是直接查询数据库。 - 手动刷新:可以通过调用
sqlSession.clearCache()
方法手动清空一级缓存。
3. 使用示例
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询,会从数据库中获取数据,并存入一级缓存
User user1 = userMapper.getUserById(1);
// 手动清除一级缓存
sqlSession.clearCache();
// 再次查询,会再次从数据库中获取数据
User user2 = userMapper.getUserById(1);
// 执行写操作后,一级缓存会被自动清空
userMapper.updateUser(user1);
User user3 = userMapper.getUserById(1);
sqlSession.close();
4. 注意事项
- 缓存的粒度:一级缓存的粒度是 SqlSession,因此在同一个 SqlSession 中进行多次查询时,缓存会生效。
- 数据一致性:由于一级缓存仅在同一个 SqlSession 中有效,因此在高并发场景下,不同的 SqlSession 之间不会共享缓存,从而避免了数据不一致的问题。
- 缓存的限制:一级缓存仅在单个 SqlSession 中有效,无法跨 SqlSession 共享缓存数据。如果需要更广泛的缓存共享,可以考虑使用二级缓存。
通过合理使用一级缓存,可以在单个 SqlSession 的范围内减少数据库的访问次数,提高查询性能。
MyBatis 一级缓存配置详解
一、基本概念
MyBatis 一级缓存是 SqlSession 级别 的缓存,默认开启,作用于同一个 SqlSession 生命周期内。
核心机制:
- 首次查询时,结果存入 SqlSession 的缓存;
- 后续相同查询直接读取缓存;
- 执行更新操作(INSERT/UPDATE/DELETE)或手动调用
clearCache()
会清空缓存。
二、配置要点
默认配置
无需显式配置,MyBatis 自动启用一级缓存。<!-- 无需额外设置 -->
缓存范围控制(重要)
通过localCacheScope
参数调整缓存作用范围:- SESSION(默认):缓存对整个 SqlSession 有效
- STATEMENT:每次查询后自动清空缓存(相当于禁用一级缓存)
<settings> <setting name="localCacheScope" value="SESSION"/> </settings>
强制刷新缓存
- 在 SQL 映射文件中通过
flushCache
属性控制:<select id="selectUser" resultType="User" flushCache="true"> SELECT * FROM user WHERE id = #{id} </select>
- 执行更新操作后自动清空缓存。
- 在 SQL 映射文件中通过
三、行为验证示例
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询(访问数据库)
User user1 = mapper.selectUser(1);
// 第二次查询(读取缓存)
User user2 = mapper.selectUser(1);
System.out.println(user1 == user2); // 输出 true
// 执行更新操作
mapper.updateUser(user1);
// 第三次查询(强制访问数据库)
User user3 = mapper.selectUser(1);
System.out.println(user2 == user3); // 输出 false
四、注意事项
事务影响
在 Spring 整合场景中,若 SqlSession 被事务管理器复用,可能导致一级缓存生命周期延长。缓存失效场景
- SqlSession 关闭
- 执行
sqlSession.clearCache()
- 查询条件或 SQL 语句不同
- 查询参数变化(即使 SQL 相同)
性能与数据一致性
- 频繁查询相同数据时,一级缓存可提升性能
- 高并发场景需注意缓存可能导致的数据不一致问题
五、总结建议
- 保留默认配置:除非有明确需求,否则无需修改
localCacheScope
- 谨慎处理长事务:避免因缓存生命周期过长导致数据过期
- 结合二级缓存:分布式场景建议启用二级缓存(需额外配置)
通过理解一级缓存机制和配置参数,可更好地平衡性能与数据一致性需求。
一、一级缓存的核心机制
1. 默认行为
• 自动开启:无需任何配置,随 SqlSession
的创建自动生效。
• 生命周期:与 SqlSession
绑定,SqlSession
关闭或调用 clearCache()
时销毁。
• 缓存范围:默认作用域为 STATEMENT
(每次 SQL 执行后清空缓存)。
2. 缓存键生成
• 规则:基于 SQL 语句、参数、结果集元数据(列名、类型)生成唯一键。
• 自定义键:通过实现 org.apache.ibatis.cache.CacheKey
接口自定义生成逻辑。
二、配置作用域
在 mybatis-config.xml
中通过 <settings>
配置 localCacheScope
,可选值如下:
作用域 | 行为描述 | 适用场景 |
---|---|---|
STATEMENT(默认) | 每次执行 SQL 语句(SELECT /INSERT /UPDATE /DELETE )后清空一级缓存。 |
高频更新场景(如电商订单表) |
SESSION | 仅在调用 sqlSession.commit() 或 rollback() 时清空一级缓存。 |
批量操作场景(如批量插入数据) |
JDBC | 完全禁用一级缓存,直接使用数据库查询结果。 | 调试或特殊需求(避免缓存干扰) |
1. 配置示例(XML)
<configuration>
<settings>
<setting name="localCacheScope" value="SESSION"/> <!-- 修改为 SESSION 或 JDBC -->
</settings>
</configuration>
2. Spring Boot 配置
在 application.properties
中设置:
mybatis.local-cache-scope=SESSION
三、手动控制一级缓存
1. 清空当前 SqlSession
的缓存
• Java 代码:
sqlSession.clearCache(); // 清空一级缓存
• Mapper XML:在增删改操作中强制刷新缓存:
<update id="deleteUser" parameterType="int" flushCache="true">
DELETE FROM users WHERE id = #{id}
</update>
2. 在事务中管理缓存
• 提交事务时自动刷新(localCacheScope=SESSION
):
sqlSession.beginTransaction();
try {
// 执行多个更新操作
sqlSession.commit(); // 提交时自动清空一级缓存
} catch (Exception e) {
sqlSession.rollback(); // 回滚时不刷新缓存
}
四、高级配置与最佳实践
1. 作用域选择指南
• STATEMENT:
• 优点:频繁刷新缓存,数据一致性高。
• 缺点:性能略低(每次 SQL 后清空)。
• SESSION:
• 优点:减少刷新次数,提升批量操作性能。
• 缺点:需手动管理事务边界,避免脏读。
• JDBC:
• 优点:完全绕过缓存,直接从数据库读取。
• 缺点:性能开销大,仅建议调试使用。
2. 代码示例
• 场景:在 Service 层中手动控制缓存刷新:
public class UserService {
public User updateUser(User user) {
sqlSession.update("com.example.UserMapper.updateUser", user);
sqlSession.clearCache(); // 强制清空一级缓存
return user;
}
}
• 场景:使用 SESSION
作用域批量插入数据:
public void batchInsert(List<User> users) {
sqlSession.beginTransaction();
for (User user : users) {
sqlSession.insert("com.example.UserMapper.insertUser", user);
}
sqlSession.commit(); // 提交时自动刷新缓存
}
五、注意事项
线程安全性:
•SqlSession
不是线程安全的,多线程环境下需通过线程池管理或使用ThreadLocal
绑定。与二级缓存的关系:
• 一级缓存是二级缓存的基础,二级缓存的命中数据会先存入一级缓存。
• 若关闭一级缓存(localCacheScope=JDBC
),二级缓存将直接查询数据库,失去意义。性能权衡:
• 频繁调用clearCache()
会显著降低性能,需结合业务场景谨慎使用。
• 对读多写少的场景(如配置表查询),优先使用默认STATEMENT
作用域。
六、总结
• 核心配置:通过 localCacheScope
控制作用域(STATEMENT
/SESSION
/JDBC
)。
• 手动刷新:通过 sqlSession.clearCache()
或 Mapper XML 的 flushCache="true"
强制刷新。
• 适用场景:
• STATEMENT:高频更新表(如订单、库存)。
• SESSION:批量操作(如批量插入、批量更新)。
• JDBC:调试或需完全绕过缓存的情况。
• 最佳实践:
• 默认使用 STATEMENT
,仅在批量操作时切换为 SESSION
。
• 显式调用 clearCache()
时需评估性能影响。
• 结合二级缓存(Redis/EhCache)优化跨会话查询性能。
通过灵活配置一级缓存,可以在数据一致性和查询性能之间找到平衡点,满足不同业务场景的需求。
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 |
语句级缓存,每次查询后清空 | 写多读少,强一致性要求 | 可能增加数据库压力 |
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 |
语句级缓存,每次查询后清空 | 写多读少,强一致性要求 | 可能增加数据库压力 |
MyBatis的localCacheScope
配置项用于控制一级缓存的作用域,是优化数据库交互性能的关键参数。以下是详细说明:
1. 参数作用
- SESSION(默认值):启用SqlSession级别的一级缓存,同一会话中的相同查询会复用缓存结果。
- STATEMENT:禁用一级缓存,每次查询独立执行,不共享缓存。
2. 配置方式
在mybatis-config.xml
中配置:
<configuration>
<settings>
<!-- 控制一级缓存作用域 -->
<setting name="localCacheScope" value="SESSION"/> <!-- 默认启用缓存 -->
<!-- <setting name="localCacheScope" value="STATEMENT"/> 禁用一级缓存 -->
</settings>
</configuration>
3. 行为差异
配置值 | 缓存行为 | 适用场景 |
---|---|---|
SESSION |
同一SqlSession中相同查询复用缓存,写入操作自动刷新缓存 | 读多写少,需减少数据库压力 |
STATEMENT |
每次查询独立执行,不共享缓存(相当于禁用一级缓存) | 高频更新数据,需严格数据一致性 |
4. 注意事项
- 自动刷新机制:即使启用
SESSION
,执行INSERT/UPDATE/DELETE后,MyBatis会自动清空一级缓存。 - 手动控制:可在Mapper XML中通过
flushCache
和useCache
覆盖全局配置:<select id="queryData" resultType="Data" flushCache="true" useCache="false"> SELECT * FROM table </select>
- 性能权衡:禁用缓存(
STATEMENT
)会增加数据库访问次数,需根据业务场景评估。
5. 监控与调试
- 日志分析:启用MyBatis日志,观察缓存命中情况:
# log4j.properties log4j.logger.org.apache.ibatis=DEBUG
- JVM监控:使用VisualVM等工具监控堆内存变化,避免缓存过大导致OOM。
6. 典型场景
- 启用缓存:报表查询、历史数据分析等读多写少场景。
- 禁用缓存:实时排行榜、金融交易等需严格数据一致性的场景。
通过合理配置localCacheScope
,可以在数据一致性与查询性能之间找到最佳平衡点。建议结合二级缓存(Mapper级缓存)和Redis等分布式缓存构建多级缓存体系。
MyBatis 一级缓存的 localCacheScope
配置详解
1. 核心概念
localCacheScope
是 MyBatis 中用于控制 一级缓存作用范围 的配置项。- 默认值:
SESSION
(即会话级别)。 - 作用:定义一级缓存(
SqlSession
级别的缓存)在什么范围内生效,直接影响缓存的生命周期和共享范围。
2. 可选值与含义
属性值 | 含义 |
---|---|
SESSION |
缓存作用于 整个 SqlSession 会话,同一会话中的所有查询共享同一缓存,跨会话不共享。 |
STATEMENT |
缓存仅对 当前执行的 SQL 语句生效,语句执行后立即清空,等同于“临时关闭一级缓存”。 |
3. 配置方法
在 mybatis-config.xml
中通过 <settings>
标签配置:
<configuration>
<settings>
<!-- 默认值:SESSION -->
<setting name="localCacheScope" value="SESSION"/>
<!-- 或者配置为 STATEMENT -->
<!-- <setting name="localCacheScope" value="STATEMENT"/> -->
</settings>
</configuration>
4. 两种模式的对比
配置值 | 作用范围 | 缓存生命周期 | 适用场景 |
---|---|---|---|
SESSION |
整个 SqlSession 会话 |
会话关闭(sqlSession.close() )或提交/回滚事务后清空 |
需要在同一会话中多次查询相同数据(如事务内) |
STATEMENT |
单条 SQL 语句 | SQL 执行结束后立即清空缓存 | 需要避免缓存复用(如测试或特殊场景) |
5. 典型场景与验证
# 场景 1:SESSION
模式(默认)
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询(命中数据库)
User user1 = mapper.selectUserById(1);
System.out.println("第一次查询:从数据库获取");
// 第二次查询(命中一级缓存)
User user2 = mapper.selectUserById(1);
System.out.println("第二次查询:从缓存获取");
System.out.println(user1 == user2); // 输出 true(引用相同对象)
}
# 场景 2:STATEMENT
模式
<settings>
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询(命中数据库)
User user1 = mapper.selectUserById(1);
// 第二次查询(再次查询数据库,缓存已清空)
User user2 = mapper.selectUserById(1);
System.out.println(user1 == user2); // 输出 false(两次查询结果是不同对象)
}
6. 关键特性与注意事项
# (1) 缓存失效场景
- 跨
SqlSession
:不同会话的缓存不共享。 - 执行增删改操作:
insert
、update
、delete
会清空当前SqlSession
的一级缓存。 - 提交或回滚事务:调用
sqlSession.commit()
或sqlSession.rollback()
后,缓存会被清空。 - 手动清空:调用
sqlSession.clearCache()
。
# (2) Spring 集成问题
- 问题:Spring 默认每次数据库操作会新建
SqlSession
,导致一级缓存无法跨操作复用。 - 解决方案:
- 使用
@Transactional
注解,确保同一事务内操作共享同一个SqlSession
。 - 手动管理
SqlSession
生命周期:@Autowired private SqlSessionFactory sqlSessionFactory; public void testCache() { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 所有操作在同一个 sqlSession 中 User user1 = mapper.selectUserById(1); User user2 = mapper.selectUserById(1); System.out.println(user1 == user2); // 输出 true } }
- 使用
# (3) 缓存键(CacheKey)生成规则
MyBatis 通过 CacheKey
确定缓存是否命中,其生成逻辑基于以下因素:
- SQL 语句的
statementId
(即 Mapper 接口方法的全限定名)。 - 查询参数(如
id=1
和id=2
的参数不同,缓存不命中)。 - 分页参数(如
offset
和limit
)。 - 动态 SQL(如
WHERE
条件的变化)。
7. 与二级缓存的关系
- 优先级:一级缓存(
SqlSession
级别)优先于二级缓存(跨SqlSession
共享)。 - 查询流程:
- 先检查一级缓存。
- 若未命中,再检查二级缓存。
- 若均未命中,查询数据库并将结果存入一级缓存和二级缓存(若开启)。
8. 总结
SESSION
模式:适合需要跨多次查询复用数据的场景(如事务内操作)。STATEMENT
模式:等同于临时关闭一级缓存,适用于需避免缓存复用的场景。- 关键点:确保在同一
SqlSession
中操作,避免跨会话或事务提交导致的缓存失效。
通过合理配置 localCacheScope
,可以最大化利用一级缓存提升性能,同时避免因缓存失效导致的重复数据库查询。
在 MyBatis 里,localCacheScope
是用来配置一级缓存作用范围的一个参数。它能够对一级缓存的行为产生影响,下面就详细介绍 localCacheScope
。
1. localCacheScope
的取值
localCacheScope
有两个可选值:SESSION
和 STATEMENT
。
1.1 SESSION
(默认值)
当 localCacheScope
设置为 SESSION
时,一级缓存的作用范围是整个 SqlSession
。也就是说,在同一个 SqlSession
内,执行相同的 SQL 查询时,MyBatis 会先从缓存里查找结果,如果找到就直接返回,不会再执行数据库查询。
配置示例(在 mybatis-config.xml
中):
<settings>
<setting name="localCacheScope" value="SESSION"/>
</settings>
代码示例:
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
public class SessionCacheScopeExample {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
InputStream inputStream = SessionCacheScopeExample.class.getClassLoader().getResourceAsStream(resource);