事務:邏輯上一組操做,這些操做要麼一塊兒成功,要麼一塊兒失敗。html
特性:(ACID)java
原子性(atomicity): 事務不可分割,要麼全執行,要麼不執行。(如mysql
一致性(consistency): 事務執行先後數據的完整性保持一致(如銀行轉賬,A轉賬給B,必須保證A的錢必定轉給B,必定不會出現A的錢轉了但B沒收到,不然數據庫的數據就處於不一致(不正確)的狀態。)spring
隔離性(isolation) : 一個事務執行種不該該受到其餘事務干擾(在一個事務內部的操做對其餘事務是不產生影響,這須要事務隔離級別來指定隔離性;)sql
持久性(durability):事務一旦結束,數據就持久到數據庫。數據庫
一、數據庫分爲本地事務和全局事務(即分佈式事務)編程
本地事務:獨立的一個數據庫,能保證在該數據庫上操做的ACID.api
分佈式事務(全局事務): 多個數據庫源的事務(由每一個數據庫的本地事務組成),分佈式事務能保證這些本地事務的全部操做的ACID,使事務可跨越多數據庫。併發
二、java事務類型分爲jdbc事務和jta事務oracle
Jdbc事務:即本地事務,經過connection對象控制管理
Jta事務:(java transaction api)java事務api,提供了事務管理的接口,比jdbc更強大,支持分佈式事務。
三、是否經過編程分別爲聲明式事務和編程式事務
聲明式事務:經過xml配置或註解實現
編程式事務:經過編程代碼在業務邏輯須要時自行實現。
小結:
事務分類:本地事務(對應jdbc事務),全局事務(對應jta事務),實現方式聲明式或編程式
Spring事務本質是數據庫對事務的支持(沒有數據庫事務支持,spring沒法提供事務功能)。單純jdbc操做數據庫使用事務步驟:
獲取鏈接-->開啓事務-->執行crud-->提交事務-->關閉鏈接
spring事務管理幫咱們作了開啓事務-->執行crud-->提交事務的工做。Spring是如何實現開啓和關閉事務呢?以註解方式爲例:
1)配置文件開啓註解驅動,在相關類和方法上使用@Transcation標識
2)Spring啓動時解析並生成相關bean,爲這些類和方法生成代理,在代理種處理事務
3)真正的數據庫層事務提交和回滾經過binlog或redo log實現。
1)髒讀:一個事務讀到了另外一個事務未提交的數據。(若是的一個事務回滾了,第二個事務就讀到了髒數據)
2)不可重複讀:一個事務發讀到了另外一個事務已提交的update的數據,致使屢次查詢結果不一致(與幻讀區別在於強調更新數據)
3)幻讀:一個事務讀到了另外一個事務已提交的insert的數據,致使屢次查詢結果不一致。(強調插入或刪除數據)
解決不可重複讀問題,只需行鎖。
解決幻讀須要鎖表。
爲解決上面的問題,須要設置事務隔離級別
隔離級別 |
髒讀 |
不可重複讀 |
幻讀 |
級別說明 |
未提交讀(read uncommited) 0 |
是 |
是 |
是 |
最低級別事務隔離,運行另外一個事務可看到這個事務未提交數據。 三種問題均存在 |
已提交讀(read commited) 1 |
否 |
是 |
是 |
保證一個事務提交後才能被另外一個事務讀取。 |
可重複讀(repeatable read) 2 |
否 |
否 |
是 |
保證一個事務被提交後才能被另外一個事務讀取並避免了不可重複讀。 |
串行化Serializable 3 |
否 |
否 |
否 |
代價最高但最可靠隔離級別,事務按順序執行。對併發性能影響最大 |
注意:
1)數據庫默認隔離級別:大多數爲已提交讀(read commited),如oracle;少數爲可重複讀(repeatable read)如mysql innodb。
Mysql中默認事務隔離級別是可重複讀時不會鎖住讀取到的行。
2)隔離級別爲讀提交時,寫數據只鎖住相應行。
3)級別爲可重複讀時,若是有索引(包括主鍵索引)的時候,以索引列爲條件更新數據,會存在間隙鎖間隙鎖、行鎖、下一鍵鎖的問題,從而鎖住一些行;若是沒有索引,更新數據時會鎖住整張表。
4)級別爲串行化時,讀寫數據都會鎖表。
通常選擇數據庫隔離級別設爲已提交讀(read commited),避免髒讀且有較好併發性能。不可重複讀和幻讀併發問題可以使用悲觀鎖或樂觀鎖控制。
隔離級別 |
說明 |
ISOLATION_DEFAULT |
PlatfromTransactionManager 默認的隔離級別,使用數據庫默認的事務隔離級別。另外四個與 JDBC 的隔離級別相對應。 |
ISOLATION_READ_UNCOMMITTED |
未提交讀,最低級別 |
ISOLATION_READ_COMMITTED |
已提交讀 |
ISOLATION_REPEATABLE_READ |
可重複讀 |
ISOLATION_SERIALIZABLE |
串行化 |
Spring事務傳播屬性即定義多個事務同時存在時,如何處理這些事務的行爲,這些屬性在TransactionDefinition中定義。7種傳播屬性以下
(假設外層事務 Service A 的 Method A() 調用 內層Service B 的 Method B())
傳播行爲 |
說明 |
場景說明 |
PROPAGATION_REQUIRED |
Spring默認傳播行爲。 支持當前事務,若是當前沒有事務則新建一個事務 |
若是ServiceB.methodB() 的事務級別定義爲 PROPAGATION_REQUIRED, 若是執行 ServiceA.methodA() 時spring已建立事務,ServiceB.methodB() 已運行在 ServiceA.methodA() 的事務內部,就再也不起新的事務。 假如 ServiceB.methodB() 運行的時候發現本身沒有在事務中,就會爲本身分配一個事務。 這樣,在 ServiceA.methodA() 或者在 ServiceB.methodB() 內的任何地方出現異常,事務都會被回滾。 |
PROPAGATION_REQUIRES_NEW |
新建事務,若是當前存在事務,把當前事務掛起。新建的事務將和被掛起的事務沒有任何關係,是兩個獨立的事務,外層事務失敗回滾以後,不能回滾內層事務執行的結果,內層事務失敗拋出異常,外層事務捕獲,也能夠不處理回滾操做 |
好比咱們設計 ServiceA.methodA() 的事務級別爲 PROPAGATION_REQUIRED,ServiceB.methodB() 的事務級別爲 PROPAGATION_REQUIRES_NEW。 當執行到 ServiceB.methodB() 的時候,ServiceA.methodA() 所在的事務就會掛起,ServiceB.methodB() 會起一個新的事務,等待 ServiceB.methodB() 的事務完成之後,它才繼續執行。與 PROPAGATION_REQUIRED 的事務區別在於事務的回滾程度了。由於 ServiceB.methodB() 是新起一個事務,那麼就是存在兩個不一樣的事務。若是 ServiceB.methodB() 已經提交,那麼 ServiceA.methodA() 失敗回滾,ServiceB.methodB() 是不會回滾的。若是 ServiceB.methodB() 失敗回滾,若是他拋出的異常被 ServiceA.methodA() 捕獲,ServiceA.methodA() 事務仍然可能提交(主要看B拋出的異常是否是A會回滾的異常)。 |
PROPAGATION_SUPPORTS |
支持當前事務,若是當前沒有事務,就以非事務方式執行。 |
假設ServiceB.methodB() 的事務級別爲 PROPAGATION_SUPPORTS,那麼當執行到ServiceB.methodB()時,若是發現ServiceA.methodA()已經開啓了一個事務,則加入當前的事務,若是發現ServiceA.methodA()沒有開啓事務,則本身也不開啓事務。這種時候,內部方法的事務性徹底依賴於最外層的事務。 |
PROPAGATION_NESTED |
若是一個活動的事務存在,則運行在一個嵌套的事務中。若是沒有活動事務,則按REQUIRED屬性執行。它使用了一個單獨的事務,這個事務擁有多個能夠回滾的保存點。內部事務的回滾不會對外部事務形成影響。它只對DataSourceTransactionManager事務管理器起效。 |
ServiceB.methodB() 的事務屬性被配置爲 PROPAGATION_NESTED, 此時二者之間又將如何協做呢? ServiceB#methodB 若是 rollback, 那麼內部事務(即 ServiceB#methodB) 將回滾到它執行前的 SavePoint 而外部事務(即 ServiceA#methodA) 能夠有如下兩種處理方式: 捕獲異常,執行異常分支邏輯 方式也是嵌套事務最有價值的地方, 它起到了分支執行的效果, 若是 ServiceB.methodB 失敗, 那麼執行 ServiceC.methodC(), 而 ServiceB.methodB 已經回滾到它執行以前的 SavePoint, 因此不會產生髒數據(至關於此方法從未執行過), 這種特性能夠用在某些特殊的業務中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都沒有辦法作到這一點。 2)外部事務回滾/提交 代碼不作任何修改, 那麼若是內部事務(ServiceB#methodB) rollback, 那麼首先 ServiceB.methodB 回滾到它執行以前的 SavePoint(在任何狀況下都會如此), 外部事務(即 ServiceA#methodA) 將根據具體的配置決定本身是 commit 仍是 rollback |
PROPAGATION_MANDATORY |
支持當前事務,若是當前沒有事務,就拋出異常。 |
這三種通常不多用到 |
PROPAGATION_NOT_SUPPORTED |
以非事務方式執行操做,若是當前存在事務,就把當前事務掛起。 |
|
PROPAGATION_NEVER |
以非事務方式執行,若是當前存在事務,則拋出異常。 |
|
參考:
https://blog.csdn.net/mawenshu316143866/article/details/81281443
http://www.javashuo.com/article/p-yfcuwrdd-gz.html
問題:使用@Transactional註解事務以後,拋了異常竟然不回滾。
序號 |
說明 |
1 |
Service類或方法上(通常不建議在接口上)添加@Transcational註解。 |
2 |
@Transcational只能應用到public可見度方法上,其餘可見度不會報錯,但不會生效 |
3 |
默認對unchecked異常(如error或runtimeexception)進行事務回滾;若是是checked異常(繼承自java.lang.exception的異常,如ioexception)則不回滾。 如對checked異常也回滾,設置rollbackFor=Exception.class |
4 |
只讀事務 @Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) 只讀標誌只在事務啓動時應用,不然即便配置也會被忽略。 |
解決註解不回滾
序號 |
說明 |
1 |
查看方法是否爲public |
2 |
異常是否爲unchecked, 如對checked異常也回滾,設置@Transactional(rollbackFor=Exception.class) 相似的還有norollbackFor,自定義不回滾的異常 |
3 |
數據庫引擎要支持事務,若是是MySQL,注意表要使用支持事務的引擎,好比innodb,若是是myisam,事務是不起做用的 |
4 |
是否開啓了對註解的解析 <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/> |
5 |
spring是否掃描到你這個包,以下是掃描到org.test下面的包 <context:component-scan base-package="org.test" ></context:component-scan> |
6 |
檢查是否是同一個類中的方法調用(如a方法調用同一個類中的b方法) ? https://blog.csdn.net/qq_30336433/article/details/83338835 https://blog.csdn.net/wsk1103/article/details/84666050 https://blog.csdn.net/jiesa/article/details/53438342 即事務生效經過aop動態代理實現,只有在代理對象間調用時可觸發切面邏輯,同一個類種調用的是源對象方法,不通過代理對象因此spring沒法切到此次調用,註解事務失效。 被註解的public方法或者類在被調用的時候,spring會爲該public方法或者類中的全部public方法生成一個代理類來代理被註解的方法。 spring同一個類中,一個方法(未註解@Transactional)調用另一個註解(@Transactional)方法時,註解失效 |
7 |
異常是否是被你catch住了 |
注意:同一個類中的註解方法互相調用時,註解機制多是無效的。
主要是由於經過aop技術建立代理對象觸發註解,調用的其實是代理對象中的方法,而直接使用類中方法則不會觸發註解。
參考:http://www.javashuo.com/article/p-keqwtuhb-cr.html
(疑問:若是是這樣,那經過一個方法中的屢次調用@autowired對象的方法難道不是調用同一個對象?單例狀況下)
測試;
Service類中的方法insert調用同一個類中的other方法。
@Service public class EmployeeService { @Autowired EmployeeDao employeeDao; public Integer insert(Employee emp){ int i=this.other(emp); return i; } @Transactional(transactionManager="test1TransactionManager") public Integer other(Employee emp){ int i=employeeDao.insert(emp); i=i/0; return i; }
因爲配置多數據源,所以須要指定事務管理器(transactionManager="test1TransactionManager")
注意:insert方法未配置事務註解@Transactional,而other方法配置了事務註解。
這種狀況下,調用異常,other事務不會回滾。
緣由:雖然上面有網友分析過,但我的理解 雖然使用當前類代理對象的other方法(日誌顯示爲代理對象),但該方法未觸發事務加強,所以事務失效。
場景二:
@Transactional(transactionManager="test1TransactionManager") public Integer insert(Employee emp){ @Transactional(transactionManager="test1TransactionManager") public Integer other(Employee emp){
即方法insert也配置事務,此時other會回滾,數據不會入庫。由於,spring事務默認傳播行爲爲required,insert已建立事務,就算other方法沒有事務也會加入insert的事務。所以會回滾。
場景三:
@Transactional(transactionManager="test1TransactionManager") public Integer insert(Employee emp){ @Transactional(transactionManager="test1TransactionManager") public Integer other(Employee emp){ int i=employeeDao.insert(emp); try { i=i/0; }catch(Exception e) { Logger.logMsg(Logger.INFO, "異常"); } return i;
即other方法中捕獲異常,很明顯,事務不會回滾,由於異常被吃掉。
就算拋出異常,以下:
try { i=i/0; }catch(Exception e) { Logger.logMsg(Logger.INFO, "異常"); throw new Exception(); }
事務也不會回滾,由於拋出的異常爲checked異常(java.lang.exception),而spring事務回滾默認爲unchecked異常。所以,可在insert註解事務配置rollbackFor=Exception.class解決
@Transactional(transactionManager="test1TransactionManager",rollbackFor=Exception.class) public Integer insert(Employee emp) throws Exception{