文章目录
前言
记录一下 用Redis实现实现Mybatis二级缓存时遇到的问题和解决方法
一、开启Mybatis二级换缓存
myBatis二级缓存在springBoot配置文件中默认是开启的,不过需要在具体mapper中设置关键词才能生效
1.在xml中开启
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hmdp.mapper.UserMapper">
<!-- 启动二级缓存,类型是自定义的缓存存到Redis中-->
<cache type="com.hmdp.config.RedisCache" ></cache>
<select id="selectAll" parameterType="com.hmdp.entity.User" resultType="com.hmdp.entity.User">
select * from tb_user
</select>
2.用注解开启
//开启缓存注解
@CacheNamespace(implementation = RedisCache.class )
public interface UserMapper extends BaseMapper<User> {
List<User> selectAll();
void insertBatch(List<User> users);
}
3. 存在的问题
1.如果用两种方式同时开启二级缓存会报错, 二者只能使用一种
2.如果使用其中一个方式开启,那么另一种方式的缓存就会失效
比如:执行mybatis-plus中自带的查询api时,使用注解方式的二级缓存生效 但是手写的mapper.xml文件二级缓存会失效
解决方法:通过 缓存空间引用标签或者注解进行关联
如果在xml中开启二级缓存,那么注解就要引用 xml对应的nameSpace
@CacheNamespaceRef(name= "com.hmdp.mapper.UserMapper" )
如果在java中开启二级缓存,那么xml文件中就要引用对应的class文件所在的相对位置
<cache-ref namespace="com.hmdp.mapper.UserMapper"/>
二、配置Redis缓存
默认都是配置好的 可以是redisPool 或者是redisTemplate
三、实现Mybatis缓存到Redis的缓存策略
mybatis缓存时通过 实现cache接口实现的,所以我们要实现cache并实存到Redis的逻辑
public class RedisCache implements Cache {
//读写锁
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
private Object value;
private final String id;
private StringRedisTemplate stringRedisTemplate;
public RedisCache(String id) {
this.id = id;
}
//
private StringRedisTemplate getStringRedisTemplate(){
if (stringRedisTemplate==null){
stringRedisTemplate= (StringRedisTemplate) ApplicationContextHolder.getBeanByName("stringRedisTemplate");
}
return stringRedisTemplate;
}
@Override
public String getId() {
return id;
}
@Override
public void putObject(Object key, Object value) {
this.value=value;
String s = JSONUtil.parse(value).toString();
getStringRedisTemplate().opsForHash().put(id,key.toString(),s);
getStringRedisTemplate().expire(id,3,TimeUnit.MINUTES);
}
@Override
public Object getObject(Object key) {
try {
//根据key从redis中获取数据
Object o = getStringRedisTemplate().opsForHash().get(id, key.toString());
if (o==null)
return null;
return JSONUtil.toList((String) o, User.class);
//问题点
} catch (Exception e) {
e.printStackTrace();
log.error("缓存出错 ");
}
return null;
}
@Override
public Object removeObject(Object key) {
if (key != null) {
getStringRedisTemplate().delete(key.toString());
}
return null;
}
@Override
public void clear() {
log.debug("清空缓存");
Set<String> keys = getStringRedisTemplate().keys( this.id );
if (keys!= null &&keys.size()>0) {
getStringRedisTemplate().delete(keys);
}
}
@Override
public int getSize() {
Long size = (Long) getStringRedisTemplate().execute((RedisCallback<Long>) RedisServerCommands::dbSize);
return size.intValue();
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
}
疑问:
为什么RedisTemplate不通过spring的注解@Autowire等进行依赖注入。而是通过ApplicationContextHolder
获得Redis呢
因为mybatis的二级缓存使用的是责任链设计模式,在责任传递的过程中是属于mybatis范畴,是不受Spring监管的,也就是 依赖注入是失效的。
ApplicationContextHolder存在的意义
用于依赖注入 获得RedisTemplate实例,实现ApplicationContextAware 接口是在Spring生命周期时,负责注入的部分,实现他就能得到被Spring管理的RedisTemplate实例对象
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextHolder.applicationContext = applicationContext;
}
//根据bean name 获取实例
public static Object getBeanByName(String beanName) {
if (beanName == null || applicationContext == null) {
return null;
}
return applicationContext.getBean(beanName);
}
//只适合一个class只被定义一次的bean(也就是说,根据class不能匹配出多个该class的实例)
public static Object getBeanByType(Class clazz) {
if (clazz == null || applicationContext == null) {
return null;
}
return applicationContext.getBean(clazz);
}
public static String[] getBeanDefinitionNames() {
return applicationContext.getBeanDefinitionNames();
}
}
存在的问题
如果设置过期时间,那么在重写getObject
时候注意返回nll而不是空的引用类型 因为Mybatis判断缓存是否存在只判断类型是不是空,而不是里面的值是不是空
Object o = getStringRedisTemplate().opsForHash().get(id, key.toString());
if (o==null)
return null;
return JSONUtil.toList((String) o, User.class);
总结
提示:生于忧患,死于安乐