有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

我有一个关于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”的“一侧”来确定何时填充数据库中的外键字段


共 (3) 个答案

  1. # 1 楼答案

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

  2. # 2 楼答案

    我不知道你的测试试图证明什么,但事实是,在处理双向关联时,你必须处理好关联的双方。不这样做是不正确的。句号

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

    As with all bi-directional relationships it is your object model's and application's responsibility to maintain the relationship in both direction. There is no magic in JPA, if you add or remove to one side of the collection, you must also add or remove from the other side, see object corruption. Technically the database will be updated correctly if you only add/remove from the owning side of the relationship, but then your object model will be out of synch, which can cause issues.

    换句话说,在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);
        }
    }
    

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

  3. # 3 楼答案

    JPA中的实体关系有拥有方和相反方。数据库更新由拥有方的状态决定。在你的例子中Employee是一个拥有方,因为mappedBy属性

    JPA 2.0 specification开始:

    2.9 Entity Relationships

    ...

    Relationships may be bidirectional or unidirectional. A bidirectional relationship has both an owning side and an inverse (non-owning) side. A unidirectional relationship has only an owning side. The owning side of a relationship determines the updates to the relationship in the database, as described in section 3.2.4.

    The following rules apply to bidirectional relationships:

    • The inverse side of a bidirectional relationship must refer to its owning side by use of the mappedBy element of the OneToOne, OneToMany, or ManyToMany annotation. The mappedBy element designates the property or field in the entity that is the owner of the relationship.
    • The many side of one-to-many / many-to-one bidirectional relationships must be the owning side, hence the mappedBy element cannot be specified on the ManyToOne annotation.
    • For one-to-one bidirectional relationships, the owning side corresponds to the side that contains the corresponding foreign key.
    • For many-to-many bidirectional relationships either side may be the owning side.