Spring Data JPA Specification using CriteriaBuilder with a one to many relationship Spring Data JPA Specification using CriteriaBuilder with a one to many relationship spring spring

Spring Data JPA Specification using CriteriaBuilder with a one to many relationship


I found a solution. To map a one to many attribute, in the metamodel I added the following:

public static volatile CollectionAttribute<User, Application> applications;

I also needed to add a metamodel for the Application entity.

@StaticMetamodel(Application.class)public class Application_ {    public static volatile SingularAttribute<Application, Long> applicationId;}

Then in my Specification, I could access the applications for a user, using the .join() method on the Root<User> instance. Here is the Predicate I formed.

final Predicate appPredicate = root.join(User_.applications).get(Application_.applicationId).in(appIds);

Also, it is worth noting that my Specification as it is written in the question will not work if any of the input values are empty. A null Predicate passed to the .and() method of CriteriaBuilder will cause a NullPointerException. So, I created an ArrayList of type Predicate, then added each Predicate to the list if the corresponding parameter was non-empty. Finally, I convert the ArrayList to an array to pass it to the .and() function of the CriteriaBuilder. Here is the final Specification:

public class UserSpecification {    public static Specification<User> findByFirstNmLastNmEmailApp(String firstNm, String lastNm, String email, Collection<Long> appIds) {        return new Specification<User>() {            @Override            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {                final Collection<Predicate> predicates = new ArrayList<>();                if (!StringUtils.isEmpty(firstNm)) {                    final Predicate firstNmPredicate = cb.like(cb.lower(root.get(User_.firstNm), firstNm));                    predicates.add(firstNmPredicate);                }                if (!StringUtils.isEmpty(lastNm)) {                    final Predicate lastNmPredicate = cb.like(cb.lower(root.get(User_.lastNm), lastNm));                    predicates.add(lastNmPredicate);                }                if (!StringUtils.isEmpty(email)) {                    final Predicate emailPredicate = cb.like(cb.lower(root.get(User_.email), email));                    predicates.add(emailPredicate);                }                if (!appIds.isEmpty()) {                    final Predicate appPredicate = root.join(User_.applications).get(Application_.applicationId).in(appIds);                    predicates.add(appPredicate);                }                return cb.and(predicates.toArray(new Predicate[predicates.size()]));            }        };    }}