用于查询 latest/newest/most 最近记录的 JPA 规范谓词

JPA Specification predicate for querying latest/newest/most recent records

我正在尝试将以下查询转换为 JPA 规范,以便更灵活地查询我的规则实体,但我找不到任何方法将以下 SQL 查询转换为规格。我一直在寻找使用规格查询“disctint on”的可能性,但我找不到任何东西。

SELECT DISTINCT ON (name, key) * FROM (SELECT * FROM rules WHERE activated_at < NOW() AND name IN (?1) AND key IN (?2) ORDER BY activated_at DESC) AS tmp;

上面的查询为每个名称 + 键的组合提供了 1 个规则,每个组合都有最近的 activated_at 个时间戳。

一些背景:

IN 子句直接使用如下谓词,但我找不到查询最近 activated_at 时间戳的方法。

return (root, query, criteriaBuilder) -> root.get(key).in(keys);

这是否可以通过 JPA 规范实现?
有没有人对如何实现它有任何指示或指点?

使用@Toru 建议的查询

SELECT * FROM rules r 
    WHERE 
    name IN (?1) 
    AND key IN (?2)
    AND activated_at = (
        SELECT max(avtivated_at) from rule r2
        where r2.name = r.name and r2.key = r.key
        and activated_at < NOW()
    )

在规范中

public class Specs {
    public static Specification<Rules> getMaxActivatedRules(String name, String key) {

        return (root, query, builder) -> {

            // SubQuery portion start
            Subquery<Date> subQuery = query.subquery(Date.class);
            Root<Rules> subRoot = subQuery.from(Rules.class);

            Expression<Date> maxActivatedDateExpr = builder.max(subRoot.get(Rules_.activatedAt));
            
            Predicate subqueryNameEqual = builder.equal(subRoot.get(Rules_.name), name);
            Predicate subqueryKeyEqual = builder.equal(subRoot.get(Rules_.key), key);
            Predicate subqueryActivatedAtLessThenNow = builder.lt(root.get(Rules_.activatedAt), builder.literal("NOW()"));           
            subQuery.select(maxActivatedDateExpr).where(subqueryNameEqual, subqueryKeyEqual, subqueryActivatedAtLessThenNow);
            // Subquery portion end

            Predicate subQueryEqual = builder.equal(root.get(Rules_.activatedAt), subQuery);
            Predicate nameEqual = builder.equal(root.get(Rules_.name), name);
            Predicate keyEqual = builder.equal(root.get(Rules_.key), key);

            return builder.and(subQueryEqual, nameEqual, keyEqual );
        };
    }

}

@Ratul 的回答进行了一些修改,下面的实现正是我要找的。区别在于我不想将子查询名称和键基于输入参数,而是让子查询基于当前根对象的值。

private Specification<Rule> isActiveRule() {
    return (root, query, builder) -> {
      Subquery<Instant> subquery = query.subquery(Instant.class);
      Root<Rule> subRoot = subquery.from(Rule.class);

      Expression<Instant> maxActivatedAt = builder.greatest(subRoot.get(Rule_.activatedAt));

      Predicate subqueryNameEqual = builder.equal(subRoot.get(Rule_.name), root.get(Rule_.name));
      Predicate subqueryKeyEqual = builder.equal(subRoot.get(Rule_.key), root.get(Rule_.key));
      Predicate subQueryActivatedAtBeforeNow = builder.lessThan(subRoot.get(Rule_.activatedAt), Instant.now());
      subquery.select(maxActivatedAt).where(subqueryNameEqual, subqueryKeyEqual, subQueryActivatedAtBeforeNow);

      Predicate subQueryEqual = builder.equal(root.get(Rule_.activatedAt), subquery);

      return builder.and(subQueryEqual);
    };
  }

此实现的巧妙之处在于,它允许我 select 将哪些谓词包含在我的 SpecificationBuilder class 中的规范中。因此,如果我不想提供名称或产品密钥,而只是使用 isActiveRule() 谓词,我将获得所有活动的规则。

例如

var spec = RuleSpecificationBuilder.builder()
    .nameIn(names)
    .keyIn(keys)
    .isActiveRule()
    .build();

// OR

var spec = RuleSpecificationBuilder.builder()
    .isActiveRule()
    .build();

// OR

RuleSpecificationBuilder.builder()
    .nameIn(names)
    .isActiveRule()
    .build();

// etc..

谓词 nameIn(names)、keyIn(keys) 和 isActiveRule() 转换为此查询,@Toru 在评论中指定:

SELECT * FROM rules r WHERE name IN (?1) AND key IN (?2) and activated_at = (SELECT max(activated_at) from rule r2 where r2.name = r.name and r2.key = r.key and activated_at < NOW())

... 其中 isActiveRule() 谓词转换为 activated_at = (SELECT max(activated_at) from rule r2 where r2.name = r.name and r2.key = r.key and activated_at < NOW())