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 的分散式快取的簡單實現方式,當然還有很多不足之處,比如快取資料庫崩潰等異常情況未考慮,希望能對讀者在建立分散式快取時提供一種思路。