事務的基本概念 什么是事務 事務的ACID特性 事務的隔離級別 JDBC中使用事務 事務控制語句 設置隔離級別 設置事務回滾點 JDBC中使用事務例子 spring中的事務管理 Spring事務原理 TransactionDefinition 基本事務屬性的定義 傳播行為 隔離級別 只讀 事務超時 回滾規則 Spring 編程式事務和聲明式事務的區別 spring事務實例 不用事務實現轉賬 編程式事務處理實現轉賬(TransactionTemplate ) 聲明式事務處理實現轉賬(基于AOP的 xml 配置) 聲明式事務處理實現轉賬(基于AOP的 注解 配置) Spring的事務管理默認只對運行期異常進行回滾
事務的基本概念什么是事務 事務是用戶定義的一個數據庫操作序列,這些操作要么全做,要么全不做,是一個不可分割的工作單位。例如,在關系數據庫中,一個事務可以是一個sql語句,一組sql語句或整個程序。 事務的開始和結束可以由用戶顯式控制,如果用戶沒有顯式的定義事務,則由數據庫管理系統按照默認規定自動劃分事務。例如:mysql數據庫默認一條sql語句一個事務,默認會開啟并提交事務. 在SQL中,定義事務的語句一般有三條: BEGIN TRANSACTION; COMMIT; ROLLBACK; 事務通常是以 BEGIN TRANSACTION開始,以COMMIT或ROLLBACK結束;COMMIT表示提交,即提交事務的所有操作,具體地說,就是將事務中所有對數據庫的更新寫回到磁盤上的物理數據庫中,事務正常結束;ROLLBACK表示回滾,即在事務運行過程中發生了某種故障,事務不能繼續執行,系統將事務中對數據庫的所有已完成的更新操作全部撤銷,回到事務開始時的狀態。
事務的ACID特性原子性(Atomicity) 原子性是指事務是一個不可分割的工作單位,事務中的操作要么全部成功,要么全部失敗。比如在同一個事務中的SQL語句,要么全部執行成功,要么全部執行失敗。 一致性(Consistency) 事務必須使數據庫從一個一致性狀態變換到另外一個一致性狀態。以轉賬為例子,A向B轉賬,假設轉賬之前這兩個用戶的錢加起來總共是2000,那么A向B轉賬之后,不管這兩個賬戶怎么轉,A用戶的錢和B用戶的錢加起來的總額還是2000,這個就是事務的一致性。 隔離性(Isolation) 一個事務的執行不能被其他事務干擾。事務的隔離性是多個用戶并發訪問數據庫時,數據庫為每一個用戶開啟的事務,不能被其他事務的操作數據所干擾,多個并發事務之間要相互隔離。 持久性(Durability) 持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即使數據庫發生故障也不應該對其有任何影響。 事務的四大特性中最麻煩的是隔離性,下面重點介紹一下事務的隔離級別 事務的隔離級別多個線程開啟各自的事務來操作數據庫中數據時,數據庫系統要負責隔離操作,以保證各個線程在獲取數據時的準確性。 如果事務不考慮隔離性,可能會引發如下問題: 1、臟讀 臟讀指一個事務讀取了另外一個事務未提交的數據。 事務A訪問了數據庫,它干了一件事情,往數據庫里加上了新來的牛人的名字,但是沒有提交事務。 insert into T values (4, '牛D'); 這時,來了另一個事務B,他要查詢所有牛人的名字。 select Name from T; 這時,如果沒有事務之間沒有有效隔離,那么事務B返回的結果中就會出現“牛D”的名字。這就是“臟讀(dirty read)”。 2、不可重復讀 不可重復讀指在一個事務內讀取表中的某一行數據,多次讀取結果不同。 事務A訪問了數據庫,他要查看ID是1的牛人的名字,于是執行了 select Name from T where ID = 1; 這時,事務B來了,因為ID是1的牛人改名字了,所以要更新一下,然后提交了事務。 update T set Name = '不牛' where ID = 1; 接著,事務A還想再看看ID是1的牛人的名字,于是又執行了 select Name from T where ID = 1; 結果,兩次讀出來的ID是1的牛人名字竟然不相同,這就是不可重復讀(unrepeatable read)。 3、虛讀(幻讀) 虛讀(幻讀)是指在一個事務內讀取到了別的事務插入的數據,導致前后讀取不一致。 事務A訪問了數據庫,他想要看看數據庫的牛人都有哪些,于是執行了 select * from T; 這時候,事務B來了,往數據庫加入了一個新的牛人。 insert into T values(4, '牛D'); 這時候,事務A忘了剛才的牛人都有哪些了,于是又執行了。 select * from T; 結果,第一次有三個牛人,第二次有四個牛人。 相信這個時候事務A就蒙了,剛才發生了什么?這種情況就叫“幻讀(phantom problem)”。 為了防止出現臟讀、不可重復讀、幻讀等情況,我們就需要根據我們的實際需求來設置數據庫的隔離級別。 事務隔離級別Serializable(串行化):可避免臟讀、不可重復讀、虛讀情況的發生。 Repeatable read(可重復讀):可避免臟讀、不可重復讀情況的發生。 Read committed(讀已提交):可避免臟讀情況發生。 Read uncommitted(讀未提交):最低級別,以上情況均無法保證。
mysql數據庫默認的事務隔離級別是:Repeatable read(可重復讀) oracle數據庫默認的事務隔離級別是:read commited(讀已提交) 查看數據庫默認的隔離級別:select @@tx_isolation; 設置隔離級別:set session transaction isolation level JDBC中使用事務事務控制語句 當Jdbc程序向數據庫獲得一個Connection對象時,默認情況下這個Connection對象會自動向數據庫提交在它上面發送的SQL語句。若想關閉這種默認提交方式,讓多條SQL在一個事務中執行,可使用下列的JDBC控制事務語句. Connection.setAutoCommit(false); //開啟事務(start transaction) Connection.rollback(); //回滾事務(rollback) Connection.commit(); //提交事務(commit)
設置隔離級別Connection.setTransactionIsolation(int level) :參數可選值如下: Connection.TRANSACTION_READ_UNCOMMITTED; //讀未提交 Connection.TRANSACTION_READ_COMMITTED; //讀已提交 Connection.TRANSACTION_REPEATABLE_READ; //可重復讀 Connection.TRANSACTION_READ_SERIALIZABLE。 //序列化
設置事務回滾點 在開發中,有時候可能需要手動設置事務的回滾點,在JDBC中使用如下的語句設置事務回滾點: Savepoint sp = connection.setSavepoint(); connection.rollback(sp); connection.commit(); //回滾后必須通知數據庫提交事務 JDBC中使用事務例子這是單元測試類,所以有@Before @Test package com.test.transaction.tset; public class TransactionTest { private Connection con = null; private Statement stmt = null; private ResultSet rs = null; private String url = "jdbc:mysql://localhost:3306/transaction?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC"; private String username = "root" ; private String password = "123456789" ; private String sql1 = "update account set money = money+100 where name='B'"; private String sql2 = "update account set money = money-100 where name='A'"; Class.forName("com.mysql.cj.jdbc.Driver") ; }catch(ClassNotFoundException e){ System.out.println("找不到驅動程序類 ,加載驅動失敗!"); con = DriverManager.getConnection(url, username, password ) ; stmt = con.createStatement() ; System.out.println("數據庫連接失敗!"); * @Description:模擬轉賬成功時的業務場景 public void testTransaction1(){ //通知數據庫開啟事務(start transaction) con.setAutoCommit(false); System.out.println("success"); } catch (SQLException e) { System.out.println("數據庫連接關閉失敗!"); * @Description:模擬轉賬過程中出現異常導致有一部分SQL執行失敗后讓數據庫自動回滾事務 public void testTransaction2(){ //通知數據庫開啟事務(start transaction) con.setAutoCommit(false); //用這句代碼模擬執行完SQL1之后程序出現了異常而導致后面的SQL無法正常執行,事務也無法正常提交,此時數據庫會自動執行回滾操作 System.out.println("success"); } catch (SQLException e) { System.out.println("數據庫連接關閉失敗!"); * @Description:模擬轉賬過程中出現異常導致有一部分SQL執行失敗時手動通知數據庫回滾事務 public void testTransaction3(){ //通知數據庫開啟事務(start transaction) con.setAutoCommit(false); //用這句代碼模擬執行完SQL1之后程序出現了異常而導致后面的SQL無法正常執行,事務也無法正常提交,此時數據庫會自動執行回滾操作 System.out.println("success"); } catch (SQLException e) { } catch (SQLException e) { System.out.println("數據庫連接關閉失敗!"); public void testTransaction4(){ String sql3 = "update account set money = money + 100 where name='C'"; //通知數據庫開啟事務(start transaction) con.setAutoCommit(false); //用這句代碼模擬執行完SQL1之后程序出現了異常而導致后面的SQL無法正常執行,事務也無法正常提交,此時數據庫會自動執行回滾操作 System.out.println("success"); } catch (SQLException ex) { } catch (SQLException e) { System.out.println("數據庫連接關閉失敗!");
spring中的事務管理Spring事務原理Spring事務的本質其實就是數據庫對事務的支持,沒有數據庫的事務支持,spring是無法提供事務功能的。對于純JDBC操作數據庫,想要用到事務,可以按照以下步驟進行: 1.獲取連接 Connection con = DriverManager.getConnection() 2.開啟事務con.setAutoCommit(false); 3.執行CRUD 4.提交事務/回滾事務 con.commit() / con.rollback(); 5.關閉連接 con.close(); 使用Spring的事務管理功能后,我們可以不再寫步驟 2 和 4 的代碼,而是由Spirng 自動完成。?那么Spring是如何在我們書寫的 CRUD 之前和之后開啟事務和關閉事務的呢?解決這個問題,也就可以從整體上理解Spring的事務管理實現原理了。下面簡單地介紹下,注解方式為例子 1.配置文件開啟注解驅動,在相關的類和方法上通過注解@Transactional標識。 2.spring 在啟動的時候會去解析生成相關的bean,這時候會查看擁有相關注解的類和方法,并且為這些類和方法生成代理,并根據@Transaction的相關參數進行相關配置注入,這樣就在代理中為我們把相關的事務處理掉了(開啟正常提交事務,異常回滾事務)。 3.真正的數據庫層的事務提交和回滾是通過bin log或者redo log實現的。 TransactionDefinition 基本事務屬性的定義什么是事務屬性呢?事務屬性可以理解成事務的一些基本配置,描述了事務策略如何應用到方法上。事務屬性包含了5個方面,如圖所示:  傳播行為當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播 在TransactionDefinition類中,spring提供了7種傳播屬性,接下來分別用簡單示例來說明。 溫馨提醒:下文提到的加入當前事務,指的是底層使用同一個Connection,但是事務狀態對象是可以重新創建的,并不影響。文章提到的當前只存在一個事務,表示的是共用底層的一個Connection,而不在乎創建了多少個事務狀態對象(TransactionStatus)。 1、PROPAGATION_REQUIRED 說明: 如果當前已經存在事務,那么加入該事務,如果不存在事務,創建一個事務,這是默認的傳播屬性值。 看一個小例子,代碼如下:
@Transactional
public void service(){
serviceA();
serviceB();
}
@Transactional
serviceA();
@Transactional
serviceB();
|
serviceA 和 serviceB 都聲明了事務,默認情況下,propagation=PROPAGATION_REQUIRED,整個service調用過程中,只存在一個共享的事務,當有任何異常發生的時候,所有操作回滾。 2、PROPAGATION_SUPPORTS 說明:如果當前已經存在事務,那么加入該事務,否則創建一個所謂的空事務(可以認為無事務執行)。 看一個小例子,代碼如下:
public void service(){
serviceA();
throw new RunTimeException();
}
@Transactional (propagation=Propagation.SUPPORTS)
serviceA();
|
serviceA執行時當前沒有事務,所以service中拋出的異常不會導致 serviceA回滾。 再看一個小例子,代碼如下:
public void service(){
serviceA();
}
@Transactional (propagation=Propagation.SUPPORTS)
serviceA(){
do sql 1
1 / 0 ;
do sql 2
}
|
由于serviceA運行時沒有事務,這時候,如果底層數據源defaultAutoCommit=true,那么sql1是生效的,如果defaultAutoCommit=false,那么sql1無效,如果service有@Transactional標簽,serviceA共用service的事務(不再依賴defaultAutoCommit),此時,serviceA全部被回滾。 3、 PROPAGATION_MANDATORY 說明:當前必須存在一個事務,否則拋出異常。 看一個小例子,代碼如下:
public void service(){
serviceB();
serviceA();
}
serviceB(){
do sql
}
@Transactional (propagation=Propagation.MANDATORY)
serviceA(){
do sql
}
|
這種情況執行 service會拋出異常,如果defaultAutoCommit=true,則serviceB是不會回滾的,defaultAutoCommit=false,則serviceB執行無效。 4、PROPAGATION_REQUIRED_NEW 說明:如果當前存在事務,先把當前事務相關內容封裝到一個實體,然后重新創建一個新事務,接受這個實體為參數,用于事務的恢復。更直白的說法就是暫停當前事務(當前無事務則不需要),創建一個新事務。 針對這種情況,兩個事務沒有依賴關系,可以實現新事務回滾了,但外部事務繼續執行。 看一個小例子,代碼如下:
@Transactional
public void service(){
serviceB();
try {
serviceA();
} catch (Exception e){
}
}
serviceB(){
do sql
}
@Transactional (propagation=Propagation.REQUIRES_NEW)
serviceA(){
do sql 1
1 / 0 ;
do sql 2
}
|
當調用service接口時,由于serviceA使用的是REQUIRES_NEW,它會創建一個新的事務,但由于serviceA拋出了運行時異常,導致serviceA整個被回滾了,而在service方法中,捕獲了異常,所以serviceB是正常提交的。 注意,service中的try … catch 代碼是必須的,否則service也會拋出異常,導致serviceB也被回滾。 5、Propagation.NOT_SUPPORTED 說明:如果當前存在事務,掛起當前事務,然后新的方法在沒有事務的環境中執行,沒有spring事務的環境下,sql的提交完全依賴于 defaultAutoCommit屬性值 。 看一個小例子,代碼如下:
@Transactional
public void service(){
serviceB();
serviceA();
}
serviceB(){
do sql
}
@Transactional (propagation=Propagation.NOT_SUPPORTED)
serviceA(){
do sql 1
1 / 0 ;
do sql 2
}
|
當調用service方法的時候,執行到serviceA方法中的1/0代碼時,拋出了異常,由于serviceA處于無事務環境下,所以 sql1是否生效取決于defaultAutoCommit的值,當defaultAutoCommit=true時,sql1是生效的,但是service由于拋出了異常,所以serviceB會被回滾。 6、 PROPAGATION_NEVER 說明: 如果當前存在事務,則拋出異常,否則在無事務環境上執行代碼。 看一個小例子,代碼如下:
public void service(){
serviceB();
serviceA();
}
serviceB(){
do sql
}
@Transactional (propagation=Propagation.NEVER)
serviceA(){
do sql 1
1 / 0 ;
do sql 2
}
|
上面的示例調用service后,若defaultAutoCommit=true,則serviceB方法及serviceA中的sql1都會生效。 7、 PROPAGATION_NESTED 說明: 如果當前存在事務,則使用 SavePoint 技術把當前事務狀態進行保存,然后底層共用一個連接,當NESTED內部出錯的時候,自行回滾到 SavePoint這個狀態,只要外部捕獲到了異常,就可以繼續進行外部的事務提交,而不會受到內嵌業務的干擾,但是,如果外部事務拋出了異常,整個大事務都會回滾。 注意: spring配置事務管理器要主動指定 nestedTransactionAllowed=true,如下所示:
< bean id = "dataTransactionManager"
class = "org.springframework.jdbc.datasource.DataSourceTransactionManager" >
< property name = "dataSource" ref = "dataDataSource" />
< property name = "nestedTransactionAllowed" value = "true" />
</ bean >
|
看一個小例子,代碼如下:
@Transactional
public void service(){
serviceA();
try {
serviceB();
} catch (Exception e){
}
}
serviceA(){
do sql
}
@Transactional (propagation=Propagation.NESTED)
serviceB(){
do sql1
1 / 0 ;
do sql2
}
|
serviceB是一個內嵌的業務,內部拋出了運行時異常,所以serviceB整個被回滾了,由于service捕獲了異常,所以serviceA是可以正常提交的。 再來看一個例子,代碼如下:
@Transactional
public void service(){
serviceA();
serviceB();
1 / 0 ;
}
@Transactional (propagation=Propagation.NESTED)
serviceA(){
do sql
}
serviceB(){
do sql
}
|
由于service拋出了異常,所以會導致整個service方法被回滾。(這就是跟PROPAGATION_REQUIRES_NEW不一樣的地方了,NESTED方式下的內嵌業務會受到外部事務的異常而回滾。) 隔離級別
只讀 這是事務的第三個特性,是否為只讀事務。如果事務只對后端的數據庫進行該操作,數據庫可以利用事務的只讀特性來進行一些特定的優化。通過將事務設置為只讀,你就可以給數據庫一個機會,讓它應用它認為合適的優化措施。 事務超時 為了使應用程序很好地運行,事務不能運行太長的時間。因為事務可能涉及對后端數據庫的鎖定,所以長時間的事務會不必要的占用數據庫資源。事務超時就是事務的一個定時器,在特定時間內事務如果沒有執行完畢,那么就會自動回滾,而不是一直等待其結束。 回滾規則 事務五邊形的最后一個方面是一組規則,這些規則定義了哪些異常會導致事務回滾而哪些不會。默認情況下,事務只有遇到運行期異常時才會回滾,而在遇到檢查型異常時不會回滾(這一行為與EJB的回滾行為是一致的) 。但是你可以聲明事務在遇到特定的檢查型異常時像遇到運行期異常那樣回滾。同樣,你還可以聲明事務遇到特定的異常不回滾,即使這些異常是運行期異常。 Spring 編程式事務和聲明式事務的區別 編程式事務處理:所謂編程式事務指的是通過編碼方式實現事務,允許用戶在代碼中精確定義事務的邊界。即類似于JDBC編程實現事務管理。管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對于編程式事務管理,spring推薦使用TransactionTemplate。 聲明式事務處理:管理建立在AOP之上的。其本質是對方法前后進行攔截,然后在目標方法開始之前創建或者加入一個事務,在執行完目標方法之后根據執行情況提交或者回滾事務。聲明式事務最大的優點就是不需要通過編程的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中做相關的事務規則聲明(或通過基于@Transactional注解的方式),便可以將事務規則應用到業務邏輯中。 簡單地說,編程式事務侵入到了業務代碼里面,但是提供了更加詳細的事務管理;而聲明式事務由于基于AOP,所以既能起到事務管理的作用,又可以不影響業務代碼的具體實現。 spring事務實例不用事務實現轉賬 我們還是以轉賬為實例。不用事務看如何實現轉賬。在數據庫中有如下表 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./schema/beans"
xmlns:xsi= "http://www./2001/XMLSchema-instance"
xmlns:context= "http://www./schema/context"
xmlns:aop= "http://www./schema/aop"
xsi:schemaLocation="http: //www./schema/beans
http: //www./schema/beans/spring-beans.xsd
http: //www./schema/aop
http: //www./schema/aop/spring-aop.xsd
http: //www./schema/context
http: //www./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 賬戶的金額卻沒有增加。這在實際應用中肯定是不允許的,那么如何解決呢? 編程式事務處理實現轉賬(TransactionTemplate ) 上面轉賬的兩步操作中間發生了異常,但是第一步依然在數據庫中進行了增加操作。實際應用中不會允許這樣的情況發生,所以我們這里用事務來進行管理。 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./schema/beans"
xmlns:xsi= "http://www./2001/XMLSchema-instance"
xmlns:context= "http://www./schema/context"
xmlns:aop= "http://www./schema/aop"
xsi:schemaLocation="http: //www./schema/beans
http: //www./schema/beans/spring-beans.xsd
http: //www./schema/aop
http: //www./schema/aop/spring-aop.xsd
http: //www./schema/context
http: //www./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); // 回滾
}
|
聲明式事務處理實現轉賬(基于AOP的 xml 配置) Dao 層和 Service 層與我們最先開始的不用事務實現轉賬保持不變。主要是 applicationContext.xml 文件變化了。 我們在 applicationContext.xml 文件中配置 aop 自動生成代理,進行事務管理: ①、配置管理器 ②、配置事務詳情 ③、配置 aop
<?xml version= "1.0" encoding= "UTF-8" ?>
<beans xmlns= "http://www./schema/beans"
xmlns:xsi= "http://www./2001/XMLSchema-instance"
xmlns:context= "http://www./schema/context"
xmlns:aop= "http://www./schema/aop"
xmlns:tx= "http://www./schema/tx"
xsi:schemaLocation="http: //www./schema/beans
http: //www./schema/beans/spring-beans.xsd
http: //www./schema/aop
http: //www./schema/aop/spring-aop.xsd
http: //www./schema/context
http: //www./schema/context/spring-context.xsd
http: //www./schema/tx
http: //www./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>
|
測試類這里我們就不描述了,也是分有異常和無異常進行測試,發現與預期結果是吻合的。 聲明式事務處理實現轉賬(基于AOP的 注解 配置) 分為如下兩步: ①、在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);
}
}
|
Spring的事務管理默認只對未被捕獲的運行期異常進行回滾Spring的事務管理默認只對出現未被捕獲的運行期異常(java.lang.RuntimeException及其子類)進行回滾; 如果一個方法拋出Exception或者Checked異常,Spring事務管理默認不進行回滾。 關于異常的分類:https://www.jb51.net/article/32246.htm 改變默認方式 在@Transaction注解中定義noRollbackFor和RollbackFor指定某種異常是否回滾。 @Transaction(noRollbackFor=RuntimeException.class) @Transaction(RollbackFor=Exception.class) 這樣就改變了默認的事務處理方式。 Spring事務異常回滾,捕獲異常不拋出就不會回滾為了打印清楚日志,很多方法我都加tyr catch,在catch中打印日志。但是這邊情況來了,當這個方法異常時候日志是打印了,但是加的事務卻沒有回滾。 例: 類似這樣的方法不會回滾 (一個方法出錯,另一個方法不會回滾) : userCapabilityQuotaDao.save(capabilityQuota);
下面的方法回滾(一個方法出錯,另一個方法會回滾): userCapabilityQuotaDao.save(capabilityQuota); throw new RuntimeException();
或者: userCapabilityQuotaDao.save(capabilityQuota); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
為什么不回滾呢??是對spring的事務機制不明白。!! *****默認spring事務只在發生未被捕獲的 runtimeexcetpion時才回滾 ****** spring aop 異常捕獲原理:被攔截的方法需顯式拋出異常,并不能經任何處理,這樣aop代理才能捕獲到方法的異常,才能進行回滾,默認情況下aop只捕獲runtimeexception的異常,但可以通過 。
|