【问题标题】:Spring Jpa Childs are not deleted on parent entity update in OneToMany Relationship在 OneToMany 关系中的父实体更新时不会删除 Spring Jpa Childs
【发布时间】:2020-11-09 17:41:42
【问题描述】:

我在更新我的实体时遇到问题。如您所见,我有三个实体。

LabelValueEntity 拥有来自 LabelSwitchEntity 类的列表。
LabelSwitchEntity 拥有来自 SwitchCaseEntity 类的列表。

从我的 SQL 语句中可以看出,namelabelValueUUID 字段是唯一的。我的表格中只允许该组合的一行。

当我使用 LabelSwitchEntity 类的新列表更新父实体 (LabelValueEntity) 时,我希望 Hibernate 删除旧列表本身并创建新实体。一个一个地更新孩子有点困难。这就是为什么我想直接删除所有相关的孩子。

当我更新 LabelValueEntity 并为其提供包含具有唯一名称的 LabelSwitchEntitylabelValueUUID(数据库中已经存在的实体)组合的列表时,我得到一个唯一约束违规异常。好吧,这个错误很明显,因为正如我所说,该组合存在于数据库中。我希望 Hibernate 足够聪明,可以在插入子之前删除它。

我做错了什么?

@Entity
@Table(name = "label_value")
class LabelValueEntity(uuid: UUID? = null,
                       ...
                       @OneToMany(
                               mappedBy = "labelValueUUID",
                               cascade = [CascadeType.ALL],
                               fetch = FetchType.EAGER,
                               orphanRemoval = true)
                       @Fetch(FetchMode.SUBSELECT)
                       val labelSwitchEntities: List<LabelSwitchEntity>? = emptyList()
) : BaseEntity(uuid)


@Entity
@Table(name = "label_switch")
class LabelSwitchEntity(uuid: UUID? = null,
                        @Column(name = "label_value_uuid", nullable = false)
                        val labelValueUUID: UUID,
                        @OneToMany(
                                mappedBy = "labelSwitchUUID",
                                cascade = [CascadeType.ALL],
                                fetch = FetchType.EAGER,
                                orphanRemoval = true
                        )
                        val switchCaseEntities: List<SwitchCaseEntity>,
                        @Column
                        val name: String,
                        ...
) : BaseEntity(uuid)

@Entity
@Table(name = "switch_case")
class SwitchCaseEntity(uuid: UUID? = null,
                       ...
                       @Column(name = "label_switch_uuid", nullable = false)
                       val labelSwitchUUID: UUID
) : BaseEntity(uuid)
CREATE TABLE label_switch
(
    uuid UUID NOT NULL PRIMARY KEY,
    label_value_uuid UUID REFERENCES label_value(uuid) ON DELETE CASCADE,
    name CHARACTER VARYING (255) NOT NULL,
    UNIQUE (label_value_uuid, name)
);

CREATE TABLE switch_case
(
    uuid UUID NOT NULL PRIMARY KEY,
    label_switch_uuid UUID NOT NULL REFERENCES label_switch(uuid) ON DELETE CASCADE
);

基础实体

@MappedSuperclass
abstract class BaseEntity(givenId: UUID? = null) : Persistable<UUID> {

    @Id
    @Column(name = "uuid", length = 16, unique = true, nullable = false)
    private val uuid: UUID = givenId ?: UUID.randomUUID()

    @Transient
    private var persisted: Boolean = givenId != null

    override fun getId(): UUID = uuid

    @JsonIgnore
    override fun isNew(): Boolean = !persisted

    override fun hashCode(): Int = uuid.hashCode()

    override fun equals(other: Any?): Boolean {
        return when {
            this === other -> true
            other == null -> false
            other !is BaseEntity -> false
            else -> getId() == other.getId()
        }
    }

    @PostPersist
    @PostLoad
    private fun setPersisted() {
        persisted = true
    }
}

【问题讨论】:

  • this 不是完全相同的问题吗?
  • 为了完整性:你的BaseEntity是什么?
  • @MichaelPiefel 为了完整性,我添加了 BaseEntity

标签: java spring kotlin jpa spring-data-jpa


【解决方案1】:

这里是事务的来源。如果你在一个用@Transactional 注释的方法中处理所有逻辑(无论是来自javax 还是spring),hibernate 将在其范围内进行脏检查,但不会验证。

换句话说——只要有事务,hibernate 就会跟踪对托管实体所做的更改,但它不会验证它——这是数据库的责任。在内存中处理实体时,不会检查实体的一致性。只需确保您将一致的状态刷新到数据库(事务提交阶段的实体应该是一致的)。

其他选项是可能的,当您的数据库允许时,例如在 postgres 中,您可以将约束标记为延迟。当约束被定义为deferred 时,它不会在打开的事务中检查,仅在提交时检查。

【讨论】:

    【解决方案2】:

    经过一番努力,我得出了这个解决方案:

    fun updateLabelValue(updatedLabelValue: LabelValueDTO): LabelValueDTO {
            val updatedEntity = updatedLabelValue.copy(labelSwitches = emptyList())
            labelValueRepository.saveAndFlush(labelValueMapper.map(updatedEntity))
            val labelValueEntity = labelValueMapper.map(updatedLabelValue)
            return labelValueMapper.map(labelValueRepository.save(labelValueEntity))
        }
    

    它很丑,不是我想要的。我首先用一个空数组保存实体,以便 Hibernate 可以删除孤儿,然后我使用更新的 labelValue 和新的孤儿进行插入。

    如果您有建议或更好的解决方案,请将其放在下方,谢谢。

    【讨论】:

    • 一个更好的解决方案是不使用 Hibernate/JPA。 JPA 使映射实体变得轻而易举,但对于外行来说,它的行为通常是不可预测的。像弗拉德这样的专业人士知道每一个细节,但我花了无数时间与同事讨论我们无法理解的事情。
    猜你喜欢
    • 2019-07-09
    • 1970-01-01
    • 2016-04-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多