为什么有必要将orm中双向关系的一方区分为拥有方?

时间:2012-10-04 14:24:25

标签: hibernate jpa orm bidirectional owner

在一对多的关系中,通常用@ManyToOne注释的字段是所有者 - 另一方具有'mappedBy'属性。但是,如果我跳过'mappedBy'并使用@JoinColumn(同一列)注释双方,我可以更新双方 - 更改被提交给db。

我没有两个单向关系而不是一个双向关系 - 只有一个连接列。

如果不选择一方作为关系所有者,我会遇到哪些问题?

我的实体看起来类似于以下内容:

@Entity
public class B {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Long id;


   @ManyToOne
   @JoinColumn(name = "parent_id")
   @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
   private B parent;

   @OneToMany()
   @JoinColumn(name = "parent_id")
   @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
   private List<B> children = new ArrayList<B>();
...
}

它似乎对性能没有任何影响(至少插入看起来不错) 这是一个简单的测试和日志输出:

Session session = HibernateUtil.getSessionFactory().openSession();
    session.beginTransaction();
    B a = new B("a");
    B b = new B("b");
    B c = new B("c");
    B d = new B("d");
    B e = new B("e");
    session.save(a);
    session.save(b);
    session.save(c);
    session.save(d);
    session.save(e);
    session.getTransaction().commit();
    System.out.println("all objects saved");
    session.beginTransaction();
    a.getChildren().add(b);
    a.getChildren().add(c);
    session.save(a);
    session.getTransaction().commit();
    System.out.println("b and c added as children");
    session.beginTransaction();
    a.getChildren().add(d);
    a.getChildren().add(e);
    session.getTransaction().commit();
    System.out.println("e and f added as children");
    session.close();

Hibernate: insert into B (fieldInB, parent_id) values (?, ?)
Hibernate: insert into B (fieldInB, parent_id) values (?, ?)
Hibernate: insert into B (fieldInB, parent_id) values (?, ?)
Hibernate: insert into B (fieldInB, parent_id) values (?, ?)
Hibernate: insert into B (fieldInB, parent_id) values (?, ?)
all objects saved
Hibernate: update B set parent_id=? where id=?
Hibernate: update B set parent_id=? where id=?
b and c added as children
Hibernate: update B set parent_id=? where id=?
Hibernate: update B set parent_id=? where id=?
e and f added as children

4 个答案:

答案 0 :(得分:1)

您没有看到额外的SQL语句,因为您没有正确设置关联的两侧。例如,你说:

a.getChildren().add( b );

并假设现在ab已关联。但是当你在这里拨打b.getParent()时会发生什么?假设此代码“将b作为孩子添加到a”,然后返回此新子b,并且调用者想要导航b.getParent()

而你应该做的是:

a.getChildren().add( b );
b.setParent( a );

现在您的惯用Java代码继续工作(b.getParent()行为正常)。现在,在这里,您将看到多个SQL语句。这就是为什么你需要选择一方作为“所有者”。

另外,虽然完全是下铺,但请考虑:

a.getChildren().add( b );
b.setParent( c );

这里写入数据库的是什么?双方都有效地为b命名了另一个父母。那我们相信哪个?

答案 1 :(得分:0)

我认为article可能有所帮助,特别是我在下面提到的内容:

  

使单向不等于双向的“东西”是什么?当我们有单向关系时,只有Customer类具有对User类的引用;你只能通过Customer.getUser()获得用户,你不能做其他方式。当我们编辑User类时,我们启用了User来获取客户User.getCustomer()。

您也可以在SO上查看question

question看起来也非常有用。

  

主要区别在于双向关系提供双向导航访问,因此您可以在没有显式查询的情况下访问另一方。此外,它允许您向两个方向应用级联选项。

在这个主题上有很多关于SO的问题。这些对你很有帮助:))

答案 2 :(得分:0)

这样效率很低,因为保存B会发出

  • 更新对象B
  • 更新所有儿童父对象B的引用
  • 级联到更新其父引用的对象B的所有子级

而不是

  • 更新对象B
  • 级联到更新其父引用的对象B的所有子级

答案 3 :(得分:0)

通常每侧(一个/多个)有不同的实体类型,但在这种情况下,单个实体类型在两侧。因此,当你说你正在更新双方时,你实际上只更新一方因为只有一个实体(这里是B)......因为这是一个与自身连接的实体,所以Hibernate知道它映射的是什么。 / p>

至于你没有选择一方担任关系所有者会遇到什么问题:我刚刚在我自己的代码中尝试了这个问题(删除了我某个实体的@OneToMany一侧的“mappedBy”),并且运行时获得“java.sql.BatchUpdateException:failed batch”。简而言之,您将从Hibernate获得奇怪的SQL异常,无法正确映射您的类。