ITEEDU

6.3. 值集合和多对多关联(Collections of Values and Many To Many Associations)

任何值集合和实体集合如果被映射为多对多关联(Java集合中的语义)就需要一个集合表。这个表中包含外键字段,元素字段还可能有索引字段。

使用<key>元素来申明从集合表到其拥有者类的表(from the collection table to the table of the owning class)的外键关键字。

<key column="column_name"/>
1

column(必需):外键字段的名称

对于类似与map和list的带索引的集合, 我们需要一个<index>元素。对于list来说, 这个字段包含从零开始的连续整数。对于map来说,这个字段可以包含任意Hibernate类型的值。

<index
        column="column_name"                (1)
        type="typename"                     (2)
/>
1

column(必需):保存集合索引值的字段名。

2

type (可选,默认为整型integer):集合索引的类型。

还有另外一个选择,map可以是实体类型的对象。在这里我们使用<index-many-to-many>元素。

<index-many-to-many
        column="column_name"                (1)
        class="ClassName"                   (2)
/>
1

column(必需):集合索引值中外键字段的名称

2

class (required):(必需):集合的索引使用的实体类。

对于一个值集合, 我们使用<element>标签。

<element
        column="column_name"                (1)
        type="typename"                     (2)
/>
1

column(必需):保存集合元素值的字段名。

2

type (必需):集合元素的类型

一个拥有自己表的实体集合对应于多对多(many-to-many)关联关系概念。多对多关联是针对Java集合的最自然映射关联关系,但通常并不是最好的关系模型。

 

<many-to-many
        column="column_name"                               (1)
        class="ClassName"                                  (2)
        outer-join="true|false|auto"                       (3)
/>
1

column(必需): 这个元素的外键关键字段名

2

class (必需): 关联类的名称

3

outer-join (可选 - 默认为auto): 在Hibernate系统参数中hibernate.use_outer_join被打开的情况下,该参数用来允许使用outer join来载入此集合的数据。

例子:

首先, 一组字符串:

<set name="names" table="NAMES">
    <key column="GROUPID"/>
    <element column="NAME" type="string"/>
</set>

包含一组整数的bag(还设置了order-by参数指定了迭代的顺序):

<bag name="sizes" table="SIZES" order-by="SIZE ASC">
    <key column="OWNER"/>
    <element column="SIZE" type="integer"/>
</bag>

一个实体数组,在这个案例中是一个多对多的关联(注意这里的实体是自动管理生命周期的对象(lifecycle objects),cascade="all"):

<array name="foos" table="BAR_FOOS" cascade="all">
    <key column="BAR_ID"/>
    <index column="I"/>
    <many-to-many column="FOO_ID" class="com.illflow.Foo"/>
</array>

一个map,通过字符串的索引来指明日期:

<map name="holidays" table="holidays" schema="dbo" order-by="hol_name asc">
    <key column="id"/>
    <index column="hol_name" type="string"/>
    <element column="hol_date" type="date"/>
</map>

一个组件的列表:

<list name="carComponents" table="car_components">
    <key column="car_id"/>
    <index column="posn"/>
    <composite-element class="com.illflow.CarComponent">
            <property name="price" type="float"/>
            <property name="type" type="com.illflow.ComponentType"/>
            <property name="serialNumber" column="serial_no" type="string"/>
    </composite-element>
</list>

6.4. 一对多关联(One To Many Associations)

一对多关联直接连接两个类对应的表,而没有中间集合表。(这实现了一个一对多的关系模型)(译者注:这有别与多对多的关联需要一张中间表)。 这个关系模型失去了一些Java集合的语义:

  • map,set或list中不能包含null值

  • 一个被包含的实体的实例只能被包含在一个集合的实例中

  • 一个被包含的实体的实例只能对应于集合索引的一个值中

一个从Foo到Bar的关联需要额外的关键字字段,可能还有一个索引字段指向这个被包含的实体类,Bar所对应的表。这些字段在映射时使用前面提到的<key>和<index>元素。

<one-to-many>标记指明了一个一对多的关联。

<one-to-many class="ClassName"/>
1

class(必须):被关联类的名称。

例子

<set name="bars">
    <key column="foo_id"/>
    <one-to-many class="com.illflow.Bar"/>
</set>

注意:<one-to-many>元素不需要定义任何字段。 也不需要指定表名。

重要提示:如果一对多关联中的<key>字段定义成NOT NULL,那么当创建和更新关联关系时Hibernate可能引起约束违例。为了预防这个问题,你必须使用双向关联,并且在“多”这一端(Set或者是bag)指明inverse="true"。

6.5. 延迟初始化(延迟加载)(Lazy Initialization)

(译者注: 本翻译稿中,对Lazy Initiazation和Eager fetch中的lazy,eager采取意译的方式,分别翻译为延迟初始化和预先抓取。lazt initiazation就是指直到第一次调用时才加载。)

集合(不包括数组)是可以延迟初始化的,意思是仅仅当应用程序需要访问时,才载入他们的值。对于使用者来说,初始化是透明的, 因此应用程序通常不需要关心这个(事实上,透明的延迟加载也就是为什么Hibernate需要自己的集合实现的主要原因)。但是, 如何应用程序试图执行以下程序:

s = sessions.openSession();
User u = (User) s.find("from User u where u.name=?", userName, Hibernate.STRING).get(0);
Map permissions = u.getPermissions();
s.connection().commit();
s.close();

Integer accessLevel = (Integer) permissions.get("accounts");  // Error!

这个错误可能令你感到意外。因为在这个Session被提交(commit)之前, permissions没有被初始化,那么这个集合将永远不能载入他的数据了。 解决方法是把读取集合数据的语句提到Session被提交之前。

另外一种选择是不使用延迟初始化集合。既然延迟初始化可能引起上面这样错误,默认是不使用延迟初始化的。但是, 为了效率的原因, 我们希望对绝大多数集合(特别是实体集合)使用延迟初始化。

延迟初始化集合时发生的例外被封装在LazyInitializationException中。

使用可选的 lazy 属性来定义延迟初始化集合:

<set name="names" table="NAMES" lazy="true">
    <key column="group_id"/>
    <element column="NAME" type="string"/>
</set>

在一些应用程序的体系结构中,特别是使用hibernate访问数据的结构, 代码可能会用在不用的应用层中, 可能没有办法保证当一个集合在初始化的时候, session仍然打开着。 这里有两个基本方法来解决这个问题:

  • 在基于Web的应用程序中, 一个servlet过滤器可以用来在用户请求的完成之前来关闭Session。当然,这个地方(关闭session)严重依赖于你的应用程序结构中例外处理的正确性。在请求返回给用户之前关闭Session和结束事务是非常重要的,即使是在构建视图(译者注: 返回给用户的HTML页面)的时候发生了例外,也必须确保这一点。考虑到这一点,servlet过滤器可以保证能够操作这个Session。我们推荐使用一个ThreadLocal变量来保存当前的Session。

  • 在一个有单独的商业层的应用程序中, 商业逻辑必须在返回之前“准备好”Web层所需要的所有集合。通常, 应用程序为每个Web层需要的集合调用Hibernate.initialize()(必须在Session被关闭之前调用)或者通过使用FETCH子句来明确获取到整个集合。

你可以使用Hibernate Session API中的filter() 方法来在初始化之前得到集合的大小:

( (Integer) s.filter( collection, "select count(*)" ).get(0) ).intValue()

filter() 或者 createFilter()同样被用于有效的重新载入一个集合的子集而不需要载入整个集合。