JPA / Hibernate: нарушение ограничения удаления обратных OneToOne - PullRequest
0 голосов
/ 08 мая 2018

Проблема:

У меня есть отношение @ 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).
...