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
excluyendoREMOVE
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
andStudents_Courses
, pero también está eliminando los registros deCourses
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.