Spring Boot 3.4升级后调用DeleteAll触发TransientObjectException问题
Spring Boot 3.4多对多关联删除循环级联问题分析与解决
问题背景
- 升级Spring Boot 3.4后,集成测试因多对多关联删除逻辑失败,相同代码在Spring Boot 3.3环境下可正常运行
- 模型包含
DocumentationUnitDTO与ProcedureDTO,二者为双向多对多关联,关联表documentation_unit_procedure中存储DocumentationUnitDTO的排序字段 ProcedureDTO中对DocumentationUnitDTO的引用使用FetchType.EAGER加载,删除操作时触发了DocumentationUnitDTO -> ProcedureDTO -> DocumentationUnitDTO的循环级联调用,导致异常
已验证的临时缓解方法
- 给
ProcedureDTO中的多对多属性标记@Transient,删除操作恢复正常 - 切换至Hibernate 6.6版本运行代码,删除逻辑可正常执行
问题根因
Spring Boot 3.4默认集成的Hibernate 6.5.x系列版本,在处理双向多对多关联+EAGER加载的删除逻辑时存在bug:删除DocumentationUnitDTO时,EAGER加载会立即初始化关联的ProcedureDTO实例,而ProcedureDTO的反向关联又会触发DocumentationUnitDTO的EAGER加载,形成循环依赖;同时Hibernate的级联删除逻辑在这种循环状态下出现了状态判断错误,导致删除操作失败。
而Hibernate 6.6版本已修复该双向多对多EAGER加载的循环处理bug,因此切换版本后问题消失。
正式解决方案
方案1:升级Hibernate至6.6+
直接指定Hibernate版本为6.6及以上,覆盖Spring Boot 3.4的默认依赖版本:
<!-- Maven 配置 --> <dependency> <groupId>org.hibernate.orm</groupId> <artifactId>hibernate-core</artifactId> <version>6.6.0.Final</version> </dependency>
// Gradle 配置 implementation 'org.hibernate.orm:hibernate-core:6.6.0.Final'
此方案直接修复底层bug,无需修改业务代码,优先推荐。
方案2:调整关联加载策略为LAZY
将ProcedureDTO中的多对多关联加载类型从FetchType.EAGER改为FetchType.LAZY,避免删除时立即触发反向关联的加载,打破循环依赖:
@ManyToMany(fetch = FetchType.LAZY) @JoinTable( name = "documentation_unit_procedure", inverseJoinColumns = @JoinColumn(name = "documentation_unit_id"), joinColumns = @JoinColumn(name = "procedure_id"))
注意:如果业务场景必须使用EAGER加载,此方案不适用
方案3:拆分双向多对多为显式中间表关联
由于关联表中存在自定义排序字段,推荐将双向多对多拆分为两个一对多关联,显式映射中间表实体,从根源避免循环加载问题:
- 创建中间表实体:
@Entity @Table(name = "documentation_unit_procedure") public class DocumentationUnitProcedureDTO { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "documentation_unit_id") private DocumentationUnitDTO documentationUnit; @ManyToOne @JoinColumn(name = "procedure_id") private ProcedureDTO procedure; private Integer sortOrder; // 排序字段 // 省略getter、setter及构造方法 }
- 修改
DocumentationUnitDTO的关联:
@OneToMany(mappedBy = "documentationUnit", cascade = CascadeType.ALL, orphanRemoval = true) private List<DocumentationUnitProcedureDTO> procedureAssociations;
- 修改
ProcedureDTO的关联:
@OneToMany(mappedBy = "procedure") private List<DocumentationUnitProcedureDTO> documentationUnitAssociations;
这种方式不仅解决了循环级联问题,还能直接操作中间表的排序字段,更适配复杂关联场景的业务需求。
内容的提问来源于stack exchange,提问作者Ralph von Petersdorff-Campen
相关产品推荐
相关产品推荐

