navigation

Java 8 for constructing JPA criteria queries

Java 8 for constructing JPA criteria queries

by
June 30, 2015
frontpage, Java
One Comment

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.

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

Cvetelin Andreev

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.

More Posts - Website

Follow Me:
TwitterFacebookLinkedInGoogle Plus

Do you want more great blogs like this?

Subscribe for Dreamix Blog now!

  • xt w

    Thank you for your article, very well written, but I still do not understand the role of “description class”