Hibernate允许在元数据中指定默认的关联加载策略。你可以通过重写Hibernate查询方法来修改默认的策略。这里有个小建议:你不需要立即理解所有的选项;我们建议先整体浏览这部分的内容,在以后的程序可以作为参考。
在Hibernate的映射中,集合的映射和普通对象的映射有不同的地方,所以,将分别来讨论这两种情况。首先来考虑Bid和Item之间的双向关联。
单点关联
对于<many-to-one>或者<one-to-one>关联来说,如果类的映射制定了代理的情况下可以使用延迟加载的策略。对于Item类,指定lazy='true':
<class name="Item" lazy="true">
现在,记住关联是从Bid指向Item的:
<many-to-one name="item" class="Item">
当从数据库中取得Bid的时候,关联的属性可能会持有一个代理对象,这个代理对象会代理所有的方法调用,采用的方式延迟从数据库中的加载。
Hibernate使用了两个不同的对象,所以即使是动态的关联也能被代理,当取得代理对象的时候,它可能是Item的一个子类。我们甚至可以选择Item实现的接口作为代理。这种方式可以通过proxy属性来实现,当然这代替了lazy="true":
<class name="Item" proxy="ItemInterface">
只要我们在Item指定了proxy或者lazy的属性的话,那么任何指向Item的关联都会被代理并且延迟加载,除非通过outer-join重写了这种关联策略。
对于outer-join,有三种可能的值:
1)outer-join="auto",默认设置。当这个属性没有被指定时,如果制定了proxy属性,那么会延迟加载关联的对象,如果没有指定,则会通过外连接来加载。
2)Outer-join="true",Hibernate会通过外连接来取得数据,而不管是否指定了proxy属性。这种方法允许你利用同一个代理类来为不同的关联指定的不同的加载策略。
3)Outer-join="false",就算proxy属性被禁用了,仍然不会通过外连接来加载数据。当数据存在于二级缓存的时候,这种策略的用处很大,如果数据不存在的话,那么会立即通过SQL SELECT来加载数据。
因此,如果你想为关联关系指定eager加载的话,假定proxy已经被指定了,我们可以作如下配置:
<many-to-one name="item" class="Item" constrained="true">
对于一对一关联来说,延迟加载只有在关联对象一直存在的时候才有效。通过指定constrained="true"来实现。例如,如果item只有一个bid,那么对于Bid的映射应该如下:
<one-to-one name="item" class="Item" constrained="true">
Constrained属性告诉Hibernate关联对象是必须的,所以不能为null。
为了实现批量加载,我们在Item的映射中指定batch-size:
<class name="Item" lazy="true" batch-size="9">
Batch size属性限制了一次单独批量加载的对象的数目。
在对于集合的配置中,也会遇到同名的属性,但是其意义就完全不同。
集合
对于集合来说,加载策略不在仅仅是针对实体关联本身,同时也针对集合的值。
和类一样,集合也拥有属于自己的代理,通常被叫做集合包装器。而与类不同的是,集合的包装器一直存在,就算没有指定延迟加载。
集合映射可以指定lazy属性,一个outer-join属性。你可以同时指定它们或同时不指定它们。当然同时指定没有任何意义。下面列出有用的选项:
1)同时不指定,相当于outer-join="false",lazy="false"。集合会通过二级缓存或者通过SQL立即加载数据。这是默认的配置,同时在二级缓存被设置的时候也是最用的。
2)Outer-join="true",Hibernate通过外连接来取得数据,对于这种设置方式,Hibernate对于一个集合智慧去一次数据,因此当一个持久化类拥有多个集合的时候,不能使用这种方式。
3)Lazy="true",当集合第一次被访问的时候,采取加载数据。
对于集合来说,我们并不推荐eager加载,因此我们为集合配置lazy="true"。这也是集合类型最常用的选项。
我们也可以对集合设置批量加载。在这种情况下,batch size不再集合中bid的数量,而是集合的数量。
这个映射告诉Hibernate在一次加载中取得9个集合。从另外一方面说,如果在当前的session中存在5个Item的实例的话,Hibernate会在同一个SQL查询中同时加载5个集合。如果有11个items的话,那么只有9个被加载。在层次化的对象体系中,批量加载能有效的减少查询的数量。
让我们来讨论一个比较特别的例子:多对多关联。通常你会采用一个中间表来表示两个多对多关联的表之间的关联关系。这个中间表被认为是你决定使用eager加载的因素。请看下面多对多的关联的例子,这个关联从Category指向Item:
在这个例子中,eager加载策略仅仅对CATEGORY_ITEM有效。如果我们通过这种策略来加载一个Category的话,Hibernate会自动在CATEGORY_ITEM中取得所有的关联的实体,而不是指向ITEM的实例。
这些关联在多对多的关良已经被指定,当然可以通过相同的SQL查询来实现eager加载。下面是其配置:
现在Hibernate不会在Category被加载的时候通过一个单一的查询来加载所有的Items。然而,延迟加载仍然作为一个默认的加载策略,而对于每一个持久化类仅仅局限于一个集合采取eager加载。
设置加载深度
现在我们来讨论一个全局的加载策略的设置:最大加载深度。这个设置控制了在一次查询中外连接表的数目。考虑完整的关联从Category到Item,再从Item到Bid。第一个是多对多关联而第二个是一对多关良;因此两个关联都被映射为集合。如果我们为每个关联都设置了outer-join="true"的话,那么在加载一个Category实例的时候,有多少查询会被执行呢?是不是只有Items被eager加载了呢?还是Item所有的Bids?
你可能期望通过一个单一的查询来完成包含CATEGORY,CATEGORY_ITEM,ITEM和BID表。然而,这并不是默认的情况。
Hibernate外连接的行为被hibernate.max_fetch_depth的全局选项控制。如果这个值设为1,Hibernate会取得Category以及在CATEGORY_ITEM中的链接。如果设置为2,那么会在查询中包含Items。设置为3的话,那么就会包含Bids。
在这里建议外连接查询的深度由你的数据库表的数目决定;首先用小于4的数字来进行测试,然后增加或者减少这个数字。这个全局的选项同样也作用于采用eager加载策略的单个关联的映射。
请记住eager加载策略仅仅在通过标识符并且使用criteria查询的时候或者通过手工的遍历对象图的时候有效。任何通过HQL的查询都可以在运行时指定具体的策略,这样做的话就会忽略默认的配置。
但是,有时你仅仅是想简单的通过代理或者集合包装器来完成这些任务。
初始化延迟关联
当任何属于其的方法被调用的时候,代理或者集合包装器都会被自动的初始化。然而,必须通过一个已经存在的Session进行。如果你关闭了Session还要试图访问代理或者集合包装器的话,那么只会抛出异常。
正是由于以上原因,在关闭session之前显式的初始化一个对象是非常有用的。当然这种方法并不像那种通过HQL查询得到对象那么方便。
我们使用静态方法Hibernate.initialize()来手工实现初始化:
Hibernate.initialize()方法可以传给集合包装器或者代理对象。也可以通过调用Hibernate.isInitialized()方法来查询当前的状态。
另一种解决方案就是保持session一直处于open状态。当然这回造成程序设计以及事务划分的问题;在第八章将会讨论这个问题。当然首选你的第一选择仍然是通过HQL或者criteria来取得数据。