前言
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 的分布式缓存的简单实现方式,当然还有很多不足之处,比如缓存数据库崩溃等异常情况未考虑,希望能对读者在创建分布式缓存时提供一种思路。