用Redis做Mybatis二級快取
首先在pom.xml檔案裡面新增依賴
然後再在application.yml
檔案裡面一旦有這個配置,你伺服器啟動時就會與redis做連線,所以啟動伺服器時一定要先啟動redis
如果我們要對redis做使用者控制的話,不然還要對它配置使用者密碼之類的
接下來我們再來做個快取的實現,我們做個util包,在util包裡面寫個ApplicationContextHolder類來獲取ApplicationContext(應用上下文,spring的核心物件)。所以這個類要繼承ApplicationContextAware——當一個類實現了這個介面(ApplicationContextAware)之後,這個類就可以方便獲得ApplicationContext中的所有bean。換句話說,就是這個類可以直接獲取spring配置檔案中,所有有引用到的bean物件。(相當於是自己包裝了ApplicationContext的一個getBean方法)
package com.yy.hospital.util; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class ApplicationContextHolder implements ApplicationContextAware { private static ApplicationContext ctx; @Override //向工具類注入applicationContext public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ctx = applicationContext; //ctx就是注入的applicationContext } //外部呼叫ctx public static ApplicationContext getCtx(){ return ctx; } //getbean有兩種方式拿:1)按型別拿 2)按名字拿 //從應用上下文裡面獲得類例項(即bean容器裡面獲得類容器) //為什麼我們現在要採用這種麻煩的方法(以前直接用Autowired註解自動裝配進去了)--- 這與redis連線池有關 //用Redis時,建了許多連線池,我們在redis裡面拿快取物件時,快取物件與每個連線都有一個RedisTemplate,你在注入時用自動注入,不同 // RedisTemplate是同類型同名的,注入時你得到的是哪個連線使用的redisTemplate呢?所以你注入時分不清 //所以我們重新封裝一個getBean的方法,按指定型別或名字來拿bean例項 public static <T> T getBean(Class<T> tClass){ return ctx.getBean(tClass); } @SuppressWarnings("unchecked") public static <T> T getBean(String name){ return (T) ctx.getBean(name); } }
ApplicationContextHolder是為接下來Mybatis的快取類做準備的。所以我們來定義RedisCache類,來作為Mybatis二級快取所使用的類。它要繼承Cache介面(對Mybatis來說,你要用自定義的類來實現二級快取,就要繼承Mybatis的Cache介面)
package com.yy.hospital.util; import org.apache.ibatis.cache.Cache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /* 使用redis實現mybatis二級快取 */ public class RedisCache implements Cache { //slf4j的日誌記錄器 private static final Logger logger = LoggerFactory.getLogger(RedisCache.class); //快取物件唯一標識 private final String id; //orm的框架都是按物件的方式快取,而每個物件都需要一個唯一標識. //用於事務性快取操作的讀寫鎖 private static ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); //處理事務性快取中做的 //操作資料快取的--跟著執行緒走的 private RedisTemplate redisTemplate; //Redis的模板負責將快取物件寫到redis伺服器裡面去 //快取物件的是失效時間,30分鐘 private static final long EXPRIRE_TIME_IN_MINUT = 30; //構造方法---把物件唯一標識傳進來 public RedisCache(String id){ if(id == null){ throw new IllegalArgumentException("快取物件id是不能為空的"); } this.id = id; } @Override public String getId() { return this.id; } //給模板物件RedisTemplate賦值,並傳出去 private RedisTemplate getRedisTemplate(){ if(redisTemplate == null){ //每個連線池的連線都要獲得RedisTemplate redisTemplate = ApplicationContextHolder.getBean("redisTemplate"); } return redisTemplate; } /* 儲存快取物件的方法 */ @Override public void putObject(Object key, Object value) { try{ RedisTemplate redisTemplate = getRedisTemplate(); //使用redisTemplate得到值操作物件 ValueOperations operation = redisTemplate.opsForValue(); //使用值操作物件operation設定快取物件 operation.set(key,value,EXPRIRE_TIME_IN_MINUT, TimeUnit.MINUTES); //TimeUnit.MINUTES系統當前時間的分鐘數 logger.debug("快取物件儲存成功"); }catch (Throwable t){ logger.error("快取物件儲存失敗"+t); } } /* 獲取快取物件的方法 */ @Override public Object getObject(Object key) { try { RedisTemplate redisTemplate = getRedisTemplate(); ValueOperations operations = redisTemplate.opsForValue(); Object result = operations.get(key); logger.debug("獲取快取物件"); return result; }catch (Throwable t){ logger.error("快取物件獲取失敗"+t); return null; } } /* 刪除快取物件 */ @Override public Object removeObject(Object key) { try{ RedisTemplate redisTemplate = getRedisTemplate(); redisTemplate.delete(key); logger.debug("刪除快取物件成功!"); }catch (Throwable t){ logger.error("刪除快取物件失敗!"+t); } return null; } /* 清空快取物件 當快取的物件更新了的化,就執行此方法 */ @Override public void clear() { RedisTemplate redisTemplate = getRedisTemplate(); //回撥函式 redisTemplate.execute((RedisCallback)collection->{ collection.flushDb(); return null; }); logger.debug("清空快取物件成功!"); } //可選實現的方法 @Override public int getSize() { return 0; } @Override public ReadWriteLock getReadWriteLock() { return readWriteLock; } }
這樣,二級快取的工具類我們就寫好了,接著我們要把工具類配置到對映器介面上去
第一種策略是直接在mapper介面上加上註解@CacheNamespace(implementation = 。。。。)
第二種策略就是在mapper.xml檔案中加cache標籤
記住,對於同一個mapper介面,他不能同時用兩種策略(一個mapper裡面同時用了註解SQL和.xml檔案的SQL話,只能用其中一種策略)
最後,在啟動類加上註解@EnableCaching就ok了
最後,我們通過資料庫兩次重複查詢測試可以發現,redis裡面已經存在快取物件了(注意,快取都是給查詢用的)
有幾個注意點
1)在記憶體中儲存物件,物件要序列化
2)在.xml使用cache標籤,只針對xml裡的查詢語句有用,所以針對要用快取的查詢,應該放在同一個mapper介面或者.xml檔案中
3)使用了使用者Token的登入相關的方法,最好不要做快取操作
4)對快取物件進行增刪改操作,快取物件會被清除掉