You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Hibernate 6添加Predicate时抛出重复注册异常的原因及解决

Hibernate 6中复用Predicate导致IllegalArgumentException异常的解决方案

在将项目从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 countQuery = cb.createQuery(Long.class); final Root query = countQuery.from(getEntityClass()); final List predicates = buildPredicates(cb, query, form); if (!predicates.isEmpty()) { countQuery.where(predicates.toArray(new Predicate[0])); } final List totals = em.createQuery(countQuery.select(cb.countDistinct(query))).getResultList(); if (!totals.isEmpty()) { return totals.get(0); } return 0L; } ```

同时需要修改getAll方法中调用nbTotal的代码,传入form而非predicates:

return new GetAllResponse<T>(results, nbTotal(form));

内容的提问来源于stack exchange,提问作者Abned

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 11:15:36