21. 示例:父子关系(Parent Child Relationships) / 21.2. 双向的一对多关系(Bidirectional one-to-many)

假设我们要实现一个简单的从Parent到Child的<one-to-many>关联。

<set name="children">
    <key column="parent_id"/>
    <one-to-many class="Child"/>
</set>

如果我们运行下面的代码

Parent p = .....;
Child c = new Child();
p.getChildren().add(c);
session.save(c);
session.flush();

Hibernate会产生两条SQL语句:

  • 一条INSERT语句,为c创建一条记录

  • 一条UPDATE语句,创建从pc的连接

这样做不仅效率低,而且违反了列parent_id非空的限制。我们可以通过在集合类映射上指定not-null="true"来解决违反非空约束的问题:

<set name="children">
    <key column="parent_id" not-null="true"/>
    <one-to-many class="Child"/>
</set>

然而,这并非是推荐的解决方法。

这种现象的根本原因是从pc的连接(外键parent_id)没有被当作Child对象状态的一部分,因而没有在INSERT语句中被创建。因此解决的办法就是把这个连接添加到Child的映射中。

<many-to-one name="parent" column="parent_id" not-null="true"/>

(我们还需要为类Child添加parent属性)

现在实体Child在管理连接的状态,为了使collection不更新连接,我们使用inverse属性。

<set name="children" inverse="true">
    <key column="parent_id"/>
    <one-to-many class="Child"/>
</set>

下面的代码是用来添加一个新的Child

Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
c.setParent(p);
p.getChildren().add(c);
session.save(c);
session.flush();

现在,只会有一条INSERT语句被执行!

为了让事情变得井井有条,可以为Parent加一个addChild()方法。

public void addChild(Child c) {
    c.setParent(this);
    children.add(c);
}

现在,添加Child的代码就是这样

Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
p.addChild(c);
session.save(c);
session.flush();