Spring事物

 Spring的事務
Spring的事務管理不需與任何特定的事務API耦合。對不同的持久層訪問技術,編程式事務提供一致的事務編程風格,通過模板化的操作一致性地管理事務。聲明式事務基於Spring AOP實現,卻並不需要程序開發者成爲AOP專家,亦可輕易使用Spring的聲明式事務管理。

6.3.1  Spring支持的事務策略
Spring事務策略是通過PlatformTransactionManager接口體現的,該接口是Spring事務策略的核心。該接口的源代碼如下:

public interface PlatformTransactionManager

{

    //平臺無關的獲得事務的方法

    TransactionStatus getTransaction(TransactionDefinition definition)

        throws TransactionException;

    //平臺無關的事務提交方法

    void commit(TransactionStatus status) throws TransactionException;

    //平臺無關的事務回滾方法

    void rollback(TransactionStatus status) throws TransactionException;

}

PlatformTransactionManager是一個與任何事務策略分離的接口,隨着底層不同事務策略切換,應用必須採用不同的實現類。PlatformTransactionManager接口沒有與任何事務資源捆綁在一起,它可以適應於任何的事務策略,結合Spring的IoC容器,可以向PlatformTransactionManager注入相關的平臺特性。

PlatformTransactionManager接口有許多不同的實現類,應用程序面向與平臺無關的接口編程,對不同平臺的底層支持,由PlatformTransactionManager接口的實現類完成。從而,應用程序無須與具體的事務API耦合。因此,使用PlatformTransactionManager接口,可將代碼從具體的事務API中解耦出來。

即使使用特定容器管理的JTA,代碼依然無須執行JNDI查找,無須與特定的JTA資源耦合在一起。通過配置文件,JTA資源傳給PlatformTransactionManager的實現類。因此,程序的代碼可在JTA事務管理和非JTA事務管理之間輕鬆切換。

在PlatformTransactionManager接口內,包含一個getTransaction(TransactionDefinition definition)方法,該方法根據一個TransactionDefinition參數,返回一個TransactionStatus對象。TransactionStatus對象表示一個事務。TransactionStatus被關聯在當前執行的線程。

getTransaction(TransactionDefinition definition)返回的TransactionStatus對象,可能是一個新的事務,也可能是一個已經存在的事務對象。如果當前執行的線程已經處於事務管理下,返回當前線程的事務對象,否則,返回當前線程的調用堆棧已有的事務對象。

TransactionDefinition接口定義了一個事務規則,該接口必須指定如下幾個屬性值:

  ● 事務隔離,當前事務和其他事務的隔離程度。例如,這個事務能否看到其他事務未提交的數據等。

  ● 事務傳播,通常,在事務中執行的代碼都會在當前事務中運行。但是,如果一個事務上下文已經存在,有幾個選項可指定該事務性方法的執行行爲。例如,大多數情況下,簡單地在現有的事務上下文中運行;或者掛起現有事務,創建一個新的事務。Spring提供EJB CMT(Contain Manager Transaction,容器管理事務)中所有的事務傳播選項。

  ● 事務超時,事務在超時前能運行多久。事務的最長持續時間。如果事務一直沒有被提交或回滾,將在超出該時間後,系統自動回滾事務。

  ● 只讀狀態,只讀事務不修改任何數據。在某些情況下(例如使用Hibernate時),只讀事務是非常有用的優化。

TransactionStatus代表事務本身,它提供了簡單的控制事務執行和查詢事務狀態的方法。這些方法在所有的事務API中都是相同的。TransactionStatus接口的源代碼如下:

public interface TransactionStatus

{

    //判斷事務是否是新建的事務

    boolean isNewTransaction();

    //設置事務回滾

    void setRollbackOnly();

    //查詢事務是否已有回滾標誌

    boolean isRollbackOnly();

}

Spring的事務管理由PlatformTransactionManager的不同實現類完成。在Spring上下文中配置PlatformTransactionManager Bean時,必須針對不同環境提供不同的實現類。

下面提供不同的持久層訪問環境,及其對應的PlatformTransactionManager實現類的  配置。

JDBC數據源的局部事務策略:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans

       http://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- 定義數據源Bean,使用C3P0數據源實現 -->

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">

    <!-- 指定連接數據庫的驅動 -->

    <property name="driverClass" value="com.mysql.jdbc.Driver"/>

    <!-- 指定連接數據庫的URL -->

    <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

    <!-- 指定連接數據庫的用戶名 -->

    <property name="user" value="root"/>

    <!-- 指定連接數據庫的密碼 -->

    <property name="password" value="32147"/>

    <!-- 指定連接數據庫連接池的最大連接數 -->

    <property name="maxPoolSize" value="40"/>

    <!-- 指定連接數據庫連接池的最小連接數 -->

    <property name="minPoolSize" value="1"/>

    <!-- 指定連接數據庫連接池的初始化連接數 -->

    <property name="initialPoolSize" value="1"/>

    <!-- 指定連接數據庫連接池的連接最大空閒時間 -->

    <property name="maxIdleTime" value="20"/>

</bean>

<!-- 配置JDBC數據源的局部事務管理器 -->

<!-- 使用DataSourceTransactionManager 類,該類實現PlatformTransactionManager接口 -->

<!-- 針對採用數據源連接的特定實現 -->

<bean id="transactionManager"

        class="org.springframework.jdbc.datasource.
        DataSourceTransactionManager">

        <!-- DataSourceTransactionManager  bean需要依賴注入一個DataSource
        bean的引用 -->

         <property name="dataSource" ref="dataSource"/>

    </bean>

</beans>

對於容器管理JTA數據源,全局事務策略的配置文件如下:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 配置JNDI數據源Bean -->

    <bean id="dataSource" class="org.springframework.jndi.
    JndiObjectFactoryBean">

    <!--  容器管理數據源的JNDI -->

         <property name="jndiName" value="jdbc/jpetstore"/>

    </bean>

    <!-- 使用JtaTransactionManager類,該類實現PlatformTransactionManager接
    口 -->

    <!-- 針對採用全局事務管理的特定實現 -->

    <!-- JtaTransactionManager不需要知道數據源,或任何其他特定資源 -->

    <!-- 因爲它使用容器的全局事務管理 -->

    <bean id="transactionManager"

        class="org.springframework.transaction.jta.
        JtaTransactionManager" />

</beans>

對於採用Hibernate持久層訪問策略時,局部事務策略的配置文件如下:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 定義數據源Bean,使用C3P0數據源實現 -->

    <bean id="dataSource" class="com.mchange.v2.c3p0.
    ComboPooledDataSource" destroy-method="close">

        <!-- 指定連接數據庫的驅動 -->

        <property name="driverClass" value="com.mysql.jdbc.Driver"/>

        <!-- 指定連接數據庫的URL -->

        <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

        <!-- 指定連接數據庫的用戶名 -->

        <property name="user" value="root"/>

        <!-- 指定連接數據庫的密碼 -->

        <property name="password" value="32147"/>

        <!-- 指定連接數據庫連接池的最大連接數 -->

        <property name="maxPoolSize" value="40"/>

        <!-- 指定連接數據庫連接池的最小連接數 -->

        <property name="minPoolSize" value="1"/>

        <!-- 指定連接數據庫連接池的初始化連接數 -->

        <property name="initialPoolSize" value="1"/>

        <!-- 指定連接數據庫連接池的連接最大空閒時間 -->

        <property name="maxIdleTime" value="20"/>

    </bean>

    <!-- 定義Hibernate的SessionFactory -->

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
    LocalSessionFactoryBean">

        <!-- 依賴注入SessionFactory所需的數據源,正是上文定義的dataSource -->

        <property name="dataSource" ref="dataSource"/>

        <!-- mappingResources屬性用來列出全部映射文件 -->

        <property name="mappingResources">

              <list>

                  <!-- 以下用來列出所有的PO映射文件 -->

                <value>lee/MyTest.hbm.xml</value>

              </list>

        </property>

          <!-- 定義Hibernate的SessionFactory的屬性 -->

        <property name="hibernateProperties">

             <props>

                <!-- 指定Hibernate的連接方言 -->

                <prop key="hibernate.dialect">org.hibernate.dialect.
                MySQLDialect</prop>

                <!-- 是否根據Hibernate映射創建數據表時,選擇create、update、
                create-drop -->

                  <prop key="hibernate.hbm2ddl.auto">update</prop>

             </props>

        </property>

    </bean>

    <!-- 配置Hibernate的局部事務管理器 -->

    <!-- 使用HibernateTransactionManager類,該類是PlatformTransactionManager
    接口,針對採用Hibernate持久化連接的特定實現 -->

    <bean id="transactionManager"

      class="org.springframework.orm.hibernate3.
      HibernateTransactionManager">

            <!-- HibernateTransactionManager  Bean需要依賴注入一個
            SessionFactorybean的引用 -->

         <property name="sessionFactory" ref="sessionFactory"/>

     </bean>

</beans>

對於採用Hibernate持久層訪問策略時,全局事務策略的配置文件如下:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 配置JNDI數據源Bean -->

    <bean id="dataSource" class="org.springframework.jndi.
    JndiObjectFactoryBean">

        <!--  容器管理數據源的JNDI -->

         <property name="jndiName" value="jdbc/jpetstore"/>

    </bean>

    <!--定義Hibernate的SessionFactory -->

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
    LocalSessionFactoryBean">

        <!-- 依賴注入SessionFactory所需的數據源,正是上文定義的dataSource Bean -->

        <property name="dataSource" ref="dataSource"/>

        <!-- mappingResources屬性用來列出全部映射文件 -->

        <property name="mappingResources">

              <list>

                  <!-- 以下用來列出所有的PO映射文件 -->

                <value>lee/MyTest.hbm.xml</value>

              </list>

        </property>

          <!-- 定義Hibernate的SessionFactory的屬性 -->

        <property name="hibernateProperties">

             <props>

                <!-- 指定Hibernate的連接方言 -->

                <prop key="hibernate.dialect">org.hibernate.dialect.
                MySQLDialect</prop>

                <!-- 是否根據Hiberante映射創建數據表時,選擇create、update、
                create-drop -->

                  <prop key="hibernate.hbm2ddl.auto">update</prop>

             </props>

         </property>

    </bean>

    <!-- 使用JtaTransactionManager類,該類是PlatformTransactionManager接口,
            針對採用數據源連接的特定實現 -->

    <!--  JtaTransactionManager不需要知道數據源,或任何其他特定資源,

            因爲使用容器的全局事務管理 -->

    <bean id="transactionManager"

           class="org.springframework.transaction.jta.
           JtaTransactionManager" />

</beans>

不論採用哪種持久層訪問技術,只要使用JTA數據源,Spring事務管理器的配置都是一樣的,因爲它們都採用的是全局事務管理。

可以看到,僅僅通過配置文件的修改,就可以在不同的事務管理策略間切換,即使從局部事務到全局事務的切換。

提示:Spring所支持的事務策略非常靈活,Spring的事務策略允許應用程序在不同事務策略之間自由切換,即使需要在局部事務策略和全局事務策略之間切換,只需要修改配置文件,而應用程序的代碼無須任何改變。這種靈活的設計,又何嘗不是因爲面向接口編程帶來的優勢,可見面向接口編程給應用程序更好的適應性。

6.3.2  Spring事務策略的優勢
雖然在上面的配置片段中,僅僅配置了JDBC局部事務管理器、Hibernate局部事務管理器、JDBC全局事務管理器等。但Spring支持大部分持久化策略的事務管理器。

不論採用何種持久化策略,Spring都提供了一致的事務抽象,因此,應用開發者能在任何環境下,使用一致的編程模型。無須更改代碼,應用就可在不同的事務管理策略中切換。Spring同時支持聲明式事務管理和編程式事務管理。

使用編程式事務管理,開發者使用的是Spring事務抽象,而無須使用任何具體的底層事務API。Spring的事務管理將代碼從底層具體的事務API中抽象出來,該抽象可以使用在任何底層事務基礎之上。

使用聲明式策略,開發者通常書寫很少的事務管理代碼,因此,不依賴Spring或任何其他事務API。Spring的聲明式事務無須任何額外的容器支持,Spring容器本身管理聲明式事務。使用聲明事務策略,無須在業務代碼中書寫任何事務代碼,可以讓開發者更好地專注於業務邏輯的實現。Spring管理的事務支持多個事務資源的跨越,但無法支持跨越遠程調用的事務上下文傳播。

6.3.3  使用TransactionProxyFactoryBean創建事務代理
Spring同時支持編程式事務策略和聲明式事務策略,大部分時候,都推薦採用聲明式事務策略,使用聲明事務策略的優勢十分明顯:

  ● 聲明式事務能大大降低開發者的代碼書寫量。而且聲明式事務幾乎不需要影響應用的代碼。因此,無論底層事務策略如何變化,應用程序無須任何改變。

  ● 應用程序代碼無須任何事務處理代碼,可以更專注於業務邏輯的實現。

  ● Spring則可對任何POJO的方法提供事務管理,而且Spring的聲明式事務管理無須容器的支持,可在任何環境下使用。

  ● EJB的CMT無法提供聲明式回滾規則。而通過配置文件,Spring可指定事務在遇到特定異常時自動回滾。Spring不僅可在代碼中使用setRollbackOnly回滾事務,也可在配置文件中配置回滾規則。

  ● 由於Spring採用AOP的方式管理事務,因此,可以在事務回滾動作中插入用戶自己的動作,而不僅僅是執行系統默認的回滾。

提示:本節不打算全面介紹Spring的各種事務策略,因此本節不會介紹編程式事務。如果讀者需要更全面瞭解Spring事務的相關方面,請參閱筆者所著的《Spring2.0寶典》     一書。

對於採用聲明式事務策略,可以使用TransactionProxyFactoryBean來配置事務代理Bean。正如它的類名所暗示的,它是一個工廠Bean,工廠Bean用於生成一系列的Bean實例,這一系列的Bean實例都是Proxy。

可能讀者已經想到了,既然TransactionProxyFactoryBean產生的是代理Bean,可見這種事務代理正是基於Spring AOP組件的。配置TransactionProxyFactoryBean時,一樣需要指定目標Bean。

每個TransactionProxyFactoryBean爲一個目標Bean生成事務代理,事務代理的方法改寫了目標Bean的方法,就是在目標Bean的方法執行之前加入開始事務,在目標Bean的方法正常結束之前提交事務,如果遇到特定異常則回滾事務。

TransactionProxyFactoryBean創建事務代理時,需要了解當前事務所處的環境,該環境屬性通過PlatformTransactionManager實例傳入,而相關事務傳入規則在TransactionProxy- FactoryBean的定義中給出。

下面給出聲明式事務配置文件的完整代碼:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 定義數據源Bean,使用C3P0數據源實現 -->

    <bean id="dataSource" class="com.mchange.v2.c3p0.
    ComboPooledDataSource" destroy-method="close">

        <!-- 指定連接數據庫的驅動 -->

        <property name="driverClass" value="com.mysql.jdbc.Driver"/>

        <!-- 指定連接數據庫的URL -->

        <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

        <!-- 指定連接數據庫的用戶名 -->

        <property name="user" value="root"/>

        <!-- 指定連接數據庫的密碼 -->

        <property name="password" value="32147"/>

        <!-- 指定連接數據庫連接池的最大連接數 -->

        <property name="maxPoolSize" value="40"/>

        <!-- 指定連接數據庫連接池的最小連接數 -->

        <property name="minPoolSize" value="1"/>

        <!-- 指定連接數據庫連接池的初始化連接數 -->

        <property name="initialPoolSize" value="1"/>

        <!-- 指定連接數據庫連接池的連接最大空閒時間 -->

        <property name="maxIdleTime" value="20"/>

    </bean>

    <!-- 定義Hibernate的SessionFactory -->

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
    LocalSessionFactoryBean">

        <!-- 依賴注入SessionFactory所需的數據源,正是上文定義的dataSource -->

        <property name="dataSource" <ref="dataSource"/>

        <!-- mappingResources屬性用來列出全部映射文件 -->

        <property name="mappingResources">

              <list>

                  <!-- 以下用來列出所有的PO映射文件 -->

                <value>lee/Person.hbm.xml</value>

              </list>

        </property>

          <!-- 定義Hibernate的SessionFactory屬性 -->

        <property name="hibernateProperties">

             <props>

                <!-- 指定Hibernate的連接方言 -->

                <prop key="hibernate.dialect">org.hibernate.dialect.
                MySQLDialect</prop>

                <!-- 是否根據Hiberante映射創建數據表時,選擇create、update、
                create-drop -->

                  <prop key="hibernate.hbm2ddl.auto">update</prop>

             </props>

        </property>

    </bean>

    <!-- 配置DAO Bean,該Bean將作爲目標Bean使用 -->

    <bean id="personDAOTarget" class="lee.PersonDaoImpl">

        <!-- 採用依賴注入來傳入SessionFactory的引用 -->

        <property name="sessionFactory" ref="sessionFactory"/>

    </bean>

    <!-- 配置Hibernate的事務管理器 -->

    <!-- 使用HibernateTransactionManager類,該類實現PlatformTransactionManager
    接口,針對採用Hibernate持久化連接的特定實現 -->

    <bean id="transactionManager"

        class="org.springframework.orm.hibernate3.
        HibernateTransactionManager">

        <!-- HibernateTransactionManager Bean,它需要依賴注入一個SessionFactory
        Bean的引用 -->

        <property name="sessionFactory" ref="sessionFactory"/>

    </bean>

    <!--  配置personDAOTarget Bean的事務代理 -->

    <bean id="personDAO"

        class="org.springframework.transaction.interceptor.
        TransactionProxyFactoryBean">

          <!-- 依賴注入PlatformTransactionManager的bean引用,此處使用
          Hibernate的bean -->

          <!-- 局部事務器,因此transactionManager 傳入Hibernate事務管理器的
          引用 -->

          <property name="transactionManager" ref="transactionManager"/>

          <!-- 需要生成代理的目標bean -->

          <property name="target" ref="personDAOTarget"/>

          <!-- 指定事務屬性 -->

          <property name="transactionAttributes">

              <props>

                  <!-- 以下部分爲定義事務回滾規則 -->

                    <prop key="insert*">PROPAGATION_REQUIRED,
                  -MyCheckedException</prop>

                    <prop key="update*">PROPAGATION_REQUIRED</prop>

                    <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>

            </props>

        </property>

    </bean>

</beans>

在上面的定義文件中,沒有對DAO對象採用Service層包裝。通常情況下,DAO層上應有一層Service層。事務代理則以Service層Bean爲目標Bean。此處爲了簡化配置,TransactionProxyFactoryBean直接以DAO bean作爲目標bean,這一點不會影響事務代理的生成。

事務回滾規則部分定義了三個回滾規則:

第一個回滾規則表示所有以insert開始的方法,都應該滿足該事務規則。PROPAGATION_REQUIRED事務傳播規則指明,該方法必須處於事務環境中,如果當前執行線程已處於事務環境下,則直接執行;否則,啓動新的事務然後執行該方法。該規則還指定,如果方法拋出MyCheckedException的實例及其子類的實例,則強制回滾。MyCheckedException前的“-”表示強制回滾;“+”則表示強制提交,即某些情況下,即使拋出異常也強制提交;

第二個回滾規則表示所有以update開頭的方法,都遵守PROPAGATION_REQUIRED的事務傳播規則;

第三個回滾規則表示除前面規定的方法外,其他所有方法都採用PROPAGATION_ REQUIRED事務傳播規則,而且只讀。

常見的事務傳播規則有如下幾個:

  ● PROPAGATION_MANDATORY,要求調用該方法的線程必須處於事務環境中,否則拋出異常。

  ● PROPAGATION_NESTED,如果執行該方法的線程已處於事務環境下,依然啓動新的事務,方法在嵌套的事務裏執行。如果執行該方法的線程序並未處於事務中,也啓動新的事務,然後執行該方法,此時與PROPAGATION_REQUIRED相同。

  ● PROPAGATION_NEVER,不允許調用該方法的線程處於事務環境下,如果調用該方法的線程處於事務環境下,則拋出異常。

  ● PROPAGATION_NOT_SUPPORTED,如果調用該方法的線程處在事務中,則先暫停當前事務,然後執行該方法。

  ● PROPAGATION_REQUIRED,要求在事務環境中執行該方法,如果當前執行線程已處於事務中,則直接調用;如果當前執行線程不處於事務中,則啓動新的事務後執行該方法。

  ● PROPAGATION_REQUIRES_NEW,該方法要求有一個線程在新的事務環境中執行,如果當前執行線程已處於事務中,先暫停當前事務,啓動新的事務後執行該方法;如果當前調用線程不處於事務中,則啓動新的事務後執行該方法。

  ● PROPAGATION_SUPPORTS,如果當前執行線程處於事務中,則使用當前事務,否則不使用事務。

程序裏原來使用personDAO的地方,無須變化。因爲,配置文件裏將personDAO目標Bean的id改成personDAOTarget,爲TransactionProxyFactoryBean工廠Bean所產生的代理Bean命名爲personDAO。該代理Bean會包含原有personDAO的所有方法,而且爲這些方法增加了不同的事務處理規則。

程序面向PersonDaoImpl類所實現的接口編程,TransactionProxyFactoryBean生成的代理Bean也會實現TransactionProxyFactoryBean接口。因此,原有的程序中調用DAO組件的代碼無須任何改變。程序運行時,由事務代理完成原來目標Bean完成的工作。

事實上,Spring不僅支持對接口的代理,整合CGLIB後,Spring甚至可對具體類生成代理。只要設置proxyTargetClass屬性爲true就可以。如果目標Bean沒有實現任何接口,proxyTargetClass屬性默認被設爲true,此時Spring會對具體類生成代理。當然,通常建議面向接口編程,而不要面向具體的實現類編程。

6.3.4  使用繼承簡化事務配置
仔細觀察配置文件中兩個事務代理Bean的配置時,發現兩個事務代理Bean的大部分配置完全相同,如果配置文件中包含大量這樣的事務代理Bean配置,配置文件將非常臃腫。考慮到大部分事務代理Bean的配置都大同小異,可以使用Bean繼承來簡化事務代理的配置。

正如前面部分介紹的,Bean繼承是將所有子Bean中相同的配置定義成一個模板,並將此模板Bean定義成一個抽象Bean。考慮所有事務代理Bean中,有如下部分是大致相   同的:

  ● 事務代理Bean所使用的事務管理器。

  ● 事務傳播規則。

因此,現在配置文件中定義如下的事務代理模板Bean,其配置代碼如下:

<!-- 定義所有事務代理Bean的模板 -->

<bean id="txProxyTemplate" abstract="true"

        class="org.springframework.transaction.interceptor.
        TransactionProxyFactoryBean">

    <!-- 爲事務代理Bean注入生成代理所需的PlatformTransactionManager實例 -->

    <property name="transactionManager" ref="transactionManager"/>

        <!-- 定義生成事務代理通用的事務屬性 -->

        <property name="transactionAttributes">

            <props>

                <!-- 所有的方法都應用PROPAGATION_REQUIRED的事務傳播規則 -->

                <prop key="*">PROPAGATION_REQUIRED</prop>

            </props>

    </property>

</bean>

而真正的事務代理Bean,則改爲繼承上面的事務模板Bean。考慮到將目標Bean定義在Spring容器中可能增加未知的風險,因此將目標Bean定義成嵌套Bean。

<!-- 讓事務代理Bean繼承模板Bean -->

<bean id="personDAO" parent="txProxyTemplate">

    <!-- 這裏採用嵌套Bean的方式來定義目標Bean,當然也可以引用已存在的Bean -->

    <property name="target">

        <bean class="lee.personDAO"/>

    </property>

</bean>

此時的personDAO Bean無須具體地定義事務屬性,它將在其父Bean txProxyTemplate中獲取事務定義屬性。此處採用嵌套Bean來定義目標Bean,因此,並未將目標Bean直接暴露在Spring的上下文中讓其他模塊調用。當然,也可採用一個已經存在的Bean作爲目標Bean;子Bean的事務屬性定義,完全可覆蓋事務代理模板裏的事務屬性定義。如下例所示:

<!-- 讓事務代理bean繼承模板Bean -->

<bean id="personDAO" parent="txProxyTemplate">

    <!-- 這裏,採用引用已存在的bean的方式來定義目標Bean -->

    <property name="target" ref ="personDAOTarget"/>

    <!-- 覆蓋事務代理模板bean中的事務屬性定義 -->

    <property name="transactionAttributes">

        <props>

            <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>

            <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>

            <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>

         </props>

    </property>

</bean>

可見,採用Bean繼承方式定義事務代理的方式,可以很好地簡化事務代理的配置,可以避免配置事務代理Bean時的冗餘配置。

提示:使用Bean繼承可以很好地簡化事務代理Bean的配置,通過將各事務代理Bean共同的配置信息提取成事務模板Bean,可以讓實際的事務代理Bean的配置更加簡潔;而且,配置方式相當直觀。儘量將目標Bean配置成嵌套Bean,這樣的方式可以保證更好的內聚性。

如果讀者還記得前面介紹的AOP知識,應該知道還有一種更加簡潔的配置,就是利用Bean後處理器,讓Bean後處理器爲容器中其他Bean自動創建事務代理。

6.3.5  使用自動創建代理簡化事務配置
回顧6.2.6節和6.2.7節,讀者可能已經想到如何自動創建代理。是的,正是通過6.2.6節和6.2.7節所給出的兩個自動代理創建類來生成事務代理。

正如前文已經提到的,使用BeanNameAutoProxyCreator和DefaultAdvisorAutoProxy- Creator來創建代理時,並不一定是創建事務代理,關鍵在於傳入的攔截器,如果傳入事務攔截器,將可自動生成事務代理。

下面是使用BeanNameAutoProxyCreator自動生成事務代理的配置文件:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Spring配置文件的根元素,以及相應的Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 定義數據源Bean,使用C3P0數據源實現 -->

    <bean id="dataSource" class="com.mchange.v2.c3p0.
    ComboPooledDataSource" destroy-method="close">

        <!-- 指定連接數據庫的驅動 -->

        <property name="driverClass" value="com.mysql.jdbc.Driver"/>

        <!-- 指定連接數據庫的URL -->

        <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

        <!-- 指定連接數據庫的用戶名 -->

        <property name="user" value="root"/>

        <!-- 指定連接數據庫的密碼 -->

        <property name="password" value="32147"/>

        <!-- 指定連接數據庫連接池的最大連接數 -->

        <property name="maxPoolSize" value="40"/>

        <!-- 指定連接數據庫連接池的最小連接數 -->

        <property name="minPoolSize" value="1"/>

        <!-- 指定連接數據庫連接池的初始化連接數 -->

        <property name="initialPoolSize" value="1"/>

        <!-- 指定連接數據庫連接池的連接最大空閒時間 -->

        <property name="maxIdleTime" value="20"/>

    </bean>

    <!-- 使用JDBC的局部事務策略 -->

    <bean id="transactionManager"

        class="org.springframework.jdbc.datasource.DataSource-
        TransactionManager">

        <!-- 爲事務管理器注入所需的數據源Bean -->

        <property name="dataSource" ref="dataSource"/>

    </bean>

    <!-- 配置目標Bean,該目標Bean將由Bean後處理器自動生成代理 -->

    <bean id="test1" class="lee.TransactionTestImpl">

        <!-- 依賴注入目標Bean所必需的數據源Bean -->

        <property name="ds" ref="dataSource"/>

    </bean>

    <!-- 配置目標Bean,該目標Bean將由Bean後處理器自動生成代理 -->

    <bean id="test2" class="lee.TestImpl">

        <!-- 依賴注入目標Bean所必需的數據源Bean -->

        <property name="ds" ref="dataSource"/>

    </bean>

    <!-- 配置事務攔截器Bean -->

    <bean id="transactionInterceptor"

        class="org.springframework.transaction.interceptor.
        TransactionInterceptor">

        <!-- 事務攔截器bean需要依賴注入一個事務管理器 -->

        <property name="transactionManager" ref="transactionManager"/>

        <property name="transactionAttributes">

            <!-- 下面定義事務傳播屬性 -->

            <props>

                <prop key="insert*">PROPAGATION_REQUIRED</prop>

                <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>

                <prop key="*">PROPAGATION_REQUIRED</prop>

            </props>

        </property>

    </bean>

    <!-- 定義BeanNameAutoProxyCreator的Bean後處理器 -->

    <bean class="org.springframework.aop.framework.autoproxy.
    BeanNameAutoProxyCreator">

    <!-- 指定對滿足哪些bean name的bean自動生成業務代理 -->

        <property name="beanNames">

            <!-- 下面是所有需要自動創建事務代理的Bean -->

            <list>

                <value>test1</value>

                <value>test2</value>

            </list>

            <!-- 此處可增加其他需要自動創建事務代理的Bean -->

        </property>

        <!-- 下面定義BeanNameAutoProxyCreator所需的攔截器 -->

        <property name="interceptorNames">

            <list>

                <value>transactionInterceptor</value>

                <!-- 此處可增加其他新的Interceptor -->

            </list>

        </property>

    </bean>

</beans>

如果配置文件中僅有兩個目標Bean,可能不能很清楚地看出這種自動創建代理配置方式的優勢,但如果有更多目標Bean需要自動創建事務代理,則可以很好地體會到這種配置方式的優勢:配置文件只需要簡單地配置目標Bean,然後在BeanNameAutoProxyCreator配置中增加一行即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章