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

使用@OneToMany后返回重复父对象的Hibernate解决方案问询

问题描述

我有A、B、C三个实体,其中A为父实体,包含B和C的集合,实体代码如下:

@Entity
@Table(name="A")
public class A implements Serializable {

    @Id
    @Column(name="id")
    private int id;

    @OneToMany(mappedBy = "a", fetch = FetchType.LAZY)
    @JsonManagedReference
    private Set<B> bSet;

    @OneToMany(mappedBy = "a", fetch = FetchType.LAZY)
    @JsonManagedReference
    private Set<C> cSet;

}

@Entity
@Table(name="B")
public class B implements Serializable {

    @Id
    @Column(name="id")
    private int id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "a_id", referencedColumnName = "id")
    @JsonBackReference
    private A a;

    //other fields...
}

@Entity
@Table(name="C")
public class C implements Serializable {

    @Id
    @Column(name="id")
    private int id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "a_id", referencedColumnName = "id")
    @JsonBackReference
    private A a;

   //other fields...

}

通过如下方式从数据库查询数据:

@Override
@Cacheable(cacheNames = "AJpaRepository.findAll", cacheManager = "mysqlCacheManager")
@Query(value = "FROM A a"
    + " LEFT JOIN FETCH a.bSet"
    + " LEFT JOIN FETCH a.cSet")
List<A> findAll();

期望查询结果为每个A对象唯一:

A : {id =1, bSet=[...], cSet=[...]}
A : {id =2, bSet=[...], cSet=[...]}
A : {id =3, bSet=[...], cSet=[...]}
A : {id =4, bSet=[...], cSet=[...]} 
...

但实际得到重复的A对象(内容完全一致):

A : {id =1, bSet=[...], cSet=[...]}
A : {id =1, bSet=[...], cSet=[...]}
A : {id =1, bSet=[...], cSet=[...]}
A : {id =2, bSet=[...], cSet=[...]} 
...

请问能否在Hibernate层面优雅处理该问题,而非在业务层使用Set或distinct去重?

解决方案

可以在Hibernate层面通过两种方式优雅解决这个问题:

1. 在JPQL查询中添加DISTINCT关键字

直接修改查询语句,添加SELECT DISTINCT,Hibernate会自动对根实体A进行去重,同时保留已加载的关联集合数据:

@Override
@Cacheable(cacheNames = "AJpaRepository.findAll", cacheManager = "mysqlCacheManager")
@Query(value = "SELECT DISTINCT a FROM A a"
    + " LEFT JOIN FETCH a.bSet"
    + " LEFT JOIN FETCH a.cSet")
List<A> findAll();

这种方式简洁高效,Hibernate会在结果集处理阶段根据实体ID自动去重,避免业务层额外处理。

2. 使用DistinctRootEntityResultTransformer

如果使用Hibernate Session API进行查询,可以设置结果转换器来实现根实体去重,示例代码如下:

List<A> resultList = session.createQuery("FROM A a LEFT JOIN FETCH a.bSet LEFT JOIN FETCH a.cSet")
    .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
    .list();

在Spring Data JPA中也可以自定义Repository方法,通过获取Session来应用该转换器,但相比第一种方式会繁琐一些,适合复杂查询场景。

问题根源

出现重复A实体的核心原因是笛卡尔积:当A同时关联多个B和C实体时,LEFT JOIN FETCH会生成笛卡尔积结果集,Hibernate会为每一行记录创建一个A实例,最终导致重复。上述两种方式都是让Hibernate在结果处理阶段,根据实体ID去重,确保每个根实体唯一。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 23:18:27