Hibernate 6添加Predicate时抛出重复注册异常的原因及解决
在将项目从Hibernate 5.6(Spring Boot 2.7)升级到Hibernate 6后,调用nbTotal方法时抛出如下异常:
java.lang.IllegalArgumentException: Already registered a copy: SqmBasicValuedSimplePath(com.louis.scrapping.api.entities.ExtractionTable(6762665638604).onglet)
相关代码
抽象类GetAllService代码
```java 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
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()); }
}
</details> <details><summary>实现类GetAllExtractionTable代码</summary> ```java 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; } }
异常原因
Hibernate 6对Criteria API的实现做了严格限制:为一个查询构建的Predicate实例绑定到该查询的上下文,无法直接复用到另一个独立查询中。原代码里getAll方法构建的predicates被传入nbTotal方法,用于另一个count查询,这违反了Hibernate 6的约束,从而抛出异常。
解决方案
修改nbTotal方法,不再复用已有Predicate,而是基于当前count查询的Root重新构建Predicate。修改后的代码如下:
修改后的nbTotal方法代码
```java protected Long nbTotal(GetAllForm form) { final CriteriaBuilder cb = em.getCriteriaBuilder(); final CriteriaQuery同时需要修改getAll方法中调用nbTotal的代码,传入form而非predicates:
return new GetAllResponse<T>(results, nbTotal(form));
内容的提问来源于stack exchange,提问作者Abned

