没有级联删除目标的级联删除关系

Posted

技术标签:

【中文标题】没有级联删除目标的级联删除关系【英文标题】:Cascade remove relation without cascade remove target 【发布时间】:2017-11-17 19:28:38 【问题描述】:

我在Student_Teacher-表(无实体)中的学生和老师之间存在多对多关系。

Student:       Teacher(owning-side):   Student_Teacher
1= Tim         50= Mrs. Foo            1=   1   50
2= Ann         51= Mr. Bar             2=   1   51
                                       3=   2   50
                                       4=   2   51

正如您在上面看到的每个学生当前都与每个老师相关

现在我喜欢删除 Ann,我喜欢使用数据库的级联技术从 Student_Teacher-table 中删除条目,但我不喜欢删除其他学生、老师或其他关系。

这就是我在学生实体中所拥有的:

@ManyToMany(mappedBy="students")
public Set<Teacher> getTeachers() 
    return teachers;

这就是我在教师实体中所拥有的:

@ManyToMany
@JoinTable(name="Student_Teacher", joinColumns = 
    @JoinColumn(name="StudentID", referencedColumnName = "TeacherID", nullable = false)
, inverseJoinColumns = 
    @JoinColumn(name="TeacherID", referencedColumnName = "StudentID", nullable = false)
)
public Set<Student> getStudents() 
    return students;

现在我喜欢使用数据库的delete cascade 功能。我重复一遍:数据库的删除级联功能仅针对 Student_Teacher 表!

问题:

org.h2.jdbc.JdbcSQLException: Referentielle Integrität verletzt: "FK_43PMYXR2NU005M2VNEB99VX0X: PUBLIC.Student_Teacher FOREIGN KEY(StudentID) REFERENCES PUBLIC.Student(StudentID) (2)"
Referential integrity constraint violation: "FK_43PMYXR2NU005M2VNEB99VX0X: PUBLIC.Student_Teacher FOREIGN KEY(StudentID) REFERENCES PUBLIC.Student(StudentID) (2)"; SQL statement:
delete from "Student" where name='Ann'
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
    at org.h2.message.DbException.get(DbException.java:179)
    at org.h2.message.DbException.get(DbException.java:155)
    at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:425)

我不能用的是

@ManyToMany(cascade=CascadeType.REMOVE)

因为文档告诉我:

(可选)必须级联到关联目标的操作。

“目标”是老师,所以这个级联会删除老师(我不喜欢删除)。

问题:

如何配置实体以仅使用数据库的级联功能删除 Ann 和关系?

概念证明:

我尝试了另一个功能,我注意到可以像这样自然地配置外键:

@ManyToMany(cascade =  CascadeType.REMOVE )
@JoinTable(name="Student_Teacher", joinColumns = 
    @JoinColumn(name="StudentID", referencedColumnName = "TeacherID", nullable = false, foreignKey=@ForeignKey(foreignKeyDefinition="FOREIGN KEY (StudentID) REFERENCES Student ON DELETE NO ACTION"))
, inverseJoinColumns = 
    @JoinColumn(name="TeacherID", referencedColumnName = "StudentID", nullable = false, foreignKey=@ForeignKey(foreignKeyDefinition="FOREIGN KEY (TeacherID) REFERENCES Teacher ON DELETE NO ACTION"))
)
public Set<Student> getStudents() 
    return students;

问题是:这工作正常,但要触发删除Student_Teacher 中的条目,我必须在两侧指定@ManyToMany(cascade = CascadeType.REMOVE )。 Hibernate 不解析foreignKeyDefinition,只看到CascadeType.REMOVE 并将目标实体(和引用的学生Tim)从缓存中删除,但它们仍在数据库中!!!所以我必须在掉线后立即清除休眠会话,以重新读取教师 Mrs. Foo 和 Mr. Bar 以及学生 Tim 的存在。

【问题讨论】:

在学生和老师之间保留多对多关系表的意义何在,就像您说的,“每个学生都与每个老师都有关系”? @Redisson_RuiGu 每个学生都与每个老师相关是当前的情况。安实际上已经毕业了,安依赖的内心担忧不清楚(当然,有人应该问过她,但不是我,嘿,安是我的想象……也许她的生活方式,与巴先生或 Foo 夫人的期望不同 - 谁是也是我的一个想象)。好吧,我需要删除 Ann,因为她不再是大学的学生了。但是,如果 Ann 退学,Tim、Foo 夫人和 Bar 先生都不会被免职。 【参考方案1】:

现在我喜欢使用数据库的删除级联功能。一世 重复:数据库的删除级联功能针对 仅限 Student_Teacher 表!

只需在数据库模式级别定义级联删除,数据库就会自动完成。但是,如果关联的拥有方在同一个持久化上下文实例中加载/操作,那么持久化上下文显然会处于不一致的状态,从而导致管理拥有方时出现问题,因为 Hibernate 不知道后面做了什么它的背面。如果启用二级缓存,事情会变得更加复杂。

所以你可以这样做,注意不要在同一个会话中加载Teachers,但我不推荐这样做,我写这篇文章只是为了回答这部分问题。

如何配置实体以仅删除 Ann 和关系 使用数据库的级联功能?

在 JPA/Hibernate 级别上没有这样的配置。映射中的大多数 DDL 声明仅用于自动生成模式,在涉及实体实例生命周期和关联管理时会被忽略。

我不能使用的是

@ManyToMany(cascade=CascadeType.REMOVE)

实体生命周期操作的级联和关联管理是两个完全相互独立的不同概念。在这里你考虑了前者,而你需要后者。

您面临的问题是,当Teacher 是拥有方时,您想打破与Student(标有mappedBy 的反面)的关联。您可以通过从与其关联的所有教师中删除该学生来做到这一点,但这可能会导致加载大量数据(所有关联的教师及其所有学生)。这就是为什么为关联表引入一个单独的实体可能是一个很好的折衷方案,正如 @Mark 已经建议的那样,正如我在之前的一些 answers 中关于类似主题以及其他一些潜在改进的建议一样。

【讨论】:

请阅读问题中的 PoC。 PoC 表明我必须刷新完整的缓存,以保持其他级联关系同步到 2nd lvl 缓存,不仅是老师,还有所有学生。 这就是为什么我会放弃数据库级别的级联并使用替代方案。【参考方案2】:

您可以为关系创建一个新实体TeacherStudent,然后安全地使用 CascadeType.REMOVE:

@Entity
public class Student 
    @OneToMany(mappedBy="student",cascade=CascadeType.REMOVE)
    public Set<TeacherStudent> teacherStudents;


@Entity
public class Teacher 
    @OneToMany(mappedBy="teacher",cascade=CascadeType.REMOVE)
    public Set<TeacherStudent> teacherStudents;


@Entity
public class TeacherStudent 
    @ManyToOne
    public Teacher teacher;

    @ManyToOne
    public Student student;

您必须处理 TeacherStudent 的复合外键。你可以看看https://***.com/a/29116687/3670143。

关于ON DELETE CASCADE 的另一个相关主题是JPA + Hibernate: How to define a constraint having ON DELETE CASCADE

【讨论】:

【参考方案3】:

正如您在上面看到的每个学生都与每个老师相关

关键是在“每个 A 都与每个 B 相关”的情况下,则不需要多对多表来保持这种关系。因为在这种情况下,逻辑上 A 和 B 是相互独立的。添加/删除/修改 A 对 B 没有影响,反之亦然。这种行为正是您所追求的,因为您希望级联操作在关系表处停止:

仅删除针对 Student_Teacher 表的级联功能!

关系表仅在“每个 A 都与 B子集相关”的情况下才有用。

所以解决您的问题实际上是一个相当的问题:删除Student_Teacher 表。

【讨论】:

理所当然地,Tim 可以从 Mr. Foo 教,但不能从 Mr. Bar。 那么我会改变原来的描述,因为第一个断言并不总是正确的。 正确。第一个断言并不总是正确的。这就是我试图用“当前”这个词来指出的,我没能说清楚。【参考方案4】:

由于我们遇到了类似的问题,但最终以另一种方式解决了,这是我们的解决方案:

我们换了

@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) 

在我们与

的关系中
@ManyToMany(fetch = FetchType.LAZY, cascade = 
            CascadeType.DETACH,
            CascadeType.MERGE,
            CascadeType.REFRESH,
            CascadeType.PERSIST
    )

它成功地删除了关联而不删除链接实体。

【讨论】:

以上是关于没有级联删除目标的级联删除关系的主要内容,如果未能解决你的问题,请参考以下文章

关于mysql的级联删除(之前好多人咨询过我)

具有多对多关系的级联删除[重复]

休眠 - 多对多关系中的级联删除

删除性能的级联:删除一行其 1-Many 行的最快方法是啥?

sql 级联删除问题

15.翻译系列:EF 6中的级联删除EF 6 Code-First 系列