⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 新建 文本文档之六.txt

📁 Hibernate使用说明书
💻 TXT
📖 第 1 页 / 共 2 页
字号:
Chapter 6. 集合类(Collections) 
Prev     Next 

--------------------------------------------------------------------------------

Chapter 6. 集合类(Collections)
6.1. 持久化集合类(Persistent Collections)
(译者注:在阅读本章的时候,以后整个手册的阅读过程中,我们都会面临一个名词方面的问题,那就是“集合”。"Collections"和"Set"在中文里对应都被翻译为“集合”,但是他们的含义很不一样。Collections是一个超集,Set是其中的一种。大部分情况下,本译稿中泛指的未加英文注明的“集合”,都应当理解为“Collections”。在有些二者同时出现,可能造成混淆的地方,我们用“集合类”来特指“Collecions”,“集合(Set)”来指"Set",一般都会在后面的括号中给出英文。希望大家在阅读时联系上下文理解,不要造成误解。 与此同时,“元素”一词对应的英文“element”,也有两个不同的含义。其一为集合的元素,是内存中的一个变量;另一含义则是XML文档中的一个标签所代表的元素。也请注意区别。 本章中,特别是后半部分是需要反复阅读才能理解清楚的。如果遇到任何疑问,请记住,英文版本的reference是惟一标准的参考资料。) 

这部分不包含大量的Java代码例子。我们假定你已经了解如何使用Java自身的集合类框架(Java's collections framework)。 其实如果是这样, 这里就真的没有什么东西需要学习了... 用一句话来做个总结,你就用你已经掌握的知识来使用它们吧,不用为了适应Hibernate而作出改变. 

Hibernate可以持久化以下java集合的实例, 包括java.util.Map, java.util.Set, java.util.SortedMap, java.util.SortedSet, java.util.List, 和任何持久实体或值的数组。类型为java.util.Collection或者java.util.List的属性还可以使用"bag"语义来持久。 

警告:用于持久化的集合,除了集合接口外,不能保留任何实现这些接口的类所附加的语义(例如:LinkedHashSet带来的迭代顺序)。所有的持久化集合,实际上都各自按照 HashMap, HashSet, TreeMap, TreeSet 和 ArrayList 的语义直接工作。更深入地说,对于一个包含集合的属性来说,必须把Java类型定义为接口(也就是Map, Set 或者List等),而绝不能是HashMap, TreeSet 或者 ArrayList。存在这个限制的原因是,在你不知道的时候,Hibernate暗中把你的Map, Set 和 List 的实例替换成了它自己的关于Map, Set 或者 List 的实现。(所以在你的程序中,谨慎使用==操作符。)(译者说明: 为了提高性能等方面的原因,在Hibernate中实现了几乎所有的Java集合的接口 。) 

Cat cat = new DomesticCat();
Cat kitten = new DomesticCat();
....
Set kittens = new HashSet();
kittens.add(kitten);
cat.setKittens(kittens);
session.save(cat);
kittens = cat.getKittens(); //Okay, kittens collection is a Set
(HashSet) cat.getKittens(); //Error!
集合遵从对值类型的通常规则:不能共享引用, 与其包含的实体共存亡。由于存在底层的关联模型,集合不支持空值语义;并且hibernate不会区分一个null的集合引用和一个不存在元素的空集合。 

集合实例在数据库中根据指向对应实体的外键而得到区别。这个外键被称为集合的关键字。在Hibernate配置文件中使用<key> 元素来映射这个集合的关键字。 

集合可以包含几乎所有的Hibernate类型, 包括所有的基本类型, 自定义类型,实体类型和组件。集合不能包含其他集合。这些被包含的元素的类型被称为集合元素类型。集合的元素在Hibernate中被映射为<element>, <composite-element>, <one-to-many>, <many-to-many> 或者 <many-to-any>。 

除了Set和Bag之外的所有集合类型都有一个索引(index)字段,这个字段映射到一个数组或者List的索引或者Map的key。Map的索引的类型可以是任何基本类型, 实体类型或者甚至是一个组合类型(但不能是一个集合类型)。数组和list的索引肯定是整型,integer。在Hibernate配置文件中使用 <index>, <index-many-to-many>, <composite-index> 或者 <index-many-to-any>等元素来映射索引。 

集合类可以产生相当多种类的映射,涵盖了很多通常的关系模型。我们建议你练习使用schema生成工具, 以便对如何把不同的映射定义转换为数据库表有一个感性认识。 

6.2. 映射集合(Mapping a Collection)
在Hibernate配置文件中使用<set>, <list>, <map>, <bag>, <array> 和 <primitive-array>等元素来定义集合,而<map>是最典型的一个。 

<map
    name="propertyName"                                         
    table="table_name"                                          
    schema="schema_name"                                        
    lazy="true|false"                                           
    inverse="true|false"                                        
    cascade="all|none|save-update|delete|all-delete-orphan"     
    sort="unsorted|natural|comparatorClass"                     
    order-by="column_name asc|desc"                             
    where="arbitrary sql where condition"                       
    outer-join="true|false|auto"                                
    batch-size="N"                                              (11)
    access="field|property|ClassName"                           (12)
>

    <key .... />
    <index .... />
    <element .... />
</map>
 name 集合属性的名称 
 
 table (可选——默认为属性的名称)这个集合表的名称(不能在一对多的关联关系中使用) 
 
 schema (可选) 表的schema的名称, 他将覆盖在根元素中定义的schema 
 
 lazy (可选——默认为false) lazy(可选--默认为false) 允许延迟加载(lazy initialization )(不能在数组中使用) 
 
 inverse (可选——默认为false) 标记这个集合作为双向关联关系中的方向一端。 
 
 cascade (可选——默认为none) 让操作级联到子实体 
 
 sort(可选)指定集合的排序顺序, 其可以为自然的(natural)或者给定一个用来比较的类。 
 
 order-by (可选, 仅用于jdk1.4) 指定表的字段(一个或几个)再加上asc或者desc(可选), 定义Map,Set和Bag的迭代顺序 
 
 where (可选) 指定任意的SQL where条件, 该条件将在重新载入或者删除这个集合时使用(当集合中的数据仅仅是所有可用数据的一个子集时这个条件非常有用) 
 
 outer-join(可选)指定这个集合,只要可能,应该通过外连接(outer join)取得。在每一个SQL语句中, 只能有一个集合可以被通过外连接抓取(译者注: 这里提到的SQL语句是取得集合所属类的数据的Select语句) 
 
(11) batch-size (可选, 默认为1) 指定通过延迟加载取得集合实例的批处理块大小("batch size")。 
 
(12) access(可选-默认为属性property):Hibernate取得属性值时使用的策略 
 

建立列表(List)和数组(Array)需要一个单独表字段用来保存列表(List)或数组(Array)的索引(foo[i]中的i)。如果你的关系模型中没有一个索引字段, 例如:如果你处理的是老式的遗留数据, 你可以用无序的Set来替代。这会让那些以为List应该是访问无序集合的比较方便的方法的人感到气馁。Hibernate集合严格遵守Set,List和Map接口中包涵的自然语义。 List元素不能正确的自发对他们自己进行排序! 

在另一方面, 那些准备使用List来模拟bag的语义的人有一个合法的委屈(a legitimate grievance)。bag是一个无序,没有索引的集合并且可能包含多个相同的元素。在Java集合框架中没有Bag接口(虽然你可以用List模拟它)。Hibernate允许你映射类型为List或者Collection的属性到<bag>元素。注意: Bag语义事实上并不是Collection规范(contract)的一部分并且事实上它和List规范中的语义是相矛盾的。 

具有inverse="false"标记的大型Hibernate bag效率是相当低的,应该尽量避免。Hibernate无法创建,删除和更新它的单个记录, 因为他们没有关键字来识别单个记录。 

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"/>
 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>
6.4. 一对多关联(One To Many Associations)
一对多关联直接连接两个类对应的表,而没有中间集合表。(这实现了一个一对多的关系模型)(译者注:这有别与多对多的关联需要一张中间表)。 这个关系模型失去了一些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"。 

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子句来明确获取到整个集合。 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -