1. 程式人生 > >MyBatis 快取之Redis簡單實現

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