SpringBoot框架下通过Redis实现Mybatis二级缓存


前言

记录一下 用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);

总结

提示:生于忧患,死于安乐

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值