這裏是根據我們自己的業務特點(極少數據更新且不要求數據精確,某些查詢的時間又比較長),我們採用了 Redis 做緩存,使用 Hash 數據結構緩存數據。
我們的業務查詢都是通過 Service 層整合多個 DAO 完成 DTO 的組裝,基於業務特點,沒必要將各個 DAO 的數據緩存,而是直接緩存 Service 的最終 DTO 結果。
首先,在 Spring Boot 裏實例化 RedisTemplate:
@Configuration
public class RedisConfig {
private static final Logger logger = Logger.getLogger(RedisConfig.class);
@Bean
public RedisConnectionFactory genFactory(){
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
jedisConnectionFactory.setHostName("192.168.1.82");
jedisConnectionFactory.setUsePool(true);
return jedisConnectionFactory;
}
@Bean
public 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 {
}
比如我們想緩存下面這個查詢結果,就直接加上註解:@CurieCache
public 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
@Aspect
public class RedisAdvice {
private Logger logger = LoggerFactory.getLogger(RedisAdvice.class);
@Autowired
private 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();
}
}
}