Redis-替代Mybatis二级缓存

Posted 玩葫芦的卷心菜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis-替代Mybatis二级缓存相关的知识,希望对你有一定的参考价值。


编程不良人Redis链接
Redis面经属实有点懵,回头补一下

1、二级缓存

作缓存的对象类型需要实现序列化

1.1、cache使用

在dao层对应的mapper配置文件中使用cache标签即可开启二级缓存

<cache/>

缓存是key-value形式,key可以看作select语句(key还包含其他信息’)
开启后,查询语句首先去通过select查询本地缓存,

  • 若发现对应select命中缓存则直接返回结果
  • 若没发现则请求数据库后返回结果会被添加到本地缓存,下次查询发现是同一条语句就会直接返回,不需要请求数据库,大大增加了查询效率

1.2、原理

Mybatis开启Cache后会默认使用实现了Cache接口的PerpetualCache类做缓存操作
也可以通过指定type使用不同的Cache子类做二级缓存

<cache type=""/>
public class PerpetualCache implements Cache {
    private final String id;
    private final Map<Object, Object> cache = new HashMap();

    public PerpetualCache(String id) {
        this.id = id;
    }

    public String getId() {
        return this.id;
    }

    public int getSize() {
        return this.cache.size();
    }

    public void putObject(Object key, Object value) {
        this.cache.put(key, value);
    }

    public Object getObject(Object key) {
        return this.cache.get(key);
    }

    public Object removeObject(Object key) {
        return this.cache.remove(key);
    }

    public void clear() {
        this.cache.clear();
    }

    public boolean equals(Object o) {
        if (this.getId() == null) {
            throw new CacheException("Cache instances require an ID.");
        } else if (this == o) {
            return true;
        } else if (!(o instanceof Cache)) {
            return false;
        } else {
            Cache otherCache = (Cache)o;
            return this.getId().equals(otherCache.getId());
        }
    }

    public int hashCode() {
        if (this.getId() == null) {
            throw new CacheException("Cache instances require an ID.");
        } else {
            return this.getId().hashCode();
        }
    }
}

单体应用使用二级缓存绰绰有余,但是使用集群后,每个服务对应一个JVM,本地缓存是不共享的,所以就需要使用一个外界中间件提供共享缓存来实现二级缓存-Redis

2、Redis替代二级缓存

上面说到开启cache后使用Cache子类来进行缓存操作
所以我们通过实现Cache的自定义类
使用RedisTemplate来远程操作Redis进行缓存的操作,所有的缓存统一在redis上获取和添加

2.1、获取ApplicationContext

Cache实现类并没有添加到容器中,所以需要获取上下文来注入RedisTemplate

将类添加到容器的方式:@Service、@Controller、@Component等,里面可以随意使用自动注入

@Component
public class ApplicationContextUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }

    public static Object getBean(String beanName){
        return applicationContext.getBean(beanName);
    }
}

2.2、自定义cache实现类

1、必须有常量字符串id
2、必须构造器赋予id(为mapper对应namespace,一个dao通常对应一个表的所有操作)

发现使用以id作key的hash类缓存很适合,同一个id下所有key为同一个dao的所有查询语句
hset namespace (select xx from) xxxx
hset namespace (select xx from) xxxx
清空缓存 清空id的对应缓存就将表更新后的对应的所有缓存清掉了
del namespace
缺点是级联查询的缓存有问题,通过cache-ref可解决(在下方)

3、put为添加缓存
4、get为获取缓存
5、当表更新后,就需要清空这张表对应的所有缓存,通过clear实现

public class RedisCache implements Cache {
    private final String id;

    //id指定namespace
    public RedisCache(String id) {
        System.out.println("id==========="+id);
        this.id = id;
    }

    @Override
    public String getId() {
        return this.id;
    }

    @Override
    public void putObject(Object key, Object value) {
        System.out.println("key==="+key);
        System.out.println("value==="+value);
        getRedisTemplate().opsForHash().put(id,key.toString(),value);
    }

    @Override
    public Object getObject(Object key) {
        System.out.println("key:"+key);
        return getRedisTemplate().opsForHash().get(id,key.toString());
    }

    @Override
    public Object removeObject(Object o) {
        return null;
    }

    @Override
    public void clear() {
        getRedisTemplate().delete(id);
    }

    @Override
    public int getSize() {
        return 0;
    }
	
	//方便获取redisTemplate
    private RedisTemplate getRedisTemplate(){
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());//指定key的序列化类型
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());//指定hash类型的key序列化类型
        return redisTemplate;
    }
}

2.3、关联查询的缓存处理

关联查询的时候,会根据两个表进行查询
当其中一个表更新删除对应缓存后,另一张表的关联缓存可能还在就会导致一致性错误

所以通过缓存引用来解决关联查询的缓存问题

在关联查询的任意一方添加缓存引用即可

<cache-ref namespace="com.chime.redis_study.dao.UserDao"/>

当在对应mapper添加缓存引用后,之后该namespace的所有查询都会使用引用的缓存
如在EmpMapper里引用了EmpDao,EmpDao和UserDao共享UserDao的缓存空间,之后任意一表更新就会清空共享的所有缓存

3、缓存优化策略

3.1、对key进行优化

我们发现自动生成的key过长,可以通过MD5加密将其缩减为32位16进制字符串
MD5特点:

  1. 通过MD5加密后生成32位16进制字符串
  2. 不同内容文件经过加密,加密结果不一致(如何比较两文件内容不同?–通过MD5加密后比较加密串)
  3. 相同内容文件多次经过MD5加密结果一致
    @Test
    public void testMd5(){
        String key="-453465999:1075164090:com.chime.redis_study.dao.UserDao.findAll:0:2147483647:select id,name,age from user:SqlSessionFactoryBean";
        String s = DigestUtils.md5DigestAsHex(key.getBytes());
        System.out.println(s);
    }

a22ac04c2dcd56aa81f82243f644e503

优化的Cache实现类(只需要修改与key关联的put、set就行)

public class RedisCache implements Cache {

    @Override
    public void putObject(Object key, Object value) {
        getRedisTemplate().opsForHash().put(id,getKeyToMd5(key.toString()),value);
    }

    @Override
    public Object getObject(Object key) {
        return getRedisTemplate().opsForHash().get(id,getKeyToMd5(key.toString()));
    }
    
    public String getKeyToMd5(String key){
        return DigestUtils.md5DigestAsHex(key.getBytes());
    }
}

推荐Redis做缓存时key长度过长使用MD5对key进行优化

以上是关于Redis-替代Mybatis二级缓存的主要内容,如果未能解决你的问题,请参考以下文章

Redis-替代Mybatis二级缓存

Redis-替代Mybatis二级缓存

真正的mybatis_redis二级缓存

使用Redis做MyBatis的二级缓存

MyBatis系列目录--5. MyBatis一级缓存和二级缓存(redis实现)

03.redis+ssm整合(mybatis二级缓存)