首页 > 解决方案 > 在 Criteria Query Specifications 中合并不同类型的规范

问题描述

我有一个与Activity实体及其相应元模型相关的实体 -并且由 JPA 模型生成器生成。@ManyToOneEventActivity_Event_

我创建了专门的课程ActivitySpecificationsEventSpecifications. 这些类仅包含其 return 的静态方法Specification。例如:

public interface EventSpecifications {

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

  ...
}

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

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

ActivitySpecifications例子:

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

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

我想选择所有状态 =: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>,但我只能得到Path<Event>使用root.get(Activity_.event)

标签: springhibernatejpacriteria-api

解决方案


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

但是,引入您自己的接口应该不难,该接口易于转换,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以为所有这些关联重用规范。


推荐阅读