前面我们已经对集合进行了足够的讨论。本段中,我们将着重讲述集合在运行时的事宜。
Hibernate定义了三种基本类型的集合:
值数据集合
一对多关联
多对多关联
这个分类是区分了不同的表和外键关系类型,但是它没有告诉我们关系模型的所有内容。 要完全理解他们的关系结构和性能特点,我们必须同时考虑“用于Hibernate更新或删除集合行数据的主键的结构”。 因此得到了如下的分类:
有序集合类
集合(sets)
包(bags)
所有的有序集合类(maps, lists, arrays)都拥有一个由<key>
和
<index>
组成的主键。
这种情况下集合类的更新是非常高效的——主键已经被有效的索引,因此当Hibernate试图更新或删除一行时,可以迅速找到该行数据。
集合(sets)的主键由<key>
和其他元素字段构成。
对于有些元素类型来说,这很低效,特别是组合元素或者大文本、大二进制字段;
数据库可能无法有效的对复杂的主键进行索引。
另一方面,对于一对多、多对多关联,特别是合成的标识符来说,集合也可以达到同样的高效性能。(
附注:如果你希望SchemaExport
为你的<set>
创建主键,
你必须把所有的字段都声明为not-null="true"
。)
<idbag>
映射定义了代理键,因此它总是可以很高效的被更新。事实上,
<idbag>
拥有着最好的性能表现。
Bag是最差的。因为bag允许重复的元素值,也没有索引字段,因此不可能定义主键。
Hibernate无法判断出重复的行。当这种集合被更改时,Hibernate将会先完整地移除
(通过一个(in a single DELETE
))整个集合,然后再重新创建整个集合。
因此Bag是非常低效的。
请注意:对于一对多关联来说,“主键”很可能并不是数据库表的物理主键。 但就算在此情况下,上面的分类仍然是有用的。(它仍然反映了Hibernate在集合的各数据行中是如何进行“定位”的。)
根据我们上面的讨论,显然有序集合类型和大多数set都可以在增加、删除、修改元素中拥有最好的性能。
可论证的是对于多对多关联、值数据集合而言,有序集合类比集合(set)有一个好处。因为Set
的内在结构,
如果“改变”了一个元素,Hibernate并不会更新(UPDATE)
这一行。
对于Set
来说,只有在插入(INSERT)
和删除(DELETE)
操作时“改变”才有效。再次强调:这段讨论对“一对多关联”并不适用。
注意到数组无法延迟载入,我们可以得出结论,list, map和idbags是最高效的(非反向)集合类型,set则紧随其后。 在Hibernate中,set应该时最通用的集合类型,这时因为“set”的语义在关系模型中是最自然的。
但是,在设计良好的Hibernate领域模型中,我们通常可以看到更多的集合事实上是带有inverse="true"
的一对多的关联。对于这些关联,更新操作将会在多对一的这一端进行处理。因此对于此类情况,无需考虑其集合的更新性能。
在把bag扔进水沟之前,你必须了解,在一种情况下,bag的性能(包括list)要比set高得多:
对于指明了inverse="true"
的集合类(比如说,标准的双向的一对多关联),
我们可以在未初始化(fetch)包元素的情况下直接向bag或list添加新元素!
这是因为Collection.add()
)或者Collection.addAll()
方法
对bag或者List总是返回true(这点与与Set不同)。因此对于下面的相同代码来说,速度会快得多。
Parent p = (Parent) sess.load(Parent.class, id); Child c = new Child(); c.setParent(p); p.getChildren().add(c); //no need to fetch the collection! sess.flush();
偶尔的,逐个删除集合类中的元素是相当低效的。Hibernate并没那么笨, 如果你想要把整个集合都删除(比如说调用list.clear()),Hibernate只需要一个DELETE就搞定了。
假设我们在一个长度为20的集合类中新增加了一个元素,然后再删除两个。
Hibernate会安排一条INSERT
语句和两条DELETE
语句(除非集合类是一个bag)。
这当然是显而易见的。
但是,假设我们删除了18个数据,只剩下2个,然后新增3个。则有两种处理方式:
逐一的删除这18个数据,再新增三个;
删除整个集合类(只用一句DELETE语句),然后增加5个数据。
Hibernate还没那么聪明,知道第二种选择可能会比较快。 (也许让Hibernate不这么聪明也是好事,否则可能会引发意外的“数据库触发器”之类的问题。)
幸运的是,你可以强制使用第二种策略。你需要取消原来的整个集合类(解除其引用), 然后再返回一个新的实例化的集合类,只包含需要的元素。有些时候这是非常有用的。
显然,一次性删除并不适用于被映射为inverse="true"
的集合。