我有一个关于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”的“One”一面确定数据库中的外键字段何时被填充?
答案 0 :(得分:7)
我不知道你的测试试图证明什么,但事实是你必须在处理双向关联时处理关联的两个方面。不这样做是不正确的。期。
更新虽然axtavt提到的规范参考当然是准确的,但我坚持认为,你必须设置双向关联的两面。不这样做是不正确的,并且第一个持久化上下文中的实体之间的关联是已损坏。 JPA wiki book就是这样的:
与所有双向关系一样,对象模型和应用程序负责维护双向关系。 JPA中没有任何魔力,如果您在集合的一侧添加或删除,您还必须在另一侧添加或删除,请参阅object corruption。从技术上讲,如果您只是从关系的拥有方添加/删除数据库,那么数据库将会正确更新,但随后您的对象模型将不同步,这可能会导致问题。
换句话说,在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);
}
}
我再说一遍,不这样做是不正确的。您的测试只能起作用,因为您在新的持久化上下文中(即非常特殊的情况,而不是一般情况下)访问数据库,但在许多其他情况下代码会中断。
答案 1 :(得分:5)
JPA中的实体关系具有拥有和反面。数据库更新由拥有方的状态决定。在您的情况下,由于Employee
属性,mappedBy
是拥有方。
2.9实体关系
...
关系可能是双向的或 单向。双向的 关系既有自己的一面 和反(非拥有)方。一个 单向关系只有 拥有一方。拥有的一面 relationship确定更新 数据库中的关系,如 在章节中描述 3.2.4。
以下规则适用于双向关系:
- 双向的反面 关系必须指其拥有 通过使用的mappedBy元素 OneToOne,OneToMany或ManyToMany 注解。 mappedBy元素 指定中的属性或字段 作为所有者的实体 关系。
- 多方面 一对多/多对一 必须是双向关系 拥有方,因此是mappedBy 元素无法指定 ManyToOne注释。
- 对于 双向一对一 关系,拥有方 对应于包含的一面 相应的外键。
- 对于 多对多双向 任何一方的关系都可能是 拥有一面。
答案 2 :(得分:2)
如果你只更新前一个上下文中的拥有方,那么新的持久化上下文中的第二个测试成功的原因是持久性提供程序显然不知道在持久化时你也没有更新反面。它只关心持久性目的的拥有方。但是,当您从持久性提供程序获取持久性对象时,提供程序会在两端正确设置双向关联(简单地假设它们也是正确持久化的)。但是,正如这里的许多其他人已经指出的那样,持久性提供程序不负责完成新创建的双向关联,并且应始终在代码中正确维护双向关联。