在 Criteria Query Specifications 中合并不同类型的规范

Merge specifications of different types in Criteria Query Specifications

我有一个 Activity 实体,它与 Event 实体及其对应的元模型有 @ManyToOne 关系 - 生成了 Activity_Event_通过 JPA 模型生成器。

我创建了专门的 类 ActivitySpecificationsEventSpecifications。那些 类 仅包含 return Specification 的静态方法。例如:

public interface EventSpecifications {

   static Specification<Event> newerThan(LocalDateTime date) {
       return (root, cq, cb) -> cb.gt(Event_.date, date);
   }

  ...
}

所以当我想构建匹配多个规范的查询时,我可以在 JpaSpecificationExecutor<Event> 存储库上使用 findAll 执行以下语句。

EventSpecifications.newerThan(date).and(EventSpecifications.somethingElse())

ActivitySpecifications示例:

static Specification<Activity> forActivityStatus(int status) { ... }

如何使用 ActivitySpecifications 中的 EventSpecifications ?我的意思是喜欢合并不同类型的规范。对不起,但我什至不知道如何正确地询问,但有一个简单的例子:

我想要 select 所有状态为 :statusactivity.event.date 大于 :date

的活动
static Specification<Activity> forStatusAndNewerThan(int status, LocalDateTime date) {
    return forActivityStatus(status)
         .and((root, cq, cb) -> root.get(Activity_.event) .... 
         // use EventSpecifications.newerThan(date) somehow up there
}

这样的事情可能吗?

我想到的最接近的是使用以下内容:

return forActivityStatus(status)
             .and((root, cq, cb) -> cb.isTrue(EventSpecifications.newerThan(date).toPredicate(???, cq, cb));

其中 ??? 需要 Root<Event>,但我只能使用 root.get(Activity_.event).

获得 Path<Event>

在其基本形式中,规范被设计为仅当它们引用相同的根时才可组合。

但是,引入您自己的接口应该不会太困难,该接口可以轻松转换为 Specification 并且允许编写引用任意实体的规范。

首先,添加以下接口:

@FunctionalInterface
public interface PathSpecification<T> {

    default Specification<T> atRoot() {
        return this::toPredicate;
    }

    default <S> Specification<S> atPath(final SetAttribute<S, T> pathAttribute) {
        // you'll need a couple more methods like this one for all flavors of attribute types in order to make it fully workable
        return (root, query, cb) -> {
            return toPredicate(root.join(pathAttribute), query, cb);
        };
    }

    @Nullable
    Predicate toPredicate(Path<T> path, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
}

然后您重写规范如下:

public class ActivitySpecifications {

    public static PathSpecification<Activity> forActivityStatus(ActivityStatus status) {
        return (path, query, cb) -> cb.equal(path.get(Activity_.status), cb.literal(status));
    }
}

public class EventSpecifications {

    public static PathSpecification<Event> newerThan(LocalDateTime date) {
        return (path, cq, cb) -> cb.greaterThanOrEqualTo(path.get(Event_.createdDate), date);
    }
}

完成后,您应该能够按以下方式编写规范:

activityRepository.findAll(
    forActivityStatus(ActivityStatus.IN_PROGRESS).atRoot()
    .and(newerThan(LocalDateTime.of(2019, Month.AUGUST, 1, 0, 0)).atPath(Activity_.events))
)

上述解决方案的另一个优点是指定 WHERE 条件与指定路径分离,因此如果 ActivityEvent 之间有多个关联,您可以重用 Event 所有这些的规格。

考虑以下几点:

ClassA {
 id;
}

ClassB {
 foreignId; //id of A
}

用于组合 Specification<ClassA> specA, Specification<ClassB> specB

specB = specB.and(combineSpecs(specA);

private static Specification<ClassB> combineSpecs(Specification<ClassA> specA) {
   return (root_b,query,builder) {
     Subquery<ClassA> sub = query.subquery(ClassA.class);
     Root<ClassA> root_a = sub.from(ClassA.class);
     Predicate p1 = specA.toPredicate(root_a,query,builder);
     Predicate p2 = builder.equal(root_a.get("id"),root_b.get("foreignId"));
     Predicate predicate = builder.and(p1,p2);
     sub.select(root_a).where(predicate);
     return builder.exists(sub);
   };
}