简体   繁体   English

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

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

I have a question about JPA-2.0 (provider is Hibernate) relationships and their corresponding management in Java. 我有一个关于JPA-2.0(提供者是Hibernate)关系及其在Java中的相应管理的问题。 Let's assume i have a Department and an Employee entity: 假设我有一个部门和一个员工实体:

@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;
  ...
}

Now i know i have to manage the Java relationships myself, as in the following unit test: 现在我知道我必须自己管理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());
}

If i leave out either e.setDepartment(d) or d.getEmployees().add(e) the assertions will fail. 如果我遗漏了e.setDepartment(d)d.getEmployees().add(e)断言将失败。 So far, so good. 到现在为止还挺好。 What if i commit the database transaction in between? 如果我之间提交数据库事务怎么办?

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

Do i still need to manage both sides of the relation? 我还需要管理关系的两个方面吗? No, as it turns out, i don't have to. 不,事实证明,我没有必要。 With this modification 有了这个修改

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

the assertions still succeed. 断言仍然成功。 However, if i only set the other side: 但是,如果我只设置另一方:

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

the assertions fail. 断言失败了。 Why? 为什么? Is is because the Employee is the owning side of the relation? 是因为员工是关系的拥有者吗? Can i change that behavior by annotating differently? 我可以通过不同的注释来改变这种行为吗? Or is it just always the "One" side of the "OneToMany" that determines when the foreign key field in the database is filled? 或者它是否始终是“OneToMany”的“一”侧确定何时填充数据库中的外键字段?

I don't know what your test is trying to demonstrate but the fact is you must handle both sides of the association when working with bidirectional associations. 我不知道你的测试试图证明什么,但事实是你在处理双向关联时必须处理关联的两个方面。 Not doing so is incorrect. 不这样做是不正确的。 Period. 期。

Update: While the spec reference mentioned by axtavt is of course accurate, I insist, you definitely must set both sides of a bi-directional association. 更新:虽然axtavt提到的规范参考当然是准确的,但我坚持认为,你必须设置双向关联的两面。 Not doing so is incorrect and the association between your entities in the first persistence context is broken . 不这样做是不正确的,并且第一个持久化上下文中的实体之间的关联被破坏 The JPA wiki book puts it like this: JPA wiki书就是这样的:

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

In other words, the only correct and safe way to manage your bidirectional association in Java is to set both sides of the link. 换句话说,在Java中管理双向关联的唯一正确安全的方法是设置链接的两侧。 This is usually done using defensive link management methods, like this: 这通常使用防御性链接管理方法完成,如下所示:

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

I repeat, not doing so is incorrect. 我再说一遍,不这样做是不正确的。 Your test only works because you're hitting the database in a new persistence context (ie a very particular situation, not the general one) but the code would break in many other situations. 您的测试只能起作用,因为您在新的持久化上下文(即非常特殊的情况,而不是一般情况)中访问数据库,但代码会在许多其他情况下中断。

Entity relationships in JPA have owning and inverse sides. JPA中的实体关系具有拥有和反向。 Database updates are determined by the state of the owning side. 数据库更新由拥有方的状态决定。 In your case Employee is an owning side due to the mappedBy attribute. 在您的情况下,由于mappedBy属性, Employee是拥有者。

From the JPA 2.0 specification : JPA 2.0规范

2.9 Entity Relationships 2.9实体关系

... ...

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. 关系的拥有方确定数据库中关系的更新,如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. 双向关系的反面必须通过使用OneToOne,OneToMany或ManyToMany批注的mappedBy元素来引用其拥有方。 The mappedBy element designates the property or field in the entity that is the owner of the relationship. mappedBy元素指定作为关系所有者的实体中的属性或字段。
  • 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. 一对多/多对一双向关系的许多方面必须是拥有方,因此不能在ManyToOne批注上指定mappedBy元素。
  • 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. 对于多对多双向关系,任何一方都可能是拥有方。

The reason why the second test in a new persistence context succeeds if you only update the owning side in a previous context is that the persistence provider obviously can't know that when persisting you did not update the inverse side as well. 如果你只更新前一个上下文中的拥有方,那么新的持久化上下文中的第二个测试成功的原因是持久性提供程序显然无法知道在持久化时你也没有更新反面。 It only cares about the owning side for persistence purposes. 它只关心持久性目的的拥有方。 However, when you get persistent objects from a persistence provider, the provider sets the bidirectional associations properly on both sides (it is simply assumed they were persisted properly, too). 但是,当您从持久性提供程序获取持久性对象时,提供程序会在两端正确设置双向关联(简单地假设它们也是正确持久化的)。 However, as many others here have already pointed out, it is not the responsibility of the persistence provider to complete newly created bidirectional associations and you should always properly maintain bidirectional associations in your code. 但是,正如这里的许多其他人已经指出的那样,持久性提供程序不负责完成新创建的双向关联,并且应始终在代码中正确维护双向关联。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM