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

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版本对双向关联的一致性校验更为严格:

  1. 实体映射的大小写不匹配会导致Hibernate无法正确识别关联关系,进而将已持久化的Basket实例误判为transient;
  2. 删除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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 17:03:16