天天看點

Hibernate延遲加載以及利用Spring事務完美解決延遲加載問題

   延遲加載機制是為了避免一些無謂的性能開銷而提出來的,所謂延遲加載就是當在真正需要資料的時候,才真正執行資料加載操作。

下面先來看個例子:

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類似的操作。

繼續閱讀