with vladmihalcea update one many example column additional java sql hibernate annotations many-to-many

java - vladmihalcea - update many to many hibernate



Hibernar muchos a muchos eliminar en cascada (3)

Encontré el mapeo correcto (y lo probé con JUnit con un caso extenso) en un escenario similar. No creo que vaya a publicar el código de prueba porque llevaría mucho tiempo adaptarse a este ejemplo. De todos modos la clave es:

  • No use el atributo mappedBy para las anotaciones, use columnas de unión
  • Listar los posibles CascadeTypes excluyendo REMOVE

En el ejemplo de 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<>();

Pruebas extensas de JUnit verificaron que:

  • Puedo agregar cursos a los estudiantes y viceversa sin problemas.
  • Si elimino un curso de un estudiante, el curso no se elimina
  • Viceversa
  • Si elimino un estudiante, todos los cursos se separan pero aún se mantienen (a otros estudiantes) en la base de datos
  • Viceversa

Tengo 3 tablas en mi base de datos: Students , Courses and Students_Courses

Los estudiantes pueden tener múltiples cursos y los cursos pueden tener múltiples estudiantes. Hay una relación de muchos a muchos entre los Students y los Courses .

Tengo 3 casos para mi proyecto y cursos agregados a mi tabla de Courses .

  • (a) Cuando agrego un usuario, se guarda bien,
  • (b) Cuando agrego cursos para el estudiante, se crean nuevas filas en User_Courses - nuevamente, el comportamiento esperado.
  • (c) Cuando intento eliminar al alumno, está eliminando los registros apropiados en Students and Students_Courses , pero también está eliminando los registros de Courses que no son necesarios. Incluso si no tengo ningún usuario en un curso, quiero que el curso esté allí.

A continuación se muestra mi código para tablas y anotar clases.

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`) )

Este es el código Java generado por 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; } }

¿Cómo puedo lograr lo que he descrito? No pude encontrar ninguna documentación razonable en la web.


Según lo que me ha dicho, no quiere que cascade = CascadeType.ALL en el método getCourseses en Student. Tenga en cuenta que las cascadas de Hibernate no son lo mismo que las cascadas de bases de datos. Incluso si no tiene ninguna cascada, Hibernate eliminará el registro Students_Courses.

La mejor manera de pensar en las cascadas de Hibernate es que si llama a una operación en una entidad y esa operación aparece en la lista en cascada, entonces se llamará a esa operación en todas las entidades secundarias.

Por ejemplo, cuando llama a eliminar en Estudiante, ya que eliminar está en la lista en cascada para Cursos, Hibernate llamará a eliminar en cada una de las entidades del Curso a las que hace referencia ese estudiante. Es por eso que estás viendo la desaparición de los registros del curso.

No se preocupe por las cascadas de bases de datos, Hibernate se encargará de aquellas por su cuenta.


Solo necesita eliminar cascade = CascadeType.ALL en la clase de Estudiante , solo no se requiere ningún cambio en la clase de Cursos

y agregue el siguiente código en cascada = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH} ..

Esto significa que al eliminar el registro de clase de propietario no eliminará un registro de no propietario.

Después de esto, en Eliminar, se eliminará solo de la tabla de alumnos y del curso de alumnos. Los datos de la tabla de curso siguen siendo los mismos.