Spring筆記——事務

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

1.Spring支持的事務策略

Java EE應用的傳統事務有兩種策略:全局事務和局部事務,全局事務由應用服務器管理,需要底層服務器的JTA支持。局部事務和底層所採用的持久化技術有關,當採用JDBC持久化技術時,需要使用Connection對象來操作事務;而採用Hibernate持久化技術時,需要使用Session對象來操作事務。

全局事務可以跨多個事務性的資源(典型的例子是關係數據庫和消息隊列);使用局部事務,應用服務器不需要參與事務管理,因此不能保證跨多個事務性資源的事務的正確性。當然,實際上大部分應用都使用單一事務性的資源。

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

public interface PlatformTransactionManager
{
    // 平臺無關的獲得事務的方法
    public abstract TransactionStatus getTransaction(TransactionDefinition transactiondefinition)
        throws TransactionException;
    // 平臺無關的事務提交方法
    public abstract void commit(TransactionStatus transactionstatus)
        throws TransactionException;
    // 平臺無關的事務回滾方法
    public abstract void rollback(TransactionStatus transactionstatus)
        throws TransactionException;
}

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

PlatformTransactionManager接口有許多不同的實現類,應用程序面向與平臺無關的接口編程,當底層採用不同的持久層技術時,系統只需使用不同的PlatformTransactionManager實現類即可——而這種切換通常由Spring容器負責管理,應用程序既無須與具體的事務API耦合,也無須與特定實現類耦合,從而將應用和持久化技術、事務API徹底分離開來。

Spring的事務管理機制是一種典型的策略模式,PlatformTransactionManager代表事務管理接口,但它並不知道底層如何管理事務,它只要求事務管理需要提供開始事務、提交事務和回滾事務三個方法,但具體如何實現則交給其實現類來完成。

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

getTransaction(TransactionDefinition transactiondefinition)返回的TransactionStatus對象,可能是一個新的事務,也可能是一個已經存在的事務對象。如果當前執行的線程已經處於事務管理下,則返回當前線程的事務對象;否則,系統將新建一個事務對象後返回。

TransactionDefinition 接口定義了一個事務規則,該接口必須指定如下幾個屬性值:
☞ 事務隔離當前事務和其他事務的隔離程度。例如,這個事務能否看到其他事務未提交的數據等。
☞ 事務傳播通常,在事務中執行的代碼都會在當前事務中運行。但是,如果一個事務上下文已經存在,有幾個選項可指定該事務性方法的執行行爲。例如,大多數情況下,簡單地在現有的事務上下文中運行;或者掛起現有事務,創建一個新的事務。
☞ 事務超時事務在超時前能運行多久,也就是事務的最長持續時間。如果事務一直沒有被提交或回滾,將在超出該時間後,系統自動回滾事務。
☞ 只讀狀態只讀事務不修改任何數據。在某些情況下(例如使用Hibernate時),只讀事務是非常有用的優化。

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

public interface TransactionStatus
{
    // 判斷事務是否爲新建的事務
    boolean isNewTransaction();
    // 設置事務回滾
    public abstract void setRollbackOnly();
    // 查詢事務是否已有回滾標誌
    public abstract boolean isRollbackOnly();
}

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

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

JDBC數據源的局部事務策略的配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.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://127.0.0.1/javaee" />
        <!-- 指定鏈接數據庫的用戶名 -->
        <property name="user" value="root" />
        <!-- 指定鏈接數據庫的密碼 -->
        <property name="password" value="123456" />
        <!-- 指定鏈接數據庫連接池的最大連接數 -->
        <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時需要依賴注入DataSource的引用 -->
        <property name="dataSource" ref="dataSource" />
    </bean>
</beans>

容器管理的JTA全局事務的配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        <!-- 容器管理數據源的JNDI -->
        <property name="jndiName" value="jdbc/jpetstore" />
    </bean>

    <!-- 使用JtaTransactionManager類,該類實現PlatformTransactionManager接口 -->
    <!-- 針對採用全局事務管理的特定實現 -->
    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
</beans>

從上面的配置文件來看,當配置JtaTransactionManager全局事務管理策略時,只需指定事務管理器實現類即可,無須傳入額外的事務資源。這是因爲全局事務的JTA資源由Java EE服務器提供,而Spring容器能自行從Java EE服務器中獲取該事務資源,所以無須使用依賴注入來配置。

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.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://127.0.0.1/javaee" />
        <!-- 指定鏈接數據庫的用戶名 -->
        <property name="user" value="root" />
        <!-- 指定鏈接數據庫的密碼 -->
        <property name="password" value="123456" />
        <!-- 指定鏈接數據庫連接池的最大連接數 -->
        <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.MySQLInnoDBDialect</prop>
                <!-- 是否根據Hibernate映射創建數據表 -->
                <prop key="hibernate.hbm2ddl.auto">update</prop>
            </props>
        </property>
    </bean>

    <!-- 配置Hibernate局部事務管理器,使用HibernateTransactionManager類 -->
    <!-- 該類是PlatformTransactionManager接口針對採用Hibernate的特定實現類 -->
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <!-- 配置HibernateTransactionManager需要依賴注入SessionFactory -->
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
</beans>

如果底層採用Hibernate持久層技術,但事務採用JTA全局事務,則Spring配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <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 -->
        <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.MySQLInnoDBDialect</prop>
                <!-- 是否根據Hibernate映射創建數據表 -->
                <prop key="hibernate.hbm2ddl.auto">update</prop>
            </props>
        </property>
    </bean>

    <!-- 使用JtaTransactionManager類,該類實現PlatformTransactionManager接口 -->
    <!-- 針對採用全局事務管理的特定實現 -->
    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
</beans>

從上面的配置文件可以看出,不論採用哪種持久層訪問技術,只要使用JTA全局事務,Spring事務管理的配置完全一樣,因爲他們採用的都是全局事務管理策略。

從上面的配置文件可以看出,當採用Spring事務管理策略時,應用程序無須與具體的事務策略耦合。Spring提供瞭如下兩種事務管理方式:
☞ 編程式事務管理:即使利用Spring編程式事務時,程序也可直接獲取容器中的transactionManager Bean,該Bean總是PlatformTransactionManager的實例,所以可以通過該接口所提供的3個方法來開始事務、提交事務和回滾事務。
☞聲明式事務管理:無須在Java程序中書寫任何的事務操作代碼,而是通過在XML文件中爲業務組件配置管理事務代理(AOP代理的一種),AOP爲事務代理所織入的增強處理也有Spring提供:在目標方法執行之前,織入開始事務;在目標方法執行之後,織入結束事務。

2.使用TransactionProxyFactoryBean創建事務代理

Spring同時支持編程式事務策略和聲明式事務策略,大部分時候,我們都推薦採用聲明式事務策略。使用聲明式事務策略的優勢十分明顯:
☞ 聲明式事務能大大降低開發者的代碼書寫量,而且聲明式事務幾乎不影響應用的代碼。因此,無論底層的事務策略如何變化,應用程序都無須任何改變。
☞ 應用程序代碼無須任何事務處理代碼,可以更專注於業務邏輯的實現。
☞ Spring則可對任何POJO的方法提供事務管理,而且Spring的聲明式事務管理無須容器的支持,可在任何環境下使用。
☞ EJB的CMT無法提供聲明式回滾規則:而通過配置文件,Spring可指定事務在遇到特定異常時自動回滾。Spring不僅可在代碼中使用setRollbackOnly回滾事務,也可在配置文件中配置回滾規則。
☞ 由於Spring採用AOP的方式管理事務,因此,可以在事務回滾動作中插入用戶自己的動作,而不僅僅是執行系統默認的回滾。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.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://127.0.0.1/javaee" />
        <!-- 指定鏈接數據庫的用戶名 -->
        <property name="user" value="root" />
        <!-- 指定鏈接數據庫的密碼 -->
        <property name="password" value="123456" />
        <!-- 指定鏈接數據庫連接池的最大連接數 -->
        <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時需要依賴注入DataSource的引用 -->
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!-- 配置一個業務邏輯Bean -->
    <bean id="newsDao" class="org.crazyit.app.dao.impl.NewsDaoImpl">
        <property name="ds" ref="dataSource"/>
    </bean>

    <!-- 爲業務邏輯Bean配置事務代理 -->
    <bean id="newsDaoTrans" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <!-- 爲事務代理工廠Bean注入事務管理器 -->
        <property name="transactionManager" ref="transactionManager"/>
        <property name="target" ref="newsDao"/>
        <!-- 指定事務屬性 -->
        <property name="transactionAttributes">
            <props>
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>
</beans>

上面的配置文件中第一段粗體字代碼配置了一個事務管理器,該事務管理器是針對JDBC局部事務的特定實現類。程序第二段粗體字代碼爲test Bean配置了事務代理。

配置事務代理時需要傳入一個事務管理器、一個目標Bean,並指定該事物代理的事務屬性,事務屬性由transactionManager屬性指定。上面事務屬性只有一條事務傳播規則,該規則指定對於所有方法都使用PROPAGATION_REQUIRED的傳播規則。Spring支持的事務傳播規則如下:
☞ PROPAGATION_MANDATORY:要求調用該方法的線程必須處於事務環境中,否則拋出異常
☞ PROPAGATION_NESTED:如果執行該方法的線程已處於事務環境下,依然啓動新的事務,方法在嵌套的事務裏執行。如果執行該方法的線程並未處於事務中,也啓動新的事務,然後執行該方法,此時與PROPAGATION_REQUIRED相同
☞ PROPAGATION_NEVER:不允許調用該方法的線程處於事務環境系下,如果調用該方法的線程處於事務環境下,則拋出異常
☞ PROPAGATION_NOT_SUPPORTED:如果調用該方法的線程處於事務中,則先暫停當前事務,然後執行該方法
☞ PROPAGATION_REQUIRED:要求在事務環境中執行該方法,如果當前執行線程已處於事務中,則直接調用;如果當前執行線程不處於事務中,則啓動新的事務後執行該方法
☞ PROPAGATION_REQUIRED_NEW:該方法要求在新的事務環境中執行,如果當前執行線程已處於事務中,則先暫停當前事務,啓動新事務後執行該方法;如果當前調用線程不處於事務中,則啓動新的事務後執行方法
☞ PROPAGATION_SUPPORTS:如果當前執行線程處於事務中,則使用當前事務,否則不使用事務

事實上,Spring不僅支持對接口的代理,整合CGLIB後,Spring甚至可以對具體類生成代理,只要設置proxy-target-class屬性爲true就可以。如果目標Bean沒有實現任何接口,proxy-target-class屬性默認被設爲true,此時Spring會對具體類生成代理。

<!-- 表示使用cglib,而非JDK的動態代理,因爲Controller沒有實現接口,所以要配置這裏 -->
<aop:aspectj-autoproxy proxy-target-class="true" />

3.Spring2.X的事務配置策略

雖然前面介紹的TransactionProxyFactoryBean配置策略簡單易懂,但配置起來極爲繁瑣:每個目標Bean都需要額外配置一個TransactionProxyFactoryBean代理,這種方式將導致配置文件急劇增加。
Spring2.X的XML Schema方式提供了更簡潔的事務配置策略,Spring2.X提供了tx命名空間來配置事務管理,tx命名空間下提供了<tx:advice.../>元素來配置事務增強處理,一旦使用該元素配置了事務增強處理,就可以直接使用<aop:advisor.../>爲容器中一批Bean配置自動事務代理。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    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-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.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://127.0.0.1/javaee" />
        <!-- 指定鏈接數據庫的用戶名 -->
        <property name="user" value="root" />
        <!-- 指定鏈接數據庫的密碼 -->
        <property name="password" value="123456" />
        <!-- 指定鏈接數據庫連接池的最大連接數 -->
        <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時需要依賴注入DataSource的引用 -->
        <property name="dataSource" ref="dataSource" />
    </bean>

     <!-- 配置一個業務邏輯Bean -->
    <bean id="newsDao" class="org.crazyit.app.dao.impl.NewsDaoImpl">
        <property name="ds" ref="dataSource"/>
    </bean>

    <!-- 配置事務增強處理Bean,指定事務管理器 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!-- 用於配置詳細的事務語義 -->
        <tx:attributes>
            <!-- 所有以'get'開頭的方法時read-only的 -->
            <tx:method name="get*" read-only="true"/>
            <!-- 其他方法使用默認的事務設置 -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- AOP配置的元素 -->
    <aop:config>
        <!-- 配置一個切入點,匹配 org.crazyit.app.dao.iml包下所有以Impl結尾的類裏、所有方法的執行 -->
        <aop:pointcut id="myPointcut" expression="execution(* org.crazyit.app.dao.iml.*Impl.*(..))"/>
        <!-- 指定在txAdvice切入點應用txAdvice事務增強處理 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/>
    </aop:config>
</beans>

上面的配置文件的第一段粗體字代碼使用XML Schema啓用了Spring配置文件的tx、aop兩個命名空間,配置文件的第二段粗體字代碼配置了一個事務增強處理,配置<tx:advice.../>元素時只需指定一個transaction-manager屬性,該屬性的默認值是‘transactionManager’

配置文件中最後一段粗體字是<aop:config.../>定義,它確保由txAdvice切面定義事務增強處理能在合適的點被織入。首先我們定義了一個切入點,它匹配org.crazyit.app.dao.iml包下所有以Impl結尾的類所包含的所有方法,我們把該切入點叫做myPointcut。然後用一個Advisor把這個切入點與txAdvice綁定在一起,表示當myPointcut執行時,txAdvice定義的增強處理將被織入。

當我們使用這種配置策略時,無須爲每個業務Bean專門配置事務代理,Spring AOP會爲業務組件自動生成代理,程序可以直接請求容器中test Bean,該Bean的方法已經具有了事務性——因爲該Bean的實現類位於org.crazyit.app.dao.iml包下,且以Impl結尾,和myPointcut切入點匹配。

配置<method.../>子元素可以指定如下幾個屬性:
☞ name:必選屬性,與該事務語義關聯的方法名。該屬性支持視同通配符
☞ propagation:指定事務傳播行爲,該屬性值可爲Propagation枚舉類的任一枚舉值。該屬性的默認值爲Propagation.REQUIRED
☞ isolation:指定事務隔離級別,該屬性值可爲Isolation枚舉類的任一枚舉值,該屬性的默認值爲Isolation.DEFAULT
☞ timeout:指定事務超時的時間(以秒爲單位),指定-1意味着不超時,該屬性的默認值是-1
☞ read-only:指定事務是否只讀。該屬性的默認值是false
☞ rollback-for:指定出發事務回滾的異常類(應使用全限定類名),該屬性可指定多個異常類,多個異常類之間以英文逗號隔開
☞ no-rollback-for:指定不觸發事務回滾的異常類(應使用全限定類名),該屬性可指定多個異常類,多個異常類之間以英文逗號隔開

提示:在默認情況下,只有當拋出運行時異常和unChecked異常時,Spring事務框架纔會自動回滾事務。也就是說,只有當拋出一個RuntimeException或其子類實例,以及Error對象,Spring纔會自動回滾事務。如果事務方法中拋出Checked異常,則事務不會自動回滾。

通過使用rollback-for屬性可強制Spring遇到特定Checked異常時自動回滾事務,下面的XML配置片段示範了這種用法。

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <!-- 所有以get開頭的方法是隻讀的,且當事務方法拋出NoItemException異常時回滾 -->
        <tx:method name="get*" read-only="true" rollback-for="exception.NoItemException"/>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

如果想讓事務方法拋出指定異常時強制不回滾事務,則可通過no-rollback-for屬性來指定,如下面的配置片段所示。

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <!-- 所有以get開頭的方法是隻讀的,且當事務方法拋出NoItemException異常時不回滾 -->
        <tx:method name="get*" read-only="true" no-rollback-for="exception.NoItemException"/>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

4.使用@Transactional

Spring還允許將事務配置在設置在Java類定義中,這需要藉助於@Transactional,該註解既可用於修飾Spring Bean類,也可用於修飾Bean類中的某個方法。
如果使用@Transactional修飾Bean類,表明這些事務設置對整個Bean類起作用;如果使用@Transactional修飾Bean類的某個方法,表明這些事務設置只對該方法有效。

使用@Transactional時可指定如下方法。
☞ isolation:用於指定事務隔離的級別。默認爲底層事務的隔離級別
☞ noRollbackFor:指定遇到指定異常時強制不回滾事務
☞ noRollbackForClassName:指定遇到指定多個異常時強制不回滾事務。該屬性值可以指定多個異常類名
☞ propagation:指定事務傳播屬性
☞ readOnly:指定事務是否只讀
☞ rollbackFor:指定遇到指定異常時強制回滾事務
☞ rollbackForClassName:指定遇到指定多個異常時強制回滾事務。該屬性值可以指定多個異常類名
☞ timeout:指定事務的超時時長

根據上面的解釋不難看出,其實該Annotation所指定的屬性與<tx:advice.../>元素中能指定的事務屬性基本上是對應的,他們的意義也基本相似。
下面使用@Transactional修飾需要添加事務的方法

public class NewsDaoImpl implements NewsDao {

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void insert(String title, String content) {
        .........
    }
}

上面的Bean類中insert()方法使用了@Transactional修飾,表明該方法就會具有事務性。僅使用這個Annotation修飾還不夠,還需要讓Spring根據Annotation來配置事務代理。所以還需要在Spring配置文件中增加如下配置片段:

<!-- 配置JDBC數據源的局部事務管理器,使用DataSourceTransactionManager類 -->
<!-- 該類實現PlatformTransactionManager接口,是針對採用數據源連接的特定實現 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 配置DataSourceTransactionManager時需要依賴注入DataSource的引用 -->
    <property name="dataSource" ref="dataSource" />
</bean>

<!-- 根據Annotation來生成事務代理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章