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">
