Spring Boot升级Hibernate至6.6.5.Final后删除操作触发TransientObjectException
Hibernate 6.6.5.Final升级后deleteById抛出TransientObjectException的解决方案
问题场景
在Spring Boot Starter Parent 3.4.2环境下,将Hibernate从6.5.3.Final升级至6.6.5.Final后,执行basketRepository.deleteById(id)时抛出以下异常:
org.hibernate.TransientObjectException: persistent instance references an unsaved transient instance of 'entity.Basket' (save the transient instance before flushing) at org.hibernate.engine.spi.CascadingActions$9.cascade(CascadingActions.java:382) at org.hibernate.engine.spi.CascadingActions$9.cascade(CascadingActions.java:372) at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:570) at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:492) at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:253) at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:192) at org.hibernate.event.internal.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:169)
涉及的实体关联结构如下:
@Entity data class Basket ( @OneToOne var user: User? = null, @ManyToOne var goods: Goods? = null, // 其他字段 )
@Entity data class User ( @OneToOne(mappedBy = "user", cascade = [CascadeType.REMOVE]) var basket: Basket? = null, // 其他字段 )
@Entity data class Goods ( @OneToMany(mappedBy = "goods", cascade = [CascadeType.ALL]) var baskets: MutableList<Basket> = mutableListOf(), // 其他字段 )
注:原Goods实体中mappedBy = "Goods"存在大小写错误,这是引发异常的潜在核心原因之一。
核心原因
Hibernate 6.6.x版本对双向关联的一致性校验更为严格:
- 实体映射的大小写不匹配会导致Hibernate无法正确识别关联关系,进而将已持久化的Basket实例误判为transient;
- 删除Basket时未同步User和Goods中的关联引用,触发Hibernate在flush阶段的关联校验失败。
可行解决方案
1. 修正实体映射错误
确保@OneToMany的mappedBy属性值与关联实体中的字段名完全一致(大小写敏感):
将Goods实体中的mappedBy = "Goods"改为mappedBy = "goods",同时建议将集合字段名从basket改为baskets(符合语义),避免混淆。
2. 手动同步双向关联后再删除
在删除Basket前,先同步所有关联实体的引用,确保内存中关联关系一致:
@Transactional fun deleteBasket(id: Long) { val basket = basketRepository.findById(id).orElseThrow() // 同步User关联 basket.user?.let { user -> user.basket = null userRepository.save(user) } // 同步Goods关联 basket.goods?.let { goods -> goods.baskets.remove(basket) goodsRepository.save(goods) } // 执行删除 basketRepository.delete(basket) }
3. 调整级联策略并添加解除关联逻辑
在Basket的关联字段上添加CascadeType.DETACH,同时在删除前主动解除关联:
@Entity data class Basket ( @OneToOne(cascade = [CascadeType.DETACH]) var user: User? = null, @ManyToOne(cascade = [CascadeType.DETACH]) var goods: Goods? = null, // 其他字段 )
删除方法中先解除关联再执行删除:
@Transactional fun deleteBasketById(id: Long) { val basket = basketRepository.findById(id).orElseThrow() basket.user = null basket.goods = null basketRepository.save(basket) basketRepository.deleteById(id) }
4. 数据库层面级联删除(推荐)
使用Hibernate的@OnDelete注解让数据库处理级联删除,规避内存中的关联校验问题:
@Entity data class User ( @OneToOne(mappedBy = "user", cascade = [CascadeType.REMOVE]) @OnDelete(action = OnDeleteAction.CASCADE) var basket: Basket? = null, // 其他字段 )
@Entity data class Goods ( @OneToMany(mappedBy = "goods", cascade = [CascadeType.ALL]) @OnDelete(action = OnDeleteAction.CASCADE) var baskets: MutableList<Basket> = mutableListOf(), // 其他字段 )
此方式需要确保数据库支持级联删除(如MySQL、PostgreSQL等均支持),删除时无需手动同步关联,由数据库直接处理。
内容的提问来源于stack exchange,提问作者CodeSun
相关产品推荐
相关产品推荐

