我们已经在集合类(collections)上面花了很多口舌了。这一章,我们要着重关注集合类在运行时的一些问题。
Hibernate定义了三种不同的基本集合类
值集合
一对多关联
多对多关联
这个分类方法是根据不同的表和外键关系来区分的,但是没有确切的告诉我们关系模型。要完整的了解关系结构和性能的区别,我们必须考虑Hibernate再更新或者删除集合类记录时的主键结构。这样的话,我们会得到如下的分类:
有序集合类
集合(sets)
包(bags)
所有的有序集合类(map,list,array)都有一个由<key>和<index>组合的主键字段。这种集合类的更新非常高效——主键有效地排了序,Hibernate要更新或者删除一个元素的时候可以很高效的找到它。
集合(Sets)有一个由<key>和某个元素字段组成的主键。对于某些元素类型,特别是组合元素以及大本文、二进制字段,效率会来的比较低;数据库无法对复杂的主键有效索引。另一方面,对一对多关系或者多对多关系来说,特别是使用“人造”的标识符的时候,它的性能同样出色。(注:如果你希望SchemaExport为你创建一个<set>的主键,你必须把所有的字段都声明成non-null="true"。)
包(Bags)是最差劲的。因为包允许元素值重复,也没有索引字段,所以无法定义主键。Hibernate没有办法来区分重复的行。Hibernate的处理方法是,每当更改的时候,完全删除(用一个DELETE),再重新创建这个集合类。这可能是效率极低的。
请注意,对于一对多关联,"主键"可能不是数据库表的物理主键——当时就算考虑这种情况,上面分类描述仍然是正确的。(反映了Hibernate是如何在不同的集合类中“定位”某条记录的。)
根据我们上面的讨论,显然有序类型和大多数set可以在增加/删除/修改元素的时候得到最好的性能。
但是,在多对多关联,或者对值元素而言,有序集合类比集合(set)有一个好处。因为Set的结构,如果“改变”了一个元素,Hibernate并不会UPDATE这一行。对Set来说,只有INSERT和DELETE才有效。注意这一段描述对一对多关联并不适用。
注意到数组无法延迟转载,我们可以得出结论,list, map和set是最高效的集合类型。(当然,我们警告过了,由于集合中的值的关系,set可能性能下降。)
Set可以被看作是Hibernate程序中最普遍的集合类型。
这个版本的Hibernate有一个没有写在文档中的功能。<idbag>可以对值集合和多对多关联实现bag语义,并且性能比上面任何类型都高!
好了,在你把bag扔到水沟里面再踩上一只脚之前,有一种情况下bag(包括list)要比set性能高得多。对于指明了inverse="true"的集合类(比如说,标准的双向一对多关联),我们可以在不初始化(fetch)包元素的情况下就增加新元素!这是因为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不要这么聪明也是好事,否则可能会引发意外的数据库触发器什么的。)
幸运的是,你可以强制使用第二种策略。你需要把原来的整个集合类都取消(取消其引用),然后返回一个新实例化的集合类,只包含需要的元素。有些时候这是非常有用的。