

Java 8 for constructing JPA criteria queries
How to use Stream API and Lambda functions for flexible JPA criteria queries construction.
One of the things I found interesting and evolutionary after 2 years away from actual coding is the way of constructing JPA criteria queries using object-oriented approach. How I got involved in this you can read here.
Approach
Simple enough:
1.Define criteria methods
The methods return
org.springframework.data.jpa.domain.Specification
and trough Lambda functions define the JPA criterias.
public Specification<User> activeUsers(long from) { return (root, query, cb) -> { Subquery<Integer> sq = query.subquery(Integer.class); return cb.and( cb.greaterThanOrEqualTo(root.get(User_.createdDate), new Date(from)), cb.isTrue(root.get(User_.enabled)), cb.exists(sq.select(cb.literal(1)).where(cb.equal(root, sq.from(RssReadTask.class).get(RssReadTask_.user))))); }; } public Specification<User> proUsers() { return (root, query, cb) -> { final String proUserPermissionsName = ProUserPermissions.class.getSimpleName(); return cb.and( cb.or( cb.equal(root.get(User_.userLevel), proUserPermissionsName), cb.equal(root.get(User_.userLevel), proUserPermissionsName.toLowerCase()) ) ); }; } public Specification<User> userByCreatedRange(Long from, Long to) { return (root, query, cb) -> { List<Predicate> predicates = new ArrayList<>(); if (from != null && from > 0) { predicates.add(cb.greaterThanOrEqualTo(root.get(User_.createdDate), new Date(from))); } if (to != null && to > 0) { predicates.add(cb.lessThanOrEqualTo(root.get(User_.createdDate), new Date(to))); } Predicate[] p = predicates.toArray(new Predicate[predicates.size()]); return p.length == 0 ? null : p.length == 1 ? p[0] : cb.and(p); }; }
2. Define some helper methods and classes
private <T> CriteriaQuery<T> applyFilter(CriteriaQuery<T> query, Class<?> cls, Specification filter, Root<?> root, CriteriaBuilder cb) { Predicate[] where = Stream.of(filter) .filter(f -> f != null) .map(new FilterPredicateFunction<>(root, query, cb)) .filter(f -> f != null) .toArray(Predicate[]::new); return where.length > 0 ? query.where(where) : query; } private static class FilterPredicateFunction<T> implements java.util.function. Function<Specification, Predicate> { private final Root<T> root; private final CriteriaQuery<?> query; private final CriteriaBuilder cb; public FilterPredicateFunction(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) { this.root = root; this.query = query; this.cb = cb; } @Nullable @Override @SuppressWarnings("unchecked") public Predicate apply(@Nullable Specification input) { if (input instanceof Specification) { return input.toPredicate(root, query, cb); } else { throw new IllegalArgumentException(); } } } @Override public Specification and(Specification... filters) { return (root, query, cb) -> cb.and(Arrays.asList(filters) .stream() .filter(f -> f != null) .map(f -> (f.toPredicate(root, query, cb)) .filter(f -> f != null) .toArray(Predicate[]::new)); }
3.Use
The following method queries the DB for all the users that have more than one RssReadTask object associated, have certain permission object a associated and are created in a specified time range. Adding different criterias based on input parameters is quite easy.
@RequestMapping(value = "/count", method = RequestMethod.GET, params = {"activeOnly"}) public Long count( @RequestParam(value = "from", defaultValue = "0") long from, @RequestParam(value = "to", defaultValue = "0") long to, @RequestParam(value = "activeOnly", defaultValue = "false") boolean activeOnly, @RequestParam(value = "proOnly", defaultValue = "false") boolean proOnly) { List<org.springframework.data.jpa.domain.Specification> filterList = new ArrayList<>(); if (activeOnly) { filterList.add(activeUsers(from)); } if (proOnly) { filterList.add(proUsers()); } filterList.add(userByCreatedRange(from, to))) CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Long> query = cb.createQuery(Long.class); Root<User> root = query.from(User.class); return em.createQuery(applyFilter(query.select(cb.count(root)), User.class, and(filterList), root, cb, false)).getSingleResult(); }
Conclusion
The best thing about the approach is that all the criteria methods activeUsers, proUsers, userByCreatedRange are reusable and can be applied in more complex queries using the Java 8 Streams API.