Spring的事務(wù)管理

1.數(shù)據(jù)庫(kù)事務(wù)基礎(chǔ)知識(shí)

1.1.何為數(shù)據(jù)庫(kù)事務(wù)

數(shù)據(jù)庫(kù)事務(wù)的4個(gè)特性

  • 原子性:組成一個(gè)事務(wù)的多個(gè)數(shù)據(jù)庫(kù)操作是一個(gè)不可分割的原子單元,只有所有的操作執(zhí)行成功,整個(gè)事務(wù)才提交。
  • 一致性:事務(wù)操作成功后,數(shù)據(jù)庫(kù)所處的狀態(tài)和業(yè)務(wù)規(guī)則是一致的,即數(shù)據(jù)不會(huì)被破壞。
  • 隔離性:在并發(fā)操作時(shí),不同的事務(wù)擁有各自的數(shù)據(jù)空間,它們的操作不會(huì)對(duì)對(duì)方產(chǎn)生干擾,準(zhǔn)確的說(shuō),并非要求做到完全無(wú)干擾。
  • 持久性:一旦事務(wù)提交成功之后,事務(wù)中所有的數(shù)據(jù)操作都必須持久化到數(shù)據(jù)庫(kù)中。

數(shù)據(jù)庫(kù)管理系統(tǒng)一般采用重執(zhí)日志來(lái)保證原子性、一致性和持久性。重執(zhí)日志記錄了數(shù)據(jù)庫(kù)變化的每一個(gè)動(dòng)作,數(shù)據(jù)庫(kù)在一個(gè)事務(wù)中執(zhí)行一部分操作后發(fā)生錯(cuò)誤退出,數(shù)據(jù)庫(kù)即可根據(jù)重執(zhí)行日志撤銷已經(jīng)執(zhí)行的操作。此外,對(duì)于已經(jīng)提交的事務(wù),即使數(shù)據(jù)庫(kù)崩潰,在重啟數(shù)據(jù)庫(kù)時(shí)也能夠根據(jù)日志對(duì)尚未持久化的數(shù)據(jù)進(jìn)行相應(yīng)的重執(zhí)行操作

和Java程序采用對(duì)象鎖機(jī)制進(jìn)行線程同步類似,數(shù)據(jù)庫(kù)管理系統(tǒng)采用數(shù)據(jù)庫(kù)鎖機(jī)制保證事務(wù)的隔離性。當(dāng)多個(gè)事務(wù)試圖對(duì)相同的數(shù)據(jù)進(jìn)行操作時(shí),只有持有鎖的事務(wù)才能操作數(shù)據(jù),直到前一個(gè)事務(wù)完成后,后面的事務(wù)才有機(jī)會(huì)對(duì)數(shù)據(jù)進(jìn)行操作。Oracle數(shù)據(jù)庫(kù)還使用了數(shù)據(jù)版本的機(jī)制,在回滾段位數(shù)據(jù)的每個(gè)變化都保存一個(gè)版本,使數(shù)據(jù)的更改不影響數(shù)據(jù)的讀取。

1.2.數(shù)據(jù)的并發(fā)問(wèn)題

一個(gè)數(shù)據(jù)庫(kù)可能擁有多個(gè)訪問(wèn)客戶端,這些客戶端都可用并發(fā)的方式訪問(wèn)數(shù)據(jù)庫(kù)。數(shù)據(jù)庫(kù)中的相同數(shù)據(jù)可能同時(shí)被多個(gè)事務(wù)訪問(wèn),如果沒(méi)有采取必要的隔離措施,就會(huì)導(dǎo)致各種并發(fā)問(wèn)題,破壞數(shù)據(jù)的完整性

  • 臟讀:A事務(wù)讀取B事務(wù)尚未提交的更改數(shù)據(jù),并在這個(gè)數(shù)據(jù)基礎(chǔ)上進(jìn)行操作
  • 不可重復(fù)讀:A事務(wù)讀取了B事務(wù)已經(jīng)提交的更改數(shù)據(jù)
  • 幻象讀:A事務(wù)讀取了B事務(wù)提交的新增數(shù)據(jù),這時(shí)A事務(wù)將出現(xiàn)幻象讀的問(wèn)題

兩類丟失事件

  • A事務(wù)在撤銷時(shí),“不小心”將B事務(wù)已經(jīng)轉(zhuǎn)入賬戶的金額給抹去了
  • A事務(wù)覆蓋B事務(wù)已經(jīng)提交的數(shù)據(jù),造成B事務(wù)所做操作丟失
1.3.數(shù)據(jù)庫(kù)鎖機(jī)制

數(shù)據(jù)庫(kù)通過(guò)鎖機(jī)制解決并發(fā)訪問(wèn)的問(wèn)題,雖然不同的數(shù)據(jù)庫(kù)在實(shí)現(xiàn)細(xì)節(jié)上存在差別,但原理基本上是一樣的。按鎖定的對(duì)象不一樣,一般可以分為表鎖定和行鎖定。前者對(duì)整張表鎖定,后者對(duì)表中特定行進(jìn)行鎖定。從并發(fā)事務(wù)鎖定的關(guān)系上看,可以分為共享鎖和獨(dú)占鎖。共享鎖定會(huì)防止獨(dú)占鎖定,但允許其他的共享鎖定。而獨(dú)占鎖定既防止其他的獨(dú)占鎖定,也防止其他的共享鎖定。為了更改數(shù)據(jù),數(shù)據(jù)庫(kù)必須在進(jìn)行更改的行上施加獨(dú)占鎖定,select、update、delete和select for update 語(yǔ)句都會(huì)隱式采用必要的行鎖定。oracle數(shù)據(jù)庫(kù)常用的5種鎖定

  • 行共享鎖定:一般是通過(guò)select for update語(yǔ)句隱式獲得行共享鎖定,行共享鎖定并
    不防止對(duì)數(shù)據(jù)進(jìn)行更新操作,但是可以防止其他會(huì)話獲取獨(dú)占性數(shù)據(jù)表鎖定

  • 行獨(dú)占鎖定:通過(guò)insert,update和delete語(yǔ)句隱式獲取,這種鎖定可以防止其他會(huì)話獲取一個(gè)共享鎖定,共享行獨(dú)占鎖或獨(dú)占鎖定

  • 表共享鎖定:這種鎖定可以防止其他會(huì)話獲取行獨(dú)占鎖定,或者防止其他表共享行獨(dú)占鎖定或表獨(dú)占鎖定,但它允許在表中擁有多個(gè)行共享和表共享鎖定。該鎖定可以讓會(huì)話具有對(duì)表事務(wù)級(jí)一致性訪問(wèn),因?yàn)槠渌麜?huì)話在用戶提交或者回滾該事務(wù)并釋放對(duì)該表的鎖定之前不能更改這張被鎖定的表

  • 表共享行獨(dú)占鎖:這種鎖定可以防止其他會(huì)話獲取一個(gè)表共享、行獨(dú)占或者表獨(dú)占鎖定,但允許其他行共享鎖定。這種鎖定類似于表共享鎖定,只是一次只能對(duì)一張表放置一個(gè)表共享行獨(dú)占鎖定。

  • 表獨(dú)占鎖定:這種鎖定可以防止其他會(huì)話對(duì)該表的任何其他鎖定

1.4.事務(wù)隔離級(jí)別

數(shù)據(jù)庫(kù)為用戶提供了自動(dòng)鎖機(jī)制,只要用戶指定會(huì)話的事務(wù)隔離級(jí)別,數(shù)據(jù)庫(kù)就會(huì)分析事務(wù)中的SQL語(yǔ)句,然后自動(dòng)為事務(wù)操作的數(shù)據(jù)資源添加合適的鎖。此外,數(shù)據(jù)庫(kù)還會(huì)維護(hù)這些鎖,當(dāng)一個(gè)資源上的鎖數(shù)目太多時(shí),自動(dòng)進(jìn)行鎖升級(jí)以提高系統(tǒng)的運(yùn)行性能,而這一過(guò)程對(duì)用戶來(lái)說(shuō)完全是透明的

隔離級(jí)別 臟讀 不可重復(fù)讀 幻象讀 第一類丟失更新 第二類丟失更新
READ UNCOMMITED 允許 允許 允許 不允許 允許
READ COMMITED 不允許 允許 允許 不允許 允許
REPEATABLE READ 不允許 不允許 允許 不允許 不允許
SERIALIZABLE 不允許 不允許 不允許 不允許 不允許
1.5.JDBC對(duì)事務(wù)的支持

并不是所有的數(shù)據(jù)庫(kù)都支持事務(wù),即使支持事務(wù)的數(shù)據(jù)庫(kù)也并非支持所有的事務(wù)隔離級(jí)別。用戶可以根據(jù)Connection.getMetaData()方法獲取DatabaseMetaData對(duì)象,并通過(guò)該對(duì)象的supportsTransactions()、supportsTransactionIsolationLevel(int level)
方法查看底層數(shù)據(jù)庫(kù)的事務(wù)支持情況

Connection默認(rèn)情況下是自動(dòng)提交的,即每條執(zhí)行的SQL語(yǔ)句都對(duì)應(yīng)一個(gè)事務(wù)。為了將多條SQL語(yǔ)句當(dāng)成一個(gè)事務(wù)執(zhí)行,必須先通過(guò)Connection.setAutoCommit(false)阻止Connection自動(dòng)提交,并通過(guò)Connection.setTransactionIsolation()設(shè)置事務(wù)的隔離級(jí)別。

2.ThreadLocal基礎(chǔ)知識(shí)

2.1.ThreadLocal是什么

ThreadLocal,不是一個(gè)線程,而是保存線程本地化對(duì)象的容器,當(dāng)運(yùn)行多線程環(huán)境的某個(gè)對(duì)象使用ThreadLocal維護(hù)變量時(shí),ThreadLocal為每個(gè)使用該變量的線程分配一個(gè)獨(dú)立的變量副本。所以每個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其他線程所對(duì)應(yīng)的副本。從線程的角度看,這個(gè)變量就像線程專有的本地變量

InheritableThreadLocal繼承與ThreadLocal,它自動(dòng)為子線程復(fù)制一份從父線程那里繼承而來(lái)的本地變量:在創(chuàng)建子線程時(shí),子線程會(huì)接收所有可繼承的線程本地變量的初始值。當(dāng)必須將本地線程變量自動(dòng)傳送給所有創(chuàng)建的子線程時(shí),應(yīng)盡可能地使用InheritableThreadLocal,而非ThreadLocal

2.2.ThreadLocal的接口方法
  • void set(Object value):設(shè)置當(dāng)前線程的線程局部變量的值
  • public Object get():返回當(dāng)前線程所對(duì)應(yīng)的線程局部變量
  • public void remove():將當(dāng)前線程局部變量的值刪除,目的是為了減少內(nèi)存的占用。該方法時(shí)Java 5.0新增的,需要指出的是,當(dāng)線程結(jié)束后,對(duì)應(yīng)該線程的局部變量將自動(dòng)被垃圾回收,所以顯示調(diào)用該方法清除線程的局部變量并不是必需的操作,但它可以加快內(nèi)存回收的速度
  • protected Object initialValue():返回該線程局部變量的初始值,該方法時(shí)一個(gè)protected方法,顯示是為了讓子類覆蓋而設(shè)計(jì)的
2.3.一個(gè)ThreadLocal實(shí)例
public class SequenceNumber {
    //通過(guò)匿名內(nèi)部類覆蓋ThreadLocal的initialValue()方法,指定初始值
    private static ThreadLocal<Integer> seqNum=new ThreadLocal<Integer>(){
        public Integer initialValue(){
            return 0;
        }
    };

    //獲取下一個(gè)序列值
    public int getNextNum(){
        seqNum.set(seqNum.get()+1);
        return seqNum.get();
    }

    public static void main(String[] args) {
        SequenceNumber sn =new SequenceNumber();
        TestClient t1=new TestClient(sn);
        TestClient t2=new TestClient(sn);
        TestClient t3=new TestClient(sn);
        t1.start();
        t2.start();
        t3.start();
    }

    static class TestClient extends Thread{
        private SequenceNumber sn;
        public TestClient(SequenceNumber sn){
            this.sn=sn;
        }

        public void run(){
            for (int i=0;i<3;i++){//每個(gè)線程輸出三個(gè)序列值
                System.out.println("thread["+Thread.currentThread().getName()+"] sn["+sn.getNextNum()+"]");
            }
        }
    }
}

//輸出內(nèi)容
thread[Thread-0] sn[1]
thread[Thread-0] sn[2]
thread[Thread-0] sn[3]
thread[Thread-1] sn[1]
thread[Thread-1] sn[2]
thread[Thread-1] sn[3]
thread[Thread-2] sn[1]
thread[Thread-2] sn[2]
thread[Thread-2] sn[3]

考查輸出結(jié)果信息,發(fā)現(xiàn)每個(gè)線程所產(chǎn)生的序號(hào)雖然都是共享同一個(gè)SequenceNumber實(shí)例,但它們并沒(méi)有相互干擾,而是各自產(chǎn)生獨(dú)立的序列號(hào),這是因?yàn)橥ㄟ^(guò)ThreadLocal為每個(gè)線程提供了單獨(dú)的副本

2.4.與Thread同步機(jī)制的比較

在同步機(jī)制中,通過(guò)對(duì)象的鎖機(jī)制保證同一時(shí)間只有一個(gè)線程訪問(wèn)變量。這時(shí)該變量是多個(gè)線程共享的,使用同步機(jī)制要求程序縝密地分析什么時(shí)候?qū)ψ兞窟M(jìn)行讀/寫、什么時(shí)候需要鎖定某個(gè)對(duì)象、什么時(shí)候釋放對(duì)象鎖等繁雜的問(wèn)題,程序設(shè)計(jì)和編寫難度較大

而ThreadLocal從另一個(gè)角度來(lái)解決多線程的并發(fā)訪問(wèn)。ThreadLocal為每個(gè)線程提供了一個(gè)獨(dú)立的變量副本,從而隔離了多個(gè)線程對(duì)訪問(wèn)數(shù)據(jù)的沖突。因?yàn)槊總€(gè)線程都擁有自己的變量副本,因而也就沒(méi)有必要對(duì)該變量進(jìn)行同步,ThreadLocal提供了線程安全的對(duì)象封裝,在編寫多線程時(shí),可以把不安全的變量封裝進(jìn)ThreadLocal

2.5.Spring使用ThreadLocal解決線程安全問(wèn)題

一般情況下,只有無(wú)狀態(tài)的Bean才可以在多線程環(huán)境下共享。在Spring中,絕大部分Bean都可以聲明為singleton作用域。正是因?yàn)镾pring對(duì)一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全的“狀態(tài)性對(duì)象”采用ThreadLocal進(jìn)行封裝,讓它們也成為線程安全的“狀態(tài)性對(duì)象”,因此,有狀態(tài)的Bean就能夠以singleton的方式在多線程中正常工作

web應(yīng)用一般分為控制層,業(yè)務(wù)層和持久層,在不同測(cè)層次編寫對(duì)應(yīng)的邏輯,下層通過(guò)接口向上層開放功能調(diào)用。在一般情況下,從接收請(qǐng)求返回響應(yīng)所經(jīng)過(guò)的所有程序調(diào)用都屬于同一個(gè)線程,這樣用戶就可以根據(jù)需要,將一些非線程安全的變量以ThreadLocal存放,在同一次請(qǐng)求響應(yīng)的調(diào)用線程中,所有對(duì)象所訪問(wèn)的同一ThreadLocal變量都是當(dāng)前線程所綁定。

3.Spring對(duì)事務(wù)管理的支持

Spring為事務(wù)管理提供了一致的編程模板,在高層次建立了統(tǒng)一的事務(wù)抽象,像Spring DAO為不同的持久化類提供了模板類一樣,Spring也提供了事務(wù)模板類TransactionTemplate。通過(guò)TransactionTemplate并配合使用事務(wù)回調(diào)TransactionCallback指定具體的持久化操作,就可以通過(guò)編程方式實(shí)現(xiàn)事務(wù)管理,而無(wú)須關(guān)注資源獲取、復(fù)用、釋放、事務(wù)同步和異常處理等操作。

3.1.事務(wù)管理關(guān)鍵抽象

在事務(wù)管理SPI的抽象層主要包括3個(gè)接口,分別是PlatformTransactionManager、TransactionDefinition和TransactionStatus

  • TransactionDefinition:定義了Spring兼容的事務(wù)屬性

    • 事務(wù)隔離:當(dāng)前事務(wù)和其它事務(wù)的隔離程度
    • 事務(wù)傳播:通常在一個(gè)事務(wù)中執(zhí)行的所有代碼都會(huì)運(yùn)行于同一事務(wù)上下文中
    • 事務(wù)超時(shí):事務(wù)在超時(shí)前能運(yùn)行多久,超過(guò)時(shí)間后,事務(wù)被回滾
    • 只讀狀態(tài):只讀事務(wù)不修改任何數(shù)據(jù),資源事務(wù)管理者可以針對(duì)可讀事務(wù)應(yīng)用一些優(yōu)化措施,提高運(yùn)行性能
  • TransactionStatus:代表一個(gè)事務(wù)的具體運(yùn)行狀態(tài)。事務(wù)管理器可以通過(guò)該接口獲取事務(wù)運(yùn)行期的狀態(tài)信息,也可以通該接口間接地回滾事務(wù),它相比在拋出異常時(shí)回滾事務(wù)的方式更具有可控性。SavepointManager接口擁有以下幾個(gè)方法

    • Object createSavepoint():創(chuàng)建一個(gè)保存點(diǎn)對(duì)象,以便在后面可以利用rollbackToSavepoint方法使事務(wù)回滾到特定的保存點(diǎn)上
    • void rollbackSavepoint(Object savepoint):將事務(wù)回滾到特定的保存點(diǎn)上,
      被回滾的保存點(diǎn)將自動(dòng)釋放
    • void releaseSavepoint(Object savepoint):釋放一個(gè)保存點(diǎn)。如果事務(wù)提交,
      則所有的保存點(diǎn)會(huì)被自動(dòng)釋放,無(wú)須手工清除
  • PlatformTransactionManager:通過(guò)JDBC的事務(wù)管理知識(shí)可以知道,事務(wù)只能被提交或回滾

3.2.Spring的事務(wù)管理器實(shí)現(xiàn)類

Spring將事務(wù)管理委托給底層具體的持久化實(shí)現(xiàn)框架來(lái)完成。因此,Spring為不同的持久化框架提供了PlatformTransactionManager接口的實(shí)現(xiàn)類。

  • Spring JDBC和MyBatis:如果使用Spring JDBC或MyBatis,由于它們都基于數(shù)據(jù)源的
    Connection訪問(wèn)數(shù)據(jù)庫(kù),所以可以使用DataSourceTransactionManager,只要在Spring中進(jìn)行以下配置就可以了
<!--配置一個(gè)dataSource的數(shù)據(jù)源-->
<bean id="dataSource"
          class="org.apache.commons.dbcp.BasicDataSource"
          destroy-method="close"
          p:driverClassName="${jdbc.driverClassName}"
          p:url="${jdbc.url}"
          p:username="${jdbc.username}"
          p:password="${jdbc.password}"/>
<!--基于數(shù)據(jù)源的事務(wù)管理器-->
 <bean id="txManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
          p:dataSource-ref="dataSource"/><!--引用數(shù)據(jù)源-->
  • jpa:jpa通過(guò)javax.persistence.EntityTransaction管理JPA事務(wù),EntityTransaction對(duì)象可以通過(guò)EntityTransaction.getTransaction()方法獲得,在底層,JPA依然通過(guò)JDBC的Connection的事務(wù)方法完成最終的控制,因此要配置一個(gè)EntityManagerFactory,最后才配置JpaTransactionManager,
<bean id="entityManagerFactory"
          class="org.springframework.orm.jpa.datasource.LocalContainerEntityManagerFactoryBean"
          p:dataSource-ref="dataSource"/><!--指定一個(gè)數(shù)據(jù)源-->
          
<bean id="transactionManager"
          class="org.springframework.orm.jpa.JpaTransactionManager"
          p:entityManagerFactory-ref="entityManagerFactory"/><!--指定實(shí)體管理器-->

  • Hibernate:Spring 4.0已取消了對(duì)Hibernate3.6之前版本的支持,并全面支持Hibernate5.0;因此,只為Hibernate 3.6+提供了事務(wù)管理器。
<bean id="sessionFactory"
          class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
          p:dataSource-ref="dataSource"<!--指定一個(gè)數(shù)據(jù)源-->
          p:mappingResource="classpath:bbtFoum.hbm.xml"><!--指定Hibernate的配置文件-->
        <property name="annotatedClasses"><!--Hibernate其它屬性-->
            <list>
                <value>com.smart.User</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                <prop key="hibernate.show_sql">true</prop>
            </props>
        </property>
    </bean>
    
<bean id="transactionManager"
          class="org.springframework.orm.hibernate4.HibernateTransactionManager"
          p:sessionFactory-ref="sessionFactory"/><!--注入會(huì)話工廠-->
  • JTA:如果希望在JavaEE容器里使用JTA,則將通過(guò)JNDI和Spring的JtaTransactionManager獲取一個(gè)容器管理的DataSource。JtaTransactionManager不需要知道DataSource和其他特定的資源,因?yàn)樗萌萜魈峁┑娜质聞?wù)管理。
<jee:jndi-lookup id="accountDs" jndi-name="java:comp/env/jdbc/account"/>
<jee:jndi-lookup id="orderDs" jndi-name="java:comp/env/jdbc/order"/>
<!--通過(guò)jee命名空間獲取Java EE應(yīng)用服務(wù)器容器中的數(shù)據(jù)源-->

<!--指定JTA事務(wù)管理器-->
<bean id="transactionManager"
    class="org.springframework.transaction.jta.JtaTransactionManager"
</bean>
3.3.事務(wù)同步管理器

Spring將JDBC的Connection、Hibernate的Session等訪問(wèn)數(shù)據(jù)庫(kù)的連接或會(huì)話對(duì)象統(tǒng)稱為資源,這些資源在同一時(shí)刻是不能多線程共享的。為了讓DAO、Service類可能做到singleton,Spring的事務(wù)同步管理器類SynchronizationManager使用ThreadLocal為不同事務(wù)線程提供了獨(dú)立的資源副本,同時(shí)維護(hù)事務(wù)配置的屬性和運(yùn)行狀態(tài)信息。事務(wù)同步管理器是Spring事務(wù)管理的基石,不管用戶使用的是編程式事務(wù)管理,還是聲明式事務(wù)管理,都離不開事務(wù)同步管理器

3.4.事務(wù)傳播行為

當(dāng)我們調(diào)用一個(gè)基于Spring的Service接口方法時(shí),它將運(yùn)行于Spring管理的事務(wù)環(huán)境中,Service接口方法可能會(huì)在內(nèi)部調(diào)用其他的Service接口方法以共同完成一個(gè)完整的業(yè)務(wù)操作,因此就會(huì)產(chǎn)生服務(wù)接口方法嵌套調(diào)用的情況,Spring通過(guò)事務(wù)傳播行為控制當(dāng)前的事務(wù)如何被傳播到被嵌套調(diào)用的目標(biāo)服務(wù)接口方法中

  • 事務(wù)傳播行為
事務(wù)傳播行為類型 說(shuō)明
PROPAGATION_REQUIRED 如果當(dāng)前沒(méi)有事務(wù),則新建一個(gè)事務(wù);如果已經(jīng)存在一個(gè)事務(wù),則加入到這個(gè)事務(wù)中。這是最常見的選擇
PROPAGATION_SUPPORTS 支持當(dāng)前事務(wù),如果沒(méi)有當(dāng)前事務(wù),則以非事務(wù)方式執(zhí)行
PROPAGATION_MANDATORY 使用當(dāng)前事務(wù),如果當(dāng)前沒(méi)有事務(wù),則拋出異常
PROPAGATION_REQUIRES_NEW 新建事務(wù),如果當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起
PROPAGATION_NOT_SUPPORTED 以非事務(wù)方式執(zhí)行操作,如果當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起
PROPAGATION_NEVER 以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常
PROPAGATION_NESTED 如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行;如果當(dāng)前沒(méi)有事務(wù),則執(zhí)行與PROPAGATION_REQUIRED類似的操作

4、編程式的事務(wù)管理

Spring還是為編程式事務(wù)管理提供了模板類TransactionTemplate,以滿足一些特殊場(chǎng)合的需要。TransactionTemplate有兩個(gè)主要方法:

  • void setTransactionManager(PlatformTransactionManager transactionManager):
    設(shè)置事務(wù)管理器
  • Object execute(TransactionCallback action):在TransactionCallback回調(diào)接口中
    定義需要以事務(wù)方式組織的數(shù)據(jù)訪問(wèn)邏輯
@Service
public class ForumService{
    public ForumDao forumDao;
    public TransactionTemplate template;
    
    @Autowired
    public void setTemplate(TransactionTemplate template){//通過(guò)aop注入
        this.template =template;
    }
    
    public void addForum(final Forum forum){
        template.execute(new TransactionCallbackWithoutResult(){
            protected void doInTransactionWithoutResult(TransactionStatus status){
                forumDao.addForum(forum);//需要在事務(wù)環(huán)境中執(zhí)行的代碼
            }
        })
    }
}

由于Spring事務(wù)管理基于TransactionSynchronizationManager進(jìn)行工作,所以,如果在回調(diào)接口方法中需要顯示訪問(wèn)數(shù)據(jù)庫(kù)底層數(shù)據(jù)連接,則必須通過(guò)資源獲取工具類得到線程綁定的數(shù)據(jù)連接。這是Spring事務(wù)管理的底層協(xié)議,不容違反。如果FourmDao是基于Spring提供模板類構(gòu)建的,由于模板類已經(jīng)在內(nèi)部使用了資源獲取工具類獲取數(shù)據(jù)連接,所以用戶就不必關(guān)心底層數(shù)據(jù)庫(kù)連接的獲取問(wèn)題。

5、使用XML配置聲明式事務(wù)

大多數(shù)Spring用戶選擇聲明式事務(wù)管理的功能,這種方式對(duì)代碼的侵入性最小,可以讓事務(wù)管理代碼完全從業(yè)務(wù)代碼中移除,非常符合非侵入式輕量級(jí)容器的理念。

Spring的聲明式事務(wù)管理是通過(guò)Spring AOP實(shí)現(xiàn)的,通過(guò)事務(wù)中聲明性信息,Spring
負(fù)責(zé)將事務(wù)管理增強(qiáng)邏輯動(dòng)態(tài)織入業(yè)務(wù)方法的相應(yīng)連接點(diǎn)中。這些邏輯包括獲取線程綁定資源、開始事務(wù)、提交/回滾事務(wù)、進(jìn)行異常轉(zhuǎn)換和處理等工作。

5.1.一個(gè)將被實(shí)施事務(wù)增強(qiáng)的服務(wù)接口

BbtForum擁有4個(gè)方法,我們希望addTopic()和updateForum()方法擁有寫事務(wù)的能力,而其他兩個(gè)方法只需要擁有讀事務(wù)的能力就可以了,

@Service
@Transactional
public class BbtForum{

    public ForumDao forumDao;
    public TopicDao topicDao;
    public PostDao  postDao;
    public void addTopic(Topic topic){
        topicDao.addTopic(topic);
        postDao.addPost(topic.getPost());
    }
    
    public Forum getForum(int forumId){
        return forumDao.getForum(forumId);
    }
    
    public void updateForum(Forum forum){
        forumDao.updateForum(forum);
    }
    
    public int getForumNum(){
        return forumDao.getForumNum();
    }
}

BbtForum是一個(gè)POJO,只是簡(jiǎn)單的使用持久層的多個(gè)DAO類,通過(guò)它們的協(xié)作實(shí)現(xiàn)業(yè)務(wù)功能。在這里,我們看不到任何事務(wù)操作的代碼,所以如果直接使用BbtForum,那么這些方法都將以無(wú)事務(wù)的方式運(yùn)行?,F(xiàn)在,我們的任務(wù)是通過(guò)Spring聲明式事務(wù)配置讓這些業(yè)務(wù)方法擁有適合的事務(wù)功能。

5.2.使用原始的TransactionProxyFactoryBean
  • 聲明式事務(wù)配置
<!--1、引入DAO和DataSource的配置文件-->
  <import resource="classpath:applicationContext-dao.xml"/>
    
      <!--2、聲明事務(wù)管理-->
    <bean id="txManager" class="org.springframe.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataDource"/>
    </bean>
    
    <!--3、需要實(shí)施事務(wù)增強(qiáng)的目標(biāo)業(yè)務(wù)Bean-->
    <bean id="bbtForumTarget" class="com.example.SequenceNumber"
                              p:forumDao-ref="forumDao"
                              p:topicDao-ref="topicDao"
                              p:postDao-ref="postDao"                          
    />
    
    <!--4、使用事務(wù)代理工廠類為目標(biāo)業(yè)務(wù)Bean提供事務(wù)增強(qiáng)-->
    <bean id="bbtForum" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
            p:transactionManager-ref="txManager"http://4、1指定事務(wù)管理器
            p:target-ref="bbtForumTarget">//4、2指定目標(biāo)業(yè)務(wù)
        <property name="transactionAttributes">//4、3事務(wù)屬性
            <props>
                <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
                //4、4只讀事務(wù)
                <prop key="*">PROPAGATION_REQUIRED</prop>
                //4、5可寫事務(wù)
            </props>
        </property>
    </bean>

按照約定的習(xí)慣,需要事務(wù)增強(qiáng)的業(yè)務(wù)類一般將id取名為xxxTarget,如3處所示,這可以在字面上表示該Bean是要被代理的目標(biāo)Bean

通過(guò)TransactionProxyFactoryBean對(duì)業(yè)務(wù)類進(jìn)行代理,織入事務(wù)增強(qiáng)功能,如4處所示,首先需要為該代理類指定事務(wù)管理器,這些事務(wù)管理器實(shí)現(xiàn)了PlatformTransactionManager接口,其次,通過(guò)target屬性指定需要代理的目標(biāo)Bean;最后,為業(yè)務(wù)Bean的不同方法配置事務(wù)屬性。Spring允許通過(guò)鍵值配置業(yè)務(wù)方法的事務(wù)屬性信息,鍵可以使用通配符,如get代表目標(biāo)類中所有以get為前綴的方法,它匹配BbtForum的getForum(int forumId)和getForumNum()方法;而key=""匹配BbtForum接口的所有方法

  • 異常回滾/提交規(guī)則
    <prop>內(nèi)的值為事務(wù)屬性信息,其配置格式如下圖


    image

PROPAGATION(傳播行為),ISOLATION(隔離級(jí)別(可選)),readOnly(是否為只讀事務(wù)(可選)),-Exception(發(fā)生這些異常時(shí)回滾事務(wù)(可選)),+Exception(發(fā)生這些異常時(shí)照樣提交事務(wù)(可選))

用戶可以通過(guò)配置顯示回滾規(guī)則:指定帶正號(hào)(+)或負(fù)號(hào)(-)的異常類名(或異常匹配片段)。當(dāng)拋出負(fù)號(hào)行異常時(shí),將觸發(fā)事務(wù)回滾;將拋出正號(hào)型異常時(shí),即使這個(gè)異常時(shí)檢查型異常,事務(wù)也會(huì)提交。拋出的異?;蛟摦惓5淖嫦阮惖念惷ヅ湟?guī)則中指定了異常類名(或異常名片段),規(guī)則就會(huì)生效,如下面的設(shè)置:

    <prop key="add*">PROPAGATION_REQUIRED,-Exception</prop>

只要業(yè)務(wù)方法運(yùn)行時(shí)拋出的異?;蚱涓割惍惓5念惷ā癊xception”,事務(wù)都回滾,以下異常都符合這條規(guī)則:SQLException、ParseException。正因?yàn)镾pring采用名稱字符串包含的比較方式,所以用戶甚至可以將回滾規(guī)則設(shè)置為“-tion”。

因?yàn)镾pring默認(rèn)的事務(wù)回滾規(guī)則為“運(yùn)行期異常回滾,檢查型異常不回滾”,所以帶負(fù)號(hào)的異常設(shè)置僅對(duì)檢查型異常有意義,此外,用戶可以指定多個(gè)帶正號(hào)或負(fù)號(hào)的事務(wù)回滾/提交規(guī)則

<prop key="add*">
    PROPAGATION_REQUIRED,-XxxException,-YyyException
</prop>
5.3.基于aop/tx命名空間的配置
 <!--引入DAO和DataSource的配置文件-->
  <import resource="classpath:applicationContext-dao.xml"/>

      <!--聲明事務(wù)管理-->
    <bean id="txManager" class="org.springframe.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataDource"/>
    </bean>

    <!--使用強(qiáng)大的切點(diǎn)表達(dá)式語(yǔ)言輕松定義目標(biāo)方法-->
    <aop:config>
        <!--2.1.通過(guò)aop定義事務(wù)增強(qiáng)切面-->
        <aop:pointcut id="serviceMethod" expression="execution(* com.example.SequenceNumber.*(..))"/>
        <!--2.2.引用事務(wù)增強(qiáng)-->
        <aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice"/>
    </aop:config>

    <!--3.事務(wù)增強(qiáng)-->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!--3.1.事務(wù)屬性定義-->
        <tx:attributes>
            <tx:method name="get*" read-only="false"/>
            <tx:method name="add*" rollback-for="Exception"/>
            <tx:method name="update*"/>
        </tx:attributes>
    </tx:advice>

6.使用主機(jī)配置聲明式事務(wù)

除了基于XML的事務(wù)配置,Spring還提供了基于注解的事務(wù)配置,即通過(guò)@Transactional對(duì)需要事務(wù)增強(qiáng)的Bean接口、實(shí)現(xiàn)類或方法進(jìn)行標(biāo)注;在容器中配置基于注解的事務(wù)增強(qiáng)驅(qū)動(dòng),即可啟用基于注解的聲明式事務(wù),筆者在實(shí)際項(xiàng)目中一般采用這種配置

6.1.使用@Transactional注解

使用@Transactional對(duì)基于aop/tx的命名空間的事務(wù)配置進(jìn)行改造

@Service
@Transactional
public class BbtForum{

    public ForumDao forumDao;
    public TopicDao topicDao;
    public PostDao  postDao;
    public void addTopic(Topic topic){
        topicDao.addTopic(topic);
        postDao.addPost(topic.getPost());
    }
    
    public Forum getForum(int forumId){
        return forumDao.getForum(forumId);
    }
    
    public void updateForum(Forum forum){
        forumDao.updateForum(forum);
    }
    
    public int getForumNum(){
        return forumDao.getForumNum();
    }
}

因?yàn)樽⒔獗旧砭哂幸唤M普適性的默認(rèn)事務(wù)屬性,所以往往是要在需要事務(wù)管理的業(yè)務(wù)類中添加一個(gè)@Transactional注解,就完成了業(yè)務(wù)類事務(wù)屬性的配置

<!--對(duì)標(biāo)注@Transactional注解的Bean進(jìn)行加工處理,以織入事務(wù)管理切面-->
<tx:annotation-driven transaction-manager="txManager>/

<tx:annotation-driven>還有兩個(gè)屬性

  • proxy-target-class:如果為true,則Spring將通過(guò)創(chuàng)建子類來(lái)代理業(yè)務(wù)類;如果為false,則使用基于接口的代理。如果使用子類代理,則需要在類路徑中添加CGLib.jar類庫(kù)
  • order:如果業(yè)務(wù)類除事務(wù)切面外,還需要織入其它的切面,則通過(guò)該屬性可以控制事務(wù)切面在目標(biāo)連接點(diǎn)的織入順序
  • 關(guān)于@Transactional的屬性
    • 事務(wù)傳播行為
    • 事務(wù)隔離級(jí)別
    • 讀寫事務(wù)屬性
    • 超時(shí)時(shí)間
    • 回滾設(shè)置
  • 在何處標(biāo)注@Transactional注解:Spring建議在業(yè)務(wù)實(shí)現(xiàn)類上使用@Transaction注解
  • 在方法處使用注解:方法處的主機(jī)會(huì)覆蓋定義處的注解
  • 使用不同的事務(wù)管理器:一般情況下,一個(gè)應(yīng)用僅需使用一個(gè)事務(wù)管理器,如果希望在不同的地方使用不同的事務(wù)管理器
@Service
public class MultiForumService{
    //1、使用名為topic的事務(wù)管理器
    @Transactional("topic")
    public void addTopic(Topic topic){
        ...
    }
    
    //2、使用名為forum的事務(wù)管理器
    @Transactional("forum")
    public void updateForum(Forum forum){
        ...
    }
}

<bean id="forumTxManager" class="org.springframe.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="forumDataSource">
        <qualifier value="forum"/>
</bean>

<bean id="topicTxManager" class="org.springframe.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="topicDataSource">
        <qualifier value="topic"/>
</bean>

<tx:annotation-driven>

在1處,為事務(wù)管理器指定了一個(gè)數(shù)據(jù)源,每個(gè)事務(wù)管理器都可以綁定一個(gè)獨(dú)立的數(shù)據(jù)源,在2處,指定了一個(gè)可被@Transactional注解引用的事務(wù)管理器標(biāo)識(shí),如果到處都使用代標(biāo)識(shí)的注解,可以自定義一個(gè)綁定到特定事務(wù)管理器的注解,然后直接使用這個(gè)自定義的注解(感覺沒(méi)什么意義,都是使用注解)

@Target(ElementType.METHOD,ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Transactional("forum")//綁定到forum的事務(wù)管理器中
public @interface ForumTransaction{
    
}

7.集成特定的應(yīng)用服務(wù)器

一般來(lái)說(shuō),Spring的事務(wù)抽象與應(yīng)用服務(wù)器是無(wú)關(guān)的,不過(guò),如果用戶希望事務(wù)管理器使用特定的UserTransaction和TransactionManager對(duì)象(可以被自動(dòng)探測(cè)),以獲取更多的事務(wù)控制功能(如事務(wù)掛起等),則可以采用Spring為集成這些應(yīng)用服務(wù)器所提供的適配器

7.1.BEA WebLogic
<bean id="txManager" class="org.springframework.transaction.jta.WebLogicJtaTransactionManager"/>
7.2.WebSphere
<bean id="txManager" class="org.springframework.transaction.jta.WebSphereUowTransactionManager">
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,545評(píng)論 19 139
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,791評(píng)論 11 349
  • 此時(shí)此刻我不安焦慮懷揣著劇烈的心跳,坐在赤紅冰冷的木椅上,寒意讓我窒息。 一股寂寥淡漠之意,從我身上散發(fā)著,繼而在...
    源治閱讀 349評(píng)論 0 0
  • 【0813今日話題】 昨天聽了天天老師關(guān)于《我如何兩年內(nèi)勾搭到上百位牛人》的分享,你有什么收獲?來(lái)和大家復(fù)盤一下吧...
    梓毓爸閱讀 213評(píng)論 0 0

友情鏈接更多精彩內(nèi)容