延遲加載機制是為了避免一些無謂的性能開銷而提出來的,所謂延遲加載就是當在真正需要資料的時候,才真正執行資料加載操作。
下面先來看個例子:
public Emp findById(java.lang.Long id) {
try {
Emp instance =
(Emp) getHibernateTemplate().get("com.sxy.dao.Emp",id);
return instance;
} catch (RuntimeException re) {
throw re;
}
}
public class EmpServiceImpl implements IEmpService {
private EmpDAO empDAO;
public Emp searchEmpById(Long id)
{
Emp emp=empDAO.findById(id);
return emp;
}
public void setEmpDAO(EmpDAO empDAO) {
this.empDAO = empDAO;
}
}
保證Emp類裡dept字段映射是這樣的(lazy="proxy",預設為proxy;fetch="select")
<many-to-one name="dept" class="com.sxy.dao.Dept"
fetch="select" lazy="proxy">
<column name="DEPTNO" precision="2" scale="0" />
</many-to-one>
public static void main(String[] args) {
String app = "applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(app);
IEmpService service = (IEmpService) ctx.getBean("EmpServiceImpl");
Emp emp = service.searchEmpById(7788L);
System.out.println("雇員姓名:" + emp.getEname());
System.out.println("所屬部門:" + emp.getDept().getDname());
}
啟動運作出現異常,意思說,無法初始化代理,Session已關閉!
雇員姓名:SCOTT
Exception in thread "main"
org.hibernate.LazyInitializationException: could not initialize proxy - the owning Session was closed
很顯然錯誤是出在輸出部門資訊的時候,由于配置映射裡設定了lazy="proxy",即使用代理,此時查詢雇員資訊的時候不會去查詢部門資訊,部門資訊将在使用時才到資料庫裡查詢,當我們在用戶端調用時Session已被關閉,是以當再次通過代理查詢資料庫時抛出異常,
下面來做個測試,利用log4j輸出運作日志,log4j.properties配置如下:
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
在main方法第一句寫上
PropertyConfigurator.configure("log4j.properties");
業務類添加輸出語句
System.out.println("開始業務方法......");
Emp emp=empDAO.findById(id);
System.out.println("結束業務方法......");
運作後,輸出部分資訊如下,可以看到在結束調用DAO類的查詢方法後Session就關閉了
開始業務方法......
....
org.springframework.orm.hibernate3.SessionFactoryUtils -
Closing Hibernate Session
結束業務方法......
好,現在知道問題出在什麼地方了,有多種方式可以解決這個問題,
1,設定fetch="join"
2,設定lazy="false"
3,修改DAO類的查詢方法
4,OpenSessionInView
5,利用Spring事務機制
...我暫時就知道這麼多,下面來介紹這幾種方法,
1,fetch這個屬性設定已什麼方式去取得資料,預設是select,我們設定為join表示聯接查詢,是以當查詢雇員的時候同時也把部門聯接查詢出來了,這樣查詢方式顯然沒用到延遲加載功能,是以效率還是沒得到優化,當設定成join時lazy屬性無效
2,lazy="false" 表示不使用代理,即不使用延時加載,是以效率也得不到提高,特别是 one-to-many或many-to-many的時候效率很低,如 查詢部門名稱,如果是false,與
此部門相關的雇員也會全部查詢出來,但現在并不需要雇員資訊,如果該部門下的雇員資訊很多的情況下将極大地影響性能.
3,我們可以考慮這樣,在調用方法到時候告訴Hibernate是否需要部門資訊,因為此時
Session還未被關閉
public Emp findById(java.lang.Long id,boolean isDepet) {
Emp instance =
(Emp) getHibernateTemplate().get("com.sxy.dao.Emp",id);
if(isDepet==true)
Hibernate.initialize(instance.getDept());
return instance;
}
這樣添加多一個參數編寫有點繁瑣,幾乎每個方法都要這樣寫,而且調用顯得較麻煩
4,用OpenSessionInView事務不能及時關閉,事務使得一些資料加鎖,加鎖的資料得不到及時的通路,此外Session存在的時間也大大的延長,Session裡面有個一級緩存,如果查詢有很多資訊将長時間占用大量記憶體,直到傳回到用戶端後才釋放,Session存在時間和用戶端的網速度有關,另外資料庫連接配接得不到及時釋放.
5,利用Spring的事務機制,這種方法雖然配置麻煩點,但從設計模式,性能和易用性來講都是特别優秀的,下面詳細介紹.
<!-- 修改業務類id,把原來的名稱留給代理使用,避免修改程式代碼 -->
<bean id="EmpServiceImplTarget"
class="com.sxy.service.EmpServiceImpl">
<!-- 注入業務類需要的DAO對象 -->
<property name="empDAO" ref="EmpDAO"></property>
</bean>
<!-- 建立事務管理器執行個體 -->
<bean id="tranManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<!-- 注入事務管理器需要的SessionFactory對象-->
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<!-- 業務類代理,代理工廠Bean對象,産生代理 -->
<bean id="EmpServiceImpl"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 注入代理工廠需要的事務管理器 -->
<property name="transactionManager" ref="tranManager"></property>
<!-- 注入需要代理的類 -->
<property name="target" ref="EmpDAOTarget"></property>
<!-- 定義一個通知,某些方法按照某種事務規則處理 -->
<property name="transactionAttributes">
<props>
<!-- *号通配符,表示以search打頭的方法全部應用此通知 -->
<prop key="search*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
然後修改業務方法,啟動運作main方法,可以看到部門資訊輸出了
public Emp searchEmpById(Long id)
{
System.out.println("開始業務方法......");
Emp emp=empDAO.findById(id);
Hibernate.initialize(emp.getDept());
System.out.println("結束業務方法......");
return emp;
}
由于采用了事務代理,在調用業務方法就開始了事務,當調用DAO方法 findById時,它會被加到目前事務中,DAO方法執行完畢後傳回到業務方法,此時由于事務還沒結束(送出)是以Session也不會被關閉,是以還可以調用initialize方法進行資料裝載..
檢視log4j輸出的運作日志,很清晰的可以看到執行過程
...
org.hibernate.transaction.JDBCTransaction - begin//開始事務
...
開始業務方法......
...
Select emp0_.EMPNO as EMPNO1_0_,...
...
結束業務方法......
...
org.hibernate.transaction.JDBCTransaction - commit//送出
...
org.springframework.orm.hibernate3.SessionFactoryUtils - Closing Hibernate Session //關閉Session
...
這樣的方式比第四種方式好很多了,事務也得到了及時的關閉,Session在使用完畢後就關閉了,生命期縮短提高了性能
上面配置是1.x的配置方式,下面看看2.x的配置方式
首先修改<beans>根節點
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<!-- 2.x的配置方式 -->
<!--業務類的id名稱不需修改,(1.x的配置方式需要修改)-->
<bean id="EmpServiceImpl"
class="com.sxy.service.EmpServiceImpl">
<!-- 注入業務類需要的DAO對象 -->
<property name="empDAO" ref="EmpDAO"></property>
</bean>
<!-- 建立事務管理器執行個體 -->
<bean id="tranManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<!-- 注入事務管理器需要的SessionFactory對象-->
<property name="sessionFactory"
ref="sessionFactory"></property>
</bean>
<!--定義一個通知,某些方法按照某種事務規則處理(類似aop通知) -->
<tx:advice id="txAdvice" transaction-manager="tranManager">
<tx:attributes>
<!-- *号通配符,表示以search打頭的方法全部應用此通知内包含的一個特征 -->
<tx:method name="search*" propagation="REQUIRED"/>
<tx:method name="*" propagation="REQUIRED" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--以aop的方式切入到應用通知的類 -->
<aop:config>
<!-- 定義切面(關注點) -->
<aop:pointcut id="serviceMethod"
expression="execution(* com.sxy.service.*.*(..))"/>
<!-- 整合通知和切面,即把通知應用到切面-->
<aop:advisor advice-ref="txAdvice"
pointcut-ref="serviceMethod"/>
<!--<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.sxy.service.*.*(..))/>也可這樣用-->
</aop:config>
說明一下execution(* com.sxy.service.*.*(..))
第一個* 代表作用區域,如public,private,*代表全部作用域
第二個* 表示類,接口
第三個* 表示方法
com.sxy.service是包名,也可以用通配符
Spring事務傳播行為類型
1.x中的transactionAttributes屬性符和2.x的propagation屬性的取值
(a)PROPAGATION_REQUIRED: 如果目前沒有事務,就建立一個事務,如果已經存在一個事務中,加入到這個事務中。這是最常見的選擇。
(b)PROPAGATION_SUPPORTS: 支援目前事務,如果目前沒有事務,就以非事務方式執行。
(c)PROPAGATION_MANDATORY:使用目前的事務,如果目前沒有事務,就抛出異常。
(d)PROPAGATION_REQUIRES_NEW:建立事務,如果目前存在事務,把目前事務挂起。
(e)PROPAGATION_NOT_SUPPORTED:以非事務方式執行操作,如果目前存在事務,就把目前事務挂起。
(f)PROPAGATION_NEVER:以非事務方式執行,如果目前存在事務,則抛出異常。
(g)PROPAGATION_NESTED:如果目前存在事務,則在嵌套事務内執行。如果目前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。