簡體   English   中英

JPA:關於OneToMany關系中阻抗不匹配的問題

[英]JPA: question on impedance mismatch in OneToMany relations

我有一個關於JPA-2.0(提供者是Hibernate)關系及其在Java中的相應管理的問題。 假設我有一個部門和一個員工實體:

@Entity
public class Department {
  ...
  @OneToMany(mappedBy = "department")
  private Set<Employee> employees = new HashSet<Employee>();
  ...
}

@Entity
public class Employee {
  ...
  @ManyToOne(targetEntity = Department.class)
  @JoinColumn
  private Department department;
  ...
}

現在我知道我必須自己管理Java關系,如下面的單元測試:

@Transactional
@Test
public void testBoth() {
  Department d = new Department();
  Employee e = new Employee();
  e.setDepartment(d);
  d.getEmployees().add(e);
  em.persist(d);
  em.persist(e);
  assertNotNull(em.find(Employee.class, e.getId()).getDepartment());
  assertNotNull(em.find(Department.class, d.getId()).getEmployees());
}

如果我遺漏了e.setDepartment(d)d.getEmployees().add(e)斷言將失敗。 到現在為止還挺好。 如果我之間提交數據庫事務怎么辦?

@Test
public void testBoth() {
  EntityManager em = emf.createEntityManager();
  em.getTransaction().begin();
  Department d = new Department();
  Employee e = new Employee();
  e.setDepartment(d);
  d.getEmployees().add(e);
  em.persist(d);
  em.persist(e);
  em.getTransaction().commit();
  em.close();
  em = emf.createEntityManager();
  em.getTransaction().begin();
  assertNotNull(em.find(Employee.class, e.getId()).getDepartment());
  assertNotNull(em.find(Department.class, d.getId()).getEmployees());
  em.getTransaction().commit();
  em.close();
}

我還需要管理關系的兩個方面嗎? 不,事實證明,我沒有必要。 有了這個修改

e.setDepartment(d);
//d.getEmployees().add(e);

斷言仍然成功。 但是,如果我只設置另一方:

//e.setDepartment(d);
d.getEmployees().add(e);

斷言失敗了。 為什么? 是因為員工是關系的擁有者嗎? 我可以通過不同的注釋來改變這種行為嗎? 或者它是否始終是“OneToMany”的“一”側確定何時填充數據庫中的外鍵字段?

我不知道你的測試試圖證明什么,但事實是你在處理雙向關聯時必須處理關聯的兩個方面。 不這樣做是不正確的。 期。

更新:雖然axtavt提到的規范參考當然是准確的,但我堅持認為,你必須設置雙向關聯的兩面。 不這樣做是不正確的,並且第一個持久化上下文中的實體之間的關聯被破壞 JPA wiki書就是這樣的:

與所有雙向關系一樣,您的對象模型和應用程序負責維護雙向關系。 JPA中沒有任何魔力,如果您在集合的一側添加或刪除,您還必須在另一側添加或刪除,請參閱對象損壞 從技術上講,如果您只從關系的擁有方添加/刪除數據庫,那么數據庫將會正確更新,但隨后您的對象模型將不同步,這可能會導致問題。

換句話說,在Java中管理雙向關聯的唯一正確安全的方法是設置鏈接的兩側。 這通常使用防御性鏈接管理方法完成,如下所示:

@Entity
public class Department {
    ...
    @OneToMany(mappedBy = "department")
    private Set<Employee> employees = new HashSet<Employee>();
    ...

    public void addToEmployees(Employee employee) {
        this.employees.add(employee);
        employee.setDepartment(this);
    }
}

我再說一遍,不這樣做是不正確的。 您的測試只能起作用,因為您在新的持久化上下文(即非常特殊的情況,而不是一般情況)中訪問數據庫,但代碼會在許多其他情況下中斷。

JPA中的實體關系具有擁有和反向。 數據庫更新由擁有方的狀態決定。 在您的情況下,由於mappedBy屬性, Employee是擁有者。

JPA 2.0規范

2.9實體關系

...

關系可以是雙向的或單向的。 雙向關系具有擁有方和反向(非擁有方)。 單向關系只有一個擁有方。 關系的擁有方確定數據庫中關系的更新,如3.2.4節所述。

以下規則適用於雙向關系:

  • 雙向關系的反面必須通過使用OneToOne,OneToMany或ManyToMany批注的mappedBy元素來引用其擁有方。 mappedBy元素指定作為關系所有者的實體中的屬性或字段。
  • 一對多/多對一雙向關系的許多方面必須是擁有方,因此不能在ManyToOne批注上指定mappedBy元素。
  • 對於一對一的雙向關系,擁有方對應於包含相應外鍵的一側。
  • 對於多對多雙向關系,任何一方都可能是擁有方。

如果你只更新前一個上下文中的擁有方,那么新的持久化上下文中的第二個測試成功的原因是持久性提供程序顯然無法知道在持久化時你也沒有更新反面。 它只關心持久性目的的擁有方。 但是,當您從持久性提供程序獲取持久性對象時,提供程序會在兩端正確設置雙向關聯(簡單地假設它們也是正確持久化的)。 但是,正如這里的許多其他人已經指出的那樣,持久性提供程序不負責完成新創建的雙向關聯,並且應始終在代碼中正確維護雙向關聯。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM