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