小明:靚仔,我最近遇到了很邪門的事。
靚仔:哦?說來聽聽。
小明:上次看了你的文章《就這?一篇文章讓你讀懂 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
往期推薦