小明:靚仔,我最近遇到了很邪門的事。
靚仔:哦?說來聽聽。
小明:上次看了你的文章《就這?一篇文章讓你讀懂 Spring 事務》,對事務有了詳細的瞭解,但是在專案中還是遇到了問題,明明加了事務註解 @Transactional,卻沒有生效。
靚仔:那今天我就給你總結下哪些場景下事務會失效。
1、資料庫引擎不支援事務
Mysql 常用的資料庫引擎有 InnoDB 和 MyISAM,其中前者是支援事務的,而後者並不支援,MySQL 5.5.5 以前的預設儲存引擎是:MyISAM,之前的版本預設的都是:InnoDB ,所以一定要注意自己使用的資料庫支不支援事務。
2、沒有被 Spring 管理
事務方法所在的類沒有被注入Spring 容器,比如下面這樣:
public class OrderServiceImpl implements OrderService {
@Autowired
AccountMapper accountMapper;
@Autowired
ProductMapper productMapper;
@Transactional
@Override
public void placeOrder() {
// 此處省略一堆邏輯
// 修使用者改餘額和商品庫存
accountMapper.update();
productMapper.update();
}
}
這個類沒有加 @service 註解,事務是不會生效的。
3、不是 public 方法
官方文件上已經說的很清楚了,@Transactional 註解只能用於 public 方法,如果要用在非 public 方法上,可以開啟 AspectJ 代理模式。
4、異常被捕獲
比如下面這個例子:
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
AccountMapper accountMapper;
@Autowired
ProductMapper productMapper;
@Transactional
@Override
public void placeOrder() {
try{
// 此處省略一堆邏輯
// 修使用者改餘額和商品庫存
accountMapper.update();
productMapper.update();
} catch (Exception e) {
}
}
}
當該方法發生異常的時候,由於異常被捕獲,並沒有丟擲來,所以事務會失效,那這種情況下該怎麼解決呢?別急,往下看
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
AccountMapper accountMapper;
@Autowired
ProductMapper productMapper;
@Transactional
@Override
public void placeOrder() {
try{
// 此處省略一堆邏輯
// 修使用者改餘額和商品庫存
accountMapper.update();
productMapper.update();
} catch (Exception e) {
// 手動回滾
TransactionAspectSupport.crrentTransactionStatus().setRollbackOnly();
}
}
}
可以通過
TransactionAspectSupport.crrentTransactionStatus().setRollbackOnly();
手動進行回滾操作。
5、異常型別錯誤
@Transactional 註解預設只回滾 RuntimeException 型別的異常,所以在使用的時候建議修改成 Exception 型別
@Transactional(rollbackFor = Exception.class)
6、內部呼叫事務方法
這應該是最常見的事務失效的的場景了吧,也是我要重點講的情況。
有些業務邏輯比較複雜的操作,比如前面例子中的下單方法,往往在寫操作之前會有一堆邏輯,如果所有操作都放在一個方法裡,並且加上事務,那麼很可能會因為事務執行時間過長,導致事務超時,就算沒超時也會影響下單介面的效能。這時可以將寫操作提取出來,只對寫操作加上事務,那麼壓力就會小很多。
請看下面這個例子:
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
AccountMapper accountMapper;
@Autowired
ProductMapper productMapper;
@Override
public void placeOrder() {
// 此處省略一堆邏輯
this.updateByTransactional();
}
@Transactional
public void updateByTransactional() {
// 修使用者改餘額和商品庫存
accountMapper.update();
productMapper.update();
}
}
由於發生了內部呼叫,而沒有經過 Spring 的代理,事務就不會生效,官方文件中也有說明:
那這種情況下該怎麼辦呢?
方案一:改為外部呼叫
內部呼叫不行,那我改成外部呼叫不就行了麼
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
OrderTransactionService orderTransactionService;
@Override
public void placeOrder() {
// 此處省略一堆邏輯
orderTransactionService.updateByTransactional();
}
}
@Service
public class OrderTransactionService {
@Autowired
AccountMapper accountMapper;
@Autowired
ProductMapper productMapper;
@Transactional
public void updateByTransactional() {
// 修使用者改餘額和商品庫存
accountMapper.update();
productMapper.update();
}
}
這是比較容易理解的一種方法
方案二:使用程式設計式事務
既然宣告式事務有問題,那我換成程式設計式事務可還行?
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
AccountMapper accountMapper;
@Autowired
ProductMapper productMapper;
@Autowired
TransactionTemplate transactionTemplate;
@Override
public void placeOrder() {
// 此處省略一堆邏輯
// TransactionCallbackWithoutResult 無返回引數
// TransactionCallback 有返回引數
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
this.updateByTransactional();
} catch (Exception e) {
log.error("下單失敗", e);
transactionStatus.setRollbackOnly();
}
}
});
}
public void updateByTransactional() {
// 修使用者改餘額和商品庫存
accountMapper.update();
productMapper.update();
}
}
甭管他黑貓白貓,能抓住老鼠的就是好貓
方案三:通過外部方法調回來
這個是我看到網友提供的一種方法,又想用註解,又想自呼叫,那麼可以參考程式設計式事務的方式來實現。
@Component
public class TransactionComponent {
public interface Callback<T>{
T run() throws Exception;
}
public interface CallbackWithOutResult {
void run() throws Exception;
}
// 帶返回引數
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
@Nullable
public <T> T doTransactional(Callback<T> callback) throws Exception {
return callback.run();
}
// 無返回引數
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
@Nullable
public void doTransactionalWithOutResult(CallbackWithOutResult callbackWithOutResult) throws Exception {
callbackWithOutResult.run();
}
}
這樣通過 TransactionComponent 呼叫內部方法,就可以解決失效問題了。
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
AccountMapper accountMapper;
@Autowired
ProductMapper productMapper;
@Autowired
TransactionComponent transactionComponent;
@Override
public void placeOrder() {
// 此處省略一堆邏輯
transactionComponent.doTransactionalWithOutResult(() -> this.updateByTransactional());
}
public void updateByTransactional() {
// 修使用者改餘額和商品庫存
accountMapper.update();
productMapper.update();
}
}
總結
本文總結了比較常見的幾種事務失效的場景,以及一些解決方案,不一定很全。你還遇到了哪些我沒提到的場景,歡迎分享,有不足之處,也歡迎指正。
END
往期推薦