1

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>
Guillaume Polet
  • 47,259
  • 4
  • 83
  • 117
  • do you really need the oneToOne to be bidirectionnal ? – Gab Apr 03 '14 at 21:59
  • @Gab, probably not, but I don't think it changes anything if I remove it from the Employee entity. By defining it there, we only enhance the metamodel of JPA but since it does not enforce a foreign key constraint, Eclipselink won't take it into account upon insert order computation (as far as I understand EL). – Guillaume Polet Apr 03 '14 at 22:02
  • Just use `CascadeType.PERSIST` on the relationships and you'll be ok. – Svetlin Zarev Apr 04 '14 at 09:08
  • Why were you told not to use flush? If you want department inserted first, the only way to guarantee the insert order is to persist department then call flush then fix references and persist the Employee. When EclipseLink determines there is a circular reference, it will perform shallow inserts and update the FKs in a separate statement after the inserts, so another solution on most databases is to delay constraint checking until the end of the transaction. This way JPA/EclipseLink can insert in what ever order and constraints only get checked at the end when everything is set. – Chris Apr 04 '14 at 12:57
  • @SvetlinZarev Using `CascadeType.PERSIST` will not solve the issue. The use of cascade is a simple shortcut to perform an operation on a graph of objects instead of a doing it once for each object of the graph. You still won't know the order of insertion. – Guillaume Polet Apr 04 '14 at 21:20
  • @Chris not all DB accept deferred constraints checking, so I would have preferred not relying on that. For your other question, I think I was told not to use flush() because it's more a performance/implmentation detail rather than something I should care about. In a "DB way of thinking" it is only logical to think about it. In an object-Java world, you should not care about that. – Guillaume Polet Apr 04 '14 at 21:24

2 Answers2

1

IMHO with this model you don't really have the choice.

  • Insert department (without manager)
  • insert employee (with departments)
  • flush
  • update department manager.

Deleting will probably be a mess too

Otherwise you could create an association table between department and employee to hold an isManager attribute.

Or put this last in employee table (not very normalized but well...)

From a general point of view it seems that circular reference are not advised in a relational model : In SQL, is it OK for two tables to refer to each other?

Community
  • 1
  • 1
Gab
  • 7,869
  • 4
  • 37
  • 68
-1

I think that if you configure the departament column in Employee to allow null and set cascades correctly it can solve the problem. And please, do not use flush

Plínio Pantaleão
  • 1,229
  • 8
  • 13
  • By default, a ManyToOne is nullable/optional. And I don't see how setting the cascade will help. As far as I understand cascade, it is simply a shorcut avoiding the need to call several time the same method for all objects of a given graph of objects. – Guillaume Polet Apr 03 '14 at 22:01
  • sometimes you don't have the choice, and imho here it's the case – Gab Apr 03 '14 at 22:01