JPA子查询返回对象时出现ClassCastException问题排查与解决
我编写了一段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。
我的问题
- 原查询中哪里出错导致了
ClassCastException? - 如何修改查询使其正常运行?
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

