基于Redis的MybatisPlus二级缓存解决方案
作者:xiatianit
在 MyBatis-Plus 中整合 Redis 作为二级缓存,核心在于正确配置 RedisTemplate 的序列化方式以及实现 MyBatis 的 Cache 接口。由于 MyBatis 的 Cache 实现类不由 Spring 容器直接管理,因此需要特殊处理以获取 RedisTemplate。
以下是完整的配置示例与代码实现:
1. 引入依赖
确保项目中包含 MyBatis-Plus 和 Spring Data Redis 依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version> <!-- 建议使用较新版本 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 可选:用于方便地从非Spring管理的类中获取Bean -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</version>
</dependency>2. 配置 RedisTemplate (关键步骤)
MyBatis 缓存的对象必须可序列化。推荐使用 JSON 序列化(如 Jackson),因为它比 JDK 默认序列化更节省空间且可读性更好。
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 使用 String 序列化 Key
StringRedisSerializer stringSerializer = new StringRedisSerializer();
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
// 使用 Jackson 序列化 Value
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 解决 java.util.Date 等类型序列化问题,并启用默认类型信息以支持多态
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}3. 实现 MyBatis Cache 接口
创建一个自定义类实现 org.apache.ibatis.cache.Cache 接口。注意:该类由 MyBatis 创建,而非 Spring,因此不能直接 @Autowired。需通过 ApplicationContext 获取 RedisTemplate。
方案 A:使用 Hutool 的 SpringUtil(推荐,简洁)
import cn.hutool.extra.spring.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j
public class MybatisRedisCache implements Cache {
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final String id;
private RedisTemplate<String, Object> redisTemplate;
// 缓存过期时间(分钟),可根据业务调整
private static final long EXPIRE_TIME_IN_MINUTES = 30;
public MybatisRedisCache(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
getRedisTemplate().opsForHash().put(getId(), key.toString(), value);
log.debug("Put query result to redis: key={}", key);
}
@Override
public Object getObject(Object key) {
log.debug("Get cached query result from redis: key={}", key);
return getRedisTemplate().opsForHash().get(getId(), key.toString());
}
@Override
public Object removeObject(Object key) {
log.debug("Remove cached query result from redis: key={}", key);
return getRedisTemplate().opsForHash().delete(getId(), key.toString());
}
@Override
public void clear() {
log.debug("Clear all cached query results from redis for namespace: {}", getId());
getRedisTemplate().delete(getId());
}
@Override
public int getSize() {
Long size = getRedisTemplate().opsForHash().size(getId());
return size == null ? 0 : size.intValue();
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
private RedisTemplate<String, Object> getRedisTemplate() {
if (redisTemplate == null) {
// 从 Spring 容器中获取 RedisTemplate
redisTemplate = SpringUtil.getBean("redisTemplate");
}
return redisTemplate;
}
}方案 B:使用静态 ApplicationContext 持有者(如果不使用 Hutool)
若不使用 Hutool,需创建一个 SpringContextHolder 实现 ApplicationContextAware 接口来静态获取 Bean,然后在 MybatisRedisCache 中调用 SpringContextHolder.getBean("redisTemplate")。
4. 开启二级缓存
全局配置 (application.yml)
mybatis-plus:
configuration:
cache-enabled: true # 全局开启二级缓存
# 其他配置...Mapper 级别配置
在具体的 Mapper 接口或 XML 中指定使用自定义的 Redis 缓存。
方式一:注解方式(推荐)
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.CacheNamespace;
@CacheNamespace(implementation = MybatisRedisCache.class)
public interface UserMapper extends BaseMapper<User> {
// 查询方法
}方式二:XML 方式
在对应的 UserMapper.xml 文件中添加:
<mapper namespace="com.example.mapper.UserMapper">
<!-- 指定自定义的 Redis 缓存实现类 -->
<cache type="com.example.config.MybatisRedisCache"/>
<!-- 你的 SQL 语句 -->
<select id="selectById" resultType="com.example.entity.User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>5. 注意事项
- 实体类序列化:所有存入缓存的实体类(如
User)必须实现Serializable接口,否则 JSON 序列化可能出错或无法反序列化。 - 缓存一致性:MyBatis 二级缓存默认在
insert/update/delete操作后会清空该 Namespace 下的所有缓存。在分布式高并发场景下,这可能不是最优策略,建议结合业务需求考虑是否在服务层使用 Spring Cache (@Cacheable) 替代 MyBatis 二级缓存,以获得更细粒度的控制。 - Key 的设计:上述示例使用
opsForHash将同一 Mapper Namespace 下的所有缓存项存储在一个 Hash 结构中,Key 为查询条件的哈希或字符串。这有助于管理和清理特定 Mapper 的缓存。 - 一级缓存干扰:在 Spring 集成环境中,MyBatis 的一级缓存(SqlSession 级别)通常因 SqlSession 的生命周期管理而效果有限,二级缓存才是跨 SqlSession 共享的关键。确保
cache-enabled: true已设置。
到此这篇关于基于Redis的MybatisPlus二级缓存解决方案的文章就介绍到这了,更多相关MybatisPlus Redis 二级缓存内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
