Java JPA:一对一关系中的阻抗失配问题

Java JPA:一对一关系中的阻抗失配问题,java,orm,jpa,jpa-2.0,Java,Orm,Jpa,Jpa 2.0,我有一个关于JPA-2.0(提供者是Hibernate)关系及其在Java中的相应管理的问题。假设我有一个部门和一个员工实体: @Entity public class Department { ... @OneToMany(mappedBy = "department") private Set<Employee> employees = new HashSet<Employee>(); ... } @Entity public class Emplo

我有一个关于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;
  ...
}
如果省略
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提到的规范参考当然是准确的,但我坚持认为,您肯定必须设置双向关联的双方。不这样做是不正确的,并且您的实体在第一个持久性上下文中的关联被破坏。政府是这样说的:

@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中没有魔法,如果您在集合的一侧添加或删除,您还必须从另一侧添加或删除,请参阅。从技术上讲,如果只从关系的拥有方添加/删除数据库,那么数据库将正确更新,但是对象模型将不同步,这可能会导致问题

换句话说,在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);
    }
}
@实体
公共课系{
...
@OneToMany(mappedBy=“部门”)
私有集employees=newhashset();
...
公共无效添加到员工(员工){
this.employees.add(employee);
员工。设置部门(本);
}
}

我再说一遍,不这样做是不正确的。您的测试之所以有效,是因为您在一个新的持久性上下文(即非常特殊的情况,而不是一般情况)中访问数据库,但代码在许多其他情况下都会中断。

JPA中的实体关系有拥有和反向两个方面。数据库更新由拥有方的状态决定。在您的情况下,
Employee
是一个拥有方,因为
mappedBy
属性

从:

2.9实体关系

关系可以是双向的,也可以是双向的 单向的。双向的 这种关系有两个拥有者 和一个反向(非拥有)边。A. 单向关系只有 拥有方。一家公司的拥有方 关系决定要更新的内容 数据库中的关系,如 如第节所述 3.2.4.

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

  • 双向曲线的反方向 关系必须指其拥有者 通过使用 一个,一个,或许多 注释。mappedBy元素 指定中的属性或字段 作为项目所有者的实体 关系
  • 多方面的 一对多/多对一 双向关系必须是双向的 拥有方,因此是mappedBy 无法在上指定元素 多功能注释
  • 为了 一对一双向 关系,拥有方 对应于包含 相应的外键
  • 为了 多对多双向 任何一方的关系都可能是相同的 拥有方

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

您正在通过
EM.close()创建一个新的EM;em=emf.createEntityManager(),这会影响很多事情。尝试在不创建新EM的情况下使用事务。谢谢,这回答了我的问题。是的,数据库只能存储一条信息,当从数据库检索对象时,JPA将重建双方。我不是建议使用正确关系管理的快捷方式,我只是对观察到的行为感到有点困惑。因此,我将确保Java方面始终管理关系的双方,谢谢。@wallenborn抱歉,我当时不知怎么误解了这个问题。