J'ai 3 tables dans ma base de données: Students
, Courses
et Students_Courses
Les étudiants peuvent avoir plusieurs cours et les cours peuvent avoir plusieurs étudiants. Il existe une relation plusieurs à plusieurs entre Students
et Courses
.
J'ai 3 cas pour mon projet et des cours ajoutés à ma table Courses
.
User_Courses
- encore une fois, le comportement attendu.Students
et Students_Courses
, mais il supprime également les enregistrements Courses
qui ne sont pas obligatoires. Même si je n'ai aucun utilisateur dans un cours, je veux que le cours y soit. Voici mon code pour les tables et annoter les classes.
CREATE TABLE `Students` (
`StudentID` INT(11) NOT NULL AUTO_INCREMENT,
`StudentName` VARCHAR(50) NOT NULL
PRIMARY KEY (`StudentID`)
)
CREATE TABLE `Courses` (
`CourseID` INT(11) NOT NULL AUTO_INCREMENT,
`CourseName` VARCHAR(50) NOT NULL
PRIMARY KEY (`CourseID`)
)
CREATE TABLE `Student_Courses` (
`StudentId` INT(10) NOT NULL DEFAULT '0',
`CourseID` INT(10) NOT NULL DEFAULT '0',
PRIMARY KEY (`StudentId`, `CourseID`),
INDEX `FK__courses` (`CourseID`),
INDEX `StudentId` (`StudentId`),
CONSTRAINT `FK__courses` FOREIGN KEY (`CourseID`) REFERENCES `courses` (`CourseID`) ON DELETE NO ACTION,
CONSTRAINT `FK_students` FOREIGN KEY (`StudentId`) REFERENCES `students` (`StudentId`)
)
Voici le code Java généré par Hibernate:
@Entity
@Table(name = "Students")
public class Students implements Java.io.Serializable {
private Integer StudentID;
private String Students;
private Set<Courses> Courseses = new HashSet<Courses>(0);
public Students() {
}
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "StudentID", unique = true, nullable = false)
public Integer getStudentID() {
return this.StudentID;
}
public void setStudentID(Integer StudentID) {
this.StudentID = StudentID;
}
@Column(name = "Students", nullable = false, length = 50)
public String getCampaign() {
return this.Students;
}
public void setCampaign(String Students) {
this.Students = Students;
}
@ManyToMany(cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
@JoinTable(name = "Student_Courses", joinColumns = {
@JoinColumn(name = "StudentId", nullable = false, updatable = false)}, inverseJoinColumns = {
@JoinColumn(name = "CourseID", nullable = false, updatable = false)})
public Set<Courses> getCourseses() {
return this.Courseses;
}
public void setCourseses(Set<Courses> Courseses) {
this.Courseses = Courseses;
}
}
@Entity
@Table(name = "Courses")
public class Courses implements Java.io.Serializable {
private Integer CourseID;
private String CourseName;
private Set<Students> Studentses = new HashSet<Students>(0);
public Courses() {
}
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "CourseID", unique = true, nullable = false)
public Integer getCourseID() {
return this.CourseID;
}
public void setCourseID(Integer CourseID) {
this.CourseID = CourseID;
}
@Column(name = "CourseName", nullable = false, length = 100)
public String getCourseName() {
return this.CourseName;
}
public void setCourseName(String CourseName) {
this.CourseName = CourseName;
}
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "Courseses")
public Set<Students> getStudentses() {
return this.Studentses;
}
public void setStudentses(Set<Students> Studentses) {
this.Studentses = Studentses;
}
}
Comment puis-je réaliser ce que j'ai décrit? Je n'ai trouvé aucune documentation raisonnable sur le Web.
J'ai trouvé le bon mappage (et l'ai testé avec JUnit avec un cas étendu) dans un scénario similaire. Je ne pense pas que je publierai du code de test car il faudrait beaucoup de temps pour s'adapter à cet exemple. Quoi qu'il en soit, la clé est de:
mappedBy
pour les annotations, utilisez les colonnes de jointureCascadeTypes
possible en excluant REMOVE
Dans l'exemple de l'OP
@ManyToMany(fetch = FetchType.LAZY,
cascade =
{
CascadeType.DETACH,
CascadeType.MERGE,
CascadeType.REFRESH,
CascadeType.PERSIST
},
targetEntity = Course.class)
@JoinTable(name = "XTB_STUDENTS_COURSES",
inverseJoinColumns = @JoinColumn(name = "COURSE_ID",
nullable = false,
updatable = false),
joinColumns = @JoinColumn(name = "STUDENT_ID",
nullable = false,
updatable = false),
foreignKey = @ForeignKey(ConstraintMode.CONSTRAINT),
inverseForeignKey = @ForeignKey(ConstraintMode.CONSTRAINT))
private final Set<Course> courses = new HashSet<>();
@ManyToMany(fetch = FetchType.LAZY,
cascade =
{
CascadeType.DETACH,
CascadeType.MERGE,
CascadeType.REFRESH,
CascadeType.PERSIST
},
targetEntity = Student.class)
@JoinTable(name = "XTB_STUDENTS_COURSES",
joinColumns = @JoinColumn(name = "COURSE_ID",
nullable = false,
updatable = false),
inverseJoinColumns = @JoinColumn(name = "STUDENT_ID",
nullable = false,
updatable = false),
foreignKey = @ForeignKey(ConstraintMode.CONSTRAINT),
inverseForeignKey = @ForeignKey(ConstraintMode.CONSTRAINT))
private final Set<Student> students = new HashSet<>();
Des tests approfondis de JUnit ont vérifié que:
D'après ce que vous m'avez dit, vous ne voulez pas de cascade = CascadeType.ALL sur la méthode getCourseses dans Student. N'oubliez pas que les cascades Hibernate ne sont pas identiques aux cascades de bases de données. Même en l'absence de cascades, Hibernate supprimera l'enregistrement Students_Courses.
La meilleure façon de penser aux cascades Hibernate est que si vous appelez une opération sur une entité et que cette opération est répertoriée dans la liste en cascade, cette opération sera appelée sur toutes les entités enfants.
Par exemple, lorsque vous appelez delete sur Student, puisque delete figure dans la liste en cascade des cours, Hibernate appelle delete sur chacune des entités de cours référencées par cet étudiant. C'est pourquoi vous voyez les enregistrements du cours disparaître.
Ne vous inquiétez pas des cascades de bases de données, Hibernate s'en occupera tout seul.
Il vous suffit de supprimer cascade = CascadeType.ALL dans la classe Student uniquement. Aucune modification n'est requise dans la classe Courses.
et ajoutez le code ci-dessous cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH} ..
Cela signifie que lors de la suppression d'un enregistrement de classe de propriétaire, il ne supprimera pas un enregistrement de non-propriétaire.
Après cela, lorsqu’il est supprimé, il ne supprime que de la table des étudiants et les données de la table de cours de l’étudiant ..__ restent inchangées.