Прежде всего, cascade=ALL
делает противоположное тому, что вы хотите. ALL=PERSIST,MERGE,REFRESH,DETACH,REMOVE
, поэтому в основном говорится: «Когда я сохраняю, обновляю или удаляю Student
, делаю то же самое со всеми связанными Courses
», среди прочего.
Для @ManyToMany
ассоциаций PERSIST
, MERGE
, REMOVE
не имеют большого смысла. Возможно, вы захотите оставить REFRESH
и DETACH
, но вам бы посоветовали избавиться от остальных.
Во-вторых, удаление MERGE
имеет побочный эффект получения TransientObjectException
. Это происходит потому, что вы пытаетесь сохранить отдельную сущность, которая ссылается на другие отделенные сущности. Возможно, вы звоните repository.save(student)
, но это только объединяет сущность Student
, и указанные сущности Course
остаются отсоединенными.
Чтобы решить эту проблему, необходимо заменить экземпляры отсоединенного объекта на экземпляры управляемого объекта с теми же идентификаторами:
Set<Courses> managedCourses = new HashSet<>();
for (Course course : stud.getCourses()) {
managedCourses.add(courseRepository.getOne(course.getId()));
}
stud.setCourses(managedCourses);
studentRepository.save(stud); //no TransientObjectException this time!
(обратите внимание на использование getOne()
в цикле; у вас может возникнуть соблазн использовать findAllById()
, думая, что это будет более производительным, но преимущество getOne()
состоит в том, что он не получает связанное состояние объекта из источник данных. getOne()
предоставляется JpaRepository
специально для этого случая использования: для установления связей между сущностями)
Наконец, я вижу, что stud
помечен @RequestBody
, предполагая, что мы здесь в классе Controller
. Чтобы вышеуказанный подход работал, вы хотите обернуть весь метод в транзакцию, используя @Transactional
. Поскольку методы транзакционного контроллера не совсем хорошая практика, я бы предложил выделить тело метода studententry
в отдельный аннотированный компонент @Transactional
, @Service
.