javascript
SpringBoot中通过自定义缓存注解(AOP切面拦截)实现数据库数据缓存到Redis
場景
若依前后端分離版本地搭建開發(fā)環(huán)境并運行項目的教程:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108465662
基于上面搭建起來前后端分離的Vue+SpringBoot的項目。
其中數(shù)據(jù)庫使用的是Mysql,緩存層用的是Redis。
數(shù)據(jù)庫中某個表存儲的信息,在業(yè)務(wù)代碼比如定時任務(wù)中,需要頻繁的查詢。
所以將表中的數(shù)據(jù)存儲到redis中一份。
其原理是,在調(diào)用查詢方法時,判斷redis中是否已經(jīng)有,如果有則優(yōu)先從redis中查詢。
如果沒有則在數(shù)據(jù)庫中查詢后并存入到Redis中一份,并給其設(shè)置過期時間。
這樣在過期時間之內(nèi),查詢數(shù)據(jù)會從redis中查詢,過期之后會重新從Mysql中查詢并存入到Redis一份。
并且還要實現(xiàn),再對這個Mysql表進行新增、編輯、刪除的操作時,將redis中存儲的數(shù)據(jù)
進行刪除,這樣下次查詢就會查詢數(shù)據(jù)庫中最新的。
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
關(guān)注公眾號
霸道的程序猿
獲取編程相關(guān)電子書、教程推送與免費下載。
實現(xiàn)
首先在Mysql中新建一個表bus_student
然后基于此表使用代碼生成,前端Vue與后臺各層代碼生成并添加菜單。
然后來到后臺代碼中,在后臺框架中已經(jīng)添加了操作redis的相關(guān)依賴和工具類。
但是這里還需要添加aspect依賴
??????? <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects --><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>4.3.14.RELEASE</version></dependency>然后在存放配置類的地方新建新增redis緩存的注解
package com.ruoyi.system.redisAop;import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/** @Author* @Description 新增redis緩存**/ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface AopCacheEnable {//redis緩存keyString[] key();//redis緩存存活時間默認值(可自定義)long expireTime() default 3600;}以及刪除redis緩存的注解
package com.ruoyi.system.redisAop;import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/** @Description 刪除redis緩存注解**/ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AopCacheEvict {//redis中的key值String[] key(); }然后再新建一個自定義緩存切面具體實現(xiàn)類CacheEnableAspect
存放位置
package com.ruoyi.system.redisAop;import com.ruoyi.system.domain.BusStudent; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component;import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit;/** @Description 自定義緩存切面具體實現(xiàn)類**/ @Aspect @Component public class CacheEnableAspect {@Autowiredpublic RedisTemplate redisCache;/*** Mapper層切點 使用到了我們定義的 AopCacheEnable 作為切點表達式。*/@Pointcut("@annotation(com.ruoyi.system.redisAop.AopCacheEnable)")public void queryCache() {}/*** Mapper層切點 使用到了我們定義的 AopCacheEvict 作為切點表達式。*/@Pointcut("@annotation(com.ruoyi.system.redisAop.AopCacheEvict)")public void ClearCache() {}@Around("queryCache()")public Object Interceptor(ProceedingJoinPoint pjp) {Object result = null;//注解中是否有#標識boolean spelFlg = false;//判斷是否需要走數(shù)據(jù)庫查詢boolean selectDb = false;//redis中緩存的keyString redisKey = "";//獲取當前被切注解的方法名Method method = getMethod(pjp);//獲取當前被切方法的注解AopCacheEnable aopCacheEnable = method.getAnnotation(AopCacheEnable.class);//獲取方法參數(shù)值Object[] arguments = pjp.getArgs();//從注解中獲取字符串String[] spels = aopCacheEnable.key();for (String spe1l : spels) {if (spe1l.contains("#")) {//注解中包含#標識,則需要拼接spel字符串,返回redis的存儲redisKeyredisKey = spe1l.substring(1) + arguments[0].toString();} else {//沒有參數(shù)或者參數(shù)是List的方法,在緩存中的keyredisKey = spe1l;}//取出緩存中的數(shù)據(jù)result = redisCache.opsForValue().get(redisKey);//緩存是空的,則需要重新查詢數(shù)據(jù)庫if (result == null || selectDb) {try {result =? pjp.proceed();//從數(shù)據(jù)庫查詢到的結(jié)果不是空的if (result != null && result instanceof ArrayList) {//將redis中緩存的結(jié)果轉(zhuǎn)換成對象listList<BusStudent> students = (List<BusStudent>) result;//判斷方法里面的參數(shù)是不是BusStudentif (arguments[0] instanceof BusStudent) {//將rediskey-students 存入到redisredisCache.opsForValue().set(redisKey, students, aopCacheEnable.expireTime(), TimeUnit.SECONDS);}}} catch (Throwable e) {e.printStackTrace();}}}return result;}/*** 定義清除緩存邏輯,先操作數(shù)據(jù)庫,后清除緩存*/@Around(value = "ClearCache()")public Object evict(ProceedingJoinPoint pjp) throws Throwable {//redis中緩存的keyMethod method = getMethod(pjp);// 獲取方法的注解AopCacheEvict cacheEvict = method.getAnnotation(AopCacheEvict.class);//先操作dbObject result = pjp.proceed();// 獲取注解的key值String[] fieldKeys = cacheEvict.key();for (String spe1l : fieldKeys) {//根據(jù)key從緩存中刪除redisCache.delete(spe1l);}return result;}/*** 獲取被攔截方法對象*/public Method getMethod(ProceedingJoinPoint pjp) {Signature signature = pjp.getSignature();MethodSignature methodSignature = (MethodSignature) signature;Method targetMethod = methodSignature.getMethod();return targetMethod;} }注意這里的queryCache和ClearCache,里面切點表達式
分別對應(yīng)上面自定義的兩個AopCacheEnable和AopCacheEvict。
然后在環(huán)繞通知的queryCache方法執(zhí)行前后時
獲取被切方法的參數(shù),參數(shù)中的key,然后根據(jù)key去redis中去查詢,
如果查不到,就把方法的返回結(jié)果轉(zhuǎn)換成對象List,并存入到redis中,
如果能查到,則將結(jié)果返回。
然后找到這個表的查詢方法,mapper層,比如要將查詢的返回結(jié)果存儲進redis
??? @AopCacheEnable(key = "BusStudent",expireTime = 40)public List<BusStudent> selectBusStudentList(BusStudent busStudent);然后在這個表的新增、編輯、刪除的mapper方法上添加
??? /*** 新增學生** @param busStudent 學生* @return 結(jié)果*/@AopCacheEvict(key = "BusStudent")public int insertBusStudent(BusStudent busStudent);/*** 修改學生** @param busStudent 學生* @return 結(jié)果*/@AopCacheEvict(key = "BusStudent")public int updateBusStudent(BusStudent busStudent);/*** 刪除學生** @param id 學生ID* @return 結(jié)果*/@AopCacheEvict(key = "BusStudent")public int deleteBusStudentById(Integer id);注意這里的注解上的key要和上面的查詢的注解的key一致。
然后啟動項目,如果啟動時提示:
Consider marking one of the beans as @Primary, updating the consumer to acce
因為sringboot通過@Autowired注入接口的實現(xiàn)類時發(fā)現(xiàn)有多個,也就是有多個類繼承了這個接口,spring容器不知道使用哪一個。
找到redis的配置類,在RedisTemplate上添加@Primary注解
驗證注解的使用
debug啟動項目,在CacheEnableAspect中查詢注解中打斷點,然后調(diào)用查詢方法,
就可以看到能進斷點,然后就可以根據(jù)自己想要的邏輯和效果進行修改注解。
第一次查詢時redis中是沒有的,所以會走mysql查詢,在過期時間之內(nèi)就不再查詢mysq,而是查詢redis了。
然后再調(diào)用新增、編輯、刪除接口時會將redis中緩存的數(shù)據(jù)刪掉。
但是使用若依這套框架,在新增、編輯、刪除操作后會調(diào)用查詢接口,所以會直接又存儲進來。
所以可以用postman等接口測試工具測試。
然后就是當操作完之后如果redis中的數(shù)據(jù)還沒過期,前端頁面查詢的仍然是redis中的數(shù)據(jù),不是最新數(shù)據(jù)。
所以redis中過期的時間自己把握。
另外此種緩存機制,建議不要和前端請求的mapper進行混用。
建議自定義新的mapper只取用需要的數(shù)據(jù),然后給其他比如高頻率的定時任務(wù)查詢用。
總結(jié)
以上是生活随笔為你收集整理的SpringBoot中通过自定义缓存注解(AOP切面拦截)实现数据库数据缓存到Redis的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 信息系统项目管理师-案例分析专题(二)案
- 下一篇: SpringBoot中提示:Consid