`

集合类(Collections)映射

阅读更多

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

Hibernate要求持久化集合值字段必须声明为接口,比如:

public class Product {
private String serialNumber;
private Set parts = new HashSet();
public Set getParts() { return parts; }
void setParts(Set parts) { this.parts = parts; }
public String getSerialNumber() { return serialNumber; }
void setSerialNumber(String sn) { serialNumber = sn; }
}

实际的接口可能是java.util.Set, java.util.Collection, java.util.List, java.util.Map, java.util.SortedSet, java.util.SortedMap 或者...任何你喜欢的类型!("任何你喜欢的类型" 代表你需要编写 org.hibernate.usertype.UserCollectionType的实现.)

注意我们是如何用一个HashSet实例来初始化实例变量的.这是用于初始化新创建(尚未持久化)的类实例中集合值属性的最佳方法。当你持久化这个实例时——比如通过调用persist()——Hibernate 会自动把HashSet替换为Hibernate自己的Set实现。观察下面的错误:

Cat cat = new DomesticCat();
Cat kitten = new DomesticCat();
....
Set kittens = new HashSet();
kittens.add(kitten);
cat.setKittens(kittens);
session.persist(cat);
kittens = cat.getKittens(); //Okay, kittens collection is a Set
(HashSet) cat.getKittens(); //Error!

根据不同的接口类型,被Hibernate注射的持久化集合类的表现类似HashMap, HashSet, TreeMap, TreeSet or ArrayList

集合类实例具有值类型的通常行为。当被持久化对象引用后,他们会自动被持久化,当不再被引用后,自动被删除。假若实例被从一个持久化对象传递到另一个,它的元素可能从一个表转移到另一个表。两个实体不能共享同一个集合类实例的引用。因为底层关系数据库模型的原因,集合值属性无法支持空值语义;Hibernate对空的集合引用和空集合不加区别。

你不需要过多的为此担心。就如同你平时使用普通的Java集合类一样来使用持久化集合类。只是要确认你理解了双向关联的语义(后文讨论)。

用于映射集合类的Hibernate映射元素取决于接口的类型。比如, <set> 元素用来映射Set类型的属性。

<class name="Product">
<id name="serialNumber" column="productSerialNumber"/>
<set name="parts">
<key column="productSerialNumber" not-null="true"/>
<one-to-many class="Part"/>
</set>
</class>

除了<set>,还有<list>, <map>, <bag>, <array><primitive-array> 映射元素。<map>具有代表性:

<map
name="propertyName"                                         (1)
table="table_name"                                          (2)
schema="schema_name"                                        (3)
lazy="true|false"                                           (4)
inverse="true|false"                                        (5)
cascade="all|none|save-update|delete|all-delete-orphan"     (6)
sort="unsorted|natural|comparatorClass"                     (7)
order-by="column_name asc|desc"                             (8)
where="arbitrary sql where condition"                       (9)
fetch="join|select|subselect"                               (10)
batch-size="N"                                              (11)
access="field|property|ClassName"                           (12)
optimistic-lock="true|false"                                (13)
node="element-name|."
embed-xml="true|false"
>
<key .... />
<map-key .... />
<element .... />
</map>
(1)

name 集合属性的名称

(2)

table (可选——默认为属性的名称)这个集合表的名称(不能在一对多的关联关系中使用)

(3)

schema (可选) 表的schema的名称, 他将覆盖在根元素中定义的schema

(4)

lazy (可选--默认为true) 可以用来关闭延迟加载,指定一直使用预先抓取(对数组不适用)

(5)

inverse (可选——默认为false) 标记这个集合作为双向关联关系中的方向一端。

(6)

cascade (可选——默认为none) 让操作级联到子实体

(7)

sort(可选)指定集合的排序顺序, 其可以为自然的(natural)或者给定一个用来比较的类。

(8)

order-by (可选, 仅用于jdk1.4) 指定表的字段(一个或几个)再加上asc或者desc(可选), 定义Map,Set和Bag的迭代顺序

(9)

where (可选) 指定任意的SQL where条件, 该条件将在重新载入或者删除这个集合时使用(当集合中的数据仅仅是所有可用数据的一个子集时这个条件非常有用)

(10)

fetch (可选, 默认为select) 用于在外连接抓取、通过后续select抓取和通过后续subselect抓取之间选择。

(11)

batch-size (可选, 默认为1) 指定通过延迟加载取得集合实例的批处理块大小("batch size")。

(12)

access(可选-默认为属性property):Hibernate取得属性值时使用的策略

(12)

乐观锁 (可选 - 默认为 true): 对集合的状态的改变会是否导致其所属的实体的版本增长。 (对一对多关联来说,关闭这个属性常常是有理的)

从集合类可以产生很大一部分映射,覆盖了很多常见的关系模型。我们建议你试验schema生成工具,来体会一下不同的映射声明是如何被翻译为数据库表的。

任何值集合或者多对多关联需要专用的具有一个或多个外键字段的collection table、一个或多个collection element column,以及还可能有一个或多个索引字段。

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

<element
column="column_name"                     (1)
formula="any SQL expression"             (2)
type="typename"                          (3)
length="N"
precision="N"
scale="N"
not-null="true|false"
unique="true|false"
node="element-name"
/>
(1)

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

(2)

formula (可选): 用于计算元素的SQL公式

(3)

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

多对多关联(many-to-many association) 使用 <many-to-many>元素定义.

<many-to-many
column="column_name"                               (1)
formula="any SQL expression"                       (2)
class="ClassName"                                  (3)
fetch="select|join"                                (4)
unique="true|false"                                (5)
not-found="ignore|exception"                       (6)
entity-name="EntityName"                           (7)
node="element-name"
embed-xml="true|false"
/>
(1)

column(可选): 这个元素的外键关键字段名

(2)

formula (可选): 用于计算元素外键值的SQL公式.

(3)

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

(3)

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

(4)

为此关联打开外连接抓取或者后续select抓取。这是特殊情况;对于一个实体及其指向其他实体的多对多关联进全预先抓取(使用一条单独的SELECT),你不仅需要对集合自身打开join,也需要对<many-to-many>这个内嵌元素打开此属性。

(5)

对外键字段允许DDL生成的时候生成一个惟一约束。这使关联变成了一个高效的一对多关联。(此句存疑:原文为This makes the association multiplicity effectively one to many.)

(6)

not-found (可选 - 默认为 exception): 指明引用的外键中缺少某些行该如何处理: ignore 会把缺失的行作为一个空引用处理。

(7)

entity-name (可选): 被关联的类的实体名,作为class的替代。

例子:首先, 一组字符串:

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

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

<bag name="sizes"
table="item_sizes"
order-by="size asc">
<key column="item_id"/>
<element column="size" type="integer"/>
</bag>

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

<array name="addresses"
table="PersonAddress"
cascade="persist">
<key column="personId"/>
<list-index column="sortOrder"/>
<many-to-many column="addressId" class="Address"/>
</array>

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

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

一个组件的列表:(下一章讨论)

<list name="carComponents"
table="CarComponents">
<key column="carId"/>
<list-index column="sortOrder"/>
<composite-element class="CarComponent">
<property name="price"/>
<property name="type"/>
<property name="serialNumber" column="serialNum"/>
</composite-element>
</list>

一对多关联通过外键连接两个类对应的表,而没有中间集合表。 这个关系模型失去了一些Java集合的语义:

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

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

一个从ProductPart的关联需要关键字字段,可能还有一个索引字段指向Part所对应的表。 <one-to-many>标记指明了一个一对多的关联。

<one-to-many
class="ClassName"                                  (1)
not-found="ignore|exception"                       (2)
entity-name="EntityName"                           (3)
node="element-name"
embed-xml="true|false"
/>
(1)

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

(2)

not-found (可选 - 默认为exception): 指明若缓存的标示值关联的行缺失,该如何处理: ignore 会把缺失的行作为一个空关联处理。

(3)

entity-name (可选): 被关联的类的实体名,作为class的替代。

例子

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

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

重要提示:如果一对多关联中的外键字段定义成NOT NULL,你必须把<key>映射声明为not-null="true",或者使用双向关联,并且标明inverse="true"。参阅本章后面关于双向关联的讨论。

下面的例子展示一个Part实体的map,把name作为关键字。( partNamePart的持久化属性)。注意其中的基于公式的索引的用法。

<map name="parts"
cascade="all">
<key column="productId" not-null="true"/>
<map-key formula="partName"/>
<one-to-many class="Part"/>
</map>

双向关联允许通过关联的任一端访问另外一端。在Hibernate中, 支持两种类型的双向关联:

一对多(one-to-many)

Set或者bag值在一端, 单独值(非集合)在另外一端

多对多(many-to-many)

两端都是set或bag值

 

要建立一个双向的多对多关联,只需要映射两个many-to-many关联到同一个数据库表中,并再定义其中的一端为inverse(使用哪一端要根据你的选择,但它不能是一个索引集合)。

这里有一个many-to-many的双向关联的例子;每一个category都可以有很多items,每一个items可以属于很多categories:

<class name="Category">
<id name="id" column="CATEGORY_ID"/>
...
<bag name="items" table="CATEGORY_ITEM">
<key column="CATEGORY_ID"/>
<many-to-many class="Item" column="ITEM_ID"/>
</bag>
</class>
<class name="Item">
<id name="id" column="CATEGORY_ID"/>
...
<!-- inverse end -->
<bag name="categories" table="CATEGORY_ITEM" inverse="true">
<key column="ITEM_ID"/>
<many-to-many class="Category" column="CATEGORY_ID"/>
</bag>
</class>

如果只对关联的反向端进行了改变,这个改变不会被持久化。 这表示Hibernate为每个双向关联在内存中存在两次表现,一个从A连接到B,另一个从B连接到A。如果你回想一下Java对象模型,我们是如何在Java中创建多对多关系的,这可以让你更容易理解:

category.getItems().add(item);          // The category now "knows" about the relationship
item.getCategories().add(category);     // The item now "knows" about the relationship
session.persist(item);                   // The relationship won''t be saved!
session.persist(category);               // The relationship will be saved

非反向端用于把内存中的表示保存到数据库中。

要建立一个一对多的双向关联,你可以通过把一个一对多关联,作为一个多对一关联映射到到同一张表的字段上,并且在"多"的那一端定义inverse="true"

<class name="Parent">
<id name="id" column="parent_id"/>
....
<set name="children" inverse="true">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="eg.Child">
<id name="id" column="id"/>
....
<many-to-one name="parent"
class="Parent"
column="parent_id"
not-null="true"/>
</class>

在“一”这一端定义inverse="true"不会影响级联操作,二者是正交的概念!

如果你完全信奉我们对于“联合主键(composite keys)是个坏东西”,和“实体应该使用(无机的)自己生成的代用标识符(surrogate keys)”的观点,也许你会感到有一些奇怪,我们目前为止展示的多对多关联和值集合都是映射成为带有联合主键的表的!现在,这一点非常值得争辩;看上去一个单纯的关联表并不能从代用标识符中获得什么好处(虽然使用组合值的集合可能会获得一点好处)。不过,Hibernate提供了一个(一点点试验性质的)功能,让你把多对多关联和值集合应得到一个使用代用标识符的表去。

<idbag> 属性让你使用bag语义来映射一个List (或Collection)。

<idbag name="lovers" table="LOVERS">
<collection-id column="ID" type="long">
<generator class="sequence"/>
</collection-id>
<key column="PERSON1"/>
<many-to-many column="PERSON2" class="eg.Person" outer-join="true"/>
</idbag>

你可以理解,<idbag>人工的id生成器,就好像是实体类一样!集合的每一行都有一个不同的人造关键字。但是,Hibernate没有提供任何机制来让你取得某个特定行的人造关键字。

注意<idbag>的更新性能要比普通的<bag>高得多!Hibernate可以有效的定位到不同的行,分别进行更新或删除工作,就如同处理一个list, map或者set一样。

在目前的实现中,还不支持使用identity标识符生成器策略来生成<idbag>集合的标识符。

在前面的几个章节的确非常令人迷惑。 因此让我们来看一个例子。这个类:

package eg;
import java.util.Set;
public class Parent {
private long id;
private Set children;
public long getId() { return id; }
private void setId(long id) { this.id=id; }
private Set getChildren() { return children; }
private void setChildren(Set children) { this.children=children; }
....
....
}

这个类有一个Child的实例集合。如果每一个子实例至多有一个父实例, 那么最自然的映射是一个one-to-many的关联关系:

<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
</hibernate-mapping>

在以下的表定义中反应了这个映射关系:

create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255), parent_id bigint )
alter table child add constraint childfk0 (parent_id) references parent

如果父亲是必须的, 那么就可以使用双向one-to-many的关联了:

<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children" inverse="true">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
<many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/>
</class>
</hibernate-mapping>

请注意NOT NULL的约束:

create table parent ( id bigint not null primary key )
create table child ( id bigint not null
primary key,
name varchar(255),
parent_id bigint not null )
alter table child add constraint childfk0 (parent_id) references parent

另外,如果你绝对坚持这个关联应该是单向的,你可以对<key>映射声明NOT NULL约束:

<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children">
<key column="parent_id" not-null="true"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
</hibernate-mapping>

另外一方面,如果一个子实例可能有多个父实例, 那么就应该使用many-to-many关联:

<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children" table="childset">
<key column="parent_id"/>
<many-to-many class="Child" column="child_id"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
</hibernate-mapping>

表定义:

create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255) )
create table childset ( parent_id bigint not null,
child_id bigint not null,
primary key ( parent_id, child_id ) )
alter table childset add constraint childsetfk0 (parent_id) references parent
alter table childset add constraint childsetfk1 (child_id) references child
评论

相关推荐

    559.557.JAVA基础教程_集合-Collections工具类常用方法的测试(559).rar

    最后,Collections工具类还包含一些实用的静态工厂方法,如`emptyList()`、`emptyMap()`和`singletonMap(K key, V value)`,它们分别用于创建空列表、空映射以及只有一个键值对的映射。 总结起来,Collections工具...

    java集合类演示源码

    集合类的框架为集合的实现者提供了大量的接口和抽象类,并对其中的某些机制给予了描述,例如,Iterator(迭代协议)。实现Comparable接口或Comparator接口,用户可以根据需要对集合中的元素进行排序。为了方便用户...

    第13讲 JAVA集合类.ppt

    Java集合类是Java编程语言中用于存储和管理对象的关键组件,它们构成了Java Collections Framework的核心。这个框架提供了一组高效、灵活的数据结构,使得开发者能够轻松地处理数据集合,而无需关心底层实现的复杂性...

    commons-collections4-4.2-bin

    Apache Commons Collections是一个强大的Java集合框架扩展库,它为Java的内置集合类提供了丰富的功能增强。这个"commons-collections4-4.2-bin"是Apache Commons Collections的第4版的第2次更新,是一个二进制分发包...

    collections4/collections15 jar

    这个库是Apache软件基金会提供的一个强大且实用的集合框架扩展,它为Java的内置集合类提供了更多的功能和优化。当您遇到"ClassNotDef(类未定义)错误",这通常意味着您的程序试图加载的类在当前的类路径中找不到。 ...

    Java集合类详解总结

    ### Java集合类详解总结 在Java编程中,集合框架(Collection Framework)是处理一组对象的强大工具,它提供了标准的数据结构来存储和操作这些对象。Java集合框架主要包括`Collection`、`Set`、`List`、`Queue`、`...

    java中常用集合类和接口.doc

    在Java编程中,集合类(Collections)是非常重要的组成部分,它们提供了灵活的数据管理和组织方式。本文将详细介绍Java中的主要集合类及其接口,帮助读者更好地理解和应用这些工具。 #### 二、为什么使用集合类 在...

    java集合类总结

    Collections工具类提供了大量静态方法,用于对集合进行操作,如排序、填充、反转、查找、替换等。Arrays类则是针对数组的操作工具,提供了排序、复制、填充等方法。 在Java集合框架中,各种接口和类之间的关系密切...

    APress - Java Collections

    《APress - Java Collections》这本书由John Zukowski编写,深入探讨了Java集合框架的各种细节,为读者提供了理解和应用Java集合类的重要知识。本书版权属于作者John Zukowski,并于2001年出版,所有权利受法律保护...

    commons-collections-3.2.2-bin.tar包

    1. **数据结构增强**:除了Java标准库中的List、Set、Map等,Collections库还提供了如Bag(多值集合)、BidiMap(双向映射)、MultiMap(多值映射)等特殊用途的数据结构,以满足更复杂的需求。 2. **转换和工厂...

    Hibernate教程16_集合映射

    通过分析这些文件,我们可以了解到如何在XML映射文件中定义集合映射,以及在Java实体类中如何使用这些映射。同时,通过运行这些示例,可以实际体验到集合映射在操作数据时的便利性。 总的来说,Hibernate的集合映射...

    commons-collections-3.2.2-bin.zip

    Apache Commons Collections的核心在于其对Java内置集合类的增强和补充。它提供了一系列接口和实现,包括: 1. **新数据结构**:如Bag(多值集合)、MapBag(键值对可重复的Map)、MultiMap(一个键可以对应多个值...

    commons-Collections最常用类介绍.pdf

    2. 操作类:以Collections和Arrays为代表的类,用于对集合类的实例进行复杂的操作。比如,Collections类可以用来对列表进行排序、查找、填充等操作。这些操作类通常不直接持有数据,而是通过调用容器类实例的方法来...

    Python标准库之collections包的使用教程

    collections模块提供了多种高效且功能丰富的集合类,如defaultdict、namedtuple、Counter等,帮助开发者编写更高效、更易读的代码。 首先,`defaultdict`是一个增强版的字典类,它继承自dict,并添加了一个`default...

    Apache Commons Collections

    4. **Bag接口**:Java标准库中的集合类不包含计数功能,但Commons Collections提供了`Bag`接口,它可以记录每个元素出现的次数。这在处理需要计数的场景中非常有用。 5. **双向映射**:`BidiMap`接口支持双向映射,...

    commons-beanutils、commons-collections、commons-collections等常用jar 包下载

    - 新的集合实现:例如双向映射、有序集合、多值映射等。 - 新的功能:如流API支持、更强大的函数式编程接口。 这三款库在Java开发中应用广泛,特别是在处理对象属性、集合操作和通用工具类时。它们使得代码更加...

    Java集合排序及java集合类详解.pdf

    7. 集合工具类Collections:提供了很多静态方法,可以对集合进行操作,例如排序、搜索、反转、混排等。 在文档《Java集合排序及java集合类详解.pdf》中,虽然由于OCR扫描的原因,内容存在一些文字错误和漏识别,但...

    commons-collections-3.2.1.jar

    Apache Commons Collections是Apache软件基金会的一个项目,它提供了一系列强大的、用于处理Java集合框架的工具类和算法。在这个项目中,`commons-collections-3.2.1.jar`是一个重要的库文件,包含了丰富的功能,...

    JAVA集合类.pdf

    另外,为了保证集合的线程安全,可以使用Collections.synchronizedXXX()方法将集合转换为线程安全的版本,或者使用Concurrent包下的集合类,如ConcurrentHashMap。 总之,Java集合框架提供了丰富的数据结构和操作...

    commons-collections4-4.4-bin.zip

    Apache Commons Collections是一个强大的Java集合框架扩展库,它在JDK的标准集合类库基础上增加了许多有用的功能和优化。这个"commons-collections4-4.4-bin.zip"文件包含了Apache Commons Collections的4.4版本,它...

Global site tag (gtag.js) - Google Analytics