JPA:关于OneToMany关系中阻抗不匹配的问题

我有一个关于JPA-2.0(提供者是Hibernate)关系及其在Java中的相应管理的问题。 假设我有一个部门和一个员工实体:

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

现在我知道我必须自己管理Java关系,如下面的unit testing:

 @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(); } 

我还需要管理关系的两个方面吗? 不,事实certificate,我没有必要。 有了这个修改

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

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

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

断言失败了。 为什么? 是因为员工是关系的拥有者吗? 我可以通过不同的注释来改变这种行为吗? 或者它是否始终是“OneToMany”的“一”侧确定何时填充数据库中的外键字段?

JPA中的实体关系具有拥有和反面。 数据库更新由拥有方的状态决定。 在您的情况下,由于mappedBy属性, Employee是拥有者。

从JPA 2.0规范 :

2.9实体关系

关系可以是双向的或单向的。 双向关系具有拥有方和反向(非拥有方)。 单向关系只有一个拥有方。 关系的拥有方确定数据库中关系的更新,如3.2.4节所述。

以下规则适用于双向关系:

  • 双向关系的反面必须通过使用OneToOne,OneToMany或ManyToMany批注的mappedBy元素来引用其拥有方。 mappedBy元素指定作为关系所有者的实体中的属性或字段。
  • 一对多/多对一双向关系的许多方面必须是拥有方,因此不能在ManyToOne批注上指定mappedBy元素。
  • 对于一对一的双向关系,拥有方对应于包含相应外键的一侧。
  • 对于多对多双向关系,任何一方都可能是拥有方。

我不知道你的测试试图certificate什么,但事实是你在处理双向关联时必须处理关联的两个方面。 不这样做是不正确的。 期。

更新:虽然axtavt提到的规范参考当然是准确的,但我坚持认为,你必须设置双向关联的两面。 不这样做是不正确的,并且第一个持久化上下文中的实体之间的关联被破坏 。 JPA wiki书就是这样的:

与所有双向关系一样,对象模型和应用程序负责维护双向关系。 JPA中没有魔法,如果您在集合的一侧添加或删除,您还必须在另一侧添加或删除,请参阅对象损坏 。 从技术上讲,如果您只从关系的拥有方添加/删除数据库,那么数据库将会正确更新,但随后您的对象模型将不同步,这可能会导致问题。

换句话说,在Java中管理双向关联的唯一正确安全的方法是设置链接的两侧。 这通常使用防御性链接管理方法完成,如下所示:

 @Entity public class Department { ... @OneToMany(mappedBy = "department") private Set employees = new HashSet(); ... public void addToEmployees(Employee employee) { this.employees.add(employee); employee.setDepartment(this); } } 

我再说一遍,不这样做是不对的。 您的测试仅起作用,因为您在新的持久化上下文(即非常特殊的情况,而不是一般情况)中访问数据库,但代码在许多其他情况下会中断。

如果您只更新前一个上下文中的拥有方,那么新持久化上下文中的第二个测试成功的原因是持久性提供程序显然无法知道在持久化时您也没有更新反面。 它只关心持久性目的的拥有方。 但是,当您从持久性提供程序获取持久对象时,提供程序会在两侧正确设置双向关联(简单地假设它们也是正确持久化的)。 但是,正如这里的许多其他人已经指出的那样,持久性提供程序不负责完成新创建的双向关联,并且应始终在代码中正确维护双向关联。