全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-20 8 分钟 ✍️ juanwangdev

第三方缓存集成

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>

缓存工具对比

特性RedisEhcacheCaffeine
分布式共享支持支持(Terracotta)不支持(纯本地)
持久化RDB/AOF磁盘溢出不支持
过期策略支持 TTL支持 TTI/TTL支持 TTI/TTL
集群模式Sentinel/ClusterTerracotta 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();
        }
    }
}

注意事项

  1. 实体类必须实现 Serializable:第三方缓存通常通过网络或磁盘传输,序列化是必须的
  2. 缓存一致性:多表关联查询时,仅清空关联表的缓存,避免误清无关缓存
  3. Redis 连接管理:非 Spring 环境下需自行管理 Redis 连接池生命周期
  4. key 长度优化:MyBatis 自动生成的 key 较长,建议使用 MD5/SHA 压缩
  5. 缓存穿透防护:对空结果设置短 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

📝 发现内容有误?点击此处直接编辑

← 上一篇 生态工具对比
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库