Почему каскадное сохранение не работает с отношением «многие ко многим» в режиме гибернации?

#java #hibernate #orm #many-to-many #cascade

#java #режим гибернации #orm #многие ко многим #каскад

Вопрос:

Пожалуйста, посмотрите на следующие фрагменты кода из 2 классов (сущностей) Student и Course

 public class Student {
   ...

   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(name = "course_student",
        joinColumns = @JoinColumn(name="student_id"),
        inverseJoinColumns = @JoinColumn(name="course_id")
   )
   private List<Course> courses;
   
   ...
}
    
public class Course {
   ...
   
   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(name = "course_student",
        joinColumns = @JoinColumn(name="course_id"),
        inverseJoinColumns = @JoinColumn(name="student_id")
   )
   private List<Student> students;
   
   ...
}
  

и код драйвера выглядит следующим образом

 try {
    session.beginTransaction();
    Course course = new Course("Ukulele master class");
    Student student1 = new Student("Jishnu","M V","jishnumv@gmail.com");
    Student student2 = new Student("Praveen","M V","praveenmv@gmail.com");
    course.add(student1);
    course.add(student2);
    session.save(course);
    session.getTransaction().commit();
} 
  

Когда я запускаю этот код, я получаю следующее исключение

 Exception in thread "main" java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.jithin.entity.Student
  

Итак, я сомневаюсь. сохранение курса не сохраняет связанных студентов, даже если мы упомянули CascadeType .ВСЕ в классе Course. Итак, почему каскадирование не работает в случае «многие ко многим»?

примечание: Когда я сохранял оба объекта student, используя session.save() , перед сохранением объекта course . Исключений не было.

Комментарии:

1. Что вы подразумеваете под Nb: код работает отлично, когда я также сохранил студентов. ?

2. Я имею в виду, когда я сохранил оба объекта student с помощью session.save() перед сохранением объекта course. Исключений не было.

3. @jithinMV У вас courses внутри Course ?

4. @SternK О, прошу прощения, код был отредактирован неправильно. Теперь он обновлен. Спасибо, что указали на это.

Ответ №1:

  1. Вы должны использовать @JoinTable аннотацию только на стороне владельца @ManyToMany ассоциации.
 @Entity
public class Student {

    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinTable(name = "course_student",
            joinColumns = @JoinColumn(name="student_id"),
            inverseJoinColumns = @JoinColumn(name="course_id"))
    private List<Course> courses;
   
   // ...
}

@Entity
public class Course {

    @ManyToMany(mappedBy = "courses", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private List<Student> students;

   // ...
}
  
  1. Если вы используете двунаправленную ассоциацию, вы должны позаботиться о синхронизации обеих сторон ассоциации. Итак, вы должны добавить следующие методы к своей Course сущности:
 public class Course {

  public void addStudent(Student student) {
     students.add(student);
     student.getCourses().add( this );
  }

  public void removeStudent(Student student) {
     students.remove(student);
     student.getCourses().remove( this );
  }

  // ...
}
  

и тогда вы сможете сделать что-то вроде этого:

 Course course = new Course("Ukulele master class");
Student student1 = new Student("Jishnu","M V","jishnumv@gmail.com");
Student student2 = new Student("Praveen","M V","praveenmv@gmail.com");
course.addStudent(student1);
course.addStudent(student2);
session.save(course);
  
  1. Как упоминалось в документации:

Для @ManyToMany ассоциаций REMOVE переход состояния объекта не имеет смысла каскадировать, поскольку он будет распространяться за пределы таблицы ссылок. Поскольку на другую сторону могут ссылаться другие объекты на родительской стороне, автоматическое удаление может закончиться в ConstraintViolationException .

Вот почему вам следует избегать использования cascade = CascadeType.ALL в @ManyToMany .

Комментарии:

1. Спасибо за объяснение. Я попробовал код, который вы упомянули. Но это только сохранение курса в базе данных, но все же оно не сохраняет связанных студентов курса с помощью каскадирования.

2. Это должно сработать. Вы использовали точно такое же отображение, включая вспомогательные методы, как указано в моем ответе?

3. Да, я это сделал. И я нашел кое-что при просмотре той же проблемы. Когда мы используем session.save(), каскадирование происходит в каскадном типе SAVE_UPDATE, который находится в режиме гибернации. Не в JPA. Итак, либо мы должны добавить CascadeType . ВСЕ и используйте session.save() или вместо этого мы должны использовать session.persist() . В любом случае первый вариант не является хорошей практикой, потому что, как вы сказали, он также будет включать УДАЛЕНИЕ, поэтому мы можем перейти к более позднему варианту, и он также сработал

4. Возможно, это зависит от версии гибернации. Я протестировал это сопоставление с помощью further session.save(course) , и оно работает с Hibernate 5.4.10.Final