在 Criteria Query Specifications 中合并不同类型的规范
Merge specifications of different types in Criteria Query Specifications
我有一个 Activity
实体,它与 Event
实体及其对应的元模型有 @ManyToOne
关系 - 生成了 Activity_
和 Event_
通过 JPA 模型生成器。
我创建了专门的 类 ActivitySpecifications
和 EventSpecifications
。那些 类 仅包含 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 所有状态为 :status
且 activity.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
条件与指定路径分离,因此如果 Activity
和 Event
之间有多个关联,您可以重用 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);
};
}
我有一个 Activity
实体,它与 Event
实体及其对应的元模型有 @ManyToOne
关系 - 生成了 Activity_
和 Event_
通过 JPA 模型生成器。
我创建了专门的 类 ActivitySpecifications
和 EventSpecifications
。那些 类 仅包含 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 所有状态为 :status
且 activity.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
条件与指定路径分离,因此如果 Activity
和 Event
之间有多个关联,您可以重用 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);
};
}