【问题标题】:Why is it necessary to distinguish one side of a bidirectional relationship in orm as an owning side?为什么要在 orm 上区分双向关系的一方作为拥有方?
【发布时间】:2012-09-25 14:50:54
【问题描述】:

在一对多关系中,通常用@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

【问题讨论】:

    标签: hibernate jpa orm bidirectional owner


    【解决方案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 命名了不同的父级。那么我们相信哪一个呢?

    【讨论】:

      【解决方案2】:

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

      至于不选择一方作为关系所有者会遇到什么问题:我刚才在我自己的代码中尝试了这一点(删除了我的一个实体的@OneToMany 一侧的“mappedBy”),并在运行时得到“java.sql.BatchUpdateException:批处理失败”。简而言之,你会因为 Hibernate 无法正确映射你的类而得到奇怪的 SQL 异常。

      【讨论】:

      • 在我看来,hibernate 不会在这里选择一方作为所有者。如果我做 someB.setParent(anotherB);并保存 someB - 更改在 db 中可见。如果我做 someB.getChildren().add(someOtherB);并保存 someB - 更改在 db 中也可见。 (setParent(..) 是一个普通的 setter - 我什至没有在 anotherB 的子列表中设置对 someB 的引用)
      • Hibernate 确实选择了一方作为所有者:B。显然对于自加入,这没关系。当我尝试非自加入时,它不行。
      • 这绝对不是真的。 Hibernate 不会“选择”任何一方作为所有者。在没有用户说的情况下,Hibernate 只是假设双方都是所有者。
      • 也许说它正在选择拥有方是不正确的。当我唯一改变的是删除 mappedby 字段时,我的休眠状态被炸毁了。
      【解决方案3】:

      这只是效率低下,因为保存 B 会发出问题

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

      而不是

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

      【讨论】:

      • 我认为这可能会导致性能问题,但似乎并非如此。
      • 我进行了一个简单的测试 - 查看我的问题编辑 - 似乎并非如此 - 更新的数量是我所期望的
      • 您的测试没有正确设置边,请参阅史蒂夫的回答。就像我说的那样。
      • 是的,我重新测试了它,并在两边都正确设置了参考,结果就像你在回答中所说的那样,但我不能赞成你的回答 - 我没有足够的声誉来做它
      【解决方案4】:

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

      使单向不等于双向的“东西”是什么?当我们有一个单向关系时,只有 Customer 类有对 User 类的引用;你只能通过 Customer.getUser() 获得一个用户,你不能这样做。当我们编辑 User 类时,我们使 User 能够获取客户 User.getCustomer()。

      您也可以在 SO 上查看此 question

      这个question 看起来也很有帮助。

      主要区别在于双向关系提供了双向导航访问,因此您无需显式查询即可访问另一侧。它还允许您将级联选项应用于两个方向。

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

      【讨论】:

      • 我明白单向和双向之间的区别。在上述情况下,我有一个双向关系——这就是我想要的——我提到了单向关系,因为如果映射中没有指定相同的列(或根本没有列),就会发生这种情况。我的问题是:如果一个人不指定哪一方是所有者,那么在双向关系中会发生什么。
      猜你喜欢
      • 2013-09-15
      • 2022-01-26
      • 1970-01-01
      • 1970-01-01
      • 2012-01-24
      • 2011-02-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多