核心概念
二级缓存:
- MyBatis 的缓存机制,作用于 Mapper 级别
- 默认使用内存存储,可扩展为分布式缓存
- 生命周期与 SqlSessionFactory 相同
Redis:
- 开源的内存数据结构存储
- 支持持久化,高性能键值存储
- 适合分布式缓存场景
缓存策略:
- 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 # 开启二级缓存
常见错误
序列化异常:
- 原因:实体类未实现 Serializable 接口
- 解决:所有缓存对象实现 Serializable
空指针异常:
- 原因:ApplicationContextHolder 未初始化
- 解决:确保 Spring 容器正确加载
缓存穿透:
- 现象:大量查询不存在的数据
- 解决:缓存空对象或使用布隆过滤器
缓存雪崩:
- 现象:大量缓存同时失效
- 解决:设置不同的过期时间
脏读问题:
- 原因:数据库更新后缓存未失效
- 解决:使用 @CacheEvict 及时清除缓存
注意事项
缓存粒度:
- 避免缓存大对象
- 推荐缓存主键或关键查询结果
缓存一致性:
- 写操作后及时清除相关缓存
- 使用事务确保数据库和缓存操作原子性
缓存穿透防护:
@Cacheable(value = "userCache", key = "#id", unless = "#result == null") public User getUserById(Long id) { User user = getById(id); if (user == null) { // 缓存空对象,设置短过期时间 cacheNullValue(id); } return user; }
缓存雪崩防护:
// 在缓存配置中添加随机过期时间 .entryTtl(Duration.ofMinutes(30 + new Random().nextInt(10)))
使用技巧
多级缓存策略:
@Caching( cacheable = @Cacheable(value = "localCache", key = "#id"), put = @CachePut(value = "redisCache", key = "#id") ) public User getUser(Long id) { // 查询数据库 }
条件缓存:
@Cacheable(value = "userCache", key = "#id", condition = "#id > 1000")
组合键使用:
@Cacheable(value = "userOrders", key = "#userId + '_' + #page + '_' + #size")
缓存预热:
@PostConstruct public void initCache() { List<Long> popularUserIds = getPopularUserIds(); popularUserIds.forEach(this::getUserById); }
最佳实践与性能优化
缓存策略选择:
- 读多写少:使用 Cache-Aside
- 写多读少:考虑直接查库
缓存分区:
// 按业务分区 @Cacheable(value = "user::basic", key = "#id") @Cacheable(value = "user::detail", key = "#id")
大Value处理:
- 分页缓存:只缓存前N页
- 压缩缓存:使用 GZIP 压缩大对象
监控与告警:
- 监控 Redis 内存使用率
- 设置缓存命中率告警阈值
- 使用 Redis 慢查询日志
性能优化:
- 使用 Pipeline 批量操作
- 避免 KEYS 命令,使用 SCAN
- 合理设置 maxmemory-policy
分布式锁:
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); } }
缓存降级:
@CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback") public User getUserWithFallback(Long id) { // ... } private User getUserFallback(Long id, Throwable t) { return getFromLocalCache(id); // 使用本地缓存降级 }
总结
MyBatis-Plus 与 Redis 集成要点:
- 合理选择缓存层级:二级缓存适合全局共享数据,Service 缓存更灵活
- 确保缓存一致性:写操作后及时失效相关缓存
- 防护缓存风险:处理穿透、雪崩、击穿问题
- 监控与调优:持续监控缓存命中率和性能指标
- 渐进式优化:从热点数据开始,逐步扩展缓存范围
通过以上实践,可构建高性能、可扩展的数据访问层,显著提升系统吞吐量,同时保证数据一致性。