2014-04-03 85 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?