19.5. 理解集合性能(Understanding Collection performance)

前面我们已经对集合进行了足够的讨论。本段中,我们将着重讲述集合在运行时的事宜。

19.5.1. 分类(Taxonomy)

Hibernate定义了三种基本类型的集合:

  • 值数据集合

  • 一对多关联

  • 多对多关联

这个分类是区分了不同的表和外键关系类型,但是它没有告诉我们关系模型的所有内容。 要完全理解他们的关系结构和性能特点,我们必须同时考虑“用于Hibernate更新或删除集合行数据的主键的结构”。 因此得到了如下的分类:

  • 有序集合类

  • 集合(sets)

  • 包(bags)

所有的有序集合类(maps, lists, arrays)都拥有一个由<key><index>组成的主键。 这种情况下集合类的更新是非常高效的——主键已经被有效的索引,因此当Hibernate试图更新或删除一行时,可以迅速找到该行数据。

Sets have a primary key consisting of <key> and element columns. This may be less efficient for some types of collection element, particularly composite elements or large text or binary fields; the database may not be able to index a complex primary key as efficiently. On the other hand, for one to many or many to many associations, particularly in the case of synthetic identifiers, it is likely to be just as efficient. (Side-note: if you want SchemaExport to actually create the primary key of a <set> for you, you must declare all columns as not-null="true".)

<idbag>映射定义了代理键,因此它总是可以很高效的被更新。事实上, <idbag>拥有着最好的性能表现。

Bag是最差的。因为bag允许重复的元素值,也没有索引字段,因此不可能定义主键。 Hibernate无法判断出重复的行。当这种集合被更改时,Hibernate将会先完整地移除 (通过一个(in a single DELETE))整个集合,然后再重新创建整个集合。 因此Bag是非常低效的。

请注意:对于一对多关联来说,“主键”很可能并不是数据库表的物理主键。 但就算在此情况下,上面的分类仍然是有用的。(它仍然反映了Hibernate在集合的各数据行中是如何进行“定位”的。)

19.5.2. Lists, maps 和sets用于更新效率最高

根据我们上面的讨论,显然有序集合类型和大多数set都可以在增加、删除、修改元素中拥有最好的性能。

可论证的是对于多对多关联、值数据集合而言,有序集合类比集合(set)有一个好处。因为Set的内在结构, 如果“改变”了一个元素,Hibernate并不会更新(UPDATE)这一行。 对于Set来说,只有在插入(INSERT)删除(DELETE) 操作时“改变”才有效。再次强调:这段讨论对“一对多关联”并不适用。

注意到数组无法延迟载入,我们可以得出结论,list, map和idbags是最高效的(非反向)集合类型,set则紧随其后。 在Hibernate中,set应该时最通用的集合类型,这时因为“set”的语义在关系模型中是最自然的。

但是,在设计良好的Hibernate领域模型中,我们通常可以看到更多的集合事实上是带有inverse="true" 的一对多的关联。对于这些关联,更新操作将会在多对一的这一端进行处理。因此对于此类情况,无需考虑其集合的更新性能。

19.5.3. Bag和list是反向集合类中效率最高的

在把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();

19.5.4. 一次性删除(One shot delete)

偶尔的,逐个删除集合类中的元素是相当低效的。Hibernate并没那么笨, 如果你想要把整个集合都删除(比如说调用list.clear()),Hibernate只需要一个DELETE就搞定了。

假设我们在一个长度为20的集合类中新增加了一个元素,然后再删除两个。 Hibernate会安排一条INSERT语句和两条DELETE语句(除非集合类是一个bag)。 这当然是显而易见的。

但是,假设我们删除了18个数据,只剩下2个,然后新增3个。则有两种处理方式:

  • 逐一的删除这18个数据,再新增三个;

  • 删除整个集合类(只用一句DELETE语句),然后增加5个数据。

Hibernate还没那么聪明,知道第二种选择可能会比较快。 (也许让Hibernate不这么聪明也是好事,否则可能会引发意外的“数据库触发器”之类的问题。)

幸运的是,你可以强制使用第二种策略。你需要取消原来的整个集合类(解除其引用), 然后再返回一个新的实例化的集合类,只包含需要的元素。有些时候这是非常有用的。

显然,一次性删除并不适用于被映射为inverse="true"的集合。