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 […]

by Cvetelin Andreev

June 30, 2015

3 min read

Intro1 - 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 [java] org.springframework.data.jpa.domain.Specification [/java] and trough Lambda functions define the JPA criterias. [java] 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); }; } [/java]  

2. Define some helper methods and classes

[java] 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)); } [/java]

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. [java] @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(); } [/java]

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.

What are your ways of accomplishing flexibility in creating JPA queries? Do you use Java 8 Lamba and Streams API with JPA?

Categories

Cvetelin has been involved in startups (mostly tech) since 2003 year playing as (co-)founder, partner and occasionally Java full stack developer. Currently Full Stack Soldier, Startup activist and active blogger @ Dreamix. Plays, teaches and manages @ www.kabagaida.com. Founder of OfficeInTheWoods.com, fan of #futureofwork. Practice sustainable gardening and lifestyle. Runs a forest kindergarten near Sofia. Father of two.