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.