JPA: вопрос о несоответствии импеданса в отношениях OneToMany - PullRequest
4 голосов
/ 02 сентября 2010

У меня есть вопрос об отношениях JPA-2.0 (провайдер - Hibernate) и об их соответствующем управлении в Java. Давайте предположим, что у меня есть отдел и сотрудник:

@Entity
public class Department {
  ...
  @OneToMany(mappedBy = "department")
  private Set<Employee> employees = new HashSet<Employee>();
  ...
}

@Entity
public class Employee {
  ...
  @ManyToOne(targetEntity = Department.class)
  @JoinColumn
  private Department department;
  ...
}

Теперь я знаю, что сам должен управлять отношениями Java, как в следующем модульном тесте:

@Transactional
@Test
public void testBoth() {
  Department d = new Department();
  Employee e = new Employee();
  e.setDepartment(d);
  d.getEmployees().add(e);
  em.persist(d);
  em.persist(e);
  assertNotNull(em.find(Employee.class, e.getId()).getDepartment());
  assertNotNull(em.find(Department.class, d.getId()).getEmployees());
}

Если я пропущу либо e.setDepartment(d), либо d.getEmployees().add(e), утверждения не будут выполнены. Все идет нормально. Что если я зафиксирую транзакцию базы данных между ними?

@Test
public void testBoth() {
  EntityManager em = emf.createEntityManager();
  em.getTransaction().begin();
  Department d = new Department();
  Employee e = new Employee();
  e.setDepartment(d);
  d.getEmployees().add(e);
  em.persist(d);
  em.persist(e);
  em.getTransaction().commit();
  em.close();
  em = emf.createEntityManager();
  em.getTransaction().begin();
  assertNotNull(em.find(Employee.class, e.getId()).getDepartment());
  assertNotNull(em.find(Department.class, d.getId()).getEmployees());
  em.getTransaction().commit();
  em.close();
}

Мне все еще нужно управлять обеими сторонами отношений? Нет, как оказалось, мне не нужно. С этой модификацией

e.setDepartment(d);
//d.getEmployees().add(e);

утверждения все еще успешны. Однако, если я только установлю другую сторону:

//e.setDepartment(d);
d.getEmployees().add(e);

утверждения не верны. Зачем? Это потому, что Сотрудник является владельцем отношения? Могу ли я изменить это поведение, комментируя по-другому? Или это просто всегда сторона «One» в «OneToMany», которая определяет, когда поле внешнего ключа в базе данных заполнено?

Ответы [ 3 ]

7 голосов
/ 02 сентября 2010

Я не знаю, что пытается продемонстрировать ваш тест, но на самом деле вы должны обращаться с обеими сторонами ассоциации при работе с двунаправленными ассоциациями. Не делать это неправильно. Период.

Обновление: Хотя ссылка на спецификацию, упомянутая axtavt, конечно, точна, я настаиваю, вы обязательно должны установить обе стороны двунаправленной ассоциации. Не делать это неправильно, и связь между вашими сущностями в первом контексте постоянства равна нарушена . В JPA wiki book это выглядит так:

Как и во всех двунаправленных отношениях, ваша объектная модель и приложение несут ответственность за поддержание отношений в обоих направлениях. В JPA нет магии, если вы добавляете или удаляете одну сторону коллекции, вы также должны добавлять или удалять другую сторону, см. повреждение объекта . Технически база данных будет обновлена ​​корректно, если вы добавите / удалите ее только со стороны-владельца отношения, но тогда ваша объектная модель будет не синхронизирована, что может вызвать проблемы.

Другими словами, единственный правильный и безопасный способ управления двунаправленной ассоциацией в Java - это установить обе стороны ссылки. Обычно это делается с использованием методов управления защитными ссылками, например:

@Entity
public class Department {
    ...
    @OneToMany(mappedBy = "department")
    private Set<Employee> employees = new HashSet<Employee>();
    ...

    public void addToEmployees(Employee employee) {
        this.employees.add(employee);
        employee.setDepartment(this);
    }
}

Я повторяю, не делать это неправильно. Ваш тест работает только потому, что вы работаете с базой данных в новом контексте персистентности (т. Е. В очень конкретной ситуации, а не в общем), но код может сломаться во многих других ситуациях.

5 голосов
/ 02 сентября 2010

Отношения сущностей в JPA имеют свои и обратные стороны.Обновления базы данных определяются состоянием владеющей стороны.В вашем случае Employee является стороной-владельцем из-за атрибута mappedBy.

Из спецификации JPA 2.0 :

2.9 СущностьОтношения

...

Отношения могут быть двунаправленными или однонаправленными.Двунаправленное отношение имеет как владеющую сторону, так и обратную (не владеющую) сторону.Однонаправленные отношения имеют только свою сторону.Сторона-владелец отношения определяет обновления отношения в базе данных, как описано в разделе 3.2.4.

Следующие правила применяются к двунаправленным отношениям:

  • Обратная сторона двунаправленного отношения должна ссылаться на свою собственную сторону с помощью элемента mappedBy OneToOne, OneToMany или ManyToMany.аннотаций.Элемент mappedBy обозначает свойство или поле в объекте, который является владельцем отношения.
  • Многие стороны двунаправленных отношений один-ко-многим / многие-к-одному должны быть собственниками, поэтому элемент mappedBy нельзя указывать в аннотации ManyToOne.
  • Для двунаправленных отношений «один к одному» сторона-владелец соответствует стороне, которая содержит соответствующий внешний ключ.
  • Для двунаправленных отношений «многие ко многим» любая сторона может быть стороной-владельцем.
2 голосов
/ 03 сентября 2010

Причина, по которой второй тест в новом контексте персистентности завершается успешно, если вы обновляете только сторону-владельца в предыдущем контексте, заключается в том, что поставщик персистентности, очевидно, не может знать, что при сохранении вы также не обновляли обратную сторону.Он заботится только о том, чтобы иметь сторону для постоянства.Однако, когда вы получаете постоянные объекты от поставщика постоянства, поставщик правильно устанавливает двунаправленные ассоциации с обеих сторон (просто предполагается, что они также были сохранены правильно).Однако, как уже указывали многие другие, провайдер настойчивости не несет ответственности за завершение вновь созданных двунаправленных ассоциаций, и вы всегда должны правильно поддерживать двунаправленные ассоциации в своем коде.

...