Проблема:
У меня есть отношение @ OneToOne между родителем и дочерним элементом, отношение отображается дочерним элементом, а для orphanRemoval установлено значение true. У ребенка также есть отношение @ManyToMany с третьим объектом.
При установке дочернего элемента на ноль (в надежде удалить дочерний элемент) Я получаю нарушение ограничения целостности: внешний ключ ...
Тестовая модель:
A Tutor имеет отношение OneToOne с Student , которое имеет отношение ManyToMany с Course entity:
- Отношение OneToOne сопоставлено Student.tutor (столбец внешнего ключа ID_TUTOR в таблице STUDENT)
- каскад = ВСЕ
- orphanRemoval = true
- Отношение ManyToMany хранится в таблице соединений STUDENT_COURSE с внешними ключами для таблиц STUDENT и COURSE.
Я использую Hibernate 5.0.11 (JPA 2.1). Для моего теста я использовал HSQLDB, но проблема также возникает в базе данных Oracle.
Сценарий:
У меня есть наставник, связанный со студентом, содержащий список курсов. При установке для ученика Tutor значения null Hibernate напрямую выдает SQL « delete from Student, где id =? », что приводит к «нарушению ограничения целостности: внешний ключ не действует; таблица FK1XM2HEI9CHMWOQF2WFM104NMG: таблица STUDENT_COURSE»
Эта проблема не возникает, если отношение OneToMany не является "инвертированным" (нет mappedBy и внешнего ключа ID_STUDENT в таблице TUTOR). В этом случае сущность Student корректно удаляется из БД, а также связанных записей в таблице STUDENT_COURSE.
Код:
@Entity
public class Tutor {
private Long id;
private String name;
private Student student;
public Tutor() { super(); }
public Tutor(String name, Student student) {
super();
this.name = name;
setStudent(student);
}
@Id
@GeneratedValue
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@OneToOne(cascade=CascadeType.ALL, orphanRemoval=true, mappedBy="tutor")
public Student getStudent() { return student; }
public void setStudent(Student student) {
this.student = student;
if(student != null) {
student.setTutor(this);
}
}
}
@Entity
public class Student {
private Long id;
private String name;
private Map<String, Course> courses;
private Tutor tutor;
public Student() { super(); }
public Student(String name, Map<String, Course> courses) {
super();
this.name = name;
this.courses = courses;
}
@Id
@GeneratedValue
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@ManyToMany
public Map<String, Course> getCourses() { return courses; }
public void setCourses(Map<String, Course> courses) { this.courses = courses; }
@OneToOne
public Tutor getTutor() { return tutor; }
public void setTutor(Tutor tutor) { this.tutor = tutor; }
}
@Entity
public class Course {
private Long id;
private String name;
public Course() { super(); }
public Course(String name) {
super();
this.name = name;
}
@Id
@GeneratedValue
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
public class CheckManyToManyDeletation {
private static final Logger logger = Logger.getLogger(CheckManyToManyDeletation.class);
private static SessionFactory sessionFactory;
public static void main(String[] args) {
DOMConfigurator.configure("log4j-config.xml");
Class[] mappings = new Class[] {Tutor.class, Student.class, Course.class};
DBUtils.createTables(mappings);
sessionFactory = DBUtils.createSessionFactory(mappings);
try {
long tutorId = initialImport();
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Tutor tutor = session.get(Tutor.class, tutorId);
logger.info("DeletingStudent");
tutor.setStudent(null);
tx.commit();
} finally {
sessionFactory.close();
}
}
private static long initialImport() {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Student student = new Student();
student.setName("student");
Map<String, Course> courses = new HashMap<>();
student.setCourses(courses);
for (int i = 0; i < 5; i++) {
Course course = new Course();
course.setName("course" + i);
session.save(course);
courses.put(course.getName(), course);
}
Tutor tutor = new Tutor("tutor", student);
session.save(tutor);
tx.commit();
session.close();
return tutor.getId();
}
}
Примечания:
На самом деле наше приложение поставляется с огромной моделью данных:
- Сохранение и удаление объектов выполняется каскадом,
- мы инвертируем отношения OneToOne, чтобы дать ребенку тот же идентификатор, что и его родителю (через @MapsId).