MyBatis 缓存之Redis简单实现

前言

MyBatis 提供的缓存机制都是基于Cache 接口而实现,因此我们也可以通过实现该接口创建自定义的缓存实现。

Redis 的缓存实现

简单来说,在MyBatis开启二级缓存的前提下,通过使用自定义的缓存实现类,使用Redis完成对缓存信息的查询和更新。

先来看一下 maven 依赖,本文使用的是Spring boot框架,依赖信息相对简单清晰。

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
   <groupId>org.mybatis.spring.boot</groupId>
   <artifactId>mybatis-spring-boot-starter</artifactId>
   <version>1.3.2</version>
</dependency>

再来看一下具体的实现类,

public class RedisCache implements Cache {
    private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);

    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final String id;
    private RedisTemplate redisTemplate;
    private static final long EXPIRE_TIME_IN_MINUTES = 30;

    public RedisCache(String id){
        if(id == null)
            throw new IllegalArgumentException("Cache instance requires an ID");

        this.id = id;
    }

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

    private RedisTemplate getRedisTemplate() {
        if(null == this.redisTemplate){
            logger.debug("set redisTemplate");
            this.redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
        }
        return this.redisTemplate;
    }

    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public void putObject(Object key, Object value) {
        ValueOperations opsForValue = this.getRedisTemplate().opsForValue();
        opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
        logger.debug("Put query result to redis");
    }

    @Override
    public Object getObject(Object key) {
        ValueOperations opsForValue = this.getRedisTemplate().opsForValue();
        logger.debug("Get cached query result from redis");
        return opsForValue.get(key);
    }

    @Override
    public Object removeObject(Object key) {
        this.getRedisTemplate().delete(key);
        logger.debug("Remove cached query result from redis");
        return key;
    }

    @Override
    public void clear() {
        this.getRedisTemplate().execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                logger.debug("flush redis");
                redisConnection.flushDb();
                return null;
            }
        });
    }

    @Override
    public int getSize() {
        return 0;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return this.readWriteLock;
    }
}

这里我们采用了 RedisTemplate 实现对缓存的操作,而不是采用 jedis,原因在于Jedis是Redis官方推荐的面向Java的操作Redis的客户端,而RedisTemplate是SpringDataRedis中对JedisApi的高度封装。SpringDataRedis相对于Jedis来说可以方便地更换Redis的Java客户端,比Jedis多了自动管理连接池的特性,方便与其他Spring框架进行搭配使用。

需要注意的是,此处的 redisTemplate 属性不同通过 autowired 方式获得,原因在于 RedisCache 本身就不是一个bean,因此我们考虑使用一个辅助类实线bean的获取。

public class ApplicationContextHolder implements ApplicationContextAware {

    private static ApplicationContext context;

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

    public static ApplicationContext getApplicationContext(){
        return context;
    }

    public static <T> T getBean(Class<T> clazz){
        return context.getBean(clazz);
    }

    public static <T> T getBean(String name){
        return (T)context.getBean(name);
    }
}

最后还需要针对缓存做一些配置

<cache eviction="LRU" type="com.learning.cache.cache.RedisCache" flushInterval="30000" size="1024" readOnly="true"/>

mapper 文件内容相对简单,主要是基于主键信息的增删查改操作,此处不再赘述。

测试

当我们使用浏览器发送下述请求时,能获得如下的相应信息,
这里写图片描述

而此时,Redis 数据库中存在如下的 key-value 信息,
这里写图片描述

当我们再次发送上述请求能够获得同样的响应信息,观察如下日志信息,

  • 第一次请求的日志信息
    这里写图片描述

  • 第二次请求的日志信息
    这里写图片描述

可以看出,每次查询的时候先查询缓存中是否存在相关信息,如果存在直接返回,如果不存在,则去访问数据库,并将结果信息写入缓存以供下次查询。

同样,我们可以借助 RESTClient 工具发送 post 请求,实现对数据库信息的修改,
这里写图片描述

而此时,Redis 数据库中的信息已被刷新,
这里写图片描述

这也说明了二级缓存的刷新时机,即事务的提交会刷新该namespace下的二级缓存。

总结

本文描述了一种基于 RedisTemplate 的分布式缓存的简单实现方式,当然还有很多不足之处,比如缓存数据库崩溃等异常情况未考虑,希望能对读者在创建分布式缓存时提供一种思路。

猜你喜欢

转载自blog.csdn.net/tjreal/article/details/80467569