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

Hibernate中@ManyToMany关联返回空Set,改为List则正常

问题原因及解决办法

核心原因

这个问题的本质是HashSet依赖equals()和hashCode()方法判断元素唯一性,而你的实体类在这两个方法的实现上存在问题,导致JPA加载关联数据时无法正确识别元素,最终集合为空。

具体细节:

  • Lombok的@Data注解会自动基于所有字段生成equals()和hashCode(),这包含了非id字段(比如version、业务字段)。当Hibernate通过FetchType.EAGER加载关联的TaxonomyElement时,可能先创建代理对象,或加载过程中字段未完全初始化,导致equals/hashCode判断出错,认为元素已存在于HashSet中(或无法匹配),从而不添加到集合里。
  • 你用final修饰集合字段并直接初始化new HashSet<>(),虽然JPA允许这种写法,但Hibernate原本会替换集合为自身的PersistentSet实现(用于跟踪集合变化),final修饰可能干扰这个替换过程,导致关联数据无法被正确填充。

而List是有序集合,不做唯一性校验,只要加载到数据就直接添加,因此不会出现这个问题。

解决办法

1. 修正equals()和hashCode()实现

推荐只基于实体的id字段实现这两个方法,因为id是实体的唯一标识,且持久化后状态稳定。可以用Lombok的@EqualsAndHashCode注解指定字段:

修改AuditedEntity:

@Data
@Entity
@SuperBuilder
@AllArgsConstructor
@RequiredArgsConstructor
@Inheritance(strategy = InheritanceType.JOINED)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class AuditedEntity {
  @Id
  @GenericGenerator(name = "uuid2", strategy = "uuid2")
  @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "uuid2")
  @Column(length = 36)
  @EqualsAndHashCode.Include
  private String id;

  @Version
  @Column(nullable = false)
  private Integer version;

  @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
  @JoinTable(
    name = "audited_entity_to_taxonomy_element",
    joinColumns = {@JoinColumn(name = "audited_entity_id")},
    inverseJoinColumns = {@JoinColumn(name = "taxonomy_element_id")}
  )
  private Set<TaxonomyElement> taxonomyElements = new HashSet<>();
}

修改TaxonomyElement:

@Data
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class TaxonomyElement {
  @Id
  @GenericGenerator(name = "uuid2", strategy = "uuid2")
  @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "uuid2")
  @Column(length = 36)
  @EqualsAndHashCode.Include
  private String id;

  @ManyToMany(mappedBy = "taxonomyElements", cascade = CascadeType.ALL)
  private Set<AuditedEntity> auditedEntities = new HashSet<>();
}

2. 移除集合字段的final修饰

让Hibernate可以正常替换集合为自身的持久化实现类(比如PersistentSet),确保关联数据能被正确填充到集合中。

3. 可选:显式指定Hibernate集合实现(不推荐)

如果需要保留final,可以显式使用Hibernate的集合实现,但会耦合框架API:

import org.hibernate.collection.spi.PersistentSet;

// ...
private final Set<TaxonomyElement> taxonomyElements = new PersistentSet();

验证方式

修改后重启应用,调用findById方法,检查taxonomyElements是否能正常加载关联数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 16:10:41