MyBatis:缓存体系设计与避坑大全

一、MyBatis 缓存核心原理深度解析

1.1 缓存架构全景图

java 复制代码
┌─────────────────────────────────────────────────┐
│                  Spring Boot 应用                 │
├─────────────────────────────────────────────────┤
│            Service 层 (Spring Cache)             │
├─────────────────────────────────────────────────┤
│          Mapper 层 (MyBatis 二级缓存)             │
├─────────────────────────────────────────────────┤
│          SqlSession 层 (MyBatis 一级缓存)          │
├─────────────────────────────────────────────────┤
│             数据库连接池 (HikariCP)                │
└─────────────────────────────────────────────────┘

1.2 缓存查询完整流程

java 复制代码
// 企业级缓存查询流程图
public Object executeQuery(Object param) {
    // 1. 检查Spring Cache(如果有)
    // 2. 检查MyBatis二级缓存(开启且命中)
    // 3. 检查MyBatis一级缓存(当前SqlSession内)
    // 4. 执行SQL查询数据库
    // 5. 填充一级缓存
    // 6. 事务提交时刷入二级缓存
    // 7. 更新Spring Cache(如果有)
}

二、Spring 整合下的"一级缓存陷阱"深度剖析

2.1 Spring 事务管理对一级缓存的影响
2.1.1 核心机制

java 复制代码
// Spring事务管理下的SqlSession生命周期
@Component
@Slf4j
public class CacheTransactionAnalysis {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    /**
     * 场景1:Spring事务中的一级缓存行为
     */
    @Test
    void testCacheWithTransaction() {
        // 开启事务(默认REQUIRED)
        userService.getUserInTransaction(1L);
        // 事务提交,SqlSession关闭,一级缓存失效
    }
    
    /**
     * 场景2:非事务环境下的缓存行为
     */
    @Test
    void testCacheWithoutTransaction() {
        // 每次Mapper调用都创建新SqlSession
        User user1 = userService.getUser(1L); // SqlSession1
        User user2 = userService.getUser(1L); // SqlSession2
        // 无法命中一级缓存!
    }
}

@Service
@Slf4j
class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    /**
     * 事务方法 - 一级缓存有效
     */
    @Transactional
    public User getUserInTransaction(Long id) {
        log.info("事务开始,创建SqlSession");
        
        // 第一次查询,访问数据库
        User user1 = userMapper.selectById(id);
        log.info("第一次查询完成,一级缓存: {}", 
                System.identityHashCode(user1));
        
        // 第二次查询,命中一级缓存(同一个SqlSession)
        User user2 = userMapper.selectById(id);
        log.info("第二次查询完成,一级缓存命中: {}",
                System.identityHashCode(user2));
        
        // 验证缓存命中(同一个对象)
        log.info("是否为同一对象: {}", user1 == user2); // true
        
        return user2;
    }
    
    /**
     * 非事务方法 - 一级缓存失效
     */
    public User getUser(Long id) {
        // 每次调用都会创建新的SqlSession
        return userMapper.selectById(id);
    }
}

2.1.2 生产环境配置优化

java 复制代码
# application.yml - 一级缓存优化配置
mybatis:
  configuration:
    # 关键配置:控制一级缓存作用域
    local-cache-scope: SESSION  # 生产环境推荐SESSION
    
    # Spring整合下的特殊配置
    default-executor-type: REUSE  # 重用Statement,提升性能
    
    # 连接超时设置
    default-statement-timeout: 30000
    
    # 自动映射行为
    auto-mapping-behavior: FULL

2.1.3 一级缓存失效场景检测

java 复制代码
@Component
@Slf4j
public class FirstLevelCacheMonitor {
    
    @Autowired
    private DataSource dataSource;
    
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    /**
     * 检测一级缓存命中率
     */
    public void monitorCacheHitRate() {
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
            Configuration config = sqlSession.getConfiguration();
            
            // 模拟多次查询
            long start = System.currentTimeMillis();
            for (int i = 0; i < 100; i++) {
                sqlSession.selectOne("com.example.UserMapper.selectById", 1L);
            }
            long end = System.currentTimeMillis();
            
            log.info("查询耗时: {}ms", end - start);
            // 一级缓存特性:后续查询几乎0耗时
        }
    }
    
    /**
     * 检测Spring事务对缓存的影响
     */
    public void testSpringTransactionCache() {
        // 模拟不同传播行为
        // REQUIRED(默认):加入现有事务或创建新事务
        // REQUIRES_NEW:总是创建新事务,新SqlSession
        // NESTED:嵌套事务,特殊的一级缓存行为
        
        // 重点:不同传播级别会影响SqlSession的生命周期
    }
}

2.2 一级缓存最佳实践

java 复制代码
// 一级缓存使用规范
@Service
@Slf4j
class CacheBestPracticeService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private OrderMapper orderMapper;
    
    /**
     * 场景1:事务内批量查询 - 充分利用一级缓存
     */
    @Transactional
    public void batchProcess(Long userId) {
        // 同一个事务,同一个SqlSession
        User user = userMapper.selectById(userId); // 首次查询
        
        // 后续多次查询同一数据(命中缓存)
        for (int i = 0; i < 10; i++) {
            User cachedUser = userMapper.selectById(userId); // 命中一级缓存
            processUser(cachedUser);
        }
    }
    
    /**
     * 场景2:避免跨事务缓存依赖
     */
    public void crossTransactionIssue() {
        // ❌ 错误示例:依赖跨事务缓存
        User user1 = getUserInNewTransaction(1L); // 事务1
        User user2 = getUserInNewTransaction(1L); // 事务2,重新查询
        
        // ✅ 正确做法:事务外部缓存
        User user = getFromExternalCache(1L); // 使用Redis等外部缓存
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public User getUserInNewTransaction(Long id) {
        return userMapper.selectById(id);
    }
}

三、二级缓存"提交才生效"机制深度解析

3.1 二级缓存写入时机详解

java 复制代码
/**
 * 二级缓存生命周期演示
 */
@Component
@Slf4j
public class SecondLevelCacheLifecycle {
    
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    /**
     * 演示二级缓存写入时机
     */
    public void demonstrateCacheWriteTiming() {
        log.info("=== 二级缓存写入时机演示 ===");
        
        // 场景1:没有提交,二级缓存不写入
        SqlSession session1 = sqlSessionFactory.openSession();
        try {
            UserMapper mapper1 = session1.getMapper(UserMapper.class);
            
            log.info("第一次查询(Session1)");
            User user1 = mapper1.selectById(1L); // 查询数据库
            
            // ❌ 重要:此时二级缓存还没有数据!
            log.info("查询完成,但未提交,二级缓存为空");
            
            // session1.close(); // 关闭也会触发写入
        } finally {
            session1.rollback(); // 回滚,二级缓存不写入
        }
        
        // 场景2:提交后,二级缓存才有数据
        SqlSession session2 = sqlSessionFactory.openSession();
        try {
            UserMapper mapper2 = session2.getMapper(UserMapper.class);
            
            log.info("第二次查询(Session2,新会话)");
            User user2 = mapper2.selectById(1L); // 仍然查数据库
            
            log.info("说明:没有提交,二级缓存未生效");
            
            // 提交事务,写入二级缓存
            session2.commit();
            log.info("提交成功,数据已写入二级缓存");
            
        } finally {
            session2.close();
        }
        
        // 场景3:新会话命中二级缓存
        SqlSession session3 = sqlSessionFactory.openSession();
        try {
            UserMapper mapper3 = session3.getMapper(UserMapper.class);
            
            log.info("第三次查询(Session3)");
            User user3 = mapper3.selectById(1L); // 命中二级缓存
            
            log.info("二级缓存命中成功!");
        } finally {
            session3.close();
        }
    }
}

3.2 二级缓存配置的黄金法则

java 复制代码
<!-- mybatis-config.xml 二级缓存企业级配置 -->
<configuration>
    
    <!-- 全局缓存设置 -->
    <settings>
        <!-- 开启缓存(默认true) -->
        <setting name="cacheEnabled" value="true"/>
        
        <!-- 本地缓存作用域 -->
        <setting name="localCacheScope" value="SESSION"/>
    </settings>
    
    <!-- 缓存配置模板 -->
    <cache-template id="defaultCache">
        <!-- 回收策略:LRU最近最少使用 -->
        <property name="eviction" value="LRU"/>
        
        <!-- 刷新间隔:5分钟 -->
        <property name="flushInterval" value="300000"/>
        
        <!-- 缓存大小:1000个对象 -->
        <property name="size" value="1000"/>
        
        <!-- 只读缓存(性能最好) -->
        <property name="readOnly" value="true"/>
        
        <!-- 序列化策略:Kryo(可选) -->
        <!-- <property name="serializer" value="org.mybatis.caches.kryo.KryoCacheSerializer"/> -->
    </cache-template>
</configuration>
<!-- UserMapper.xml - 企业级配置示例 -->
<mapper namespace="com.example.mapper.UserMapper">
    
    <!-- 继承默认模板,可覆盖属性 -->
    <cache-ref namespace="defaultCache"/>
    
    <!-- 或者自定义配置 -->
    <cache
        eviction="LRU"
        flushInterval="300000"
        size="1000"
        readOnly="true"
        blocking="false"  <!-- 防止缓存击穿 -->
        type="org.mybatis.caches.ehcache.EhcacheCache">
        
        <!-- Ehcache 特有配置 -->
        <property name="memoryStoreEvictionPolicy" value="LRU"/>
        <property name="maxEntriesLocalHeap" value="1000"/>
        <property name="timeToLiveSeconds" value="300"/>
        <property name="timeToIdleSeconds" value="180"/>
    </cache>
    
    <!-- 查询语句配置 -->
    <select id="selectById" resultType="User" useCache="true">
        SELECT * FROM user WHERE id = #{id}
    </select>
    
    <!-- 企业级规范:所有DML操作必须清空缓存 -->
    <update id="updateUser" parameterType="User" flushCache="true">
        UPDATE user 
        SET name = #{name}, 
            updated_at = NOW()
        WHERE id = #{id}
    </update>
    
    <insert id="insertUser" parameterType="User" flushCache="true">
        INSERT INTO user (name, email) 
        VALUES (#{name}, #{email})
    </insert>
    
    <delete id="deleteUser" parameterType="Long" flushCache="true">
        DELETE FROM user WHERE id = #{id}
    </delete>
</mapper>

3.3 生产环境二级缓存监控

java 复制代码
/**
 * 二级缓存监控与统计
 */
@Component
@Slf4j
public class SecondLevelCacheMonitor {
    
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    /**
     * 获取缓存统计信息
     */
    public CacheStats getCacheStats(String namespace) {
        Configuration configuration = sqlSessionFactory.getConfiguration();
        Cache cache = configuration.getCache(namespace);
        
        if (cache == null) {
            return null;
        }
        
        CacheStats stats = new CacheStats();
        stats.setNamespace(namespace);
        stats.setCacheSize(cache.getSize());
        stats.setCacheType(cache.getClass().getSimpleName());
        
        // 如果是Ehcache,获取更多信息
        if (cache instanceof EhcacheCache) {
            EhcacheCache ehcache = (EhcacheCache) cache;
            net.sf.ehcache.Ehcache underlyingCache = ehcache.getEhcache();
            stats.setMemoryStoreSize(underlyingCache.getMemoryStoreSize());
            stats.setDiskStoreSize(underlyingCache.getDiskStoreSize());
            stats.setHitCount(underlyingCache.getStatistics().cacheHitCount());
            stats.setMissCount(underlyingCache.getStatistics().cacheMissCount());
            stats.setHitRatio(underlyingCache.getStatistics().cacheHitRatio());
        }
        
        return stats;
    }
    
    /**
     * 清理指定命名空间缓存
     */
    public void clearCache(String namespace) {
        Configuration configuration = sqlSessionFactory.getConfiguration();
        Cache cache = configuration.getCache(namespace);
        if (cache != null) {
            cache.clear();
            log.info("已清理缓存: {}", namespace);
        }
    }
    
    /**
     * 批量清理缓存
     */
    public void batchClearCache(Set<String> namespaces) {
        Configuration configuration = sqlSessionFactory.getConfiguration();
        Collection<Cache> caches = configuration.getCaches();
        
        for (Cache cache : caches) {
            String cacheId = cache.getId();
            if (namespaces.contains(cacheId)) {
                cache.clear();
                log.info("已清理缓存: {}", cacheId);
            }
        }
    }
    
    @Data
    public static class CacheStats {
        private String namespace;
        private int cacheSize;
        private String cacheType;
        private long memoryStoreSize;
        private long diskStoreSize;
        private long hitCount;
        private long missCount;
        private double hitRatio;
    }
}

四、二级缓存 Key 结构深度解析与优化

4.1 CacheKey 组成原理

java 复制代码
/**
 * MyBatis CacheKey 结构分析
 */
@Component
@Slf4j
public class CacheKeyAnalyzer {
    
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    /**
     * 分析缓存Key的组成
     */
    public void analyzeCacheKey(String statementId, Object parameter) {
        Configuration configuration = sqlSessionFactory.getConfiguration();
        MappedStatement ms = configuration.getMappedStatement(statementId);
        
        // 模拟创建CacheKey的过程
        CacheKey cacheKey = ms.getCacheKey(
            parameter, 
            RowBounds.DEFAULT, 
            ms.getBoundSql(parameter)
        );
        
        log.info("=== CacheKey 结构分析 ===");
        log.info("MappedStatement ID: {}", ms.getId());
        log.info("SQL: {}", ms.getBoundSql(parameter).getSql());
        log.info("参数: {}", parameter);
        log.info("RowBounds: {}", RowBounds.DEFAULT);
        log.info("Environment ID: {}", configuration.getEnvironment().getId());
        
        // 获取hashcode(MyBatis用于比较的hash)
        log.info("CacheKey HashCode: {}", cacheKey.hashCode());
        log.info("CacheKey UpdateCount: {}", cacheKey.getUpdateCount());
        
        // 重要结论:以下情况会产生不同的CacheKey
        // 1. SQL语句不同(即使一个空格差异)
        // 2. 参数值不同
        // 3. 分页参数不同
        // 4. 不同的Mapper方法
    }
    
    /**
     * 演示动态SQL对缓存的影响
     */
    public void demonstrateDynamicSqlCacheIssue() {
        // 场景:动态SQL会产生大量不同的CacheKey
        // SELECT * FROM users WHERE 1=1
        //   AND name = ?      --> CacheKey1
        //   AND age > ?       --> CacheKey2  
        //   AND name=? AND age>? --> CacheKey3
        
        // 解决方案:使用固定SQL或控制参数组合
    }
}

4.2 缓存碎片化问题解决方案

java 复制代码
<!-- 优化前:动态SQL导致缓存碎片 -->
<select id="searchUsers" parameterType="UserQuery" resultType="User">
    SELECT * FROM users WHERE 1=1
    <if test="name != null">
        AND name LIKE CONCAT('%', #{name}, '%')
    </if>
    <if test="age != null">
        AND age > #{age}
    </if>
    <if test="email != null">
        AND email = #{email}
    </if>
    <!-- 问题:参数组合爆炸,缓存命中率极低 -->
</select>

<!-- 优化方案1:固定SQL,应用层过滤 -->
<select id="searchAllUsers" resultType="User">
    SELECT * FROM users 
    WHERE created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)
    <!-- 限制数据量,缓存全部数据 -->
</select>

<!-- 优化方案2:关键查询单独缓存 -->
<select id="searchUsersByName" parameterType="String" resultType="User">
    SELECT * FROM users 
    WHERE name LIKE CONCAT('%', #{name}, '%')
    <!-- 单独缓存,命中率高 -->
</select>

<!-- 优化方案3:参数标准化 -->
<select id="searchUsersByStandardQuery" parameterType="StandardQuery" resultType="User">
    SELECT * FROM users 
    WHERE 
    <choose>
        <when test="queryType == 'RECENT'">
            created_at > DATE_SUB(NOW(), INTERVAL 7 DAY)
        </when>
        <when test="queryType == 'ACTIVE'">
            last_login > DATE_SUB(NOW(), INTERVAL 30 DAY)
        </when>
        <otherwise>
            1=1
        </otherwise>
    </choose>
    <!-- 标准化查询类型,减少参数组合 -->
</select>
/**
 * 缓存Key优化策略
 */
@Service
@Slf4j
public class CacheKeyOptimizationService {
    
    /**
     * 方法1:参数标准化
     */
    public List<User> searchUsers(UserQuery query) {
        // 标准化参数
        StandardizedQuery stdQuery = standardizeQuery(query);
        
        // 使用标准化参数查询
        return userMapper.searchWithStandardQuery(stdQuery);
    }
    
    private StandardizedQuery standardizeQuery(UserQuery query) {
        StandardizedQuery stdQuery = new StandardizedQuery();
        
        // 标准化名称查询(忽略大小写,trim)
        if (query.getName() != null) {
            stdQuery.setName(query.getName().trim().toLowerCase());
        }
        
        // 标准化年龄查询(按年龄段)
        if (query.getAge() != null) {
            stdQuery.setAgeGroup(calculateAgeGroup(query.getAge()));
        }
        
        // 标准化分页(固定分页大小)
        stdQuery.setPageSize(20);
        stdQuery.setPageNum(query.getPageNum() != null ? query.getPageNum() : 1);
        
        return stdQuery;
    }
    
    /**
     * 方法2:查询结果二次过滤
     */
    public List<User> searchWithPostFilter(UserQuery query) {
        // 1. 获取基础数据集(缓存友好)
        List<User> allUsers = userMapper.getRecentUsers(1000);
        
        // 2. 应用层过滤
        return allUsers.stream()
            .filter(user -> matchesQuery(user, query))
            .collect(Collectors.toList());
    }
}

五、二级缓存不适用场景全解析

5.1 禁止使用二级缓存的场景清单

java 复制代码
/**
 * 二级缓存禁用场景检测器
 */
@Component
@Slf4j
public class CacheSuitabilityChecker {
    
    /**
     * 检查是否适合使用二级缓存
     */
    public CacheSuitability checkSuitability(String mapperName, CacheConfig config) {
        CacheSuitability result = new CacheSuitability();
        result.setMapperName(mapperName);
        
        // 规则1:高并发写场景
        if (config.getWriteQps() > 100) { // QPS > 100
            result.setSuitable(false);
            result.addReason("高并发写场景,缓存失效频繁");
        }
        
        // 规则2:强一致性要求
        if (config.isStrongConsistency()) {
            result.setSuitable(false);
            result.addReason("强一致性要求,缓存可能导致脏读");
        }
        
        // 规则3:数据频繁变更
        if (config.getUpdateFrequency() == UpdateFrequency.SECONDS) {
            result.setSuitable(false);
            result.addReason("数据秒级变更,缓存价值低");
        }
        
        // 规则4:大结果集查询
        if (config.getAvgResultSize() > 1000) {
            result.setSuitable(false);
            result.addReason("结果集过大,内存压力大");
        }
        
        // 规则5:动态SQL复杂
        if (config.isDynamicSql()) {
            result.setSuitable(false);
            result.addReason("动态SQL导致缓存碎片化");
        }
        
        // 规则6:分页查询多变
        if (config.isPaginationVolatile()) {
            result.setSuitable(false);
            result.addReason("分页参数多变,缓存命中率低");
        }
        
        return result;
    }
    
    /**
     * 适合使用缓存的场景
     */
    public List<String> getSuitableScenarios() {
        return Arrays.asList(
            "1. 配置表/字典表(极少变更)",
            "2. 地区信息表(只读)",
            "3. 商品分类表(低频变更)",
            "4. 用户角色权限(低频变更)",
            "5. 历史数据查询(只读)",
            "6. 统计结果缓存(定时更新)"
        );
    }
    
    @Data
    public static class CacheSuitability {
        private String mapperName;
        private boolean suitable;
        private List<String> reasons = new ArrayList<>();
        
        public void addReason(String reason) {
            reasons.add(reason);
        }
    }
    
    public enum UpdateFrequency {
        SECONDS, MINUTES, HOURS, DAYS, MONTHS
    }
    
    @Data
    public static class CacheConfig {
        private int writeQps;               // 写入QPS
        private boolean strongConsistency;  // 强一致性要求
        private UpdateFrequency updateFrequency; // 更新频率
        private int avgResultSize;          // 平均结果集大小
        private boolean dynamicSql;         // 是否动态SQL
        private boolean paginationVolatile; // 分页是否多变
    }
}

5.2 生产环境缓存决策矩阵

java 复制代码
/**
 * 企业级缓存决策框架
 */
@Service
public class CacheDecisionFramework {
    
    private static final Map<CacheScenario, CacheStrategy> DECISION_MATRIX = new HashMap<>();
    
    static {
        // 初始化决策矩阵
        DECISION_MATRIX.put(CacheScenario.CONFIG_TABLE, 
            CacheStrategy.SECOND_LEVEL_CACHE);
        DECISION_MATRIX.put(CacheScenario.HOT_DATA, 
            CacheStrategy.REDIS_CACHE);
        DECISION_MATRIX.put(CacheScenario.TRANSACTIONAL_DATA, 
            CacheStrategy.NO_CACHE);
        DECISION_MATRIX.put(CacheScenario.REPORT_QUERY, 
            CacheStrategy.QUERY_CACHE);
        DECISION_MATRIX.put(CacheScenario.USER_SESSION, 
            CacheStrategy.REDIS_CACHE);
    }
    
    /**
     * 根据场景选择缓存策略
     */
    public CacheStrategy decideStrategy(CacheScenario scenario, CacheMetrics metrics) {
        CacheStrategy baseStrategy = DECISION_MATRIX.get(scenario);
        
        // 根据指标动态调整
        if (metrics != null) {
            if (metrics.getHitRatio() < 0.3) {
                // 命中率太低,考虑禁用缓存
                return CacheStrategy.NO_CACHE;
            }
            
            if (metrics.getMemoryUsage() > 0.8) {
                // 内存使用率高,考虑使用外部缓存
                return CacheStrategy.REDIS_CACHE;
            }
        }
        
        return baseStrategy;
    }
    
    public enum CacheScenario {
        CONFIG_TABLE,      // 配置表
        HOT_DATA,          // 热点数据
        TRANSACTIONAL_DATA,// 事务数据
        REPORT_QUERY,      // 报表查询
        USER_SESSION,      // 用户会话
        PRODUCT_CATALOG    // 商品目录
    }
    
    public enum CacheStrategy {
        NO_CACHE,           // 不使用缓存
        FIRST_LEVEL_CACHE,  // 仅一级缓存
        SECOND_LEVEL_CACHE, // MyBatis二级缓存
        REDIS_CACHE,        // Redis缓存
        QUERY_CACHE,        // 查询结果缓存
        HYBRID_CACHE        // 混合缓存
    }
    
    @Data
    public static class CacheMetrics {
        private double hitRatio;     // 命中率
        private double memoryUsage;  // 内存使用率
        private long qps;           // 查询QPS
        private long updateQps;     // 更新QPS
    }
}

六、MyBatis 二级缓存 vs Spring Cache 架构对比

6.1 技术栈对比分析

java 复制代码
/**
 * 缓存技术栈对比分析
 */
@Component
@Slf4j
public class CacheStackComparison {
    
    /**
     * 技术选型对比表
     */
    public void compareCacheStacks() {
        CacheComparison mybatisCache = new CacheComparison("MyBatis二级缓存");
        CacheComparison springCache = new CacheComparison("Spring Cache");
        CacheComparison redisDirect = new CacheComparison("直接Redis操作");
        
        // MyBatis二级缓存特点
        mybatisCache.setInvasionLevel("DAO层");
        mybatisCache.setGranularity("SQL级别");
        mybatisCache.setConsistency("弱一致性");
        mybatisCache.setDistributedSupport("不支持");
        mybatisCache.setLearningCurve("简单");
        mybatisCache.setPerformance("中等");
        
        // Spring Cache特点
        springCache.setInvasionLevel("Service层");
        springCache.setGranularity("方法级别");
        springCache.setConsistency("可配置");
        springCache.setDistributedSupport("支持");
        springCache.setLearningCurve("中等");
        springCache.setPerformance("高");
        
        // 直接Redis操作特点
        redisDirect.setInvasionLevel("任意层");
        redisDirect.setGranularity("业务级别");
        redisDirect.setConsistency("强一致性");
        redisDirect.setDistributedSupport("原生支持");
        redisDirect.setLearningCurve("较高");
        redisDirect.setPerformance("最高");
        
        log.info("=== 缓存技术栈对比 ===");
        log.info("1. MyBatis二级缓存: 适合简单的DAO层优化");
        log.info("2. Spring Cache: 企业级推荐,灵活可控");
        log.info("3. 直接Redis: 高性能要求,完全控制");
    }
    
    @Data
    static class CacheComparison {
        private String name;
        private String invasionLevel;   // 侵入层次
        private String granularity;     // 控制粒度
        private String consistency;     // 一致性
        private String distributedSupport; // 分布式支持
        private String learningCurve;   // 学习曲线
        private String performance;     // 性能
        
        public CacheComparison(String name) {
            this.name = name;
        }
    }
    
    /**
     * 生产环境推荐方案
     */
    public RecommendedSolution getRecommendedSolution() {
        RecommendedSolution solution = new RecommendedSolution();
        
        // 分层缓存架构
        solution.setLayer1("Caffeine/Guava本地缓存 - 毫秒级热点数据");
        solution.setLayer2("Redis集群 - 秒级业务数据");
        solution.setLayer3("MySQL + MyBatis一级缓存 - 基础数据源");
        
        // 使用策略
        solution.setStrategy("Spring Cache抽象层 + 多级缓存实现");
        
        // 监控体系
        solution.setMonitoring("缓存命中率监控 + 内存使用告警 + 一致性检查");
        
        return solution;
    }
    
    @Data
    static class RecommendedSolution {
        private String layer1;
        private String layer2;
        private String layer3;
        private String strategy;
        private String monitoring;
    }
}

6.2 混合缓存架构实现

java 复制代码
/**
 * 企业级混合缓存架构
 */
@Configuration
@EnableCaching
@Slf4j
public class HybridCacheConfig {
    
    /**
     * 一级缓存:Caffeine(本地缓存)
     */
    @Bean
    public CacheManager caffeineCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        
        // 配置不同的缓存策略
        cacheManager.setCaffeine(Caffeine.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)  // 写后10分钟过期
            .maximumSize(1000)                       // 最大1000个条目
            .recordStats());                         // 记录统计信息
        
        return cacheManager;
    }
    
    /**
     * 二级缓存:Redis(分布式缓存)
     */
    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))        // 30分钟过期
            .serializeKeysWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericJackson2JsonRedisSerializer()))
            .disableCachingNullValues();              // 不缓存null值
        
        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .transactionAware()                       // 支持事务
            .build();
    }
    
    /**
     * MyBatis二级缓存:Ehcache(可选)
     */
    @Bean
    public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() {
        EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();
        factoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
        factoryBean.setShared(true);  // 共享缓存实例
        return factoryBean;
    }
    
    /**
     * 统一的缓存抽象层
     */
    @Component
    @Primary
    public class UnifiedCacheService {
        
        @Autowired
        private CacheManager caffeineCacheManager;
        
        @Autowired
        private CacheManager redisCacheManager;
        
        @Autowired
        private SqlSessionFactory sqlSessionFactory;
        
        /**
         * 智能缓存获取
         */
        public <T> T getWithSmartCache(String key, Class<T> type, 
                                       Supplier<T> loader, CacheLevel level) {
            
            // L1: 检查本地缓存
            Cache localCache = caffeineCacheManager.getCache("local");
            T value = localCache.get(key, type);
            if (value != null) {
                log.debug("L1缓存命中: {}", key);
                return value;
            }
            
            // L2: 检查Redis缓存
            Cache redisCache = redisCacheManager.getCache("redis");
            value = redisCache.get(key, type);
            if (value != null) {
                log.debug("L2缓存命中: {}", key);
                // 回填本地缓存
                localCache.put(key, value);
                return value;
            }
            
            // L3: 检查MyBatis二级缓存(如果适用)
            if (level == CacheLevel.DAO) {
                // 这里可以集成MyBatis二级缓存检查
            }
            
            // 都没有命中,从数据源加载
            value = loader.get();
            
            // 写入缓存
            if (value != null) {
                switch (level) {
                    case LOCAL:
                        localCache.put(key, value);
                        break;
                    case DISTRIBUTED:
                        redisCache.put(key, value);
                        break;
                    case DAO:
                        // 依赖MyBatis自动缓存
                        break;
                }
            }
            
            return value;
        }
        
        public enum CacheLevel {
            LOCAL,      // 仅本地缓存
            DISTRIBUTED, // 分布式缓存
            DAO         // DAO层缓存
        }
    }
}

七、Redis 二级缓存生产级实现

7.1 安全的 Redis 缓存实现

java 复制代码
/**
 * 生产级 Redis 缓存实现
 */
@Component
@Slf4j
public class ProductionRedisCache implements Cache {
    
    private final String id;
    private final String namespace;
    private final RedisTemplate<String, Object> redisTemplate;
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    
    // 缓存配置
    private static final long DEFAULT_EXPIRE = 3600; // 默认1小时
    private static final String KEY_PREFIX = "mybatis:cache:";
    
    public ProductionRedisCache(String id) {
        this.id = id;
        this.namespace = extractNamespace(id);
        ApplicationContext context = ApplicationContextHolder.getContext();
        this.redisTemplate = context.getBean("redisTemplate", RedisTemplate.class);
        
        log.info("初始化Redis缓存: {}, namespace: {}", id, namespace);
    }
    
    private String extractNamespace(String mapperId) {
        // 从Mapper ID提取命名空间
        // com.example.mapper.UserMapper -> user
        String[] parts = mapperId.split("\\.");
        String className = parts[parts.length - 1];
        return className.replace("Mapper", "").toLowerCase();
    }
    
    @Override
    public String getId() {
        return this.id;
    }
    
    @Override
    public void putObject(Object key, Object value) {
        if (value == null) {
            return;
        }
        
        readWriteLock.writeLock().lock();
        try {
            String redisKey = buildRedisKey(key);
            redisTemplate.opsForValue().set(
                redisKey, 
                value, 
                DEFAULT_EXPIRE, 
                TimeUnit.SECONDS
            );
            
            // 记录缓存Key,便于管理
            String cacheKeyList = KEY_PREFIX + namespace + ":keys";
            redisTemplate.opsForSet().add(cacheKeyList, redisKey);
            
            log.debug("缓存写入成功: {}", redisKey);
        } catch (Exception e) {
            log.error("缓存写入失败: {}", key, e);
            // 生产环境:降级处理,不影响主流程
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
    
    @Override
    public Object getObject(Object key) {
        readWriteLock.readLock().lock();
        try {
            String redisKey = buildRedisKey(key);
            Object value = redisTemplate.opsForValue().get(redisKey);
            
            if (value != null) {
                log.debug("缓存命中: {}", redisKey);
                // 续期(可选)
                redisTemplate.expire(redisKey, DEFAULT_EXPIRE, TimeUnit.SECONDS);
            }
            
            return value;
        } catch (Exception e) {
            log.error("缓存读取失败: {}", key, e);
            return null; // 降级:返回null,走数据库查询
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
    
    @Override
    public Object removeObject(Object key) {
        readWriteLock.writeLock().lock();
        try {
            String redisKey = buildRedisKey(key);
            Object oldValue = redisTemplate.opsForValue().getAndDelete(redisKey);
            
            // 从Key集合中移除
            String cacheKeyList = KEY_PREFIX + namespace + ":keys";
            redisTemplate.opsForSet().remove(cacheKeyList, redisKey);
            
            log.debug("缓存删除成功: {}", redisKey);
            return oldValue;
        } catch (Exception e) {
            log.error("缓存删除失败: {}", key, e);
            return null;
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
    
    @Override
    public void clear() {
        readWriteLock.writeLock().lock();
        try {
            // 批量删除该命名空间下的所有Key
            String pattern = KEY_PREFIX + namespace + ":*";
            Set<String> keys = redisTemplate.keys(pattern);
            
            if (keys != null && !keys.isEmpty()) {
                redisTemplate.delete(keys);
                log.info("清理缓存命名空间: {}, 删除 {} 个Key", namespace, keys.size());
            }
        } catch (Exception e) {
            log.error("缓存清理失败: {}", namespace, e);
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
    
    @Override
    public int getSize() {
        readWriteLock.readLock().lock();
        try {
            String cacheKeyList = KEY_PREFIX + namespace + ":keys";
            Long size = redisTemplate.opsForSet().size(cacheKeyList);
            return size != null ? size.intValue() : 0;
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
    
    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }
    
    /**
     * 构建安全的Redis Key
     */
    private String buildRedisKey(Object key) {
        // 格式:mybatis:cache:{namespace}:{keyHash}
        return KEY_PREFIX + namespace + ":" + String.valueOf(key).hashCode();
    }
    
    /**
     * 缓存统计信息
     */
    public CacheStats getStats() {
        CacheStats stats = new CacheStats();
        stats.setNamespace(namespace);
        stats.setSize(getSize());
        
        // 获取内存使用情况(需要Redis info命令)
        // 这里可以扩展获取更多Redis统计信息
        
        return stats;
    }
    
    @Data
    public static class CacheStats {
        private String namespace;
        private int size;
        private long memoryUsage;
        private double hitRatio;
    }
}

7.2 Redis 缓存管理控制台

java 复制代码
/**
 * Redis 缓存管理端点
 */
@RestController
@RequestMapping("/api/cache")
@Slf4j
public class CacheManagementController {
    
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 获取所有缓存统计
     */
    @GetMapping("/stats")
    public Map<String, Object> getCacheStats() {
        Map<String, Object> result = new HashMap<>();
        Configuration configuration = sqlSessionFactory.getConfiguration();
        Collection<Cache> caches = configuration.getCaches();
        
        List<Map<String, Object>> cacheList = new ArrayList<>();
        for (Cache cache : caches) {
            Map<String, Object> cacheInfo = new HashMap<>();
            cacheInfo.put("id", cache.getId());
            cacheInfo.put("size", cache.getSize());
            cacheInfo.put("type", cache.getClass().getSimpleName());
            
            cacheList.add(cacheInfo);
        }
        
        result.put("caches", cacheList);
        result.put("total", cacheList.size());
        
        // Redis全局统计
        Properties info = redisTemplate.getRequiredConnectionFactory()
            .getConnection().info();
        result.put("redisInfo", info);
        
        return result;
    }
    
    /**
     * 清理指定缓存
     */
    @PostMapping("/clear/{namespace}")
    public ApiResponse clearCache(@PathVariable String namespace) {
        try {
            Configuration configuration = sqlSessionFactory.getConfiguration();
            Cache cache = configuration.getCache(namespace);
            
            if (cache != null) {
                cache.clear();
                log.info("手动清理缓存: {}", namespace);
                return ApiResponse.success("缓存清理成功");
            } else {
                return ApiResponse.error("缓存不存在");
            }
        } catch (Exception e) {
            log.error("清理缓存失败", e);
            return ApiResponse.error("缓存清理失败: " + e.getMessage());
        }
    }
    
    /**
     * 预热缓存
     */
    @PostMapping("/warmup/{namespace}")
    public ApiResponse warmupCache(@PathVariable String namespace) {
        // 根据业务逻辑预热缓存
        // 例如:加载热点数据到缓存中
        return ApiResponse.success("缓存预热开始");
    }
    
    @Data
    public static class ApiResponse {
        private boolean success;
        private String message;
        private Object data;
        
        public static ApiResponse success(String message) {
            ApiResponse response = new ApiResponse();
            response.setSuccess(true);
            response.setMessage(message);
            return response;
        }
        
        public static ApiResponse error(String message) {
            ApiResponse response = new ApiResponse();
            response.setSuccess(false);
            response.setMessage(message);
            return response;
        }
    }
}

八、线程安全与并发控制

8.1 生产环境线程安全配置

java 复制代码
/**
 * 线程安全的缓存配置
 */
@Configuration
public class ThreadSafeCacheConfig {
    
    /**
     * 线程安全的Cache实现
     */
    @Bean
    public CacheFactory cacheFactory() {
        return new ThreadSafeCacheFactory();
    }
    
    /**
     * 自定义Cache工厂,确保线程安全
     */
    public static class ThreadSafeCacheFactory implements CacheFactory {
        
        @Override
        public Cache getCache(String id) {
            // 根据id返回相应的Cache实现
            // 确保每个Cache都有独立的锁
            return new ThreadSafeCacheImpl(id);
        }
    }
    
    /**
     * 线程安全的Cache实现
     */
    public static class ThreadSafeCacheImpl implements Cache {
        
        private final String id;
        private final Map<Object, Object> cache = new ConcurrentHashMap<>();
        private final ReadWriteLock lock = new ReentrantReadWriteLock();
        
        // 统计信息(线程安全)
        private final AtomicLong hitCount = new AtomicLong(0);
        private final AtomicLong missCount = new AtomicLong(0);
        private final AtomicLong putCount = new AtomicLong(0);
        private final AtomicLong evictionCount = new AtomicLong(0);
        
        public ThreadSafeCacheImpl(String id) {
            this.id = id;
        }
        
        @Override
        public String getId() {
            return id;
        }
        
        @Override
        public void putObject(Object key, Object value) {
            lock.writeLock().lock();
            try {
                cache.put(key, value);
                putCount.incrementAndGet();
            } finally {
                lock.writeLock().unlock();
            }
        }
        
        @Override
        public Object getObject(Object key) {
            lock.readLock().lock();
            try {
                Object value = cache.get(key);
                if (value != null) {
                    hitCount.incrementAndGet();
                } else {
                    missCount.incrementAndGet();
                }
                return value;
            } finally {
                lock.readLock().unlock();
            }
        }
        
        @Override
        public Object removeObject(Object key) {
            lock.writeLock().lock();
            try {
                evictionCount.incrementAndGet();
                return cache.remove(key);
            } finally {
                lock.writeLock().unlock();
            }
        }
        
        @Override
        public void clear() {
            lock.writeLock().lock();
            try {
                cache.clear();
                evictionCount.addAndGet(cache.size());
            } finally {
                lock.writeLock().unlock();
            }
        }
        
        @Override
        public int getSize() {
            lock.readLock().lock();
            try {
                return cache.size();
            } finally {
                lock.readLock().unlock();
            }
        }
        
        @Override
        public ReadWriteLock getReadWriteLock() {
            return lock;
        }
        
        /**
         * 获取缓存统计信息
         */
        public CacheStats getStats() {
            CacheStats stats = new CacheStats();
            stats.setHitCount(hitCount.get());
            stats.setMissCount(missCount.get());
            stats.setPutCount(putCount.get());
            stats.setEvictionCount(evictionCount.get());
            stats.setSize(getSize());
            
            long total = hitCount.get() + missCount.get();
            stats.setHitRatio(total > 0 ? 
                (double) hitCount.get() / total : 0.0);
            
            return stats;
        }
        
        @Data
        public static class CacheStats {
            private long hitCount;
            private long missCount;
            private long putCount;
            private long evictionCount;
            private int size;
            private double hitRatio;
        }
    }
}

8.2 并发场景下的缓存策略

java 复制代码
/**
 * 并发缓存策略
 */
@Service
@Slf4j
public class ConcurrentCacheStrategy {
    
    @Autowired
    private CacheManager cacheManager;
    
    /**
     * 防止缓存击穿:互斥锁
     */
    public <T> T getWithMutex(String key, Class<T> type, 
                             Supplier<T> loader, long expireSeconds) {
        Cache cache = cacheManager.getCache("default");
        
        // 1. 尝试从缓存获取
        T value = cache.get(key, type);
        if (value != null) {
            return value;
        }
        
        // 2. 获取分布式锁
        String lockKey = "lock:" + key;
        boolean locked = acquireLock(lockKey, 3000); // 3秒超时
        
        if (locked) {
            try {
                // 双重检查
                value = cache.get(key, type);
                if (value != null) {
                    return value;
                }
                
                // 3. 加载数据
                value = loader.get();
                
                if (value != null) {
                    // 4. 写入缓存
                    cache.put(key, value);
                } else {
                    // 缓存空值,防止缓存穿透
                    cache.put(key, new NullValue(), expireSeconds);
                }
                
                return value;
            } finally {
                releaseLock(lockKey);
            }
        } else {
            // 获取锁失败,等待重试或返回默认值
            log.warn("获取缓存锁失败: {}", key);
            return null;
        }
    }
    
    /**
     * 防止缓存雪崩:随机过期时间
     */
    public void putWithRandomExpire(String key, Object value) {
        Cache cache = cacheManager.getCache("default");
        
        // 基础过期时间 + 随机偏移(防止同时过期)
        long baseExpire = 3600; // 1小时
        long randomOffset = ThreadLocalRandom.current().nextInt(300); // 0-5分钟随机
        long expireTime = baseExpire + randomOffset;
        
        // 使用带过期时间的put方法
        // 注意:Spring Cache默认不支持,需要自定义Cache实现
    }
    
    /**
     * 缓存预热:启动时加载热点数据
     */
    @PostConstruct
    public void warmUpCache() {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        
        List<Callable<Void>> tasks = Arrays.asList(
            () -> { warmUpUserCache(); return null; },
            () -> { warmUpProductCache(); return null; },
            () -> { warmUpConfigCache(); return null; }
        );
        
        try {
            executor.invokeAll(tasks);
        } catch (InterruptedException e) {
            log.error("缓存预热被中断", e);
            Thread.currentThread().interrupt();
        } finally {
            executor.shutdown();
        }
    }
    
    private boolean acquireLock(String lockKey, long timeout) {
        // 使用Redis分布式锁
        // 实现略...
        return true;
    }
    
    private void releaseLock(String lockKey) {
        // 释放锁
    }
    
    // 空值标记类
    private static class NullValue implements Serializable {
        private static final long serialVersionUID = 1L;
    }
}

九、监控、告警与故障处理

9.1 缓存监控体系

java 复制代码
# prometheus.yml - 缓存监控指标
scrape_configs:
  - job_name: 'mybatis-cache'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['localhost:8080']
        
  - job_name: 'redis-cache'
    static_configs:
      - targets: ['localhost:9121']  # Redis Exporter

# 关键监控指标
# mybatis_cache_hit_total{namespace="UserMapper"}
# mybatis_cache_miss_total{namespace="UserMapper"}
# mybatis_cache_size{namespace="UserMapper"}
# redis_memory_used_bytes
# redis_connected_clients
java 复制代码
/**
 * 缓存监控端点
 */
@Component
@Slf4j
public class CacheMetricsCollector {
    
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    @Autowired
    private MeterRegistry meterRegistry;
    
    private final Map<String, CacheMetrics> metricsMap = new ConcurrentHashMap<>();
    
    /**
     * 定时收集缓存指标
     */
    @Scheduled(fixedRate = 60000) // 每分钟收集一次
    public void collectCacheMetrics() {
        Configuration config = sqlSessionFactory.getConfiguration();
        Collection<Cache> caches = config.getCaches();
        
        for (Cache cache : caches) {
            String cacheId = cache.getId();
            CacheMetrics metrics = getOrCreateMetrics(cacheId);
            
            // 收集指标
            metrics.setSize(cache.getSize());
            
            // 发布到监控系统
            Gauge.builder("mybatis.cache.size", metrics, CacheMetrics::getSize)
                .tags("namespace", cacheId)
                .register(meterRegistry);
            
            Counter.builder("mybatis.cache.hit")
                .tags("namespace", cacheId)
                .register(meterRegistry)
                .increment(metrics.getHitCount());
            
            // 计算命中率
            double hitRatio = calculateHitRatio(metrics);
            Gauge.builder("mybatis.cache.hit.ratio", () -> hitRatio)
                .tags("namespace", cacheId)
                .register(meterRegistry);
        }
    }
    
    /**
     * 缓存健康检查
     */
    @Component
    public class CacheHealthIndicator implements HealthIndicator {
        
        @Override
        public Health health() {
            Map<String, Object> details = new HashMap<>();
            Configuration config = sqlSessionFactory.getConfiguration();
            
            boolean healthy = true;
            List<String> issues = new ArrayList<>();
            
            // 检查所有缓存
            Collection<Cache> caches = config.getCaches();
            for (Cache cache : caches) {
                String cacheId = cache.getId();
                int size = cache.getSize();
                
                details.put(cacheId + ".size", size);
                
                // 检查缓存大小是否异常
                if (size > 10000) {
                    issues.add(cacheId + "缓存过大: " + size);
                    healthy = false;
                }
            }
            
            if (healthy) {
                return Health.up().withDetails(details).build();
            } else {
                return Health.down()
                    .withDetail("issues", issues)
                    .withDetails(details)
                    .build();
            }
        }
    }
    
    /**
     * 缓存告警规则
     */
    @Component
    public class CacheAlertRules {
        
        private static final double HIT_RATIO_THRESHOLD = 0.3; // 命中率低于30%告警
        private static final int SIZE_THRESHOLD = 10000;      // 缓存大小超过10000告警
        
        @Autowired
        private AlertService alertService;
        
        @Scheduled(fixedRate = 300000) // 每5分钟检查一次
        public void checkCacheAlerts() {
            Configuration config = sqlSessionFactory.getConfiguration();
            
            for (Cache cache : config.getCaches()) {
                String cacheId = cache.getId();
                CacheMetrics metrics = getOrCreateMetrics(cacheId);
                
                // 规则1:命中率过低
                double hitRatio = calculateHitRatio(metrics);
                if (hitRatio < HIT_RATIO_THRESHOLD) {
                    alertService.sendAlert(
                        "CACHE_LOW_HIT_RATIO",
                        String.format("缓存%s命中率过低: %.2f%%", 
                            cacheId, hitRatio * 100)
                    );
                }
                
                // 规则2:缓存过大
                int size = cache.getSize();
                if (size > SIZE_THRESHOLD) {
                    alertService.sendAlert(
                        "CACHE_SIZE_TOO_LARGE",
                        String.format("缓存%s大小异常: %d", cacheId, size)
                    );
                }
            }
        }
    }
    
    @Data
    public static class CacheMetrics {
        private String namespace;
        private int size;
        private long hitCount;
        private long missCount;
        private long putCount;
        private long evictionCount;
    }
}

9.2 缓存故障处理预案

java 复制代码
/**
 * 缓存故障降级方案
 */
@Component
@Slf4j
public class CacheFailureHandler {
    
    @Autowired
    private CircuitBreakerFactory circuitBreakerFactory;
    
    /**
     * 带熔断的缓存操作
     */
    public <T> T getWithCircuitBreaker(String key, Supplier<T> supplier) {
        CircuitBreaker cb = circuitBreakerFactory.create("cache-circuit");
        
        return cb.run(
            () -> {
                // 正常缓存操作
                return getFromCache(key, supplier);
            },
            throwable -> {
                // 降级处理
                log.error("缓存操作失败,执行降级", throwable);
                return fallback(key, supplier);
            }
        );
    }
    
    /**
     * 多级降级策略
     */
    private <T> T fallback(String key, Supplier<T> supplier) {
        try {
            // 降级1:尝试本地内存缓存
            T value = getFromLocalMemory(key);
            if (value != null) {
                return value;
            }
            
            // 降级2:直接访问数据库(限流)
            return supplier.get();
            
        } catch (Exception e) {
            // 降级3:返回默认值
            log.error("所有降级策略都失败", e);
            return getDefaultValue();
        }
    }
    
    /**
     * 缓存重建策略
     */
    public <T> T rebuildCache(String key, Supplier<T> loader) {
        // 1. 标记缓存正在重建
        markRebuilding(key);
        
        try {
            // 2. 异步重建缓存
            CompletableFuture.supplyAsync(loader)
                .thenAccept(value -> {
                    // 3. 更新缓存
                    updateCache(key, value);
                    // 4. 清除重建标记
                    clearRebuildingMark(key);
                })
                .exceptionally(throwable -> {
                    log.error("缓存重建失败", throwable);
                    clearRebuildingMark(key);
                    return null;
                });
            
            // 5. 返回旧数据或null
            return getOldValue(key);
            
        } catch (Exception e) {
            clearRebuildingMark(key);
            throw e;
        }
    }
    
    /**
     * 缓存一致性检查
     */
    @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
    public void checkCacheConsistency() {
        log.info("开始缓存一致性检查");
        
        // 1. 采样检查
        List<String> sampleKeys = getSampleKeys();
        
        for (String key : sampleKeys) {
            Object cacheValue = getFromCache(key);
            Object dbValue = getFromDatabase(key);
            
            if (!Objects.equals(cacheValue, dbValue)) {
                log.warn("缓存不一致: key={}, cache={}, db={}", 
                    key, cacheValue, dbValue);
                
                // 自动修复
                fixInconsistency(key, dbValue);
            }
        }
    }
}

十、企业级缓存架构总结与最佳实践

10.1 缓存架构决策树

10.2 缓存使用黄金法则

java 复制代码
/**
 * 企业级缓存使用规范
 */
public class CacheGoldenRules {
    
    /**
     * 法则1:明确缓存目的
     */
    public static final class Rule1 {
        // ❌ 不要为了缓存而缓存
        // ✅ 明确缓存要解决的问题:降低延迟、减少数据库压力、提升吞吐量
    }
    
    /**
     * 法则2:选择合适的缓存粒度
     */
    public static final class Rule2 {
        // ❌ 大对象整体缓存
        // ✅ 按需缓存,精细控制
    }
    
    /**
     * 法则3:设置合理的过期时间
     */
    public static final class Rule3 {
        // ❌ 永久缓存
        // ✅ 根据业务特点设置TTL
        // ✅ 热数据长TTL,冷数据短TTL
    }
    
    /**
     * 法则4:处理缓存失效
     */
    public static final class Rule4 {
        // ❌ 忽略缓存失效
        // ✅ 有降级方案
        // ✅ 有重建策略
    }
    
    /**
     * 法则5:监控一切
     */
    public static final class Rule5 {
        // ❌ 无监控上线
        // ✅ 监控命中率、内存使用、响应时间
        // ✅ 设置告警阈值
    }
    
    /**
     * 法则6:容量规划
     */
    public static final class Rule6 {
        // ❌ 无限制使用内存
        // ✅ 根据业务量规划容量
        // ✅ 设置淘汰策略
    }
    
    /**
     * 法则7:线程安全
     */
    public static final class Rule7 {
        // ❌ 忽略并发问题
        // ✅ 读写锁保护
        // ✅ 避免缓存击穿
    }
    
    /**
     * 法则8:可观测性
     */
    public static final class Rule8 {
        // ❌ 黑盒缓存
        // ✅ 详细日志
        // ✅ 统计信息
        // ✅ 管理接口
    }
}

10.3 各层级缓存推荐方案

10.4 工程级结论与建议

MyBatis 缓存体系工程实践总结

核心理念
1. 缓存是补偿机制,不是架构核心

• 优先优化数据库和业务逻辑

• 缓存是用来弥补其他组件不足的
2. 分层缓存,各司其职

• 本地缓存解决热点数据

• 分布式缓存解决数据共享

• MyBatis缓存作为DAO层优化
3. 数据一致性高于性能

• 宁可慢,不能错

• 最终一致性要有明确预期

生产环境建议

针对MyBatis缓存:

• ✅ 一级缓存:默认开启,无需特殊关注

• ⚠️ 二级缓存:谨慎使用,仅适合特定场景

• ❌ 动态SQL缓存:避免使用,缓存碎片严重

企业级缓存架构:

• 推荐方案:Spring Cache + Redis + Caffeine 多级缓存

• 监控体系:命中率、内存使用、响应时间

• 降级方案:缓存失效时的业务降级策略

团队规范:

  1. 新项目默认禁用MyBatis二级缓存
  2. 使用缓存必须添加监控
  3. 重要数据必须有降级方案
  4. 定期进行缓存一致性检查

性能数据参考(仅供参考)

• 一级缓存:纳秒级访问,命中率>99%(事务内)

• 二级缓存:微秒级访问,命中率30%-80%

• Redis缓存:毫秒级访问,命中率>95%

• 数据库查询:10ms-100ms

缓存能提升性能,但也增加复杂度。

在添加缓存前,先问自己:

  1. 真的需要缓存吗?
  2. 数据一致性如何保证?
  3. 缓存失效怎么办?
  4. 如何监控和告警?

附录:完整配置示例

java 复制代码
 application-production.yml
mybatis:
  configuration:
    cache-enabled: false  # 生产环境默认关闭二级缓存
    local-cache-scope: SESSION
    lazy-loading-enabled: true
    aggressive-lazy-loading: false
    
    # 日志配置
    log-prefix: MYBATIS
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl

# 缓存配置
cache:
  multi-level:
    enabled: true
    levels:
      - name: local
        type: caffeine
        spec: maximumSize=1000,expireAfterWrite=10m
      - name: redis
        type: redis
        ttl: 30m
        key-prefix: app:cache:
        use-key-prefix: true
        
  # 缓存预热
  warmup:
    enabled: true
    cron: "0 0 6 * * ?"  # 每天6点预热
    batch-size: 100
    
  # 监控
  monitor:
    enabled: true
    export-metrics: true
    alert-thresholds:
      hit-ratio: 0.3
      memory-usage: 0.8
相关推荐
时艰.2 小时前
Java 并发编程:Callable、Future 与 CompletableFuture
java·网络
码云数智-园园2 小时前
深入理解与正确实现 .NET 中的 BackgroundService
java·开发语言
好好研究2 小时前
SpringBoot整合SpringMVC
xml·java·spring boot·后端·mvc
千寻技术帮2 小时前
10386_基于SpringBoot的外卖点餐管理系统
java·spring boot·vue·外卖点餐
曹轲恒2 小时前
SpringBoot整合SpringMVC(末)
java·spring boot·后端
_周游2 小时前
Java8 API 文档搜索引擎_2.索引模块(程序)
java·搜索引擎·intellij-idea
小马爱打代码2 小时前
Spring Boot:邮件发送生产可落地方案
java·spring boot·后端
BD_Marathon2 小时前
设计模式——接口隔离原则
java·设计模式·接口隔离原则
空空kkk2 小时前
SSM项目练习——hami音乐(二)
java