Hibernate中session.createQuery查询返回多对象异常的解决求助
这个问题我之前也踩过坑,本质是双向关联导致HQL查询产生了笛卡尔积——同一个Delivery实例因为关联了多个Customer或Item被多次返回,而Hibernate要求同一主键对应的实体只能存在一个实例,所以抛出了主键重复的异常。下面给你几个实用的解决办法:
1. 在HQL中添加DISTINCT关键字
这是最直接的方案,让Hibernate自动过滤掉重复的Delivery实例。把你的查询语句修改为:
Query query = session.createQuery("select distinct d from Delivery d where d.compositeId.deliveryId=?"); query.setParameter(0, 1);
原理是关联查询会生成笛卡尔积(比如一个Delivery关联2个Customer,就会返回2行结果),DISTINCT会让Hibernate把这些重复行映射为同一个实体实例,避免主键冲突。
2. 调整关联的抓取策略为懒加载
如果你的业务场景不需要立即加载关联的Customer或Item,可以把关联注解的fetch属性设置为FetchType.LAZY,这样Hibernate默认不会加载关联对象,也就不会触发笛卡尔积的生成。
比如在Delivery类的关联字段上配置:
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "customer_id", insertable = false, updatable = false) private Customer customer;
(注:insertable和updatable设为false是因为复合主键已经包含了customerId,避免重复映射字段)
3. 修正复合主键的equals和hashCode方法
你提到已经重写了equals但没用,大概率是你只比较了deliveryId,没有覆盖复合主键的另外两个字段invoiceId和customerId。Hibernate判断实体是否为同一个实例,完全依赖主键的equals和hashCode方法,所以必须包含所有主键字段:
// CompositeId类的equals和hashCode实现 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CompositeId that = (CompositeId) o; return Objects.equals(deliveryId, that.deliveryId) && Objects.equals(invoiceId, that.invoiceId) && Objects.equals(customerId, that.customerId); } @Override public int hashCode() { return Objects.hash(deliveryId, invoiceId, customerId); }
只有这样,Hibernate才能正确识别同一个复合主键对应的实体。
4. 使用投影查询(如果不需要完整实体)
如果你只需要Delivery的部分字段,而非整个实体对象,可以用投影查询,彻底避免加载关联对象:
Query query = session.createQuery("select d.compositeId.deliveryId, d.deliveryDate from Delivery d where d.compositeId.deliveryId=?"); query.setParameter(0, 1);
这种方式不会触发关联加载,自然不会产生重复行的问题。
内容的提问来源于stack exchange,提问作者sajithneyo

