[英]Spring Cache with JPA and Specifications
我使用 Spring Boot 2.2、Spring Data REST、Hibernate、Spring Redis 創建了一個 REST 應用程序。
我配置了一個 Redis 服務器,我想在其中緩存我所做的一些查詢。 我已經做了我能做的所有優化,但這是一個分布式應用程序,我需要通過集中緩存來幫助提高性能。 一切正常,但當我使用 Spring 存儲庫的方法時,我看不到如何創建方便的密鑰
@Cacheable(cacheNames = "contacts")
@Override
Page findAll(Specification specification, Pageable pageable);
這是我的Redis配置:
@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport implements CachingConfigurer {
@Override
public CacheErrorHandler errorHandler() {
return new RedisCacheErrorHandler();
}
@Override
@Bean("customKeyGenerator")
public KeyGenerator keyGenerator() {
return new CustomKeyGenerator();
}
}
和我的密鑰生成器:
public class CustomKeyGenerator implements KeyGenerator {
public CustomKeyGenerator() {
}
@Override
public Object generate(Object target, Method method, Object... params) {
List<Object> listParams = new ArrayList<>(Arrays.asList(params));
listParams.add(TenantContext.getCurrentTenantId());//Add tenantId as parameter
if (StoreContext.getCurrentStoreId() != null)
listParams.add(StoreContext.getCurrentStoreId());//Add storeId as parameter
return generateKey(listParams.toArray());
}
public static Object generateKey(Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
} else {
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
}
}
當我調用Page findAll(Specification specification, Pageable pageable);
, 在我的CustomKeyGenerator
我獲得了參數,但我收到了SpecificationComposition
(它是一個 Spring 幫助程序類)代替Specification
。 每次調用該方法時,它的哈希碼都不同,即使“其內容相同”。
就像每次都以相同方式散列的 Pageable 一樣( PageRequest
類),我想對Specification
做同樣的事情,以獲得 Spring 緩存機制的優勢。
你有什么提示可以告訴我正確的方法嗎?
經過很多努力,我發布了我的個人解決方案。 我不會假裝它是正確的或“最佳實踐”,所以請照原樣接受。 我想分享,因為也許可以幫助有相同需求的人。
這是密鑰生成器:
@Log4j2
public class CustomKeyGenerator implements KeyGenerator {
private static SpecificationService specificationService;
public CustomKeyGenerator() {
specificationService = SpringBeansLoadUtils.getBean(SpecificationService.class);
}
@Override
public Object generate(Object target, Method method, Object... params) {
//********************************************************************
// GET GENERICS IN CASE
// For methods like Page<Document> findAll(Specification specification, Pageable pageable);
// get the Generics type needed to
//********************************************************************
Class returnClass = method.getReturnType();
Class realClass = null;
if (Collection.class.isAssignableFrom(returnClass) || Page.class.isAssignableFrom(returnClass)) {
Type returnType = method.getGenericReturnType();
if (returnType instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) returnType;
Type[] argTypes = paramType.getActualTypeArguments();
if (argTypes.length > 0) {
realClass = (Class) argTypes[0];
}
}
}
List<Object> listParams = new ArrayList<>(Arrays.asList(params));
listParams.add(TenantContext.getCurrentTenantId());//Add tenantId as parameter
if (StoreContext.getCurrentStoreId() != null)
listParams.add(StoreContext.getCurrentStoreId());//Add storeId as parameter
return generateKey(realClass, listParams.toArray());
}
public static Object generateKey(Class clazz, Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
} else {
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
HashCodeBuilder builder = new HashCodeBuilder();
for (Object p : params) {
if (p != null && p.getClass().getSimpleName().contains("SpecificationComposition")) {
builder.append(specificationService.hashCode(clazz, (Specification) p));
} else {
builder.append(Arrays.deepHashCode(new Object[]{p}));
}
}
log.info("Hash {}", builder.hashCode());
return builder.hashCode();
}
}
}
這是完成這項工作的服務:
@Service
@Transactional
@PreAuthorize("isAuthenticated()")
@Log4j2
public class SpecificationService {
@PersistenceContext(unitName = "optixPU")
private EntityManager entityManager;
/**
* Generate an hashCode of the given specification
*
* @param clazz
* @param spec
* @return
*/
public Integer hashCode(Class clazz, @Nullable Specification spec) {
try {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery query = builder.createQuery(clazz);
Root root = query.from(clazz);
Predicate predicate = spec.toPredicate(root, query, builder);
String hash = analyzePredicate(predicate);
return hash.hashCode();
} catch (Exception e) {
log.warn("", e);
}
return null;
}
private String analyzePredicate(Predicate predicate) {
String stringRepresentation = "";
for (Expression<Boolean> e : predicate.getExpressions()) {
if (e instanceof CompoundPredicate) {
stringRepresentation += analyzePredicate((CompoundPredicate) e);
} else {
if (e instanceof InPredicate) {
InPredicate inPredicate = (InPredicate) e;
for (Object ex : inPredicate.getValues()) {
stringRepresentation += analyzeExpression((Expression) ex);
}
} else if (e instanceof LikePredicate) {
LikePredicate likePredicate = (LikePredicate) e;
String hashExpression = analyzeExpression(likePredicate.getMatchExpression());
String hashPattern = analyzeExpression(likePredicate.getPattern());
stringRepresentation += hashExpression + hashPattern;
} else if (e instanceof ComparisonPredicate) {
String operator = ((ComparisonPredicate) e).getComparisonOperator().toString();
String leftHand = analyzeExpression(((ComparisonPredicate) e).getLeftHandOperand());
String rightHand = analyzeExpression(((ComparisonPredicate) e).getRightHandOperand());
stringRepresentation += operator + leftHand + rightHand;
} else {
log.warn("Predicate not identified: {}", e);
}
}
}
return stringRepresentation;
}
private String analyzeExpression(Expression expression) {
if (expression instanceof SingularAttributePath) {
SingularAttributePath singularAttributePath = (SingularAttributePath) expression;
return singularAttributePath.getAttribute().getName();
} else if (expression instanceof LikeExpression) {
LiteralExpression likeExpression = (LiteralExpression) expression;
return likeExpression.getLiteral().toString();
}
if (expression instanceof LiteralExpression) {
return ((LiteralExpression) expression).getLiteral().toString();
} else if (expression instanceof ConcatExpression) {
ConcatExpression concatExpression = (ConcatExpression) expression;
String code1 = analyzeExpression(concatExpression.getString1());
String code2 = analyzeExpression(concatExpression.getString2());
return code1 + code2;
} else {
log.warn("Expression {} not identified", expression);
}
return null;
}
}
該服務不會處理所有Predicate
/ Expression
情況,但會在不處理時發出警告。
基本上,這個想法是從Specification
創建一個Predicate
並分析它以生成其值的字符串,然后創建一個 hashCode。
使用此代碼,生成的鍵看起來很穩定,現在我可以緩存我的查詢,如Page findAll(Specification specification, Pageable pageable);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.