Hibernate3 连接技巧

作者:    |       Hibernate ORM

在参与一个开源项目并与商业竞争对手竞争的过程中,一个乐趣就是必须实现用户根本不要求,而且在实际中可能不会使用的功能,仅仅因为竞争对手试图将他们无用的功能作为竞争优势。我们早就意识到,如果你没有这项功能,就很难告诉人们他们不需要也不应该使用这项功能。

多表映射最初是这类功能的良好例子。多年来,我们一直在重复“你的对象模型应该至少和你的关系模式一样粒度细”的口号。不幸的是,我们一直听到这种回声作为“Hibernate 不能进行多表映射”。没有人曾经向我展示过在真实应用程序中多表映射的真实有说服力的用例,但据我们的竞争对手所说,在实体属性随机分散在多个不同的物理表中是很常见的。我必须相信他们的话。我并不是说你会永远遇到这种情况,事实上,我也见过一些边缘案例,尽管这些案例至少可以被认为是作为关联的更好表示。但肯定的是,在我看来,多表映射的有效用例并不常见到足以成为一项重要特性。也许感知差异是由于只有/理智的/组织使用 Hibernate 的原因。

无论如何,我们引入了<join/>映射,这样我们就可以告诉人们不要使用它。实际上,实现它很有趣,并且帮助我对 EntityPersister 层次结构进行了几次很好的重构。

然后发生了一件有趣的事情。我开始考虑所有可以用<join/>做的事情,这些事情与通常理解的多表映射没有太大的关系。而且我相当确定,这些事情不是其他人所谈论的!

我想到的第一个应用是混合继承映射策略。在此之前,你可以在<subclass/><joined-subclass/>(现在也可以<union-subclass/>),之间进行选择,并且你必须在整个层次结构中坚持使用该策略。

现在可以编写如下映射

<class name="Superclass" 
        table="parent"
        discriminator-value="0">
    <id name="id">.....</id>
    <discriminator column="type" type="int"/>
    <property ...../>
    ...
    
    <subclass name="Subclass" 
            discriminator-value="1">
        <property .... >
        ...
    </subclass>
    
    <subclass name="JoinedSubclass" 
            discriminator-value="-1">
        <join table="child">
            <property ...../>
            ....
        </join>
    </subclass>
    
</class>

这真是太有用啦。

接下来的一件事是<join/>需要稍作调整才能使用。我在连接元素中添加了一个逆属性,用于声明连接的表不应由拥有实体更新。现在,可以将一个关联(链接)表映射为域模型中的一对多多重性,而通常关联表代表的是多对多关联。首先,我们有一个基本的在父级端的多对多映射

<class name="Parent">
    ...
    <set name="children" table="ParentChild" lazy="true">
        <key column="parentId"/>
        <many-to-many column="childId" class="Child"/>
    </set>
</class>

现在,我们使用一个<join>映射来隐藏从子级端关联表

<class name="Child">
    ...
    <join table="ParentChild" inverse="true">
        <key column="childId"/>
        <many-to-one name="parent" column="parentId"/>
    </join>
</class>

嗯,我并不是真的很确定这有多有用,但每当TopLink的家伙们吹嘘他们能这样做时,我总是羡慕不已,而我们现在几乎是免费的得到了这个功能!

第三个技巧也是受到TopLink的启发。许多将代码从TopLink迁移到Hibernate的用户发现,Hibernate的按类映射表策略与TopLink的性能特征有显著差异。Hibernate似乎有一个独特的按类映射表策略实现,其中不需要判别符列来实现多态性。相反,Hibernate对所有子类表执行外连接,并检查每个返回结果行中的主键值是否为null,以确定该行代表的子类。在大多数情况下,这提供了一个出色的性能平衡,因为它不受可怕的N+1选择问题的影响。此外,它不需要在根类表中添加类型判别符列,这对于这种关系模型来说,感觉非常不自然且冗余。

TopLink使用的一种替代方法是先进行初始查询,检查判别符列的值,如果行代表子类实例,则发出额外的查询。对于浅层继承树,这通常不是非常有效,但我们看到一些前TopLink用户创建了非常深或宽的继承树,在这种情况下,Hibernate的策略可能导致一个查询中包含太多的连接。

所以,我在<join/>中添加了外连接属性。其效果稍微微妙一些。考虑以下映射

<class name="Foo" table="foos" discriminator-value="0">
    <id name="id">...</id>
    <discriminator column="type" type="int"/>
    <property name="name"/>
    <subclass name="Bar" discriminator-value="1">
        <join table="bars">
            <key column="fooId"/>
            <property name="amount"/>
        </join>
    </subclass>
</class>

当我们对子类Bar执行HQL查询时,Hibernate将生成包含foos和bars的内连接SQL。如果我们查询超类Foo,Hibernate将使用外连接。

(请注意,在实际中你不会写上面的映射;相反,你会使用<joined-subclass/>来消除对判别符的需要)

假设我们设置outer-join="false":

<class name="Foo" table="foos" discriminator-value="0">
    <id name="id">...</id>
    <discriminator column="type" type="int"/>
    <property name="name"/>
    <subclass name="Bar" discriminator-value="1">
        <join table="bars" outer-join="false">
            <key column="fooId"/>
            <property name="amount"/>
        </join>
    </subclass>
</class>

现在,当我们查询子类时,将使用相同的SQL内连接。但是,当我们查询超类时,Hibernate不会使用外连接。相反,它将针对foos表发出初始查询,并在找到判别符值为1的行时,对bars表进行顺序选择。

在这个例子中,这并不是一个好主意。但想象一下,如果Foo有一个非常大的直接子类数量。那么我们可能会避免一个包含很多外连接的查询,而是选择几个没有连接的查询。嗯,也许有些人会发现这很有用……


返回顶部