У меня есть вопрос относительно круговых отношений в JPA, и здесь, в частности, с реализацией Eclipselink JPA. Извините, если вопрос немного длинный, но я стараюсь быть максимально точным.
Возьмем простой пример Департамента и Сотрудника, в котором у Департамента есть взаимоотношения "один-ко-многим" с сотрудниками (и, следовательно, обратное отношение "отдел" от Работника к Департаменту). Теперь добавьте индивидуальный "менеджер" от Департамента к Сотруднику (один из сотрудников Департамента является менеджером того же Департамента). Это вводит круговую взаимосвязь между двумя объектами, и обе таблицы будут иметь внешний ключ, ссылающийся на другую таблицу.
Я хотел бы иметь возможность делать все вставки без нарушения ограничения внешнего ключа. Итак, моя идея состояла в том, чтобы сначала вставить всех сотрудников (без установления отношений с отделом), затем вставить Департамент (с его менеджером, который будет установлен), и в конечном итоге обновить всех сотрудников, чтобы установить их Департамент.
Я знаю, что я мог бы использовать flush()
чтобы заставить порядок выполнения вставки, но мне сказали, что его следует избегать, и поэтому задается вопросом, есть ли способ сообщить JPA/Eclipselink, что Департамент должен быть вставлен первым, а затем Employee.
В Eclipselink я попытался добавить Employee в зависимость от ограничения класса classdescriptor класса Department, но он по-прежнему дает ошибку случайным образом.
Вот пример кода, иллюстрирующий это (проблема возникает случайным образом):
Класс отдела:
package my.jpa.test;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Persistence;
/**
* Entity implementation class for Entity: Department
*
*/
@Entity
public class Department implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@OneToMany(fetch = FetchType.EAGER)
private List<Employee> employees;
@OneToOne
@JoinColumn(name = "manager", nullable = false)
private Employee manager;
private static final long serialVersionUID = 1L;
public Department() {
super();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test-jpa");
EntityManager em = emf.createEntityManager();
Department d = new Department();
Employee manager = new Employee();
manager.setLastName("Doe");
d.setManager(manager);
Employee e1 = new Employee();
e1.setLastName("Doe");
Employee e2 = new Employee();
e2.setLastName("Smith");
em.getTransaction().begin();
em.persist(d);
manager.setDepartment(d);
e1.setDepartment(d);
e2.setDepartment(d);
em.persist(e1);
em.persist(e2);
em.persist(manager);
em.persist(d);
manager.setDepartment(d);
e1.setDepartment(d);
e2.setDepartment(d);
em.merge(manager);
em.merge(e1);
em.merge(e2);
em.getTransaction().commit();
em.clear();
Department fetchedDepartment = em.find(Department.class, d.getId());
System.err.println(fetchedDepartment.getManager().getLastName());
System.err.println(new ArrayList<Employee>(fetchedDepartment.getEmployees()));
}
public Employee getManager() {
return manager;
}
public void setManager(Employee manager) {
this.manager = manager;
}
}
Класс сотрудника:
package my.jpa.test;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
/**
* Entity implementation class for Entity: Employee
*
*/
@Entity
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String lastName;
@ManyToOne
private Department department;
@OneToOne(mappedBy = "manager")
private Department managedDepartment;
public Employee() {
super();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public Department getManagedDepartment() {
return managedDepartment;
}
public void setManagedDepartment(Department managedDepartment) {
this.managedDepartment = managedDepartment;
}
@Override
public String toString() {
return "Employee " + getLastName();
}
}
persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="test-jpa">
<class>my.jpa.test.Department</class>
<class>my.jpa.test.Employee</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
<property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE" />
<property name="javax.persistence.jdbc.user" value="sa" />
<property name="javax.persistence.jdbc.password" value="" />
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<property name="eclipselink.ddl-generation" value="drop-and-create-tables" />
<property name="eclipselink.ddl-generation.output-mode" value="database" />
<property name="eclipselink.logging.level" value="FINE"/>
<property name="eclipselink.logging.parameters" value="true"/>
</properties>
</persistence-unit>
</persistence>
Зависимости Maven:
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.172</version>
</dependency>
</dependencies>
ИМХО с этой моделью у вас действительно нет выбора.
Удаление, вероятно, будет беспорядок тоже
В противном случае вы можете создать таблицу ассоциаций между отделом и сотрудником для хранения атрибута isManager.
Или поместите это последнее в таблицу сотрудников (не очень нормализованный, но хорошо...)
С общей точки зрения кажется, что круговая ссылка не рекомендуется в реляционной модели: в SQL, нормально ли для двух таблиц ссылаться друг на друга?
Я думаю, что если вы сконфигурируете столбец отделов в Employee, чтобы разрешить нулевой и правильно установить каскады, он может решить проблему. И, пожалуйста, не используйте флеш