Mybatis二级缓存的redis实现

之前在这篇文章中:mybatis中的缓存
写过在MyBatis中通过@CacheNamespace注解开启二级缓存,本文将讲下具体的实现
本文参考:MyBatis二级缓存

Cache接口

在一级缓存中,sqlSession使用HashMap来进行缓存的存储,二级缓存默认也是用HashMap进行缓存的存储,为了保证数据的持久性也需要定时将缓存的数据刷新至硬盘,也可以使用第三方的集成工具如:Redis,来进行缓存的持久化,但是内存的空间是有限的,如果不停地向HashMap中put K-V键值对,那么肯定有一时刻会导致内存不足产生OOM。所以为了解决这个问题,我们必须限定可存储缓存的容量,如果容量满了那么就必须执行清空操作:通过溢出淘汰算法 例如:FIFO先进先出算法、LRU最近最少使用算法等。同时也需要给缓存的K-V键值对设置保质期,定时进行清理工作(MyBatis默认为不过期,即永久有效),同时二级缓存允许跨线程使用,所以二级缓存还必须保证线程安全问题
此外,还包括 命中率的统计和序列化需求等

MyBatis中只用了一个顶层的接口来实现上述功能,就是Cache接口

package org.apache.ibatis.cache;
import java.util.concurrent.locks.ReadWriteLock;

public interface Cache {
    String getId();

    void putObject(Object var1, Object var2);

    Object getObject(Object var1);

    Object removeObject(Object var1);

    void clear();

    int getSize();

    default ReadWriteLock getReadWriteLock() {
        return null;
    }
}

Cache采用了装饰器模式+责任链模式来实现功能,在下文中细说

@CacheNamespace注解

@CacheNamespace(implementation = MybatisRedisCache.class,
                eviction = FifoCache.class,
                size = 2048,
                readWrite = false,
                blocking = true,
                properties = {@Property(name = "timeout", value = "3600L")})

@CacheNamespace注解的属性如下:

public @interface CacheNamespace {
    Class<? extends Cache> implementation() default PerpetualCache.class;

    Class<? extends Cache> eviction() default LruCache.class;

    long flushInterval() default 0L;

    int size() default 1024;

    boolean readWrite() default true;

    boolean blocking() default false;

    Property[] properties() default {};
}

其中的implementation和evicition属性对应图中的PerpetualCache和LRUCache的位置
在这里插入图片描述

implementation属性

implementation属性中的类需要实现Cache接口,主要的功能就是实现具体的存储功能,下文中将默认的二级缓存存储在HashMap改成了redis中

import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;

@Slf4j
public class MybatisRedisCache implements Cache {
    private String id;
    private Long timeout = 3600L;
    private TimeUnit timeUnit = TimeUnit.SECONDS;

    private RedissonClient redissonClient;

    public MybatisRedisCache(final 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(final Object key, final Object value) {
        RMap<String, Object> map = getRedissonClient().getMap(id);
        if (value != null) {
            map.fastPut(key.toString(), value);
            map.expire(timeout, timeUnit);
        } else {
            log.warn("-------------{}------{}-----value空---", id, key);
        }
    }

    @Override
    public Object getObject(final Object key) {
        Map<String, Object> map = getRedissonClient().getMap(id);
        try {
            return map.get(key.toString());
        } catch (Exception e) {
            log.error("-------------{}------{}--------", id, key, e);
            return null;
        }
    }

    @Override
    public Object removeObject(Object key) {
        RMap<String, Object> map = getRedissonClient().getMap(id);
        return map.fastRemove(key.toString());
    }

    @Override
    public void clear() {
        RMap<String, Object> map = getRedissonClient().getMap(id);
        map.delete();
    }

    @Override
    public int getSize() {
        RMap<String, Object> map = getRedissonClient().getMap(id);
        return map.size();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return new ReentrantReadWriteLock();
    }

    private RedissonClient getRedissonClient() {
        if (redissonClient == null) {
        	//手动获取redis连接类
            redissonClient = ApplicationContextHolder.getBean(RedissonClient.class);
        }
        return redissonClient;
    }

    public void setTimeout(String timeout) {
        this.timeout = Long.valueOf(timeout);
    }
}

properties属性

properties属性就是在implementation属性中的类初始化的时候设置具体的值,本文中就是把MybatisRedisCache类中的timeout属性设置为3600毫秒

eviction属性

eviction属性就是具体的垃圾回收策略,mybatis内置的有四种,分别是:
LruCache(最近最少回收,移除最长时间不被使用的对象)
FifoCache(先进先出,按照缓存进入的顺序来移除它们)
SoftCache(软引用,移除基于垃圾回收器状态和软引用规则的对象)
WeakCache(弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象)

flushInterval属性

缓存的有效时间,单位是秒,0为永久有效

size属性

缓存容量,超过该值开始执行垃圾回收策略

readWrite属性

readWrite属性为是否开启序列化,默认开启,序列化前后值相同,但序列化ID不同
序列化的缺点就是耗时,如果想取消序列化可以通过设置该属性为false取消序列化,取消序列化后线程是不安全的,原因是MyBatis默认所有从缓存中获取数据的操作都是只读操作,不会修改数据,所以会把数据在缓存中的引用直接返回,可能读取脏数据

blocking属性

当blocking属性为false的时候
如果多个线程执行同一条sql(sql语句和参数都相同)时,会去多次查询数据库,这对于并发量大的查询接口是不好的,会给数据库造成很大的压力,浪费数据库服务器资源。
当blocking属性为true的时候
还是多个线程执行同一条sql的情况,当发现缓存中没有数据的时候,会由获得锁的那个线程去查询数据库,然后把数据存入缓存,当锁释放后,其他的线程就会直接查询缓存返回

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值