I have a question regarding circular relationships in JPA, and here in particular with Eclipselink JPA implementation. Sorry if the question is a bit long, but I try to be as precise as possible.
Let's take the simple example of Department and Employee where a Department has a one-to-many "employees" relationship (and hence the reverse many-to-one "department" relationship from Employee to Department). Now let's add a one-to-one relationship "manager" from Department towards Employee (one of the Employees of the Department is the manager of that same Department). That introduces a circular relationship between the two entities and both tables will have a foreign key referencing the other table.
I would like to be able to do all the inserts without getting a Foreign key constraint violation. So, my idea was to first insert all employees (without setting the department relationship), then insert the Department (with its manager being set), and eventually update all the employees to set their Department.
I know that I could use flush()
to force the order of insert execution but I was told that it should be avoided and hence wondering if there is a way to tell JPA/Eclipselink that Department should be inserted first, then Employee.
In Eclipselink, I did try to add Employee as a constraint dependency of the classdescriptor of the Department class but it still gives error randomly.
Here is a code example illustrating this (the issue occurs randomly):
Department class:
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;
}
}
Employee class:
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:
<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>