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

JPA子查询返回对象时出现ClassCastException问题排查与解决

JPA查询分区取首条ChildObject报错的问题与解决

我编写了一段JPA查询,试图找出符合特定条件的ParentObject关联的所有ChildObject,并且在按child.field2和child.parent.field2分区后,只取每个分区的第一条结果:

@Repository
public interface MyRepository {
    @Query("SELECT sorted_children.child_1 " +
        " FROM ( " +
        "   SELECT child AS child_1, " +
        "     ROW_NUMBER() OVER (PARTITION BY child.field2, child.parent.field2 " +
        "       ORDER BY child.parent.field1 ASC) AS rowNum " +
        "   FROM ChildObject child " +
        "   WHERE child.field1 IN (?1) AND " +
        "         child.parent.field1 IN (?2) " +
        " ) AS sorted_children" +
        " WHERE sorted_children.rowNum = 1")
    Set<ChildObject> findChildObjects(Set<Long> childField1, Set<Long> parentField1);
}

但运行时抛出了如下类型转换异常:

java.lang.ClassCastException: class org.hibernate.persister.entity.SingleTableEntityPersister cannot be cast to class org.hibernate.metamodel.mapping.EntityAssociationMapping
(org.hibernate.persister.entity.SingleTableEntityPersister and org.hibernate.metamodel.mapping.EntityAssociationMapping are in unnamed module of loader 'app')

实体类定义

ParentObject实体

@Entity
@Table(name = "parent_table", schema = "schema")
public class ParentObject {

    @Id
    @Column(name = "id")
    public Long id;

    @Column(name = "field1")
    public Long field1;

    @Column(name = "field2")
    public String field2;

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "parent_id", referencedColumnName = "id", nullable = false, insertable = false, updatable = false)
    public Set<ChildObject> children;
}

ChildObject实体

@Entity
@Table(name = "child_table", schema = "schema")
public class ChildObject {

    @Id
    @Column(name = "id")
    public Long id;

    @Column(name = "field1")
    public Long field1;

    @Column(name = "field2")
    public String field2;

    @ManyToOne
    @JoinColumn(name = "parent_id", nullable = false, updatable = false)
    public ParentObject parent;
}

尝试过的非目标查询

  • 示例1:未分区、未取首条结果(可运行,但不符合需求)
@Query("SELECT child " +
    "   FROM ChildObject child " +
    "   WHERE child.field1 IN (?1)")
Set<ChildObject> findChildObjects(Set<Long> field1);
  • 示例2:返回ParentObject而非ChildObject(可运行,但不符合需求)
@Query("SELECT sorted_children.parent " +
      " FROM ( " +
      "   SELECT child.parent AS parent, " +
      "     ROW_NUMBER() OVER (PARTITION BY child.field2, child.parent.field2 " +
        "       ORDER BY child.parent.field1 ASC) AS rowNum " +
      "   FROM ChildObject child " +
      "   WHERE child.field1 IN (?1) AND " +
      "         child.parent.field1 IN (?2) " +
      " ) AS sorted_children" +
      " WHERE sorted_children.rowNum = 1")
Set<ParentObject> findParentObjects(Set<Long> childField1, Set<Long> parentField1);

我对示例2能运行感到意外,因为它和原查询逻辑类似,都是返回子查询中的对象(据我了解Hibernate会将其内部转换为CTE)。猜测是需要对ChildObject做特殊处理才能正常返回,当前Hibernate把它识别为SingleTableEntityPersister,但期望的是EntityAssociationMapping。


我的问题

  1. 原查询中哪里出错导致了ClassCastException?
  2. 如何修改查询使其正常运行?

解决方案

1. 原查询报错原因

Hibernate在处理子查询中直接返回主实体对象(child AS child_1)并在外层查询引用时,无法正确解析实体的映射关系。当返回的是关联对象(比如示例2中的child.parent)时,Hibernate能正确处理关联映射,但直接返回主实体(ChildObject)时,它错误地尝试将实体的持久化器(SingleTableEntityPersister)转换为关联映射类型(EntityAssociationMapping),这是Hibernate在处理子查询返回主实体时的内部映射解析缺陷。

2. 修改方案

方式一:使用CTE(WITH子句)替代子查询

将子查询改写为CTE,让Hibernate更清晰地解析实体映射:

@Query("WITH sorted_children AS (" +
        "   SELECT child, " +
        "     ROW_NUMBER() OVER (PARTITION BY child.field2, child.parent.field2 " +
        "       ORDER BY child.parent.field1 ASC) AS rowNum " +
        "   FROM ChildObject child " +
        "   WHERE child.field1 IN (?1) AND " +
        "         child.parent.field1 IN (?2) " +
        ") " +
        "SELECT sc.child FROM sorted_children sc WHERE sc.rowNum = 1")
Set<ChildObject> findChildObjects(Set<Long> childField1, Set<Long> parentField1);

方式二:子查询返回实体ID,外层查询通过ID获取实体

先在子查询中获取符合条件的ChildObject的ID和行号,外层查询通过ID查询实体:

@Query("SELECT child FROM ChildObject child " +
        "WHERE child.id IN (" +
        "   SELECT sc.childId FROM (" +
        "       SELECT child.id AS childId, " +
        "         ROW_NUMBER() OVER (PARTITION BY child.field2, child.parent.field2 " +
        "           ORDER BY child.parent.field1 ASC) AS rowNum " +
        "       FROM ChildObject child " +
        "       WHERE child.field1 IN (?1) AND " +
        "             child.parent.field1 IN (?2) " +
        "   ) sc WHERE sc.rowNum = 1" +
        ")")
Set<ChildObject> findChildObjects(Set<Long> childField1, Set<Long> parentField1);

方式三:显式指定实体字段(备选)

如果前两种方式仍有问题,可以尝试在子查询中显式选择实体的所有字段,辅助Hibernate解析映射(代码冗余,仅作备选):

@Query("SELECT sorted_children.child_1 " +
        "FROM ( " +
        "   SELECT child.id, child.field1, child.field2, child.parent, " +
        "     ROW_NUMBER() OVER (PARTITION BY child.field2, child.parent.field2 " +
        "       ORDER BY child.parent.field1 ASC) AS rowNum, " +
        "     child AS child_1 " +
        "   FROM ChildObject child " +
        "   WHERE child.field1 IN (?1) AND " +
        "         child.parent.field1 IN (?2) " +
        ") AS sorted_children " +
        "WHERE sorted_children.rowNum = 1")
Set<ChildObject> findChildObjects(Set<Long> childField1, Set<Long> parentField1);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 14:05:00