简体   繁体   English

带有 JPA 和规范的 Spring Cache

[英]Spring Cache with JPA and Specifications

I created a REST application using Spring Boot 2.2, Spring Data REST, Hibernate, Spring Redis.我使用 Spring Boot 2.2、Spring Data REST、Hibernate、Spring Redis 创建了一个 REST 应用程序。

I configured a Redis server where I'd like to cache some queries I do.我配置了一个 Redis 服务器,我想在其中缓存我所做的一些查询。 I already did all optimization I could do, but this is a distributed application and I need to help performance a little bit with centralized cache.我已经做了我能做的所有优化,但这是一个分布式应用程序,我需要通过集中缓存来帮助提高性能。 Everything works fine but I don't see how I can create a convenient key when I use Spring repository's method一切正常,但当我使用 Spring 存储库的方法时,我看不到如何创建方便的密钥

@Cacheable(cacheNames = "contacts")
@Override
Page findAll(Specification specification, Pageable pageable);

This is my Redis configuration:这是我的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();
    }
}

and my key generator:和我的密钥生成器:

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);
        }
    }
}

When I call Page findAll(Specification specification, Pageable pageable);当我调用Page findAll(Specification specification, Pageable pageable); , in my CustomKeyGenerator I get parameters but I receive a SpecificationComposition (it's a Spring helper class) in place of Specification . , 在我的CustomKeyGenerator我获得了参数,但我收到了SpecificationComposition (它是一个 Spring 帮助程序类)代替Specification Its hashcode is different every time I call the method, even if "its content is the same".每次调用该方法时,它的哈希码都不同,即使“其内容相同”。

Like Pageable that is hashed in the same way every time ( PageRequest class), I'd like to do the same with Specification in order to get advantace of Spring caching mechanism.就像每次都以相同方式散列的 Pageable 一样( PageRequest类),我想对Specification做同样的事情,以获得 Spring 缓存机制的优势。

调试时,显示参数 SpecificationComposition

Do you have any hint to show me the right way?你有什么提示可以告诉我正确的方法吗?

After a lot of struggling I post my personal solution.经过很多努力,我发布了我的个人解决方案。 I don't pretend it is right or a "best practice", so please take it as it is.我不会假装它是正确的或“最佳实践”,所以请照原样接受。 I'd like to share because, maybe, could help someone with the same need.我想分享,因为也许可以帮助有相同需求的人。

This is the key generator:这是密钥生成器:

@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();
        }
    }
}

and this is the service that does the job:这是完成这项工作的服务:

@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;
    }
}

The service doesn't handle all Predicate / Expression cases, but it warns when it doesn't.该服务不会处理所有Predicate / Expression情况,但会在不处理时发出警告。

Basically the idea is to create a Predicate from the Specification and analyze it to generate a string of its values and then create an hashCode.基本上,这个想法是从Specification创建一个Predicate并分析它以生成其值的字符串,然后创建一个 hashCode。

With this code the resulting key seems stable and now I'm able to cache my Query like Page findAll(Specification specification, Pageable pageable);使用此代码,生成的键看起来很稳定,现在我可以缓存我的查询,如Page findAll(Specification specification, Pageable pageable);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM