2014-04-03 51 views
1

我有一个关于JPA循环关系的问题,特别是在Eclipselink JPA实现中。对不起,如果问题有点长,但我尽可能精确。JPA/Eclipselink如何处理循环/循环关系

让我们以部门和员工的一个简单示例为例,该部门具有一对多“员工”关系(并且因此是员工与部门之间的多对一“部门”关系)。现在让我们从部门向员工添加一对一的关系“经理”(该部门的一名员工是该部门的经理)。这引入了两个实体之间的循环关系,两个表都将有一个引用另一个表的外键。

我希望能够做到所有插入而不会导致外键约束违例。因此,我的想法是首先插入所有员工(不设置部门关系),然后插入部门(并设置其经理),并最终更新所有员工以设置部门。

我知道我可以使用flush()来强制插入执行的顺序,但我被告知应该避免它,因此想知道是否有办法告诉JPA/Eclipselink首先插入Department,然后Employee。

在Eclipselink中,我确实尝试将Employee添加为Department类的类描述符的约束依赖项,但它仍然会随机给出错误。

这里是示出这样的代码示例(该问题是随机发生的):

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; 
    } 
} 

Employee类:

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> 
+0

你真的需要oneToOne是bidirectionnal? – Gab

+0

@Gab,可能不是,但我不认为它会改变任何东西,如果我从Employee实体中删除它。通过在那里定义它,我们只增强了JPA的元模型,但由于它没有强制执行一个外键约束,Eclipselink在插入顺序计算时不会考虑它(据我了解EL)。 –

+0

只需在关系上使用'CascadeType.PERSIST'即可。 –

回答

-1

我认为,如果你配置雇员departament列允许空,并设置正确的级联它可以解决这个问题。并请不要使用冲洗

+0

默认情况下,ManyToOne可以为空或可选。我不知道如何设置级联会有所帮助。据我了解级联,它只是一个shorcut,避免了对给定的对象图形的所有对象调用几次相同的方法。 –

+0

有时候你没有选择,这里是imho – Gab

1

恕我直言,与这种模式,你真的没有选择。

  • 插入部门(不含经理)
  • 插入员工(含部门)
  • 冲洗
  • 更新部门经理。

删除可能会是一个烂摊子太

否则,你可以创建部门和员工之间的关联表来保存的IsManager的属性。

或者把这最后一张放在员工表中(不是很规范化,但是...)

从一般的角度来看似乎是循环引用未在关系模型建议: In SQL, is it OK for two tables to refer to each other?