核心概念

  1. 二级缓存

    • MyBatis 的缓存机制,作用于 Mapper 级别
    • 默认使用内存存储,可扩展为分布式缓存
    • 生命周期与 SqlSessionFactory 相同
  2. Redis

    • 开源的内存数据结构存储
    • 支持持久化,高性能键值存储
    • 适合分布式缓存场景
  3. 缓存策略

    • Cache-Aside:应用直接管理缓存(推荐)
    • Read-Through:缓存自动加载数据
    • Write-Through/Write-Behind:缓存自动写入数据源

操作步骤(详细)

步骤 1:添加依赖

<!-- MyBatis-Plus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

<!-- Redis Starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- Redis 连接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

步骤 2:配置 Redis 连接

application.yml 配置:

spring:
  redis:
    host: localhost
    port: 6379
    password: yourpassword
    database: 0
    lettuce:
      pool:
        max-active: 20
        max-idle: 10
        min-idle: 5
        max-wait: 3000

步骤 3:创建 Redis 配置类

@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // 使用 Jackson 序列化
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), 
                                   ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);
        
        // 设置序列化器
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        
        template.afterPropertiesSet();
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30)) // 默认缓存30分钟
                .disableCachingNullValues() // 不缓存null值
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(
                        new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
                        new GenericJackson2JsonRedisSerializer()));
        
        return RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
    }
}

步骤 4:自定义 MyBatis 二级缓存(Redis 实现)

public class RedisMyBatisCache implements Cache {

    private final String id;
    private final RedisTemplate<String, Object> redisTemplate;
    private static final long EXPIRE_TIME = 30; // 分钟

    public RedisMyBatisCache(String id) {
        this.id = id;
        this.redisTemplate = ApplicationContextHolder.getBean(RedisTemplate.class);
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public void putObject(Object key, Object value) {
        redisTemplate.opsForHash().put(id, key.toString(), value);
        // 设置过期时间
        redisTemplate.expire(id, EXPIRE_TIME, TimeUnit.MINUTES);
    }

    @Override
    public Object getObject(Object key) {
        return redisTemplate.opsForHash().get(id, key.toString());
    }

    @Override
    public Object removeObject(Object key) {
        Object value = getObject(key);
        redisTemplate.opsForHash().delete(id, key.toString());
        return value;
    }

    @Override
    public void clear() {
        redisTemplate.delete(id);
    }

    @Override
    public int getSize() {
        Long size = redisTemplate.opsForHash().size(id);
        return size != null ? size.intValue() : 0;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return null; // MyBatis要求返回null,使用无锁实现
    }
}

步骤 5:创建 ApplicationContextHolder

@Component
public class ApplicationContextHolder implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        context = applicationContext;
    }

    public static <T> T getBean(Class<T> clazz) {
        return context.getBean(clazz);
    }

    public static Object getBean(String name) {
        return context.getBean(name);
    }
}

步骤 6:在 Mapper 上启用二级缓存

@CacheNamespace(implementation = RedisMyBatisCache.class)
public interface UserMapper extends BaseMapper<User> {
    // 自定义方法
}

步骤 7:在 Service 层使用 Spring Cache 注解

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Cacheable(value = "userCache", key = "#id")
    @Override
    public User getUserById(Long id) {
        return getById(id);
    }

    @CacheEvict(value = "userCache", key = "#user.id")
    @Override
    public boolean updateUser(User user) {
        return updateById(user);
    }

    @CacheEvict(value = "userCache", allEntries = true)
    @Override
    public void clearUserCache() {
        // 清除所有用户缓存
    }
}

步骤 8:配置缓存管理器(可选)

application.yml 中添加:

mybatis-plus:
  configuration:
    cache-enabled: true # 开启二级缓存

常见错误

  1. 序列化异常

    • 原因:实体类未实现 Serializable 接口
    • 解决:所有缓存对象实现 Serializable
  2. 空指针异常

    • 原因:ApplicationContextHolder 未初始化
    • 解决:确保 Spring 容器正确加载
  3. 缓存穿透

    • 现象:大量查询不存在的数据
    • 解决:缓存空对象或使用布隆过滤器
  4. 缓存雪崩

    • 现象:大量缓存同时失效
    • 解决:设置不同的过期时间
  5. 脏读问题

    • 原因:数据库更新后缓存未失效
    • 解决:使用 @CacheEvict 及时清除缓存

注意事项

  1. 缓存粒度

    • 避免缓存大对象
    • 推荐缓存主键或关键查询结果
  2. 缓存一致性

    • 写操作后及时清除相关缓存
    • 使用事务确保数据库和缓存操作原子性
  3. 缓存穿透防护

    @Cacheable(value = "userCache", key = "#id", unless = "#result == null")
    public User getUserById(Long id) {
        User user = getById(id);
        if (user == null) {
            // 缓存空对象,设置短过期时间
            cacheNullValue(id);
        }
        return user;
    }
    
  4. 缓存雪崩防护

    // 在缓存配置中添加随机过期时间
    .entryTtl(Duration.ofMinutes(30 + new Random().nextInt(10)))
    

使用技巧

  1. 多级缓存策略

    @Caching(
        cacheable = @Cacheable(value = "localCache", key = "#id"),
        put = @CachePut(value = "redisCache", key = "#id")
    )
    public User getUser(Long id) {
        // 查询数据库
    }
    
  2. 条件缓存

    @Cacheable(value = "userCache", key = "#id", condition = "#id > 1000")
    
  3. 组合键使用

    @Cacheable(value = "userOrders", key = "#userId + '_' + #page + '_' + #size")
    
  4. 缓存预热

    @PostConstruct
    public void initCache() {
        List<Long> popularUserIds = getPopularUserIds();
        popularUserIds.forEach(this::getUserById);
    }
    

最佳实践与性能优化

  1. 缓存策略选择

    • 读多写少:使用 Cache-Aside
    • 写多读少:考虑直接查库
  2. 缓存分区

    // 按业务分区
    @Cacheable(value = "user::basic", key = "#id")
    @Cacheable(value = "user::detail", key = "#id")
    
  3. 大Value处理

    • 分页缓存:只缓存前N页
    • 压缩缓存:使用 GZIP 压缩大对象
  4. 监控与告警

    • 监控 Redis 内存使用率
    • 设置缓存命中率告警阈值
    • 使用 Redis 慢查询日志
  5. 性能优化

    • 使用 Pipeline 批量操作
    • 避免 KEYS 命令,使用 SCAN
    • 合理设置 maxmemory-policy
  6. 分布式锁

    public User getWithLock(Long id) {
        String lockKey = "user_lock:" + id;
        try {
            if (redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS)) {
                return getFromDB(id);
            } else {
                Thread.sleep(50);
                return getWithLock(id);
            }
        } finally {
            redisTemplate.delete(lockKey);
        }
    }
    
  7. 缓存降级

    @CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
    public User getUserWithFallback(Long id) {
        // ...
    }
    
    private User getUserFallback(Long id, Throwable t) {
        return getFromLocalCache(id); // 使用本地缓存降级
    }
    

总结

MyBatis-Plus 与 Redis 集成要点:

  1. 合理选择缓存层级:二级缓存适合全局共享数据,Service 缓存更灵活
  2. 确保缓存一致性:写操作后及时失效相关缓存
  3. 防护缓存风险:处理穿透、雪崩、击穿问题
  4. 监控与调优:持续监控缓存命中率和性能指标
  5. 渐进式优化:从热点数据开始,逐步扩展缓存范围

通过以上实践,可构建高性能、可扩展的数据访问层,显著提升系统吞吐量,同时保证数据一致性。