Hibernate 的延遲載入(Lazy Loading)在存取關聯集合或代理物件時,需要有效的 Session。若採用 DAO 模式將持久層邏輯(含 Session 開關)封裝於 DAO 實作類中,Service 層呼叫 DAO 後 Session 已關閉,導致後續存取關聯屬性時拋出 LazyInitializationException。
Spring 提供兩種機制將 Session 生命週期延伸至 View 渲染完成:
web.xml 設定,於 Spring 容器之前啟動。*-servlet.xml 設定。兩者功能相同:請求進入時綁定 Session 至當前執行緒,View 完成後關閉 Session,期間 DAO 可透明使用延遲載入。
web.xml)<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>
applicationContext.xml)<beans>
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="interceptors">
<list><ref bean="openSessionInViewInterceptor"/></list>
</property>
<property name="mappings">...</property>
</bean>
<bean name="openSessionInViewInterceptor"
class="org.springframework.orm.hibernate.support.OpenSessionInViewInterceptor">
<property name="sessionFactory"><ref bean="sessionFactory"/></property>
</bean>
</beans>
Spring 的 HibernateDaoSupport 提供 getHibernateTemplate(),DAO 僅需繼承該類別即可簡潔操作:
public class HibernateProductDAO extends HibernateDaoSupport implements ProductDAO {
public Product getProduct(Integer id) {
return (Product) getHibernateTemplate().load(Product.class, id);
}
public Integer saveProduct(Product p) {
return (Integer) getHibernateTemplate().save(p);
}
public void updateProduct(Product p) {
getHibernateTemplate().update(p);
}
}
若不想將 Session 洩漏至 Web 層,可在 Service 層使用 AOP 攔截器管理 Session:
<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 代理時,攔截器自動開啟/關閉 Session,Service 方法可透明使用延遲載入。
繼承 TestCase 並在 setUp()/tearDown() 手動綁定/解除 Session:
public abstract class MyLazyTestCase extends TestCase {
public void setUp() throws Exception {
super.setUp();
SessionFactory sf = (SessionFactory) getBean("sessionFactory");
Session s = sf.openSession();
TransactionSynchronizationManager.bindResource(sf, new SessionHolder(s));
}
public void tearDown() throws Exception {
super.tearDown();
SessionFactory sf = (SessionFactory) getBean("sessionFactory");
SessionHolder holder = (SessionHolder) TransactionSynchronizationManager.getResource(sf);
Session s = holder.getSession();
s.flush();
TransactionSynchronizationManager.unbindResource(sf);
SessionFactoryUtils.closeSessionIfNecessary(s, sf);
}
protected Object getBean(String name) { /* 從 Spring 容器取得 */ }
}
| 層級 | 解決方案 | 適用場景 |
|---|---|---|
| Web/Controller | OpenSessionInViewFilter / Interceptor | 傳統 MVC,View 需存取領域模型關聯 |
| Service | HibernateInterceptor (AOP) | 分層架構,Service 封裝業務邏輯,不洩露持久層細節 |
| 測試 | 手動綁定 Session 於 ThreadLocal | JUnit 整合測試延遲載入行為 |
Spring 與 Hibernate 的無縫整合,讓開發者同時擁有「乾淨的分層架構」與「高效能的延遲載入」。