【问题标题】:Spring Cache with JPA and Specifications带有 JPA 和规范的 Spring Cache
【发布时间】:2020-03-27 15:48:10
【问题描述】:

我使用 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 缓存机制的优势。

你有什么提示可以告诉我正确的方法吗?

【问题讨论】:

  • 谢谢@MevlütÖzdemir,这很清楚。我正在寻找一个具体的解决方案:实际上 SpecificationComposition 在 spring 包之外是不可见的。不确定是否有简单的解决方案。

标签: java spring spring-boot spring-data-rest spring-cache


【解决方案1】:

经过一番挣扎后,我发布了我的个人解决方案。我不假装它是正确的或“最佳实践”,所以请照原样接受。我想分享,因为也许可以帮助有同样需要的人。

这是密钥生成器:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-08-19
    • 2020-05-20
    • 1970-01-01
    • 1970-01-01
    • 2023-02-19
    • 1970-01-01
    • 2016-02-17
    • 2021-11-09
    相关资源
    最近更新 更多