第三方缓存集成
MyBatis 原生二级缓存是本地内存缓存,在分布式或多节点环境下无法共享。通过实现 org.apache.ibatis.cache.Cache 接口,可将 Redis、Ehcache、Caffeine 等第三方缓存集成到 MyBatis 中,实现分布式缓存与集中式管理。
Cache 接口规范
MyBatis 的缓存抽象层由 org.apache.ibatis.cache.Cache 接口定义:
Java
public interface Cache {
/** 缓存唯一标识,对应 Mapper 的 namespace */
String getId();
/** 放入缓存 */
void putObject(Object key, Object value);
/** 获取缓存 */
Object getObject(Object key);
/** 移除缓存 */
Object removeObject(Object key);
/** 清空缓存 */
void clear();
/** 缓存项数量 */
int getSize();
/** 获取读写锁(可选实现) */
ReadWriteLock getReadWriteLock();
}
核心要点:
getId()返回的字符串用于区分不同 Mapper 的缓存命名空间,putObject/getObject的 key 由 MyBatis 自动生成(包含 SQL ID、参数值等),value 为查询结果对象。
Redis 缓存实现
依赖引入
XML
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0</version>
</dependency>
<!-- 或使用 Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
自定义 Redis Cache 实现
Java
public class RedisCache implements Cache {
private final String id;
private static RedisTemplate<Object, Object> redisTemplate;
public RedisCache(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instance requires id");
}
this.id = id;
}
/**
* Spring 启动时注入 RedisTemplate
* 非 Spring 环境可通过静态方法手动注入
*/
public static void setRedisTemplate(RedisTemplate<Object, Object> template) {
redisTemplate = template;
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
if (redisTemplate != null) {
String cacheKey = buildCacheKey(key);
// 设置 30 分钟过期
redisTemplate.opsForValue().set(cacheKey, value, 30, TimeUnit.MINUTES);
}
}
@Override
public Object getObject(Object key) {
if (redisTemplate != null) {
String cacheKey = buildCacheKey(key);
return redisTemplate.opsForValue().get(cacheKey);
}
return null;
}
@Override
public Object removeObject(Object key) {
if (redisTemplate != null) {
String cacheKey = buildCacheKey(key);
redisTemplate.delete(cacheKey);
}
return null;
}
@Override
public void clear() {
if (redisTemplate != null) {
// 删除该 namespace 下所有 key
Set<Object> keys = redisTemplate.keys(id + ":*");
if (keys != null && !keys.isEmpty()) {
redisTemplate.delete(keys);
}
}
}
@Override
public int getSize() {
if (redisTemplate != null) {
Set<Object> keys = redisTemplate.keys(id + ":*");
return keys == null ? 0 : keys.size();
}
return 0;
}
@Override
public ReadWriteLock getReadWriteLock() {
// Redis 自带分布式锁,此处无需返回本地锁
return null;
}
/**
* 构建 Redis Key:namespace:md5(sql_hash)
* 使用 MD5 压缩 key 长度,避免 Redis key 过长
*/
private String buildCacheKey(Object key) {
return id + ":" + DigestUtils.md5Hex(key.toString());
}
}
Mapper XML 配置
XML
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 使用自定义 Redis 缓存 -->
<cache type="com.example.cache.RedisCache"/>
<select id="selectById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
Spring 初始化注入
Java
@Configuration
public class MyBatisCacheConfig {
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@PostConstruct
public void init() {
// MyBatis 通过反射创建 Cache 实例,需提前注入 RedisTemplate
RedisCache.setRedisTemplate(redisTemplate);
}
}
Ehcache 缓存实现
依赖引入
XML
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
ehcache.xml 配置
XML
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd">
<!-- 磁盘存储路径 -->
<diskStore path="java.io.tmpdir/ehcache"/>
<!-- 默认缓存配置 -->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="300"
overflowToDisk="true"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"/>
<!-- MyBatis 专用缓存 -->
<cache name="mybatisCache"
maxEntriesLocalHeap="5000"
timeToLiveSeconds="600"
timeToIdleSeconds="300"
overflowToDisk="false"
statistics="true"/>
</ehcache>
使用内置 Ehcache 实现
XML
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 直接使用 mybatis-ehcache 提供的实现 -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache">
<property name="timeToIdleSeconds" value="300"/>
<property name="timeToLiveSeconds" value="600"/>
</cache>
<select id="selectById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
缓存工具对比
| 特性 | Redis | Ehcache | Caffeine |
|---|---|---|---|
| 分布式共享 | 支持 | 支持(Terracotta) | 不支持(纯本地) |
| 持久化 | RDB/AOF | 磁盘溢出 | 不支持 |
| 过期策略 | 支持 TTL | 支持 TTI/TTL | 支持 TTI/TTL |
| 集群模式 | Sentinel/Cluster | Terracotta Server | 无 |
| 性能 | 中(网络 IO) | 高(内存) | 极高(本地内存) |
| 适用场景 | 多节点共享缓存 | 单节点大容量缓存 | 单节点高性能缓存 |
缓存穿透与雪崩防护
缓存空值防护
Java
@Override
public Object getObject(Object key) {
String cacheKey = buildCacheKey(key);
Object value = redisTemplate.opsForValue().get(cacheKey);
// 空值标记:缓存查询结果为空时,存储特殊标记避免穿透到 DB
if (NULL_VALUE.equals(value)) {
return null;
}
return value;
}
@Override
public void putObject(Object key, Object value) {
String cacheKey = buildCacheKey(key);
Object cacheValue = (value == null) ? NULL_VALUE : value;
redisTemplate.opsForValue().set(cacheKey, cacheValue, 30, TimeUnit.MINUTES);
}
private static final String NULL_VALUE = "__NULL_CACHE_MARKER__";
缓存雪崩防护
Java
// 过期时间增加随机值,避免大量 key 同时过期
private long getRandomTTL() {
long baseTTL = 30; // 基础 30 分钟
long randomSeconds = ThreadLocalRandom.current().nextInt(0, 300); // 随机 0-5 分钟
return baseTTL + randomSeconds / 60;
}
@Override
public void putObject(Object key, Object value) {
String cacheKey = buildCacheKey(key);
long ttl = getRandomTTL();
redisTemplate.opsForValue().set(cacheKey, value, ttl, TimeUnit.MINUTES);
}
手动刷新缓存
Java
@Service
public class UserCacheService {
@Autowired
private SqlSessionFactory sqlSessionFactory;
/**
* 手动清空指定 Mapper 的二级缓存
*/
public void clearUserCache() {
Configuration config = sqlSessionFactory.getConfiguration();
// 通过 namespace 获取对应的 Cache 实例并清空
Cache cache = config.getCache("com.example.mapper.UserMapper");
if (cache != null) {
cache.clear();
}
}
}
注意事项
- 实体类必须实现 Serializable:第三方缓存通常通过网络或磁盘传输,序列化是必须的
- 缓存一致性:多表关联查询时,仅清空关联表的缓存,避免误清无关缓存
- Redis 连接管理:非 Spring 环境下需自行管理 Redis 连接池生命周期
- key 长度优化:MyBatis 自动生成的 key 较长,建议使用 MD5/SHA 压缩
- 缓存穿透防护:对空结果设置短 TTL 的缓存标记,避免恶意请求打到数据库
要点总结
- MyBatis 通过
Cache接口抽象缓存层,可灵活集成 Redis、Ehcache 等第三方缓存 - Redis 缓存实现需注意 key 构建、TTL 设置、分布式锁与 Spring 注入时机
- Ehcache 适合单节点大容量缓存,支持磁盘溢出与 Terracotta 集群
- 缓存穿透通过空值标记防护,缓存雪崩通过随机 TTL 分散过期时间
- 实体类必须实现 Serializable,key 建议用 MD5 压缩长度
- 手动刷新缓存通过
Configuration.getCache(namespace)获取实例后调用clear()
存放路径:D:\git2\jwdev\articles\MYBATIS\专家\生态工具与扩展\第三方缓存集成.md
📝 发现内容有误?点击此处直接编辑