8

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.

Grim
  • 1,938
  • 10
  • 56
  • 123
  • What's the point of keeping a ManyToMany-relation table between student and teacher when, like you said, "every Student is related to every Teacher"? – Redisson_RuiGu Jul 05 '17 at 22:56
  • @Redisson_RuiGu Every Student is related to every Teacher is the current situation. Ann actually exmatriculate, the inner concern of Ann rely unclear (shure, someone shall have asked her, but not me, hey Ann is an imagination of mine and ... maybe her lifestyle, differs from expectantions MrBar or Mrs Foo has - who are an imagination of mine too). Well, I need to remove Ann because she is not student on the University anymore. But neither Tim nor Mrs Foo nor Mr Bar shall not be removed if Ann exmatriculate. – Grim Jul 06 '17 at 08:10

4 Answers4

5

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!

Simply define the cascade deletion on the database schema level, and the database would do it automatically. However, if the owning side of the association is loaded/manipulated in the same persistence context instance, then the persistence context will obviously be in an inconsistent state resulting in issues when managing the owning side, as Hibernate can't know what is done behind its back. Things get even more complicated if second-level caching is enabled.

So you can do it and take care not to load Teachers in the same session, but I don't recommend this and I write this only as an answer to this part of the question.

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

There is no such configuration on JPA/Hibernate level. Most DDL declarations in mappings are used only for automatic schema generation, and are ignored when it comes to entity instances lifecycle and association management.

What i can not use is the

@ManyToMany(cascade={CascadeType.REMOVE})

Cascading of entity lifecycle operations and association management are two different notions that are completely independent of each other. Here you considered the former while you need the latter.

The problem you're facing is that you want to break the association from the Student (inverse side marked with mappedBy) when the Teacher is the owning side. You can do it by removing the student from all teachers to which it is associated, but that could lead to loading lots of data (all associated teachers with all their students). That's why introducing a separate entity for the association table could be a good compromise, as already suggested by @Mark, and as I suggested as well in some of my previous answers on similar topics together with some other potential improvements.

Dragan Bozanovic
  • 23,102
  • 5
  • 43
  • 110
  • Please read the PoC in the question. The PoC works out that I must flush the complete cache in order to keep other cascade-relation sync to the 2nd lvl cache, not only the teacher, also all students too. – Grim Jul 03 '17 at 18:07
  • 3
    That's why I would give up cascading on database level and use alternatives. – Dragan Bozanovic Jul 03 '17 at 19:12
4

You may create a new entity TeacherStudent for the relationship, and then use CascadeType.REMOVE safely:

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

You'll have to take care of the composite foreign key for TeacherStudent. You may take a look at https://stackoverflow.com/a/29116687/3670143 for that.

Another relevant thread about ON DELETE CASCADE is JPA + Hibernate: How to define a constraint having ON DELETE CASCADE

Renan
  • 1,705
  • 2
  • 15
  • 32
0

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

The point is when in a situation where "every A is related to every B", then there is no need to have a many to many table to keep such relationship. Since logically A and B is independent to each other in this situation. Adding/Deleting/Modifying A makes no effect on B and vice versa. This behaviour is exactly what you are after, because you want the cascading operations stop at the relation table:

delete cascade functionality targeting the Student_Teacher-table only!

Relationship table is only useful when in situation where "every A is related to a subset of B".

So to solve your problem is actually a fairly one: Drop the Student_Teacher table.

Redisson_RuiGu
  • 1,012
  • 10
  • 13
  • As a matter of course, Tim can be teached from Mrs. Foo but not Mr. Bar. – Grim Jul 06 '17 at 11:40
  • Then I would change the original description, since the first assertion is not always true. – Redisson_RuiGu Jul 06 '17 at 11:44
  • Correct. The first asseration is not always true. Thats what I tried to point-out using the term "currently", well I failed in make this clear. – Grim Apr 20 '20 at 09:43
0

As we had a similar problem but finally solved it another way, here is our solution :

We replaced

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

in our relationship with

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

and it successfully removed the association without removing linked entity.

Anarkopsykotik
  • 497
  • 5
  • 17