PS:本篇博客源碼下載連接:http://pan.baidu.com/s/1mi3NhX2 密碼:3io2java
事務(Transaction),通常是指要作的或所作的事情。在計算機術語中是指訪問並可能更新數據庫中各類數據項的一個程序執行單元(unit)。mysql
這裏咱們以取錢的例子來說解:好比你去ATM機取1000塊錢,大致有兩個步驟:第一步輸入密碼金額,銀行卡扣掉1000元錢;第二步從ATM出1000元錢。這兩個步驟必須是要麼都執行要麼都不執行。若是銀行卡扣除了1000塊可是ATM出錢失敗的話,你將會損失1000元;若是銀行卡扣錢失敗可是ATM卻出了1000塊,那麼銀行將損失1000元。spring
如何保證這兩個步驟不會出現一個出現異常了,而另外一個執行成功呢?事務就是用來解決這樣的問題。事務是一系列的動做,它們綜合在一塊兒纔是一個完整的工做單元,這些動做必須所有完成,若是有一個失敗的話,那麼事務就會回滾到最開始的狀態,彷彿什麼都沒發生過同樣。 在企業級應用程序開發中,事務管理是必不可少的技術,用來確保數據的完整性和一致性。sql
①、原子性(Atomicity):事務是一個原子操做,由一系列動做組成。事務的原子性確保動做要麼所有完成,要麼徹底不起做用。數據庫
②、一致性(Consistency):一旦事務完成(無論成功仍是失敗),系統必須確保它所建模的業務處於一致的狀態,而不會是部分完成部分失敗。在現實中的數據不該該被破壞。編程
③、隔離性(Isolation):可能有許多事務會同時處理相同的數據,所以每一個事務都應該與其餘事務隔離開來,防止數據損壞。後端
④、持久性(Durability):一旦事務完成,不管發生什麼系統錯誤,它的結果都不該該受到影響,這樣就能從任何系統崩潰中恢復過來。一般狀況下,事務的結果被寫到持久化存儲器中。併發
首先咱們建立一個Java工程,而後導入 Spring 核心事務包app
咱們打開Spring的核心事務包,查看以下類:org.springframework.transaction框架
上面所示的三個類文件即是Spring的事務管理接口。以下圖所示:下面咱們分別對這三個接口進行簡單的介紹
Spring事務管理器的接口是org.springframework.transaction.PlatformTransactionManager,如上圖所示,Spring並不直接管理事務,經過這個接口,Spring爲各個平臺如JDBC、Hibernate等都提供了對應的事務管理器,也就是將事務管理的職責委託給Hibernate或者JTA等持久化機制所提供的相關平臺框架的事務來實現。
咱們進入到 PlatformTransactionManager 接口,查看源碼:
①、TransactionStatus getTransaction(TransactionDefinition definition) ,事務管理器 經過TransactionDefinition,得到「事務狀態」,從而管理事務。
②、void commit(TransactionStatus status) 根據狀態提交
③、void rollback(TransactionStatus status) 根據狀態回滾
也就是說Spring事務管理的爲不一樣的事務API提供一致的編程模型,具體的事務管理機制由對應各個平臺去實現。
好比下面咱們導入實現事務管理的兩種平臺:JDBC和Hibernate
而後咱們再次查看PlatformTransactionManager接口,會發現它多了幾個實現類,以下:
在上面 PlatformTransactionManager 接口中,有以下方法:
這個方法返回的是 TransactionStatus對象,而後程序根據返回的對象來獲取事務狀態,而後進行相應的操做。
而 TransactionStatus 這個接口的內容以下:
這個接口描述的是一些處理事務提供簡單的控制事務執行和查詢事務狀態的方法,在回滾或提交的時候須要應用對應的事務狀態。
上面講到的事務管理器接口PlatformTransactionManager經過getTransaction(TransactionDefinition definition)方法來獲得事務,這個方法裏面的參數是TransactionDefinition類,這個類就定義了一些基本的事務屬性。
那麼什麼是事務屬性呢?事務屬性能夠理解成事務的一些基本配置,描述了事務策略如何應用到方法上。事務屬性包含了5個方面,如圖所示:
TransactionDefinition 接口方法以下:
1、傳播行爲:當事務方法被另外一個事務方法調用時,必須指定事務應該如何傳播。
Spring 定義了以下七中傳播行爲,這裏以A業務和B業務之間如何傳播事務爲例說明:
①、PROPAGATION_REQUIRED :required , 必須。默認值,A若是有事務,B將使用該事務;若是A沒有事務,B將建立一個新的事務。
②、PROPAGATION_SUPPORTS:supports ,支持。A若是有事務,B將使用該事務;若是A沒有事務,B將以非事務執行。
③、PROPAGATION_MANDATORY:mandatory ,強制。A若是有事務,B將使用該事務;若是A沒有事務,B將拋異常。
④、PROPAGATION_REQUIRES_NEW :requires_new,必須新的。若是A有事務,將A的事務掛起,B建立一個新的事務;若是A沒有事務,B建立一個新的事務。
⑤、PROPAGATION_NOT_SUPPORTED :not_supported ,不支持。若是A有事務,將A的事務掛起,B將以非事務執行;若是A沒有事務,B將以非事務執行。
⑥、PROPAGATION_NEVER :never,從不。若是A有事務,B將拋異常;若是A沒有事務,B將以非事務執行。
⑦、PROPAGATION_NESTED :nested ,嵌套。A和B底層採用保存點機制,造成嵌套事務。
2、隔離級別:定義了一個事務可能受其餘併發事務影響的程度。
併發事務引發的問題:
在典型的應用程序中,多個事務併發運行,常常會操做相同的數據來完成各自的任務。併發雖然是必須的,但可能會致使如下的問題。
①、髒讀(Dirty reads)——髒讀發生在一個事務讀取了另外一個事務改寫但還沒有提交的數據時。若是改寫在稍後被回滾了,那麼第一個事務獲取的數據就是無效的。
②、不可重複讀(Nonrepeatable read)——不可重複讀發生在一個事務執行相同的查詢兩次或兩次以上,可是每次都獲得不一樣的數據時。這一般是由於另外一個併發事務在兩次查詢期間進行了更新。
③、幻讀(Phantom read)——幻讀與不可重複讀相似。它發生在一個事務(T1)讀取了幾行數據,接着另外一個併發事務(T2)插入了一些數據時。在隨後的查詢中,第一個事務(T1)就會發現多了一些本來不存在的記錄。
注意:不可重複讀重點是修改,而幻讀重點是新增或刪除。
在 Spring 事務管理中,爲咱們定義了以下的隔離級別:
①、ISOLATION_DEFAULT:使用後端數據庫默認的隔離級別
②、ISOLATION_READ_UNCOMMITTED:最低的隔離級別,容許讀取還沒有提交的數據變動,可能會致使髒讀、幻讀或不可重複讀
③、ISOLATION_READ_COMMITTED:容許讀取併發事務已經提交的數據,能夠阻止髒讀,可是幻讀或不可重複讀仍有可能發生
④、ISOLATION_REPEATABLE_READ:對同一字段的屢次讀取結果都是一致的,除非數據是被自己事務本身所修改,能夠阻止髒讀和不可重複讀,但幻讀仍有可能發生
⑤、ISOLATION_SERIALIZABLE:最高的隔離級別,徹底服從ACID的隔離級別,確保阻止髒讀、不可重複讀以及幻讀,也是最慢的事務隔離級別,由於它一般是經過徹底鎖定事務相關的數據庫表來實現的
上面定義的隔離級別,在 Spring 的 TransactionDefinition.class 中也分別用常量 -1,0,1,2,4,8表示。好比 ISOLATION_DEFAULT 的定義:
3、只讀
這是事務的第三個特性,是否爲只讀事務。若是事務只對後端的數據庫進行該操做,數據庫能夠利用事務的只讀特性來進行一些特定的優化。經過將事務設置爲只讀,你就能夠給數據庫一個機會,讓它應用它認爲合適的優化措施。
4、事務超時
爲了使應用程序很好地運行,事務不能運行太長的時間。由於事務可能涉及對後端數據庫的鎖定,因此長時間的事務會沒必要要的佔用數據庫資源。事務超時就是事務的一個定時器,在特定時間內事務若是沒有執行完畢,那麼就會自動回滾,而不是一直等待其結束。
5、回滾規則
事務五邊形的最後一個方面是一組規則,這些規則定義了哪些異常會致使事務回滾而哪些不會。默認狀況下,事務只有遇到運行期異常時纔會回滾,而在遇到檢查型異常時不會回滾(這一行爲與EJB的回滾行爲是一致的) 。可是你能夠聲明事務在遇到特定的檢查型異常時像遇到運行期異常那樣回滾。一樣,你還能夠聲明事務遇到特定的異常不回滾,即便這些異常是運行期異常。
編程式事務處理:所謂編程式事務指的是經過編碼方式實現事務,容許用戶在代碼中精肯定義事務的邊界。即相似於JDBC編程實現事務管理。管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對於編程式事務管理,spring推薦使用TransactionTemplate。
聲明式事務處理:管理創建在AOP之上的。其本質是對方法先後進行攔截,而後在目標方法開始以前建立或者加入一個事務,在執行完目標方法以後根據執行狀況提交或者回滾事務。聲明式事務最大的優勢就是不須要經過編程的方式管理事務,這樣就不須要在業務邏輯代碼中摻瑣事務管理的代碼,只需在配置文件中作相關的事務規則聲明(或經過基於@Transactional註解的方式),即可以將事務規則應用到業務邏輯中。
簡單地說,編程式事務侵入到了業務代碼裏面,可是提供了更加詳細的事務管理;而聲明式事務因爲基於AOP,因此既能起到事務管理的做用,又能夠不影響業務代碼的具體實現。
咱們仍是以轉帳爲實例。不用事務看如何實現轉帳。在數據庫中有以下表 account ,內容以下:
有兩個用戶 Tom 和 Marry 。他們初始帳戶餘額都爲 10000。這時候咱們進行以下業務:Tom 向 Marry 轉帳 1000 塊。那麼這在程序中能夠分解爲兩個步驟:
①、Tom 的帳戶餘額 10000 減小 1000 塊,剩餘 9000 塊。
②、Marry 的帳戶餘額 10000 增長 1000 塊,變爲 11000塊。
上面兩個步驟要麼都執行成功,要麼都不執行。咱們經過 TransactionTemplate 編程式事務來控制:
第一步:建立Java工程並導入相應的 jar 包(這裏不用事務其實不須要這麼多jar包,爲了後面的講解須要,咱們一次性導入全部的jar包)
第二步:編寫 Dao 層
AccountDao 接口:
package com.ys.dao; public interface AccountDao { /** * 匯款 * @param outer 匯款人 * @param money 匯款金額 */ public void out(String outer,int money); /** * 收款 * @param inner 收款人 * @param money 收款金額 */ public void in(String inner,int money); }
AccountDaoImpl 接口實現類
package com.ys.dao.impl; import org.springframework.jdbc.core.support.JdbcDaoSupport; import com.ys.dao.AccountDao; public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { /** * 根據用戶名減小帳戶金額 */ @Override public void out(String outer, int money) { this.getJdbcTemplate().update("update account set money = money - ? where username = ?",money,outer); } /** * 根據用戶名增長帳戶金額 */ @Override public void in(String inner, int money) { this.getJdbcTemplate().update("update account set money = money + ? where username = ?",money,inner); } }
第三步:實現 Service 層
AccountService 接口
package com.ys.service; public interface AccountService { /** * 轉帳 * @param outer 匯款人 * @param inner 收款人 * @param money 交易金額 */ public void transfer(String outer,String inner,int money); }
AccountServiceImpl 接口實現類
package com.ys.service.impl; import com.ys.dao.AccountDao; import com.ys.service.AccountService; public class AccountServiceImpl implements AccountService{ private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(String outer, String inner, int money) { accountDao.out(outer, money); accountDao.in(inner, money); } }
第四步:Spring 全局配置文件 applicationContext.xml
<?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: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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <bean id="accountDao" class="com.ys.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.ys.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> </beans>
第五步:測試
public class TransactionTest { @Test public void testNoTransaction(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountService account = (AccountService) context.getBean("accountService"); //Tom 向 Marry 轉帳1000 account.transfer("Tom", "Marry", 1000); } }
第六步:查看數據庫表 account
上面的結果和咱們想的同樣,Tom 帳戶 money 減小了1000塊。而 Marry 帳戶金額增長了1000塊。
這時候問題來了,好比在 Tom 帳戶 money 減小了1000塊正常。而 Marry 帳戶金額增長時發生了異常,實際應用中好比斷電(這裏咱們人爲構造除數不能爲0的異常),以下:
那麼這時候咱們執行測試程序,很顯然會報錯,那麼數據庫是什麼狀況呢?
數據庫account :
咱們發現,程序執行報錯了,可是數據庫 Tom 帳戶金額依然減小了 1000 塊,可是 Marry 帳戶的金額卻沒有增長。這在實際應用中確定是不容許的,那麼如何解決呢?
上面轉帳的兩步操做中間發生了異常,可是第一步依然在數據庫中進行了增長操做。實際應用中不會容許這樣的狀況發生,因此咱們這裏用事務來進行管理。
Dao 層不變,咱們在 Service 層注入 TransactionTemplate 模板,由於是用模板來管理事務,因此模板須要注入事務管理器 DataSourceTransactionManager 。而事務管理器說到底仍是用底層的JDBC在管理,因此咱們須要在事務管理器中注入 DataSource。這幾個步驟分別以下:
AccountServiceImpl 接口:
package com.ys.service.impl; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import com.ys.dao.AccountDao; import com.ys.service.AccountService; public class AccountServiceImpl implements AccountService{ private AccountDao accountDao; private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(final String outer,final String inner,final int money) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus arg0) { accountDao.out(outer, money); //int i = 1/0; accountDao.in(inner, money); } }); } }
Spring 全局配置文件 applicationContext.xml:
<?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: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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <bean id="accountDao" class="com.ys.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.ys.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> <property name="transactionTemplate" ref="transactionTemplate"></property> </bean> <!-- 建立模板 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="txManager"></property> </bean> <!-- 配置事務管理器 ,管理器須要事務,事務從Connection得到,鏈接從鏈接池DataSource得到 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> </beans>
測試文件保持不變,能夠分兩次測試,第一次兩次操做沒有發生異常,而後數據庫正常改變了。第二次操做中間發生了異常,發現數據庫內容沒變。
若是你們有興趣也能夠試試底層的PlatformTransactionManager來進行事務管理,我這裏給出主要代碼:
//定義一個某個框架平臺的TransactionManager,如JDBC、Hibernate DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 設置數據源 DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定義事務屬性 transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 設置傳播行爲屬性 TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 得到事務狀態 try { // 數據庫操做 accountDao.out(outer, money); int i = 1/0; accountDao.in(inner, money); dataSourceTransactionManager.commit(status);// 提交 } catch (Exception e) { dataSourceTransactionManager.rollback(status);// 回滾 }
Dao 層和 Service 層與咱們最早開始的不用事務實現轉帳保持不變。主要是 applicationContext.xml 文件變化了。
咱們在 applicationContext.xml 文件中配置 aop 自動生成代理,進行事務管理:
①、配置管理器
②、配置事務詳情
③、配置 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: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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <bean id="accountDao" class="com.ys.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.ys.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- 1 事務管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 2 事務詳情(事務通知) , 在aop篩選基礎上,好比對ABC三個肯定使用什麼樣的事務。例如:AC讀寫、B只讀 等 <tx:attributes> 用於配置事務詳情(屬性屬性) <tx:method name=""/> 詳情具體配置 propagation 傳播行爲 , REQUIRED:必須;REQUIRES_NEW:必須是新的 isolation 隔離級別 --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/> </tx:attributes> </tx:advice> <!-- 3 AOP編程,利用切入點表達式從目標類方法中 肯定加強的鏈接器,從而得到切入點 --> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.ys.service..*.*(..))"/> </aop:config> </beans>
測試類這裏咱們就不描述了,也是分有異常和無異常進行測試,發現與預期結果是吻合的。
分爲以下兩步:
①、在applicationContext.xml 配置事務管理器,將並事務管理器交予spring
②、在目標類或目標方法添加註解便可 @Transactional
首先在 applicationContext.xml 文件中配置以下:
<!-- 1 事務管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 2 將管理器交予spring * transaction-manager 配置事務管理器 * proxy-target-class true : 底層強制使用cglib 代理 --> <tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
其次在目標類或者方法添加註解@Transactional。若是在類上添加,則說明類中的全部方法都添加事務,若是在方法上添加,則只有該方法具備事務。
package com.ys.service.impl; import javax.annotation.Resource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.ys.dao.AccountDao; import com.ys.service.AccountService; @Transactional(propagation=Propagation.REQUIRED , isolation = Isolation.DEFAULT) @Service("accountService") public class AccountServiceImpl implements AccountService{ @Resource(name="accountDao") private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(String outer, String inner, int money) { accountDao.out(outer, money); //int i = 1/0; accountDao.in(inner, money); } }