[TOC]
參考鏈接:
MySql的四種事務(wù)隔離級別 - 超級小小黑 - 博客園
一口氣說出 6種 @Transactional 注解失效場景
一、事務(wù)
事務(wù)的特性:
A(atomicity):原子性,一個事務(wù)時一個不可分割的工作單位,要么都提交,要不都不提交。
C(consistency):一致性,事務(wù)前后,數(shù)據(jù)狀態(tài)一致。^ 一致性例子。
I(isolation):隔離性,事務(wù)互不干擾。
D(durability):持久性,一旦提交,數(shù)據(jù)永久性更改。
編程式事務(wù)
在代碼中手動提交事務(wù)、回滾等操作,代碼侵入性比較強
try {
//TODO something
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw new InvoiceApplyException("異常失敗");
}
聲明式事務(wù)
基于AOP面向切面,將業(yè)務(wù)與事務(wù)處理解耦,代碼侵入性很低,所以是最常用的事務(wù)管理方式。
聲明式事務(wù)實現(xiàn)方式有兩種,一種基于TX和AOP的xml配置文件方式,第二種就是基于@Transactional注解。
@Transactional
@GetMapping("/test")
public String test() {
int insert = cityInfoDictMapper.insert(cityInfoDict);
}
二、@Transactional介紹
1. @Transactional注解作用于哪些地方。
@Transactional作用于接口,類,方法。
- 作用于類:
表示該類的public方法都配置相同的事務(wù)屬性信息。 - 作用于方法:
類配置了@Transactional,該類下方法也配置了@Transactional,方法的事務(wù)會覆蓋類的事務(wù)。 - 作用于接口:
不推薦這種方法,因為一旦標注在Interface上并且配置了Spring AOP使用CGlib動態(tài)代理,將會導致@Transactional注解會失效。
@Transactional
@RestController
@RequestMapping
public class MybatisPlusController {
@Autowired
private CityInfoDictMapper cityInfoDictMapper;
@Transactional(rollbackFor = Exception.class)
@GetMapping("/test")
public String test() throws Exception {
return "success";
}
}
2.@Transactional注解屬性
-
propagation
propagation代表事務(wù)的傳播屬性,Propagation.REQUIRED:(默認)如果當前存在事務(wù),則加入該事務(wù),如果當前不存在事務(wù),則創(chuàng)建一個新的事務(wù)。(也就是說如果A方法和B方法都添加了注解,在默認傳播模式下,A方法內(nèi)部調(diào)用B方法,會把兩個方法的事務(wù)合并為一個事務(wù))
Propagation.SUPPORT:如果當前存在事務(wù),則加入該事務(wù);如果當前不存在事務(wù),則以非事務(wù)的方式繼續(xù)運行。
Propagation.MADATORY:如果當前存在事務(wù),則加入該事務(wù);如果當前不存在事務(wù),則拋出異常。
Propagation.REQUIRES_NEW:重新創(chuàng)建一個新的事務(wù),如果當前存在事務(wù),暫停當前的事務(wù)。(當類A中的a方法用默認
Propagation.REQUIRED模式,類B中的b方法上采用Propagation.REQUIRES_NEW模式,然后在a方法中調(diào)用b方法操作數(shù)據(jù)庫,然而a方法拋出異常,b方法并沒有進行回滾,因為Propagation.REQUIRES_NEW會暫停a方法的事務(wù))Propagation.NOT_SUPPORTED:以非事務(wù)的方式運行,如果當前存在事務(wù),暫停當前的事務(wù)。
Propagetion.NEVER:以非事務(wù)的方式運行,如果當前存在事務(wù),則拋出異常。
Propagetion.NESTED:和
Propagetion.REQUIRED效果一樣。 -
isolation
isalation事務(wù)的隔離級別Isolation.DEFAULT:(默認)使用底層數(shù)據(jù)庫默認的隔離級別。大多數(shù)數(shù)據(jù)庫默認的事務(wù)隔離級別是Read committed,比如Sql Server,Oracle。Mysql的默認隔離級別是Repeatable read。
Isolation.READ_UNCOMMITTED
允許臟讀,但不允許更新丟失。如果一個事務(wù)已經(jīng)開始寫數(shù)據(jù),則另外一個事務(wù)則不允許同時進行寫操作,但允許其他事務(wù)讀此行數(shù)據(jù)。該隔離級別可以通過”排他寫鎖“實現(xiàn)。Isolation.READ_COMMITTED
允許不可重復讀取,但不允許臟讀取。這可以通過“瞬間共享讀鎖”和“排他寫鎖”實現(xiàn)。讀取數(shù)據(jù)的事務(wù)允許其他事務(wù)繼續(xù)訪問該行數(shù)據(jù),但是未提交的寫事務(wù)將會禁止其他事務(wù)訪問該行。Isolation.REPEATABLE_READ
禁止不可重復讀取和臟讀取,但不允許臟讀取。這可以通過“瞬間共享讀鎖”和“排他寫鎖”實現(xiàn)。讀取數(shù)據(jù)的事務(wù)允許其他事務(wù)繼續(xù)訪問該行數(shù)據(jù),但是未提交的寫事務(wù)將會禁止其他事務(wù)訪問該行。Isolation.SERIALIZABLE
它要求事務(wù)序列化執(zhí)行,事務(wù)只能一個接著一個的執(zhí)行,不能并發(fā)執(zhí)行。僅僅通過“行級鎖”是無法實現(xiàn)事務(wù)序列化的,必須通過其他機制保證新插入的數(shù)據(jù)不會被剛執(zhí)行查詢操作的事務(wù)訪問到。讀數(shù)據(jù)一致性 臟讀 不可重復讀 幻讀 未提交讀 最低級別只能保證不讀取物理上損壞的數(shù)據(jù) :heavy_check_mark: :heavy_check_mark: :heavy_check_mark: 已提交讀 語句級 :x: :heavy_check_mark: :heavy_check_mark: 可重復讀 事務(wù)級 :x: :x: :heavy_check_mark: 序列化 最高級別,事務(wù)級 :x: :x: :x: 臟讀
又稱無效數(shù)據(jù)讀出。一個事務(wù)讀取另外一個事務(wù)還沒有提交的數(shù)據(jù)叫臟讀。
例如:事務(wù)T1修改了一行數(shù)據(jù),但是還沒有提交,這時候事務(wù)T2讀取了被事務(wù)T1修改后的數(shù)據(jù),之后事務(wù)T1因為某種原因Rollback了,那么事務(wù)T2讀取的就是臟數(shù)據(jù)。不可重復讀
同一個事務(wù)中,多次讀出的同一數(shù)據(jù)是不一致的。
例如:事務(wù)T1讀取某一數(shù)據(jù),事務(wù)T2讀取并修改了該數(shù)據(jù),T1為了對讀取值進行檢驗而再次讀取該數(shù)據(jù),便得到了不同的結(jié)果。-
幻讀
不好表述直接上例子吧:在倉庫管理中,管理員要給剛到的一批商品進行入庫管理,當然入庫之前肯定是要查一下之前有沒有入庫記錄,確保正確性。管理員A確保庫中不存在該商品之后給該商品進行入庫操作,假如這時管理員B因為手快將該商品進行了入庫操作。這時管理員A發(fā)現(xiàn)該商品已經(jīng)在庫中。就像剛剛發(fā)生了幻讀一樣,本來不存在的東西,突然之間他就有了。
注:三種問題看似不太好理解,臟讀側(cè)重的是數(shù)據(jù)的正確性。不可重復讀側(cè)重的是對數(shù)據(jù)的修改,幻讀側(cè)重于數(shù)據(jù)的新增和刪除。
-
timeout
timeout事務(wù)的超時時間。
默認值為-1。如果超過該時間限制但事務(wù)還沒有完成,則自動回滾事務(wù)。 -
readOnly
readOnly:指定事務(wù)是否為只讀事務(wù)。
默認值為false;為了忽略那些不需要事務(wù)的方法,比如讀取數(shù)據(jù),可以設(shè)置成read-only為true -
rellbackFor
rollbackFor用于指定能夠觸發(fā)事務(wù)回滾的異常類型,可以指定多個異常類型。 -
noRollbackFor
noRollbackFor:拋出指定的異常類型,不回滾事務(wù),也可以指定多個異常類型。
三、@Transactional失效場景
1.@Transactional應(yīng)用在非public方法上
編譯不會報錯,但是自己要注意
2.@Transactional屬性propagetion設(shè)置錯誤
若配置以下三種propagation,事務(wù)有可能不回滾。
Propagation.SUPPORT^ Propagation.SUPPORT
Propagation.NOT_SUPPORTED[^ Propagation.NOT_SUPPORTED]
Propagetion.NEVER[1]
3.@Transactional注解rollbackbakcFor設(shè)置錯誤
4.同一個類中方法調(diào)用,導致@Transactional失效
開發(fā)中避免不了會對同一個類里面的方法調(diào)用,比如有一個類Test,它的一個方法A,A再調(diào)用本類的方法B(不論方法B是用public還是private修飾),但方法A沒有聲明注解事務(wù),而B方法有。則外部調(diào)用方法A之后,方法B的事務(wù)還是不起作用的。這也是經(jīng)常犯錯誤的一個地方。
public class Test{
@GetMapping("/A")
public String A(){
String result = this.B();
return result;
}
@GetMapping("/B")
@Transactional(rollbackFor=Exception.class)
public String B(){
String result = "1";
result+="2";
throw new Exception("模擬報錯..");
result+="3";
return result;
}
}
/*訪問/A,調(diào)用的B的事務(wù)不起作用,訪問/B,B的事務(wù)起作用*/
為啥會出現(xiàn)這種情況?
其實著還是由于使用Spring AOP代理造成的,因為只有當事務(wù)方法被當前類以外的代碼調(diào)用時,才會由Spring生成代理對象來管理。
5.異常被catch,沒有拋出給Spring
這種情況最常見!
@Transactional
private Integer A() throws Exception {
int insert = 0;
try {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
cityInfoDict.setParentCityId(2);
/**
* A 插入字段為 2的數(shù)據(jù)
*/
insert = cityInfoDictMapper.insert(cityInfoDict);
/**
* B 插入字段為 3的數(shù)據(jù)
*/
b.insertB();
} catch (Exception e) {
e.printStackTrace();
}
}
如果B方法內(nèi)部拋了異常,而A方法此時try catch了B方法的異常,那這個事務(wù)還能正常回滾嗎?
答案:不能!
因為當ServiceB中拋出了一個異常以后,ServiceB標識當前事務(wù)需要rollback。但是ServiceA中由于你手動的捕獲這個異常并進行處理,ServiceA認為當前事務(wù)應(yīng)該正常commit。此時就出現(xiàn)了前后不一致,也就是因為這樣,拋出了前面的UnexpectedRollbackException異常。
spring的事務(wù)實在調(diào)用業(yè)務(wù)方法之前開始的,業(yè)務(wù)方法執(zhí)行完畢之后才執(zhí)行commitorrollback,事務(wù)是否執(zhí)行取決于是否拋出runtime異常。如果拋出runtime exception并在你的業(yè)務(wù)方法中沒有catch到的話,事務(wù)會回滾。
在業(yè)務(wù)方法中一般不需要catch異常,如果非要catch,一定要拋出throw new RuntimeException(),或者注解中指定拋異常類型@Transactional(rollbackFor=Exception),否則事務(wù)失效。
6.數(shù)據(jù)庫引擎不支持事務(wù)
mysql默認使用innodb引擎,所以基本不會出現(xiàn)這個問題,關(guān)于數(shù)據(jù)庫引擎,都使用innodb即可。
-
以非事務(wù)的方式運行,如果當前存在事務(wù),則拋出異常。
[^ Propagation.NOT_SUPPORTED]: 以非事務(wù)的方式運行,如果當前存在事務(wù),暫停當前的事務(wù) ?