利用mybatis的二级缓存自定义到redis,适用于分布式集群应用;当有频繁查询的数据库的需求时,可以减少数据库压力,提升性能。
操作步骤如下:
1.引入mybatis-plus依赖
2.创建MybatisRedisCache类
import config.SpringContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j
public class MybatisRedisCache implements Cache {
private final String id;
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final RedisTemplate<String, Object> redisTemplate;
// 添加带 String id 参数的构造函数
public MybatisRedisCache(String id) {
log.debug("MybatisRedisCache id:{}",id);
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
// 假设这里从 Spring 上下文中获取 RedisTemplate,在此之前需要确保 Spring 上下文已经初始化
//这里需要注意获取的是null,需要考虑怎么从spring上下文获取,看启动类的配置
this.redisTemplate = SpringContextHolder.getBean("redisTemplateMybatis");
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
if (key != null && value != null) {
try {
String cacheKey = getCacheKey(key);
redisTemplate.opsForValue().set(cacheKey, value, 60, TimeUnit.MINUTES);
} catch (Exception e) {
log.error("Failed to put object into Redis cache", e);
}
}
}
@Override
public Object getObject(Object key) {
if (key != null) {
try {
String cacheKey = getCacheKey(key);
return redisTemplate.opsForValue().get(cacheKey);
} catch (Exception e) {
log.error("Failed to get object from Redis cache", e);
}
}
return null;
}
@Override
public Object removeObject(Object key) {
if (key != null) {
try {
String cacheKey = getCacheKey(key);
Object value = redisTemplate.opsForValue().get(cacheKey);
redisTemplate.delete(cacheKey);
return value;
} catch (Exception e) {
log.error("Failed to remove object from Redis cache", e);
}
}
return null;
}
@Override
public void clear() {
try {
redisTemplate.delete(this.id);//TODO 这里有需要后续可以优化清除粒度,越细越好
} catch (Exception e) {
log.error("Failed to clear Redis cache", e);
}
}
@Override
public int getSize() {
try {
return redisTemplate.keys(id + "*").size();
} catch (Exception e) {
log.error("Failed to get Redis cache size", e);
return 0;
}
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
private String getCacheKey(Object key) {
return this.id + ":" + key.toString();
}
}
3.创建MybatisRedisConfig这个是redis的管理对象
@Configuration
public class MybatisRedisConfig {
@Bean(name = "redisTemplateMybatis")
public RedisTemplate<String, Object> redisTemplateMybatis(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 开启默认类型信息,这样在序列化时会保存对象的类型信息
om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
// 注册 JavaTimeModule 模块
om.registerModule(new JavaTimeModule());
// 禁用 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
// om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
4.创建上下文操作对象,用于读取上面创建的redis管理对象
@Slf4j
@Component
@Order(Ordered.HIGHEST_PRECEDENCE) // 最高优先级
public class SpringContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextHolder.applicationContext = applicationContext;
log.info("setApplicationContext 方法被调用,Spring 上下文已初始化");
}
public static <T> T getBean(String name) {
if (applicationContext == null) {
log.error("Spring 上下文未初始化,无法获取 Bean: {}", name);
return null;
}
return (T) applicationContext.getBean(name);
}
}
此处需要注意将启动类配置读取,特别是在maven聚合的时候,可能会出现上下文对象读取redis管理对象为空的情况:
@ComponentScan(basePackages = {
"com.meeting.dao.config" //SpringContextHolder 类所在包,确保在应用启动的时候加载到
})
5.配置redis存入和读取的序列化方式
@Configuration
public class MybatisRedisConfig {
@Bean(name = "redisTemplateMybatis")
public RedisTemplate<String, Object> redisTemplateMybatis(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 开启默认类型信息,这样在序列化时会保存对象的类型信息
om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
// 注册 JavaTimeModule 模块
om.registerModule(new JavaTimeModule());
// 禁用 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
// om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
.6.在mapper层添加注解@CacheNamespace,这个还有其他参数比如最大存入数量等;需要的话自行研究;
@Mapper
@CacheNamespace(implementation = MybatisRedisCache.class)
public interface MeetingInfoMapper extends BaseMapper<MeetingInfo> {
}
7.最后是开启mybatis二级缓存
mybatis-plus:
configuration:
cache-enabled: true
或者在springboot启动类添加注解
@EnableCaching //开启缓存
做完以上步骤后启动项目,观察redis是否存入对象,再次请求后看看是否打印获取对象,建议开启mybatis的日志打印:
mybatis-plus:
configuration:
cache-enabled: true
#mybatis-plus配置控制台打印完整带参数SQL语句
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 日志配置
logging:
level:
com.midea.mx.paas.meeting: debug
org.springframework: warn
com.baomidou.dynamic.datasource: DEBUG
常见问题:加载顺序导致获取空值,需要调整加载顺序:如下:
1.在启动类配置@MapperScan(basePackages = {"com.xx.dao.mapper"},lazyInitialization = "true"),延后加载mapper层,不至于导致上下文对象还没注入到spring就获取,会为空;
2.升级上下文对象优先加载:在SpringContextHolder上下文对象加上注解@Order(Ordered.HIGHEST_PRECEDENCE) // 最高优先级