Hibernate和延遲加載
Hibernate對象關系映射提供了兩種對象初始化模式:延遲加載和非延遲加載。非延遲加載在加載時獲取對象本身以及它關聯的所有對象。這可能導致在獲取一個實例時,執行成百上千的select語句。當使用雙向關聯時,這個問題被放大,常常出現初始化請求時,整個數據庫都被載入。顯然檢查每個對象的關系,並手工刪除他們會費點事,但最終我們可能會因此丟失使用ORM工具的優勢。一個明細的解決方式是使用hibernate提供的延遲載入機制。這種初始化策略在類成員被訪問時只載入它的一個對象的一對多和多對多關系。對開發人員來說,這種方式是透明的,並且只有最少數量的請求發生,這樣就獲得了最佳的性能。這種技術的一個缺點是延遲載入要求當對象還在使用中時,Hibernate的Session必須保持打開狀態。當嘗試通過DAO模式抽象持久層時,這會引起一個重要問題。為了充分地抽象持久層,所有的數據庫邏輯,包括打開、關閉Session都不能在應用層出現。最常見的是,這些邏輯隱藏在DAO的實現類中。快速和差一些的方案是:避免采用DAO模式,在應用層中包含數據連接的邏輯。這在小應用中是可行的,但在大系統中,這會是一個設計缺陷,它損害了應用的擴展性。
在Web層使用延遲加載
幸運的是,Spring框架已經提供了一個DAO模式結合Hibernate延遲加載的Web方案。對於任何不熟悉Spring框架結合Hibernate人來說,我在這裡不會深入細節,但是我希望你去閱讀“結合Spring框架的Hibernate數據庫訪問”章節。這個案例是一個Web應用,Spring提供了OpenSessionInViewerFilter和OpenSessionInViewInterceptor。使用它們中的任一個都能獲得同樣的功能。這兩者唯一不同的是interceptor在Spring容器中運行,並且在web應用的上下文中配置;fitler在Spring前運行,並且在web.xml中配置。不管使用哪一個,他們都會在請求綁定到Session的當前線程期間打開Hibernate Session。一旦綁定到線程,打開的Hibernate Session能被DAO的實現類透明地使用。Session會持續打開允許延加載訪問數據庫。一旦View邏輯完成,hibernate session會被關閉,無論是在Filter的doFilter方法中還是在Interceptor的postHandle方法中。下面是一個配置實例:
Interceptor配置
<beans>
<bean id="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="openSessionInViewInterceptor"/>
</list>
</property>
<property name="mappings">
...
</bean>
...
<bean name="openSessionInViewInterceptor"
class="org.springframework.orm.hibernate.support.OpenSessionInViewInterceptor">
<property name="sessionFactory"><ref bean="sessionFactory"/></property>
</bean>
</beans>
Filter配置
<web-app>
...
<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate.support.OpenSessionInViewFilter
</filter-class>
</filter>
...
<filter-mapping>
<filter-name>hibernateFilter</filter-name>
<url-pattern>*.spring</url-pattern>
</filter-mapping>
...
</web-app>
使用打開的session的HibernateDAO實現類很簡單。實際上,如果你已經使用Spring框架實現Hibernate的DAO對象,最有可能的是,你不
需要做任何改動。DAO必須通過方便的HibernateTemplate工具訪問Hibernate,這對數據庫訪問來說就是小菜一碟。下面是一個DAO的實例。
DAO實例
public class HibernateProductDAO extends HibernateDaoSupport implements ProductDAO {
public Product getProduct(Integer productId) {
return (Product)getHibernateTemplate().load(Product.class, productId);
}
public Integer saveProduct(Product product) {
return (Integer) getHibernateTemplate().save(product);
}
public void updateProduct(Product product) {
getHibernateTemplate().update(product);
}
}
在業務層使用延遲加載
甚至在表現層外,Spring框架也通過AOP攔截器HibernateInterceptor提供了便利的延遲加載支持。hibernate攔截器透明地攔截了配置在
Spring應用上下文中的業務對象的調用,在調用前打開hibernate session,在調用結束時關閉這個session。讓我們通過一個簡單的例子
來說明。假設我們有一個interface叫做BussinessObject:
public interface BusinessObject {
public void doSomethingThatInvolvesDaos();
}
類BusinessObjectImpl實現了BusinessObject接口:
public class BusinessObjectImpl implements BusinessObject {
public void doSomethingThatInvolvesDaos() {
// lots of logic that calls
// DAO classes Which access
// data objects lazily
}
}
通過Spring上下文的一些配置,我們可以讓HibernateInterceptor攔截對BusinessObjectImpl的調用,允許它的方法延遲訪問數據對象。
看一下下面的片斷:
<beans>
<bean id="hibernateInterceptor" class="org.springframework.orm.hibernate.HibernateInterceptor">
<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property>
</bean>
<bean id="businessObjectTarget" class="com.acompany.BusinessObjectImpl">
<property name="someDAO"><ref bean="someDAO"/></property>
</bean>
<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target"><ref bean="businessObjectTarget"/></property>
<property name="proxyInterfaces">
<value>com.acompany.BusinessObject</value>
</property>
<property name="interceptorNames">
<list>
<value>hibernateInterceptor</value>
</list>
</property>
</bean>
</beans>
當businessObject的實例被引用,HibernateInterceptor打開一個hibernate session並允許對BussinessObjectImpl的調用。當
BusinessOjbectImpl執行完成,HibernateInterceptor透明的關閉這個session。應用代碼並不知道任何持久層邏輯,但它仍然能夠使用
延遲加載訪問數據對象。
在單元測試中使用延遲加載
最後,我們要在JUnit中測試我們的延遲加載應用。覆蓋TestCase類的setUp和tearDown方法非常容易。我更喜歡將這段代碼放在一個簡便
的抽象TestCase類中,作為我所有測試的基類。
public abstract class MyLazyTestCase extends TestCase {
public void setUp() throws Exception {
super.setUp();
SessionFactory sessionFactory = (SessionFactory) getBean("sessionFactory");
Session s = sessionFactory.openSession();
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(s));
}
protected Object getBean(String beanName) {
//Code to get objects from Spring application context
}
public void tearDown() throws Exception {
super.tearDown();
SessionHolder holder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
Session s = holder.getSession();
s.flush();
TransactionSynchronizationManager.unbindResource(sessionFactory);
SessionFactoryUtils.closeSessionIfNecessary(s, sessionFactory);
}