Cascade remove relation without cascade remove target

时间:2017-06-15 10:31:03

标签: java hibernate many-to-many reverse-engineering

I have a ManyToMany-relation between student and teacher in a Student_Teacher-table (Entityless).

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

As you see above every Student is currently related to every Teacher.

Now I like to remove Ann and I like to use the database's cascading techique to remove entries from the Student_Teacher-table but I do neither like to remove other Students, nor Teacher, nor other relationship.

This is what I have in the Student-Entity:

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

This is what I have in the Teacher-Entity:

@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;
}

Now I like to use the database's delete cascade functionality. I repeat: The database's delete cascade functionality targeting the Student_Teacher-table only!

The problem:

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)

What i can not use is the

@ManyToMany(cascade={CascadeType.REMOVE})

Because of the documetation tells me:

(Optional) The operations that must be cascaded to the target of the association.

The "target" is the Teacher, so this cascade would remove the Teacher (what I do not like to remove).

Question:

How to configure the entitys to remove Ann and the relation only using the database's cascade functionality?

Proof of Concept:

I tried another feature, I have noticed the possibility to configure the foreign-key nativly like this:

@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;
}

The problem is: This works fine but to trigger the removal of the entries in Student_Teacher I have to specify @ManyToMany(cascade = { CascadeType.REMOVE }) on both sides. Hibernate do not parse the foreignKeyDefinition and only see the CascadeType.REMOVE and drops the target-entitys (and the referenced Student Tim) out of the cache, but they are still in the database!!! So I have to clear the hibernate-session immendentelly after drop to re-read the existence of the Teachers Mrs. Foo and Mr. Bar and the Student Tim.

4 个答案:

答案 0 :(得分:5)

  

现在我喜欢使用数据库的删除级联功能。一世   重复:数据库删除级联功能以此为目标   仅限Student_Teacher-table!

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

所以你可以这样做,并注意不要在同一个会话中加载Teacher,但我不推荐这个,我只是作为对这部分问题的回答。< / p>

  

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

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

  

我不能使用的是

     

@ManyToMany(cascade={CascadeType.REMOVE})

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

您遇到的问题是,当Student是拥有方时,您想要从mappedBy(标有Teacher的反面)中断关联。您可以通过从与其相关联的所有教师中删除学生来完成此操作,但这可能会导致加载大量数据(所有相关教师与所有学生一起)。这就是为什么为关联表引入一个单独的实体可能是一个很好的折衷方案,正如@Mark已经提出的那样,正如我在之前的一些answers中提到的类似主题和其他一些主题一样潜在的改进。

答案 1 :(得分:4)

您可以为关系创建新实体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://stackoverflow.com/a/29116687/3670143

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

答案 2 :(得分:0)

  

如上所述每位学生都与每位教师相关

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

  

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

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

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

答案 3 :(得分:0)

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

我们替换了

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

与我们的关系

@ManyToMany(fetch = FetchType.LAZY, cascade = {
            CascadeType.DETACH,
            CascadeType.MERGE,
            CascadeType.REFRESH,
            CascadeType.PERSIST
    })

,它成功删除了关联,而没有删除链接的实体。