任何值集合和实体集合如果被映射为多对多关联(Java集合中的语义)就需要一个集合表。这个表中包含外键字段,元素字段还可能有索引字段。
使用<key>元素来申明从集合表到其拥有者类的表(from the collection table to the table of the owning class)的外键关键字。
<key column="column_name"/>
![]() |
column(必需):外键字段的名称 |
对于类似与map和list的带索引的集合, 我们需要一个<index>元素。对于list来说, 这个字段包含从零开始的连续整数。对于map来说,这个字段可以包含任意Hibernate类型的值。
<index column="column_name"type="typename"
/>
![]() |
column(必需):保存集合索引值的字段名。 |
![]() |
type (可选,默认为整型integer):集合索引的类型。 |
还有另外一个选择,map可以是实体类型的对象。在这里我们使用<index-many-to-many>元素。
<index-many-to-many column="column_name"class="ClassName"
/>
![]() |
column(必需):集合索引值中外键字段的名称 |
![]() |
class (required):(必需):集合的索引使用的实体类。 |
对于一个值集合, 我们使用<element>标签。
<element column="column_name"type="typename"
/>
![]() |
column(必需):保存集合元素值的字段名。 |
![]() |
type (必需):集合元素的类型 |
一个拥有自己表的实体集合对应于多对多(many-to-many)关联关系概念。多对多关联是针对Java集合的最自然映射关联关系,但通常并不是最好的关系模型。
<many-to-many column="column_name"class="ClassName"
outer-join="true|false|auto"
/>
![]() |
column(必需): 这个元素的外键关键字段名 |
![]() |
class (必需): 关联类的名称 |
![]() |
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>
一对多关联直接连接两个类对应的表,而没有中间集合表。(这实现了一个一对多的关系模型)(译者注:这有别与多对多的关联需要一张中间表)。 这个关系模型失去了一些Java集合的语义:
map,set或list中不能包含null值
一个被包含的实体的实例只能被包含在一个集合的实例中
一个被包含的实体的实例只能对应于集合索引的一个值中
一个从Foo到Bar的关联需要额外的关键字字段,可能还有一个索引字段指向这个被包含的实体类,Bar所对应的表。这些字段在映射时使用前面提到的<key>和<index>元素。
<one-to-many>标记指明了一个一对多的关联。
<one-to-many class="ClassName"/>
![]() |
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"。
(译者注: 本翻译稿中,对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()同样被用于有效的重新载入一个集合的子集而不需要载入整个集合。