使用@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
相关产品推荐
相关产品推荐

