這裡是根據我們自己的業務特點(極少資料更新且不要求資料精確,某些查詢的時間又比較長),我們採用了 Redis 做緩衝,使用 Hash 資料結構快取資料。
我們的業務查詢都是通過 Service 層整合多個 DAO 完成 DTO 的組裝,基於業務特點,沒必要將各個 DAO 的資料緩衝,而是直接緩衝 Service 的最終 DTO 結果。
首先,在 Spring Boot 裡執行個體化 RedisTemplate:
@Configurationpublic class RedisConfig {private static final Logger logger = Logger.getLogger(RedisConfig.class);@Beanpublic RedisConnectionFactory genFactory(){JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();jedisConnectionFactory.setHostName("192.168.1.82");jedisConnectionFactory.setUsePool(true);return jedisConnectionFactory;}@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){logger.info("進入 RedisConfig.redisTemplate() 方法。。。。。。。。。。。。");RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));redisTemplate.setConnectionFactory(factory);return redisTemplate;}}
當然,pom.xml 檔案得加上 Redis 依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency>
然後,定義一個註解,凡是希望資料被緩衝的 Service.get**方法上都加上這個註解:
@Documented//文檔 @Retention(RetentionPolicy.RUNTIME)//在運行時可以擷取 @Target(ElementType.METHOD)//作用到類,方法,介面上等public @interface CurieCache {}
比如我們想緩衝下面這個查詢結果,就直接加上註解:
@CurieCachepublic CohortDefinitionDTO getCohortProcessSummaryById(Integer defId){List<ConceptData> datas = new ArrayList<ConceptData>();...}
最後,讓緩衝起作用,就看 Around Advice 了,詳細解釋已經在該類的注釋上了:
import java.lang.reflect.Method;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.reflect.MethodSignature;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Component;/** * 1. 使用 Redis 的 Hash 緩衝 Service 層的查詢結果 * 2. Key 使用類名 + 方法名, * 3. 任一 field 的值使用參數的String字串串連,如果無參數,就使用預設值 * 4. 希望結果被緩衝的get**方法只需要加上自訂的 CurieCache 註解,就會被攔截: * a. 如果緩衝裡有就直接從緩衝裡拿資料 * b. 否則先執行業務查詢,最終結果在返回前放入緩衝 * @author Allen */@Component@Aspectpublic class RedisAdvice {private Logger logger = LoggerFactory.getLogger(RedisAdvice.class);@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Around("execution(* com.hebta.curie.service.*Service.get*(..))") public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable {MethodSignature methodSignature = (MethodSignature) pjp.getSignature();Method method = methodSignature.getMethod();String key = null;String field = null;if (method.isAnnotationPresent(CurieCache.class)){logger.info("當前方法設定了CurieCache 註解");String target = pjp.getTarget().toString();StringBuilder kSb = new StringBuilder();kSb.append(target.substring(target.lastIndexOf(".") + 1, target.indexOf("@"))).append(":").append(method.getName());key = kSb.toString();logger.info("target is : {}, key is : {}", target, key);if (pjp.getArgs().length > 0) {logger.info("方法 {} 無參數", method.getName());StringBuilder sb = new StringBuilder();for (Object obj : pjp.getArgs()) {sb.append(obj.toString());}field = sb.toString();} else {field = "blank";}logger.info("field is : {}", field);if (redisTemplate.opsForHash().hasKey(key, field)){logger.info("緩衝裡有該結果,直接從緩衝裡取");return redisTemplate.opsForHash().get(key, field);} else {logger.info("沒有緩衝該結果,先計算,存入緩衝,再返回");Object result = pjp.proceed();redisTemplate.opsForHash().put(key, field, result);return result;}} else {logger.info("無 CurieCache 註解");return pjp.proceed();}}}