入门 24 - 延迟初始 Lazy Initialization
首先我们来看看这个主题:
入门 11 - Set 映射
依这个主题所完成的例子,请将Hibernate的show_sql设定为true,当我们使用下面的程序时,观看控制台所使用的SQL:
HibernateTest.java
import onlyfun.caterpillar.*;
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.*;
import java.util.*;
public class HibernateTest {
public static void main(String[] args) throws HibernateException {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
List users = session.find("from User");
session.close();
sessionFactory.close();
}
}
SQL运作的例子如下:
Hibernate: select user0_.USER_ID as USER_ID, user0_.NAME as NAME from USER user0_
Hibernate: select addrs0_.USER_ID as USER_ID__, addrs0_.ADDRESS as ADDRESS__ from ADDRS addrs0_ where addrs0_.USER_ID=?
Hibernate: select addrs0_.USER_ID as USER_ID__, addrs0_.ADDRESS as ADDRESS__ from ADDRS addrs0_ where addrs0_.USER_ID=?
可以看到的,除了从USER表格中读取数据之外,还向ADDRS表格读取数据,预设上,Hibernate会将所有关联到的对象,透过一连串的SQL语 句读取并加载数据,然而现在考虑一种情况,我们只是要取得所有USER的名称,而不用取得它们的邮件地址,此时自动读取相关联的对象就是不必要的。
在Hibernate中,集合类的映像可以延迟初始(Lazy Initialization),也就是在真正索取该对象的数据时,才向数据库查询,就这个例子来说,就是我们在读取User时,先不取得其中的 addrs属性中之对象数据,由于只需要读取User的name属性,此时我们只要执行一次select即可,真正需要addrs的数据时,才向数据库要 求。
要使用Hibernate的延迟初始功能,只要在集合类映像时,加上lazy="true"即可,例如在我们的User.hbm.xml中的<set>中如下设定:
User.hbm.xml
<set name="addrs" table="ADDRS" lazy="true">
<key column="USER_ID"/>
<element type="string" column="ADDRESS" not-null="true"/>
</set>
我们来看看下面这个程序:
HibernateTest.java
import onlyfun.caterpillar.*;
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.*;
import java.util.*;
public class HibernateTest {
public static void main(String[] args) throws HibernateException {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
List users = session.find("from User");
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
System.out.println(user.getName());
Object[] addrs = user.getAddrs().toArray();
for(int i = 0; i < addrs.length; i++) {
System.out.println("/taddress " + (i+1) + ": " + addrs[i]);
}
}
session.close();
sessionFactory.close();
}
}
在没有使用延迟初始时,控制台会显示以下的内容:
Hibernate: select user0_.USER_ID as USER_ID, user0_.NAME as NAME from USER user0_
Hibernate: select addrs0_.USER_ID as USER_ID__, addrs0_.ADDRESS as ADDRESS__ from ADDRS addrs0_ where addrs0_.USER_ID=?
Hibernate: select addrs0_.USER_ID as USER_ID__, addrs0_.ADDRESS as ADDRESS__ from ADDRS addrs0_ where addrs0_.USER_ID=?
caterpillar
address 1: caterpillar@caterpillar.onlyfun.net
address 2: justin@caterpillar.onlyfun.net
address 3: justin@fake.com
momor
address 1: momor@fake.com
address 2: momor@caterpillar.onlyfun.net
如果使用延迟初始,则会出现以下的内容:
Hibernate: select user0_.USER_ID as USER_ID, user0_.NAME as NAME from USER user0_
caterpillar
Hibernate: select addrs0_.USER_ID as USER_ID__, addrs0_.ADDRESS as ADDRESS__ from ADDRS addrs0_ where addrs0_.USER_ID=?
address 1: caterpillar@caterpillar.onlyfun.net
address 2: justin@caterpillar.onlyfun.net
address 3: justin@fake.com
momor
Hibernate: select addrs0_.USER_ID as USER_ID__, addrs0_.ADDRESS as ADDRESS__ from ADDRS addrs0_ where addrs0_.USER_ID=?
address 1: momor@fake.com
address 2: momor@caterpillar.onlyfun.net
请注意SQL语句出现的位置,在使用延迟初始功能前,会将所有相关联到的数据一次查完,而使用了延迟初始之后,只有在真正需要addrs的数据时,才会使用SQL查询相关数据。
Hibernate实现延迟初始功能的方法,是藉由实现一个代理对象(以Set来说,其实现的代理子类是 net.sf.hibernate.collection.Set),这个代理类实现了其所代理之对象之相关方法,每个方法的实现实际上是委托(delegate)真正的对象,查询时加载的是代理对象,在真正呼叫对象的相关方法之前,不会去初始真正的对象来执行被呼叫的方法。
所以为了能使用延迟初始,您在使用集合映像时,在宣告时必须是集合类的接口,而不是具体的实现类(例如宣告时使用Set,而不是HashSet)。
使用延迟初始的一个问题是,由于在需要时才会去查询数据库,所以session不能关闭,如果在session关闭后,再去要求被关联的对象,将会发生LazyInitializationException,像是:
Set addrs = user.getAddrs();
session.close();
// 下面这句会发生LazyInitializationException
Object[] addrs = user.getAddrs().toArray();
如果您使用了延迟初始,而在某些时候仍有需要在session关闭之后取得相关对象,则可以使用Hibernate.initialize()来先行加载相关对象,例如:
Hibernate.initialize(user.getAddrs());
session.close();
Set add = user.getAddrs();
Object[] addo = user.getAddrs().toArray();
延迟初始只是Hibernate在取得数据时的一种策略,目的是为了调节数据库存取时的时机以取得一些效能,除了延迟初始之外,还有其它的策略来调整资料库存取的方法与时机,这部份牵涉的讨论范围很大,有兴趣的话,可以参考Hibernate in Action的4.4.5。