訂閱號(hào)怎么做免費(fèi)的視頻網(wǎng)站青島網(wǎng)站seo服務(wù)
一、背景
? ? ? ? 在 springBoot 開發(fā)過程中,我們一般都是在業(yè)務(wù)方法上添加 @Transactional 注解來讓 spring 替我們管理事務(wù),但在某些特定的場(chǎng)景下,添加完注解之后,事務(wù)是不生效的,接下來詳細(xì)介紹下。
二、方法不是 public
2.1 場(chǎng)景描述
? ? ? ? 當(dāng)添加?@Transactional 注解的方法不是 public 類型的,事務(wù)會(huì)失效。如下代碼:
@Transactional
private void someTransactionalMethod() {// 業(yè)務(wù)邏輯
}
2.2 原因分析
????????在 Spring 中,只有 public 方法才能被 AOP 代理處理,因此如果 @Transactional 注解的方法不是 public 的,事務(wù)管理將失效。
2.3 解決方案
????????確保 @Transactional 注解的方法是 public。如下:
@Transactional
public void someTransactionalMethod() {// 業(yè)務(wù)邏輯
}
三、方法內(nèi)部調(diào)用
3.1 場(chǎng)景描述
????????當(dāng)一個(gè)類內(nèi)部的方法調(diào)用另一個(gè)標(biāo)注了 @Transactional 的方法時(shí),事務(wù)管理將失效。如下代碼:
@Service
public class MyServiceImpl {public void outerMethod() {publicMethod();} @Transactionalpublic void publicMethod() {// 業(yè)務(wù)邏輯}
}
3.2 原因分析
? ? ? ? 這是因?yàn)閮?nèi)部方法方法的調(diào)用沒有經(jīng)過代理類,即在?outerMethod() 方法里面調(diào)用的 publicMethod() 方法是?MyServiceImpl 對(duì)象調(diào)用的,并不是經(jīng)過 spring 代理類來調(diào)用的,所以事務(wù)會(huì)失效。
3.3 解決方案
? ? ? ? 解決方案就是通過代理對(duì)象方法調(diào)用,使用 AOP 代理進(jìn)行事務(wù)管理,如下代碼:
@Service
public class MyServiceImpl {public void outerMethod() {// 通過代理對(duì)象調(diào)用 publicMethod((MyServiceImpl) AopContext.currentProxy()).publicMethod();} @Transactionalpublic void publicMethod() {// 業(yè)務(wù)邏輯}
}
四、未被 spring 管理
4.1 場(chǎng)景描述
? ? ? ? 當(dāng)一個(gè)類沒有被 spring 管理時(shí),事務(wù)不會(huì)生效,如下代碼:
?public class MyServiceImpl {@Transactionalpublic void someTransactionalMethod() {// 業(yè)務(wù)邏輯}
}
4.2 原因分析
????????只有在 Spring 容器中管理的 bean,才能被 AOP 代理。如果 @Transactional 注解的方法所在的類沒有被 Spring 管理,事務(wù)管理將失效。
4.3 解決方案
????????確保類被 Spring 容器管理,如通過 @Service,@Component 等注解。
@Service?
public class MyServiceImpl {@Transactionalpublic void someTransactionalMethod() {// 業(yè)務(wù)邏輯}
}
五、方法用 final 或 static 修飾
5.1 場(chǎng)景描述
????????有時(shí)候,某個(gè)方法不想被子類重寫,這時(shí)可以將該方法定義成 final 的。普通方法這樣定義是沒問題的,但如果將事務(wù)方法定義成 final,那么事務(wù)將會(huì)失效。
@Service
public class UserService {@Transactionalpublic final void add(UserModel userModel){saveData(userModel);updateData(userModel);}
}
5.2 原因分析
????????spring 事務(wù)底層使用了 aop,也就是通過 jdk 動(dòng)態(tài)代理或者 cglib,幫我們生成了代理類,在代理類中實(shí)現(xiàn)的事務(wù)功能。但如果某個(gè)方法用 final 修飾了,那么在它的代理類中,就無法重寫該方法,而添加事務(wù)功能。
????????注意:如果某個(gè)方法是 static 的,同樣無法通過動(dòng)態(tài)代理,變成事務(wù)方法。
5.3 解決方案
? ? ? ? 不使用 final 或者 static 修飾方法,如下:
@Service
public class UserService {@Transactionalpublic void add(UserModel userModel){saveData(userModel);updateData(userModel);}
}
六、配置不當(dāng)
6.1 場(chǎng)景描述
????????@Transactional 注解的一些配置屬性,可能會(huì)影響事務(wù)的行為,如下代碼:
@Transactional(readOnly = true)
public void someTransactionalMethod() {// 業(yè)務(wù)邏輯
}
6.2 原因分析
????????配置了?readOnly=true 屬性,那么執(zhí)行增刪改操作時(shí)就會(huì)報(bào)錯(cuò)。因?yàn)檫@個(gè)屬性指定了此方法只能進(jìn)行讀操作。
6.3 解決方案
????????檢查配置的具體含義,確保其適當(dāng)應(yīng)用。
@Transactional(readOnly = false)
public void someTransactionalMethod() {// 業(yè)務(wù)邏輯
}
七、多線程調(diào)用
7.1 場(chǎng)景描述
? ? ? ? spring 事務(wù)在多線程場(chǎng)景下,會(huì)有問題,如下代碼
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RoleService roleService;@Transactionalpublic void add(UserModel userModel) throws Exception {userMapper.insertUser(userModel);new Thread(() -> {roleService.doOtherThing();}).start();}
}@Service
public class RoleService {@Transactionalpublic void doOtherThing() {System.out.println("保存role表數(shù)據(jù)");}
}
7.2 原因分析
????????從上面的例子中,我們可以看到事務(wù)方法 add 中,調(diào)用了事務(wù)方法 doOtherThing,但是事務(wù)方法 doOtherThing 是在另外一個(gè)線程中調(diào)用的。
????????這樣會(huì)導(dǎo)致兩個(gè)方法不在同一個(gè)線程中,獲取到的數(shù)據(jù)庫連接不一樣,從而是兩個(gè)不同的事務(wù)。如果將來?doOtherThing 方法中拋了異常,add 方法也回滾是不可能的。
????????如果看過 spring 事務(wù)源碼的朋友,可能會(huì)知道 spring 的事務(wù)是通過數(shù)據(jù)庫連接來實(shí)現(xiàn)的。當(dāng)前線程中保存了一個(gè) map,key 是數(shù)據(jù)源,value 是數(shù)據(jù)庫連接。
????????我們說的同一個(gè)事務(wù),其實(shí)是指同一個(gè)數(shù)據(jù)庫連接,只有擁有同一個(gè)數(shù)據(jù)庫連接才能同時(shí)提交和回滾。如果在不同的線程,拿到的數(shù)據(jù)庫連接肯定是不一樣的,所以是不同的事務(wù)。
7.3 解決方案
????????避免在多線程中使用 @Transactional,或者手動(dòng)管理線程間的事務(wù)。
@Service
public class MyService {@Transactionalpublic void someTransactionalMethod() {ExecutorService executorService = Executors.newSingleThreadExecutor();executorService.submit(() -> {// 手動(dòng)管理事務(wù)TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());try {// 業(yè)務(wù)邏輯transactionManager.commit(status);} catch (Exception e) {transactionManager.rollback(status);throw e;}});}
}
八、錯(cuò)誤的傳播特性
8.1 場(chǎng)景描述
????????如果我們?cè)谑謩?dòng)設(shè)置 propagation 參數(shù)的時(shí)候,把傳播特性設(shè)置錯(cuò)了,事務(wù)可能就不會(huì)生效,如下代碼:
@Service
public class UserService {@Transactional(propagation = Propagation.NEVER)public void add(UserModel userModel) {saveData(userModel);updateData(userModel);}
}
8.2 原因分析
????????propagation 參數(shù)的作用是指定事務(wù)的傳播特性,spring 目前支持 7 種傳播特性:
? ? ? ? EQUIRED:如果當(dāng)前上下文中存在事務(wù),則加入該事務(wù),如果不存在事務(wù),則創(chuàng)建一個(gè)事務(wù),這是默認(rèn)的傳播屬性值。
? ? ? ?SUPPORTS:如果當(dāng)前上下文中存在事務(wù),則支持事務(wù)加入事務(wù),如果不存在事務(wù),則使用非事務(wù)的方式執(zhí)行。
? ? ? ? MANDATORY:當(dāng)前上下文中必須存在事務(wù),否則拋出異常。
? ? ? ? REQUIRES_NEW:每次都會(huì)新建一個(gè)事務(wù),并且同時(shí)將上下文中的事務(wù)掛起,執(zhí)行當(dāng)前新建事務(wù)完成以后,上下文事務(wù)恢復(fù)再執(zhí)行。
? ? ? ? NOT_SUPPORTED:如果當(dāng)前上下文中存在事務(wù),則掛起當(dāng)前事務(wù),然后新的方法在沒有事務(wù)的環(huán)境中執(zhí)行。
? ? ? ? NEVER:如果當(dāng)前上下文中存在事務(wù),則拋出異常,否則在無事務(wù)環(huán)境上執(zhí)行代碼。
? ? ? ? NESTED:如果當(dāng)前上下文中存在事務(wù),則嵌套事務(wù)執(zhí)行,如果不存在事務(wù),則新建事務(wù)。
????????我們可以看到 add 方法的事務(wù)傳播特性定義成了 Propagation.NEVER,這種類型的傳播特性不支持事務(wù),如果有事務(wù)則會(huì)拋異常。
8.3 解決方案
????????目前只有這三種傳播特性才會(huì)創(chuàng)建新事務(wù):REQUIRED,REQUIRES_NEW,NESTED。
@Service
public class UserService {@Transactional(propagation = Propagation.REQUIRED)public void add(UserModel userModel) {saveData(userModel);updateData(userModel);}
}
九、自己吞了異常
8.1 場(chǎng)景描述
????????開發(fā)者在代碼中手動(dòng) try...catch 了異常,事務(wù)不會(huì)生效,如下代碼:
@Slf4j
@Service
public class UserService {@Transactionalpublic void add(UserModel userModel) {try {saveData(userModel);updateData(userModel);} catch (Exception e) {log.error(e.getMessage(), e);}}
}
8.2 原因分析
????????這種情況下 spring 事務(wù)當(dāng)然不會(huì)回滾,因?yàn)殚_發(fā)者自己捕獲了異常,又沒有手動(dòng)拋出,換句話說就是把異常吞掉了。
8.3 解決方案
????????如果想要 spring 事務(wù)能夠正?;貪L,必須拋出它能夠處理的異常。如果沒有拋異常,則 spring 認(rèn)為程序是正常的。如下代碼:
@Slf4j
@Service
public class UserService {@Transactionalpublic void add(UserModel userModel)throws Exception {saveData(userModel);updateData(userModel);}
}
十、手動(dòng)拋了別的異常
8.1 場(chǎng)景描述
????????即使開發(fā)者沒有手動(dòng)捕獲異常,但如果拋的異常不正確,spring 事務(wù)也不會(huì)回滾。如下代碼:
@Slf4j
@Service
public class UserService {@Transactionalpublic void add(UserModel userModel) throws Exception {try {saveData(userModel);updateData(userModel);} catch (Exception e) {log.error(e.getMessage(), e);throw new Exception(e);}}
}
8.2 原因分析
????????上面的這種情況,開發(fā)人員自己捕獲了異常,又手動(dòng)拋出了異常:Exception,事務(wù)同樣不會(huì)回滾。
????????因?yàn)?spring 事務(wù),默認(rèn)情況下只會(huì)回滾 RuntimeException(運(yùn)行時(shí)異常)和 Error(錯(cuò)誤),對(duì)于普通的 Exception(非運(yùn)行時(shí)異常),它不會(huì)回滾。
8.3 解決方案
? ? ? ? 別采取這種寫法。
十一、自定義了回滾異常
11.1 場(chǎng)景描述
????????在使用 @Transactional 注解聲明事務(wù)時(shí),有時(shí)我們想自定義回滾的異常,spring 也是支持的??梢酝ㄟ^設(shè)置 rollbackFor 參數(shù),來完成這個(gè)功能。但如果這個(gè)參數(shù)的值設(shè)置錯(cuò)了,就會(huì)引出一些莫名其妙的問題,如下代碼:
@Slf4j
@Service
public class UserService {@Transactional(rollbackFor = BusinessException.class)public void add(UserModel userModel) throws Exception {saveData(userModel);updateData(userModel);}
}
11.2 原因分析
????????如果在執(zhí)行上面這段代碼,保存和更新數(shù)據(jù)時(shí),程序報(bào)錯(cuò)了,拋了 SqlException、DuplicateKeyException 等異常。而 BusinessException 是我們自定義的異常,報(bào)錯(cuò)的異常不屬于 BusinessException,所以事務(wù)也不會(huì)回滾。
????????即使 rollbackFor 有默認(rèn)值,但阿里巴巴開發(fā)者規(guī)范中,還是要求開發(fā)者重新指定該參數(shù)。
11.3 解決方案
????????如果使用默認(rèn)值,一旦程序拋出了 Exception,事務(wù)不會(huì)回滾,這會(huì)出現(xiàn)很大的 bug。所以,建議一般情況下,將該參數(shù)設(shè)置成:Exception 或 Throwable。