'Merge specifications of different types in Criteria Query Specifications

I'm having an Activity entity which is in @ManyToOne relationship with Event entity and their corresponding metamodels - Activity_ and Event_ were generated by JPA model generator.

I've created specialized classes ActivitySpecifications and EventSpecifications. Those classes contain only static methods whose return Specification. For example:

public interface EventSpecifications {

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

  ...
}

so when I want to build query matching multiple specifications, I can execute following statement using findAll on JpaSpecificationExecutor<Event> repository.

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

and ActivitySpecifications example:

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

How do I use EventSpecifications from ActivitySpecifications ? I mean like merge specifications of different type. I'm sorry, but I don't even know how to ask it properly, but theres simple example:

I want to select all activities with status = :status and where activity.event.date is greater than :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
}

Is something like this possible?

The closest thing that comes to my mind is using the following:

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

where ??? requires Root<Event>, but I can only get Path<Event> using root.get(Activity_.event).



Solution 1:[1]

Consider the following :

ClassA {
 id;
}

ClassB {
 foreignId; //id of A
}

For combining 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);
   };
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Leena