MyBatis 原生二级缓存"难以修复"的原因解析及解决方案

 更新时间:2025年12月04日 10:28:42   作者:旷野说  
文章主要讨论了MyBatis原生二级缓存存在的问题,包括结构性缺陷、难以修复等,社区提供了多种增强插件方案,但这些方案也有各自的优缺点,本文结合实例代码介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

🚫 一、为什么 MyBatis 原生二级缓存“难以修复”?

MyBatis 二级缓存的底层设计存在结构性缺陷,不是加个插件就能完美解决的:

问题原因高并发影响
缓存 Key 粒度粗糙Key = namespace + sql + params,但 params 是对象哈希,分页/排序等参数容易哈希冲突或忽略语义差异返回错误数据(如分页错乱)
无 TTL / 过期机制默认使用 PerpetualCache,数据永不过期内存泄漏 + 脏读
跨 JVM 无法共享缓存是本地内存(JVM 内),多实例部署时各节点缓存不一致数据分裂,用户看到不同结果
写操作不自动失效关联缓存更新 User 表,不会自动清理 OrderMapper 中关联的 user_orders 缓存脏读持续存在

⚠️ 这些是架构级缺陷,靠拦截器或装饰器插件无法根本解决

🔧 二、社区增强插件方案(可选,但需谨慎)

虽然不能“修复”,但有插件替换缓存实现,提升可用性:

1.mybatis-redis(最常用)

@Mapper
@CacheNamespace(implementation = RedisCache.class)
public interface OrderMapper {
    // ...
}
  • ✅ 优点:跨实例一致、可设 TTL(需自定义)
  • ❌ 缺点:
    • 无法解决 Key 语义问题(分页/动态 SQL 缓存 key 仍可能冲突)
    • 每次查询都多一次 Redis 网络 IO,在 10w QPS 下可能成为瓶颈
    • 序列化成本高(Java 对象需序列化为 byte[])

💡 适用场景:读多写少、数据变更不频繁、对延迟不敏感的配置类数据(如字典表)

2.自定义 Cache 实现(继承 MyBatisCache接口)

你可以自己实现带版本号逻辑过期的缓存:

public class VersionedCache implements Cache {
    private final String id;
    private final RedisTemplate redis;
    @Override
    public void putObject(Object key, Object value) {
        // key = "order:123", value = { data: {...}, version: 20251203 }
        redis.opsForHash().put("mybatis:cache:" + id, serialize(key), wrapWithVersion(value));
    }
    @Override
    public Object getObject(Object key) {
        Object cached = redis.opsForHash().get("mybatis:cache:" + id, serialize(key));
        if (cached != null && isVersionValid(cached)) {
            return unwrap(cached);
        }
        return null;
    }
}
  • ✅ 优点:可控制缓存结构、加版本、加 TTL
  • ❌ 缺点:开发维护成本高,且仍受限于 MyBatis 缓存 key 生成逻辑

🛑 三、我们的选择:弃用二级缓存,自建业务缓存层

在高并发订单系统中,我们最终彻底关闭 MyBatis 二级缓存,原因如下:

  1. 控制权不足:MyBatis 缓存是“黑盒”,无法插入降级、熔断、监控逻辑
  2. 异常难追溯:缓存脏数据问题往往延迟暴露,修复成本高
  3. 违背“可观测性”原则:缓存命中/失效无法对接 Prometheus / SkyWalking

✅ 替代方案:业务层 + Redis + 幂等 + 版本号

@Service
public class OrderService {
    public Order getOrder(Long id) {
        String cacheKey = "order:v2:" + id;
        Order order = redis.get(cacheKey, Order.class);
        if (order != null) return order;
        // 双检锁 + 空值缓存防穿透
        synchronized (getLockKey(id)) {
            order = redis.get(cacheKey, Order.class);
            if (order == null) {
                order = orderMapper.findById(id);
                if (order != null) {
                    redis.setex(cacheKey, 300, order); // 5分钟过期
                } else {
                    redis.setex("empty:" + cacheKey, 60, "1"); // 防缓存穿透
                }
            }
        }
        return order;
    }
    @Transactional
    public void updateOrder(Order order) {
        orderMapper.update(order);
        // 主动失效缓存(旁路删除)
        redis.delete("order:v2:" + order.getId());
    }
}

优势

  • 缓存 key 语义清晰(含版本 v2,便于灰度/回滚)
  • 支持 TTL、空值缓存、主动失效
  • 可集成 缓存命中率监控慢查询告警
  • Flink/Canal 数据对齐 机制无缝衔接

🧠 总结:二级缓存不是“能不能修”,而是“值不值得用”

方案适合场景高并发推荐度
MyBatis 原生二级缓存单机、低频、只读数据❌ 不推荐
mybatis-redis 插件多实例、读多写少、容忍一定延迟⚠️ 谨慎评估
自定义 Cache 实现有强缓存治理能力的团队⚠️ 成本高
业务层自建缓存高并发、强一致性、可观测性要求高强烈推荐

最终口诀更新:

“二级缓存看似香,架构缺陷难躲藏;
分页排序易冲突,多机部署更遭殃;
插件替换治标难,自建缓存才稳当;
Key 带版本加 TTL,高并发下不慌张!”

如果你正在设计高并发系统,放弃对 MyBatis 二级缓存的幻想,把缓存控制权拿回业务层,才是真正的“韧性设计”。

到此这篇关于MyBatis 原生二级缓存“难以修复”的原因解析及解决方案的文章就介绍到这了,更多相关mybatis二级缓存修复内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java中ByteBuddy动态字节码操作库的使用技术指南

    Java中ByteBuddy动态字节码操作库的使用技术指南

    ByteBuddy 是一个功能强大的 Java 字节码操作库,可以帮助开发者在运行时动态生成和修改类,而无需直接接触复杂的 ASM API,本文给大家介绍了Java ByteBuddy动态字节码操作库的使用技术指南,需要的朋友可以参考下
    2025-04-04
  • SpringBoot整合Canal方法详解

    SpringBoot整合Canal方法详解

    这篇文章主要介绍了SpringBoot整合Canal,canal可以用来监控数据库数据的变化,从而获得新增数据,或者修改的数据,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2022-12-12
  • Java 数据库连接(JDBC)的相关总结

    Java 数据库连接(JDBC)的相关总结

    这篇文章主要介绍了Java 数据库连接(JDBC)的相关总结,帮助大家更好的理解和学习使用Java,感兴趣的朋友可以了解下
    2021-03-03
  • Spring时间戳(日期)格式转换方式

    Spring时间戳(日期)格式转换方式

    这篇文章主要介绍了Spring时间戳(日期)格式转换方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-03-03
  • Spring Boot 整合持久层之JdbcTemplate

    Spring Boot 整合持久层之JdbcTemplate

    持久层是 Java EE 中访问数据库的核心操作,Spring Boot 中对常见的持久层框架都提供了自动化配置,例如 JdbcTemplate 、 JPA 等,Mybatis 的自动化配置则是 Mybatis 官方提供的
    2022-08-08
  • java代码实现银行管理系统

    java代码实现银行管理系统

    这篇文章主要为大家详细介绍了java代码实现银行管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-12-12
  • Java中字符编码格式详解

    Java中字符编码格式详解

    在java应用软件中,会有多处涉及到字符集编码,有些地方需要进行正确的设置,有些地方需要进行一定程度的处理。本文主要给大家讲解java中字符的编码格式等相关问题
    2016-03-03
  • Springboot集成Proguard生成混淆jar包方式

    Springboot集成Proguard生成混淆jar包方式

    本文介绍了两种Java代码混淆工具:ClassFinal和ProGuard,ClassFinal是一个字节码加密工具,但需要额外的加密包,使用复杂,ProGuard是一款开源的Java代码混淆工具,可以有效地提高代码的安全性,但对Spring框架的注解处理不够完善
    2024-11-11
  • Java中JSON字符串与java对象的互换实例详解

    Java中JSON字符串与java对象的互换实例详解

    这篇文章主要介绍了在java中,JSON字符串与java对象的相互转换实例详解,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-08-08
  • Java构造函数里的一些坑记录super()和this()

    Java构造函数里的一些坑记录super()和this()

    这篇文章主要介绍了Java构造函数里的一些坑记录super()和this(),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03

最新评论