web-dev-qa-db-fra.com

Hibernate plusieurs à plusieurs supprimer en cascade

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.

  • (a) Quand j'ajoute un utilisateur, il est bien sauvegardé, 
  • (b) Lorsque j'ajoute des cours à l'élève, cela crée de nouvelles lignes dans User_Courses - encore une fois, le comportement attendu.
  • (c) Lorsque j'essaie de supprimer l'élève, il supprime les enregistrements appropriés dans 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.

20
Sushant Gupta

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:

  • Ne pas utiliser l'attribut mappedBy pour les annotations, utilisez les colonnes de jointure
  • Énumérer la CascadeTypes 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:

  • Je peux ajouter des cours aux étudiants et vice versa sans faille
  • Si je retire un cours d'un étudiant, le cours n'est pas supprimé
  • Vice versa
  • Si je supprime un étudiant, tous les cours sont détachés mais ils sont toujours conservés (aux autres étudiants) dans la base de données
  • Vice versa
18

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.

16
Pace

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.

0
Adit choudhary