Skip to content

Instantly share code, notes, and snippets.

@ufuk
Last active January 8, 2023 17:38
Show Gist options
  • Star 22 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • Save ufuk/2ca63e392cc61d5b5277249dc23d4eca to your computer and use it in GitHub Desktop.
Save ufuk/2ca63e392cc61d5b5277249dc23d4eca to your computer and use it in GitHub Desktop.
Easy to use query builder for JPA Criteria API
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.IteratorUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.query.criteria.internal.path.PluralAttributePath;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.repository.support.PageableExecutionUtils;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import java.util.*;
import java.util.stream.Collectors;
public class JpaEntityQueryBuilder<E> {
private final EntityManager entityManager;
private final Class<E> entityClass;
private final CriteriaBuilder criteriaBuilder;
private final CriteriaQuery<E> criteriaQuery;
private final Root<E> root;
private final List<Predicate> predicates = new ArrayList<>();
private final List<Order> orders = new ArrayList<>();
private final Map<String, Join<?, ?>> joins = new HashMap<>();
private Integer firstResult;
private Integer maxResults;
private JpaEntityQueryBuilder(EntityManager entityManager, Class<E> entityClass) {
this.entityManager = entityManager;
this.entityClass = entityClass;
this.criteriaBuilder = entityManager.getCriteriaBuilder();
this.criteriaQuery = criteriaBuilder.createQuery(entityClass);
this.root = criteriaQuery.from(criteriaQuery.getResultType());
}
public static <T> JpaEntityQueryBuilder<T> initialize(EntityManager entityManager, Class<T> entityClass) {
return new JpaEntityQueryBuilder<>(entityManager, entityClass);
}
public List<E> list() {
TypedQuery<E> typedQuery = prepareSelectTypedQuery();
if (firstResult != null) {
typedQuery.setFirstResult(firstResult);
}
if (maxResults != null) {
typedQuery.setMaxResults(maxResults);
}
return typedQuery.getResultList();
}
public E uniqueResult() {
TypedQuery<E> typedQuery = prepareSelectTypedQuery();
return typedQuery.getSingleResult();
}
public Page<E> page(PageRequest pageRequest) {
this.firstResult = Long.valueOf(pageRequest.getOffset()).intValue();
this.maxResults = pageRequest.getPageSize();
IteratorUtils.toList(pageRequest.getSort().iterator())
.forEach(sort -> addOrderBy(sort.getProperty(), sort.isAscending()));
return PageableExecutionUtils.getPage(list(), pageRequest, this::count);
}
public long count() {
CriteriaQuery<Long> countCriteriaQuery = criteriaBuilder.createQuery(Long.class);
final Root<E> root = countCriteriaQuery.from(entityClass);
joins.forEach((key, value) -> root.join(key));
countCriteriaQuery.select(criteriaBuilder.count(root)).distinct(true);
countCriteriaQuery.where(predicates.toArray(new Predicate[]{}));
TypedQuery<Long> typedQuery = entityManager.createQuery(countCriteriaQuery);
return typedQuery.getSingleResult();
}
private TypedQuery<E> prepareSelectTypedQuery() {
criteriaQuery.select(root).distinct(true);
criteriaQuery.where(predicates.toArray(new Predicate[]{})).orderBy(orders);
return entityManager.createQuery(criteriaQuery);
}
public <J> JpaEntityQueryBuilder<E> innerJoin(String attribute) {
joins.put(attribute, root.join(attribute, JoinType.INNER));
return this;
}
public JpaEntityQueryBuilder<E> innerFetch(String attribute) {
root.fetch(attribute, JoinType.INNER);
return this;
}
public JpaEntityQueryBuilder<E> addOrderBy(String path, boolean ascending) {
if (ascending) {
addAscendingOrderBy(path);
} else {
addDescendingOrderBy(path);
}
return this;
}
public JpaEntityQueryBuilder<E> addAscendingOrderBy(String path) {
orders.add(criteriaBuilder.asc(toJpaPath(path)));
return this;
}
public JpaEntityQueryBuilder<E> addDescendingOrderBy(String path) {
orders.add(criteriaBuilder.desc(toJpaPath(path)));
return this;
}
public JpaEntityQueryBuilder<E> setFirstResult(Integer firstResult) {
this.firstResult = firstResult;
return this;
}
public JpaEntityQueryBuilder<E> setMaxResults(Integer maxResults) {
this.maxResults = maxResults;
return this;
}
public JpaEntityQueryBuilder<E> objectEqualsTo(String path, Object value) {
if (value != null) {
addEqualPredicate(path, value);
}
return this;
}
public Optional<Predicate> objectEqualsToPredicate(String path, Object value) {
if (value != null) {
return Optional.of(equalPredicate(path, value));
}
return Optional.empty();
}
public JpaEntityQueryBuilder<E> like(String path, String value) {
if (StringUtils.isNotBlank(value)) {
predicates.add(criteriaBuilder.like(toJpaPath(path), '%' + value + '%'));
}
return this;
}
public JpaEntityQueryBuilder<E> likeCaseInsensitive(String path, String value) {
if (StringUtils.isNotBlank(value)) {
predicates.add(criteriaBuilder.like(criteriaBuilder.lower(toJpaPath(path)), '%' + value.toLowerCase() + '%'));
}
return this;
}
public JpaEntityQueryBuilder<E> addInDisjunction(Optional<Predicate>... optionalPredicates) {
List<Predicate> predicateList = Arrays.stream(optionalPredicates).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
if (predicateList.size() > 1) {
predicates.add(criteriaBuilder.or(predicateList.toArray(new Predicate[]{})));
} else if (predicateList.size() == 1) {
predicates.add(predicateList.get(0));
}
return this;
}
public JpaEntityQueryBuilder<E> stringEqualsTo(String path, String value) {
if (StringUtils.isNotBlank(value)) {
addEqualPredicate(path, value);
}
return this;
}
public JpaEntityQueryBuilder<E> greaterThanOrEqualsTo(String path, Comparable comparable) {
if (Objects.nonNull(comparable)) {
predicates.add(criteriaBuilder.greaterThanOrEqualTo(toJpaPath(path), comparable));
}
return this;
}
public JpaEntityQueryBuilder<E> lessThanOrEqualsTo(String path, Comparable comparable) {
if (Objects.nonNull(comparable)) {
predicates.add(criteriaBuilder.lessThanOrEqualTo(toJpaPath(path), comparable));
}
return this;
}
public JpaEntityQueryBuilder<E> between(String path, Date firstDate, Date secondDate) {
if (Objects.nonNull(firstDate) && Objects.nonNull(secondDate)) {
predicates.add(criteriaBuilder.between(toJpaPath(path), firstDate, secondDate));
}
return this;
}
public JpaEntityQueryBuilder<E> in(String path, Collection<?> collection) {
if (CollectionUtils.isNotEmpty(collection)) {
predicates.add(criteriaBuilder.in(toJpaPath(path)).value(collection));
}
return this;
}
public JpaEntityQueryBuilder<E> isNull(String path) {
predicates.add(criteriaBuilder.isNull(toJpaPath(path)));
return this;
}
public JpaEntityQueryBuilder<E> isNotNull(String path) {
predicates.add(criteriaBuilder.isNotNull(toJpaPath(path)));
return this;
}
public JpaEntityQueryBuilder<E> objectEqualsToMultiPathInDisjunction(Object value, String... paths) {
if (Objects.nonNull(value) && paths.length != 0) {
if (paths.length > 1) {
predicates.add(criteriaBuilder.or(Arrays.stream(paths).map(path -> equalPredicate(path, value)).collect(Collectors.toList()).toArray(Predicate[]::new)));
} else {
objectEqualsTo(paths[0], value);
}
}
return this;
}
public JpaEntityQueryBuilder<E> stringEqualsToMultiPathInDisjunction(String value, String... paths) {
if (StringUtils.isNotBlank(value) && paths.length != 0) {
if (paths.length > 1) {
predicates.add(criteriaBuilder.or(Arrays.stream(paths).map(path -> equalPredicate(path, value)).collect(Collectors.toList()).toArray(Predicate[]::new)));
} else {
stringEqualsTo(paths[0], value);
}
}
return this;
}
private void addEqualPredicate(String path, Object value) {
predicates.add(equalPredicate(path, value));
}
private Predicate equalPredicate(String path, Object value) {
return criteriaBuilder.equal(toJpaPath(path), value);
}
private <T> Path<T> toJpaPath(String stringPath) {
String[] pathParts = StringUtils.split(stringPath, '.');
assert pathParts != null && pathParts.length > 0 : "Path cannot be empty";
Path<T> jpaPath = null;
for (String eachPathPart : pathParts) {
if (jpaPath == null) {
jpaPath = root.get(eachPathPart);
} else if (jpaPath instanceof PluralAttributePath && joins.containsKey(((PluralAttributePath<?>) jpaPath).getAttribute().getName())) {
jpaPath = joins.get(((PluralAttributePath<?>) jpaPath).getAttribute().getName()).get(eachPathPart);
} else {
jpaPath = jpaPath.get(eachPathPart);
}
}
return jpaPath;
}
}
import ...ExampleEntity;
import ...JpaEntityQueryBuilder;
import ...SearchParams;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
@Repository
public class JpaEntityQueryBuilderExampleRepository {
@PersistenceContext
protected EntityManager entityManager;
public List<ExampleEntity> filterThenGetResultList(SearchParams searchParams, int firstResult, int maxResults) {
return JpaEntityQueryBuilder.initialize(entityManager, ExampleEntity.class)
.setFirstResult(firstResult)
.setMaxResults(maxResults)
.addDescendingOrderBy("creationDate")
.stringEqualsTo("statusCode", searchParams.getStatusCode())
.objectEqualsTo("type", searchParams.getType())
.objectEqualsToMultiPathInDisjunction(searchParams.getType(), "type", "secondaryType")
.greaterThanOrEqualsTo("creationDate", searchParams.getStartDate())
.lessThanOrEqualsTo("creationDate", searchParams.getEndDate());
.innerJoin("items")
.stringEqualsTo("items.ownerId", searchParams.getOwnerId())
.stringEqualsToMultiPathInDisjunction(searchParams.getOwnerId(), "items.ownerId", "buyerId")
.list();
}
public Page<ExampleEntity> filterThenGetPage(SearchParams searchParams, PageRequest pageRequest) {
return JpaEntityQueryBuilder.initialize(entityManager, ExampleEntity.class)
.stringEqualsTo("statusCode", searchParams.getStatusCode())
.objectEqualsTo("type", searchParams.getType())
.greaterThanOrEqualsTo("creationDate", searchParams.getStartDate())
.lessThanOrEqualsTo("creationDate", searchParams.getEndDate());
.innerJoin("items")
.stringEqualsTo("items.ownerId", searchParams.getOwnerId())
.page(pageRequest);
}
public long filterThenGetCount(SearchParams searchParams) {
return JpaEntityQueryBuilder.initialize(entityManager, ExampleEntity.class)
.stringEqualsTo("statusCode", searchParams.getStatusCode())
.objectEqualsTo("type", searchParams.getType())
.greaterThanOrEqualsTo("creationDate", searchParams.getStartDate())
.lessThanOrEqualsTo("creationDate", searchParams.getEndDate());
.innerJoin("items")
.stringEqualsTo("items.ownerId", searchParams.getOwnerId())
.count();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment