在一对多关系中,通常用@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
您看不到额外的 SQL 语句,因为您没有正确设置关联的两端。 例如,你说:
a.getChildren().add( b );
并假设a
和b
现在已关联。 但是当你在这里打电话给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
命名了不同的父级。 那么我们相信哪个呢?
我认为这篇文章可能会有所帮助,特别是我在下面提到的:
是什么"东西"使单向不等于双向?当我们有单向关系时,只有 Customer 类具有对 User 类的引用;您只能通过 Customer.getUser() 获取用户,您不能以其他方式执行。当我们编辑 User 类时,我们使 User 能够获取客户 User.getCustomer()。
您也可以在SO上检查此问题。
这个问题看起来也很有帮助。
主要区别在于双向关系提供双向导航访问,因此您可以在没有显式查询的情况下访问另一端。它还允许您将级联选项应用于两个方向。
关于这个话题,SO还有很多问题。这些将对您非常有帮助:)
只会效率低下,因为保存 B 会发出
- 更新对象 B
- 更新对象 B 的所有子项父引用
- 级联到对象 B 的所有子项,这些子项更新其父引用
而不是
- 更新对象 B
- 级联到对象 B 的所有子项,这些子项更新其父引用
每侧(一/多)都有不同的实体类型,但在这种情况下,两边都有单个实体类型。所以当你说你要更新两边时,你实际上只是在更新一面,因为只有一个实体(这里,B)......由于这是一个与自身联接的实体,Hibernate知道它是由什么映射的。
至于没有选择一方作为关系所有者会遇到什么问题:我刚刚在我自己的代码中尝试了这个(删除了我其中一个实体@OneToMany端的"mappedBy"),并在运行时得到"java.sql.BatchUpdateException:失败的批处理"。简而言之,您将从Hibernate无法正确映射您的类中获得奇怪的SQL异常。