转自:http://www.javaworld.com.tw/confluence/pages/viewpage.action?pageId=1840
首先我們來看看這個主題:
依這個主題所完成的例子,請將Hibernate的show_sql設定為true,當我們使用下面的程式時,觀看控制台所使用的SQL:
HibernateTest.javaimport 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.javaimport 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: [email protected]
address 2: [email protected]
address 3: [email protected]
momor
address 1: [email protected]
address 2: [email protected]
如果使用延遲初始,則會出現以下的內容:
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: [email protected]
address 2: [email protected]
address 3: [email protected]
momor
Hibernate: select addrs0_.USER_ID as USER_ID__, addrs0_.ADDRESS as ADDRESS__ from ADDRS addrs0_ where addrs0_.USER_ID=?
address 1: [email protected]
address 2: [email protected]
請注意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。