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()])); } }; }}