首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java
映射集合和实体关联

本章内容

l    基本的集合映射策略

l    映射值类型的集合

l    映射父/子实体关系

有两个重要(且有时难以理解)的主题——集合的映射和实体类之间关联的映射并没有出现在前面的章节中。

大多数刚接触Hibernate的开发人员在第一次尝试映射典型的父/子关系(parent/child relationship)时,都要处理集合和实体关联。本章不会立即切入正题,而是从基本的集合映射概念和简单的例子开始,之后给出实体关联中的第一个集合,当然我们在第7章会讲到更复杂的实体关联映射。为了对这部分知识获得全面的了解,建议你阅读这两章。

6.1  值类型的set、bag、list和map

值类型(value type)的对象不具备数据库同一性,它属于一个实体实例,其持久化状态被嵌入到所拥有实体的表行中——至少,在实体有一个对值类型的单个实例的引用的情况下。如果实体类有一个值类型的集合(或者对值类型实例的引用的集合),就需要一张额外的表,即所谓的集合表。

在将值类型的集合映射到集合表之前,要记住,值类型的类没有标识符或者标识符属性。值类型实例的生命期限由所拥有的实体实例的生命期限决定。值类型不支持共享的引用。

Java具有一个丰富的集合API,因此可以选择最适合领域模型设计的集合接口和实现。我们来看一些最常见的集合映射。

假设CaveatEmptor的卖主能够给Item添加图片。图片只有通过包含的货品才能访问;它不需要支持来自系统中任何其他实体的关联。应用程序通过Item类管理图片集合,增加和移除元素。图片对象在集合之外没有生命,它依赖Item实体。

在这种情况下,把图片建模为值类型就不无道理了。接下来,需要决定使用什么集合。

6.1.1  选择集合接口

在Java领域模型中,集合属性的惯用语始终相同:

使用接口来声明属性的类型,而不是实现。选择一种匹配的实现并立即初始化集合,这么做避免了未被初始化的集合(我们不建议在构造函数或者设置方法中太迟初始化集合)。

如果使用JDK 5.0,可能用JDK集合的一般版本进行编码。注意这不是必需的,你也可以在映射元数据中显式地指定集合的内容。以下是一个典型的一般的Set,包含类型参数:

开箱即用,Hibernate支持最重要的JDK集合接口。换句话说,它知道如何以持久化的方式保存JDK集合、映射和数组的语义。每个接口都有一个Hibernate支持的匹配实现,并且使用正确的组合很重要。Hibernate只包装已经在字段的声明中初始化的集合对象(或者如果不是正确的对象,有时就替换它)。

不扩展Hibernate,而是从下列集合中选择:

l    使用<set>元素映射java.util.Set。使用java.util.HashSet初始化集合。它的元素顺序没有保存,并且不允许重复元素。这在典型的Hibernate应用程序中是最常见的持久化集合。

l    可以使用<set>映射java.util.SortedSet,且sort属性可以设置成比较器或者用于内存排序的自然顺序。使用java.util.TreeSet实例初始化集合。

l    可以使用<list>映射java.util.List,在集合表中用一个额外的索引列保存每个元素的位置。使用java.util.ArrayList初始化。

l    可以使用<bag>或者<idbag>映射java.util.Collection。Java没有Bag接口或者实现;然而,java.util.Collection允许包语义(可能的重复,不保存元素顺序)。Hibernate支持持久化的包(它内部使用列表,但是忽略元素的索引)。使用java.util.ArrayList初始化包集合。

l    可以使用<map>映射java.util.Map,保存键/值对。使用java.util.HashMap初始化属性。

l    可以使用<map>元素映射java.util.SortedMap,且sort属性可以设置为比较器或者用于内存排序的自然顺序。使用java.util.TreeMap实例初始化该集合。

l    Hibernate使用<primitive-array>(对于Java基本的值类型)和<array>(对于其他的一切)支持数组(array)。但是它们很少用在领域模型中,因为Hibernate无法包装数组属性。没有字节码基础设施(BCI),就失去了延迟加载,以及为持久化集合优化过的脏检查、基本的便利和性能特性。

JPA标准没有列出所有这些选项。可能的标准集合属性类型是Set、List、Collection和Map。不考虑数组。

此外,JPA规范仅仅指出集合属性持有对实体对象的引用。值类型的集合(例如简单的String实例)没有被标准化。然而,规范文件已经提及JPA的未来版本将支持可嵌入类的集合元素(换句话说,是值类型)。如果想要通过注解映射值类型的集合,将需要特定于供应商的支持。Hibernate Annotations包括了这种支持,我们期待许多其他的JPA供应商也同样支持。

如果想要映射Hibernate不直接支持的集合接口和实现,就要告诉Hibernate定制集合的语义。Hibernate中的扩展点称作PersistentCollection;通常扩展其中现有的PersistentSet、PersistentBag或者PersistentList类中的一个。定制持久化集合不太容易编写,如果你不是个经验丰富的Hibernate用户,我们也不建议这么做。示例作为Hibernate下载包的一部分,可以在Hibernate测试套件(test suite)源代码中找到。

现在快速看一下几种始终实现货品图片集合的场景。首先以XML格式映射它,然后利用Hibernate对集合注解的支持。现在,假设图片被保存在文件系统中的某个地方,并且在数据库中只保存了文件名。我们不讨论如何用这种方法保存和加载图片,而是关注映射。

6.1.2  映射set

最简单的实现是String图片文件名的Set。首先,把集合属性添加到Item类:

现在,在Item的XML元数据中创建下列映射:

图片的文件名保存在具名ITEM_IMAGE的集合表中。从数据库的观点来看,这张表是单个的实体,是一张单独的表,但是Hibernate为你隐藏了这一点。<key>元素在引用自己的实体的主键ITEM_ID的集合表中,声明了外键列。<element>标签把这个集合声明为值类型实例的一个集合——在这个例子中,是字符串的集合。

集无法包含重复元素,因此ITEM_IMAGE集合表的主键是<set>声明中这两个列的复合:ITEM_ID和FILENAME。可以在图6-1中见到这个Schema。

允许用户不止一次地添加同一张图片似乎不可能,但是假设你允许这么做,那么在这种情况下,哪种映射最合适呢?

图6-1 字符串集合的表结构和示例数据

6.1.3  映射标识符bag

允许重复元素的无序集合被称作包(bag)。奇怪的是,Java Collections框架没有包括包实现。然而,java.util.Collection接口有包语义,因此只需要一种相匹配的实现。你有两种选择:

l    用java.util.Collection接口编写集合属性,并在声明中用JDK的一个ArrayList对它进行初始化。在Hibernate中用标准的<bag>或者<idbag>元素映射集合。Hibernate有一个内建的PersistentBag可以处理列表;但与包的约定一致,它忽略元素在ArrayList中的位置。换句话说,你得到了一个持久化的Collection。

l    用java.util.List接口编写集合属性,并在声明中用JDK的一个ArrayList把它初始化。像前一个选项一样映射它,但是在领域模型类中公开了一个不同的集合接口。这种方法有效,但不建议使用,因为使用这个集合属性的客户端可能认为元素的顺序会始终被保存着,其实如果把它作为<bag>或者<idbag>映射,就并非如此了。

建议使用第一个选项。把Item类中images的类型由Set改为Collection,并用ArrayList把它初始化:

注意,设置方法接受Collection,它可以是JDK集合接口层次结构中的任何东西。然而,Hibernate聪明到足以在持久化集合的时候替换它。(它内部也依赖ArrayList,就像你在字段的声明中所做的那样。)

还必须修改集合表以允许重复FILENAME;表需要一个不同的主键。<idbag>映射添加了一个代理键列到集合表,很像用于实体类的合成标识符:

在这个例子中,主键是生成的ITEM_IMAGE_ID,就如在图6-2中所见。注意,主键的native生成器不支持<idbag>映射,必须指定一种具体的策略。这通常不成问题,因为无论如何,现实世界的应用程序都经常使用定制的标识符生成器。也可以用占位符隔离标识符生成策略;请见3.3.4节的第3小节。

图6-2 允许重复包元素的一个代理主键

还要注意,ITEM_IMAGE_ID列没有以任何方式公开给应用程序。Hibernate在内部管理它。

一种更为可能的场景是,你希望把添加图片到Item的顺序保存在那里。实现这个有许多种好方法,其中一种是使用真实的列表,而不是包。

6.1.4  映射list

首先,更新Item类:

<list>映射需要把一个索引列(index column)新增到集合表。索引列定义元素在集合中的位置。因而,Hibernate能够保存集合元素的顺序。映射集合为<list>:

(XML DTD中也有index元素,用于与Hibernate 2.x兼容。建议使用这个新的list-index;它比较不容易引起混淆,并且作用相同。)

集合表的主键是ITEM_ID和POSITION的复合。注意,现在允许重复元素(FILENAME)了,这与列表的语义一致,请见图6-3。

持久化列表的索引从0开始。可以改变它,例如在映射中使用<list-index base="1".../>。注意,如果数据库中的索引数字不连续,Hibernate就会把空元素添加到Java列表中。

图6-3 集合表保存每个元素的位置

另一种方法,可以映射一个Java数组而不是列表。Hibernate支持这个;数组映射事实上与前一个例子是等同的,除了使用不同的元素和属性名称之外(<array>和<array-index>)。然而,基于前面阐述过的原因,Hibernate应用程序很少使用数组。

现在,假设一件货品的图片除了文件名之外还有用户提供的名称。在Java中对这个进行建模的一种方法是映射,使用与键相同的名称以及与映射的值一样文件名。

6.1.5  映射map

再一次对这个Java类做小小的变化:

再次强调,映射<map>类似于映射列表。

集合表的主键是ITEM_ID和IMAGENAME的复合。IMAGENAME列保存映射的键。还是允许重复元素;请见图6-4中表的图表形式。

图6-4 把字符串作为索引和元素的映射表

这个映射是无序的。如果想要始终按图片的名称进行排序,该怎么办?

6.1.6  排序集合和有序集合

虽然英语单词遭到惊人的滥用,单词sorted和ordered对于Hibernate持久化集合却是指不同的东西。排序集合(sorted collection)是指用一个Java比较器在内存中进行排序。有序集合(ordered collection)则指用一个包含order by子句的SQL查询在数据库级中排列。

来把图片的映射变成一个排了序的映射。首先,要改变Java属性的初始化为java.util. TreeMap,并转换到java.util.SortedMap接口:

如果把它映射为排序,Hibernate会相应地处理这个集合:

通过指定sort="natural",告诉Hibernate使用SortedMap,并根据java.lang.String的compareTo()方法对图片名称进行排序。如果需要一些其他的排序算法(例如反向字母顺序),可以在sort属性中指定实现java.util.Comparator的类名称。例如:

像下面这样映射java.util.SortedSet(包含一个java.util.TreeSet实现):

包不可能被排序(可惜没有TreeBag),列表(list)也一样;列表元素的顺序由列表索引定义。

另一种方法,不转换到Sorted*接口(和Tree*实现),你或许想要使用一个链接映射(Linked Map),并在数据库端而不是内存中给元素排序。在Java类中保留Map/HashMap声明,并创建下列映射:

order-by属性中的表达式是SQL order by子句的一个片段。在这个例子中,在集合的加载期间,Hibernate按IMAGENAME列的升序排列集合元素。甚至可以在order-by属性中包括SQL函数调用:

可以按集合表的任何列进行排列。Hibernate内部使用LinkedHashMap,它是保存关键元素插入顺序的一个映射的变形。换句话说,就是在集合的加载期间,Hibernate把元素添加到集合的顺序,就是你在应用程序中见到的迭代顺序。用Set也可以完成同样的工作:Hibernate内部使用LinkedHashSet。在Java类中,属性是一般的Set/HashSet,但是Hibernate内部包含LinkedHashSet的包装再次通过order-by属性启用了排列:

也可以让Hibernate在集合的加载期间为你排列包的元素。Java集合属性是Collection/ ArrayList或者List/ArrayList。Hibernate内部使用ArrayList来实现一个保存了插入—迭代顺序的包:

Hibernate内部用于集和映射的链接集合只在JDK 1.4或者更高的版本中可用;更早的JDK没有LinkedHashMap和LinkedHashSet。有序包在所有JDK版本中都可用;内部使用ArrayList。

在真实的系统中,你可能需要保存图片名称和文件名之外的更多东西。你或许想要给这些额外的信息创建一个Image类。对于组件的集合来说,这是一个完美的使用案例。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论