Слияние спецификаций разных типов в Criteria Query Specification - PullRequest
0 голосов
/ 04 июля 2019

У меня есть сущность Activity, которая находится в @ManyToOne связи с сущностью Event и их соответствующими метамоделями - Activity_ и Event_ были сгенерированы генератором моделей JPA.

IСоздал специализированные классы ActivitySpecifications и EventSpecifications.Эти классы содержат только статические методы, которые возвращают 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) { ... }

Как использовать EventSpecifications из 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).

1 Ответ

1 голос
/ 04 июля 2019

В своей базовой форме спецификации предназначены для компоновки, только если они ссылаются на один и тот же корень.

Однако, не должно быть слишком сложно представить свой собственный интерфейс, который легко конвертируется в 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 для всех них.

...