轉(zhuǎn)載于:http://spaces.msn.com/members/zcgly/Blog/cns!1pQwDnSfBx4siamZpHR2gqMQ!121.entry
Hibernate和Spring的延遲加載和DAO模式
原文:http://www.jroller.com/page/kbaum/20040708
作者:Karl Baum
譯者:zcgly
時間:2005-07-13
Hibernate和延遲加載
Hibernate對象關(guān)系映射提供了兩種對象初始化模式:延遲加載和非延遲加載。非延遲加載在加載時獲取對象本身以及它關(guān)聯(lián)的所有對象
。這可能導(dǎo)致在獲取一個實(shí)例時,執(zhí)行成百上千的select語句。當(dāng)使用雙向關(guān)聯(lián)時,這個問題被放大,常常出現(xiàn)初始化請求時,整個數(shù)據(jù)
庫都被載入。顯然檢查每個對象的關(guān)系,并手工刪除他們會費(fèi)點(diǎn)事,但最終我們可能會因此丟失使用ORM工具的優(yōu)勢。
一個明細(xì)的解決方式是使用hibernate提供的延遲載入機(jī)制。這種初始化策略在類成員被訪問時只載入它的一個對象的一對多和多對多關(guān)
系。對開發(fā)人員來說,這種方式是透明的,并且只有最少數(shù)量的請求發(fā)生,這樣就獲得了最佳的性能。這種技術(shù)的一個缺點(diǎn)是延遲載入要
求當(dāng)對象還在使用中時,Hibernate的Session必須保持打開狀態(tài)。當(dāng)嘗試通過DAO模式抽象持久層時,這會引起一個重要問題。為了充分
地抽象持久層,所有的數(shù)據(jù)庫邏輯,包括打開、關(guān)閉Session都不能在應(yīng)用層出現(xiàn)。最常見的是,這些邏輯隱藏在DAO的實(shí)現(xiàn)類中。快速和
差一些的方案是:避免采用DAO模式,在應(yīng)用層中包含數(shù)據(jù)連接的邏輯。這在小應(yīng)用中是可行的,但在大系統(tǒng)中,這會是一個設(shè)計(jì)缺陷,
它損害了應(yīng)用的擴(kuò)展性。
在Web層使用延遲加載
幸運(yùn)的是,Spring框架已經(jīng)提供了一個DAO模式結(jié)合Hibernate延遲加載的Web方案。對于任何不熟悉Spring框架結(jié)合Hibernate人來說,我
在這里不會深入細(xì)節(jié),但是我希望你去閱讀“結(jié)合Spring框架的Hibernate數(shù)據(jù)庫訪問”章節(jié)。這個案例是一個Web應(yīng)用,Spring提供了
OpenSessionInViewerFilter和OpenSessionInViewInterceptor。使用它們中的任一個都能獲得同樣的功能。這兩者唯一不同的是
interceptor在Spring容器中運(yùn)行,并且在web應(yīng)用的上下文中配置;fitler在Spring前運(yùn)行,并且在web.xml中配置。不管使用哪一個,
他們都會在請求綁定到Session的當(dāng)前線程期間打開Hibernate Session。一旦綁定到線程,打開的Hibernate Session能被DAO的實(shí)現(xiàn)類透
明地使用。Session會持續(xù)打開允許延遲加載訪問數(shù)據(jù)庫。一旦View邏輯完成,hibernate session會被關(guān)閉,無論是在Filter的doFilter
方法中還是在Interceptor的postHandle方法中。下面是一個配置實(shí)例:
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實(shí)現(xiàn)類很簡單。實(shí)際上,如果你已經(jīng)使用Spring框架實(shí)現(xiàn)Hibernate的DAO對象,最有可能的是,你不
需要做任何改動。DAO必須通過方便的HibernateTemplate工具訪問Hibernate,這對數(shù)據(jù)庫訪問來說就是小菜一碟。下面是一個DAO的實(shí)例
。
DAO實(shí)例
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);
}
}
在業(yè)務(wù)層使用延遲加載
甚至在表現(xiàn)層外,Spring框架也通過AOP攔截器HibernateInterceptor提供了便利的延遲加載支持。hibernate攔截器透明地攔截了配置在
Spring應(yīng)用上下文中的業(yè)務(wù)對象的調(diào)用,在調(diào)用前打開hibernate session,在調(diào)用結(jié)束時關(guān)閉這個session。讓我們通過一個簡單的例子
來說明。假設(shè)我們有一個interface叫做BussinessObject:
public interface BusinessObject {
public void doSomethingThatInvolvesDaos();
}
類BusinessObjectImpl實(shí)現(xiàn)了BusinessObject接口:
public class BusinessObjectImpl implements BusinessObject {
public void doSomethingThatInvolvesDaos() {
// lots of logic that calls
// DAO classes Which access
// data objects lazily
}
}
通過Spring上下文的一些配置,我們可以讓HibernateInterceptor攔截對BusinessObjectImpl的調(diào)用,允許它的方法延遲訪問數(shù)據(jù)對象。
看一下下面的片斷:
<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>
當(dāng)businessObject的實(shí)例被引用,HibernateInterceptor打開一個hibernate session并允許對BussinessObjectImpl的調(diào)用。當(dāng)
BusinessOjbectImpl執(zhí)行完成,HibernateInterceptor透明的關(guān)閉這個session。應(yīng)用代碼并不知道任何持久層邏輯,但它仍然能夠使用
延遲加載訪問數(shù)據(jù)對象。
在單元測試中使用延遲加載
最后,我們要在JUnit中測試我們的延遲加載應(yīng)用。覆蓋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);
}