Here is my code:
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Order;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import com.abned.forms.GetAllForm;
import com.abned.forms.PaginationForm;
import com.abned.SortingForm;
import com.abned.forms.SortingForm.SortingDirection;
public abstract class GetAllService<T> {
private final EntityManager em;
public GetAllService(EntityManager em) {
this.em = em;
}
public GetAllResponse<T> getAll(GetAllForm form) {
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<T> cq = cb.createQuery(getEntityClass());
final Root<T> query = buildRootQuery(cq);
final List<Predicate> predicates = buildPredicates(cb, query, form);
if (!predicates.isEmpty()) {
cq.where(predicates.toArray(new Predicate[predicates.size()]));
}
cq.distinct(true);
cq.orderBy(buildOrderBy(cb, query, form));
final TypedQuery<T> typedQuery = em.createQuery(cq);
buildPagination(form.getPagination(), typedQuery);
addAdditionalBuiltins(form, typedQuery);
final List<T> results = typedQuery.getResultList();
return new GetAllResponse<T>(results, nbTotal(predicates));
}
protected Root<T> buildRootQuery(CriteriaQuery<T> cq) {
return cq.from(getEntityClass());
}
protected Long nbTotal(final List<Predicate> predicates) {
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
final Root<T> query = countQuery.from(getEntityClass());
if (!predicates.isEmpty()) {
countQuery.where(predicates.toArray(new Predicate[0]));
}
final List<Long> totals = em.createQuery(countQuery.select(cb.countDistinct(query))).getResultList();
if (!totals.isEmpty()) {
return totals.get(0);
}
return 0L;
}
protected void buildPagination(PaginationForm pagination, TypedQuery<T> typedQuery) {
if (pagination == null) {
return;
}
if (null != pagination.getLimit() && pagination.getLimit() > 0) {
if (null != pagination.getPage() && pagination.getPage() > 0) {
typedQuery.setFirstResult((pagination.getPage().intValue() - 1) * pagination.getLimit());
}
typedQuery.setMaxResults(pagination.getLimit());
}
}
protected void addAdditionalBuiltins(GetAllForm form, TypedQuery<T> typedQuery) {}
protected abstract List<Predicate> buildPredicates(CriteriaBuilder cb, Root<T> query, GetAllForm form);
protected abstract Class<T> getEntityClass();
protected Order[] buildOrderBy(CriteriaBuilder cb, Root<T> query, GetAllForm form) {
final List<Order> orders = new ArrayList<>();
final SortingForm orderBy = form.getSorting();
if (orderBy != null) {
final Expression<? extends Serializable> order = getOrderByExpression(query, orderBy);
if (orderBy.getDir() == null || orderBy.getDir() == SortingDirection.ASC) {
orders.add(cb.asc(order));
} else {
orders.add(cb.desc(order));
}
}
return orders.toArray(new Order[orders.size()]);
}
protected Expression<? extends Serializable> getOrderByExpression(Root<T> query, SortingForm orderBy) {
return query.get(orderBy.getColumn());
}
}
And the concrete class that extends it:
import org.springframework.stereotype.Service;
import com.abned.entities.ExtractionTable;
import com.abned.forms.GetAllExtractionTableForm;
import com.abned.forms.GetAllForm;
import jakarta.persistence.EntityManager;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
@Service
public class GetAllExtractionTable extends GetAllService<ExtractionTable> {
public GetAllExtractionTable(EntityManager em) {
super(em);
}
@Override
protected List<Predicate> buildPredicates(CriteriaBuilder cb, Root<ExtractionTable> query, GetAllForm form) {
final List<Predicate> predicates = new ArrayList<>();
if (form instanceof GetAllExtractionTableForm) {
final GetAllExtractionTableForm criteria = (GetAllExtractionTableForm) form;
if (null != criteria.getOnglet()) {
predicates.add(cb.equal(query.get("onglet"), criteria.getOnglet()));
}
if (null != criteria.getType()) {
predicates.add(cb.equal(query.get("type"), criteria.getType()));
}
if (null != criteria.getIds() && !criteria.getIds().isEmpty()) {
predicates.add(query.get("id").in(criteria.getIds()));
}
}
return predicates;
}
@Override
protected Class<ExtractionTable> getEntityClass() {
return ExtractionTable.class;
}
}
So, why nbTotal function throw an exception java.lang.IllegalArgumentException: Already registered a copy: SqmBasicValuedSimplePath(com.louis.scrapping.api.entities.ExtractionTable(6762665638604).onglet) ?
With hibernate 5.6 (Spring boot 2.7), no exception was thrown.
Edit: 20 Mey 2023 - my solution
As @ChristianBeikov explain in comment, with hibernate 6, we can't reuse the built predicates from a query to another query.
So, my solution is to rebuild the predicate by using the new query for nbTotal method. nbTotal function is now like this:
protected Long nbTotal(GetAllForm form) {
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
final Root<T> query = countQuery.from(getEntityClass());
final List<Predicate> predicates = buildPredicates(cb, query, form);
if (!predicates.isEmpty()) {
countQuery.where(predicates.toArray(new Predicate[0]));
}
final List<Long> totals = em.createQuery(countQuery.select(cb.countDistinct(query))).getResultList();
if (!totals.isEmpty()) {
return totals.get(0);
}
return 0L;
}
You will need something like this instead:
public class JpaUtil {
public static <E, D> long count(final EntityManager entityManager,
final CriteriaBuilder cb,
final CriteriaQuery<D> criteria,
final Root<E> root) {
CriteriaQuery<Long> query = createCountQuery(cb, criteria, root, root.getModel().getJavaType());
return entityManager.createQuery(query).getSingleResult();
}
private static <T, D> CriteriaQuery<Long> createCountQuery(final CriteriaBuilder cb,
final CriteriaQuery<D> criteria,
final Root<T> root,
final Class<T> entityClass) {
final CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
final SqmCopyContext copyContext = SqmCopyContext.simpleContext();
final Root<T> countRoot = countQuery.from(entityClass);
copyContext.registerCopy(root, countRoot);
doJoins(root.getJoins(), countRoot, copyContext);
doJoinsOnFetches(root.getFetches(), countRoot, copyContext);
countQuery.select(cb.count(countRoot));
countQuery.where(((SqmPredicate) criteria.getRestriction()).copy(copyContext));
countRoot.alias(root.getAlias());
return countQuery.distinct(criteria.isDistinct());
}
@SuppressWarnings("unchecked")
private static void doJoinsOnFetches(Set<? extends Fetch<?, ?>> joins, Root<?> root, SqmCopyContext copyContext) {
doJoins((Set<? extends Join<?, ?>>) joins, root, copyContext);
}
private static void doJoins(Set<? extends Join<?, ?>> joins, Root<?> root, SqmCopyContext copyContext) {
for (Join<?, ?> join : joins) {
Join<?, ?> joined = root.join(join.getAttribute().getName(), join.getJoinType());
joined.alias(join.getAlias());
copyContext.registerCopy(join, joined);
doJoins(join.getJoins(), joined);
}
}
private static void doJoins(Set<? extends Join<?, ?>> joins, Join<?, ?> root, SqmCopyContext copyContext) {
for (Join<?, ?> join : joins) {
Join<?, ?> joined = root.join(join.getAttribute().getName(), join.getJoinType());
joined.alias(join.getAlias());
copyContext.registerCopy(join, joined);
doJoins(join.getJoins(), joined);
}
}
}