小明:靚仔滑进,我最近遇到了很邪門的事。
靚仔:哦疟赊?說來聽聽郊供。
小明:上次看了你的文章《就這?一篇文章讓你讀懂 Spring 事務》近哟,對事務有了詳細的了解驮审,但是在項目中還是遇到了問題,明明加了事務注解 @Transactional吉执,卻沒有生效疯淫。
靚仔:那今天我就給你總結(jié)下哪些場景下事務會失效。
1戳玫、數(shù)據(jù)庫引擎不支持事務
Mysql 常用的數(shù)據(jù)庫引擎有 InnoDB 和 MyISAM熙掺,其中前者是支持事務的,而后者并不支持咕宿,MySQL 5.5.5 以前的默認存儲引擎是:MyISAM币绩,之前的版本默認的都是:InnoDB ,所以一定要注意自己使用的數(shù)據(jù)庫支不支持事務府阀。
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 方法
官方文檔上已經(jīng)說的很清楚了田巴,@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) {
}
}
}
當該方法發(fā)生異常的時候,由于異常被捕獲管宵,并沒有拋出來逝慧,所以事務會失效昔脯,那這種情況下該怎么解決呢?別急笛臣,往下看
@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静陈、內(nèi)部調(diào)用事務方法
這應該是最常見的事務失效的的場景了吧,也是我要重點講的情況诞丽。
有些業(yè)務邏輯比較復雜的操作鲸拥,比如前面例子中的下單方法,往往在寫操作之前會有一堆邏輯僧免,如果所有操作都放在一個方法里刑赶,并且加上事務,那么很可能會因為事務執(zhí)行時間過長懂衩,導致事務超時撞叨,就算沒超時也會影響下單接口的性能。這時可以將寫操作提取出來浊洞,只對寫操作加上事務牵敷,那么壓力就會小很多。
請看下面這個例子:
@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();
}
}
由于發(fā)生了內(nèi)部調(diào)用法希,而沒有經(jīng)過 Spring 的代理枷餐,事務就不會生效,官方文檔中也有說明:
那這種情況下該怎么辦呢苫亦?
方案一:改為外部調(diào)用
內(nèi)部調(diào)用不行毛肋,那我改成外部調(diào)用不就行了么
@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 無返回參數(shù)
// TransactionCallback 有返回參數(shù)
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();
}
}
甭管他黑貓白貓村生,能抓住老鼠的就是好貓
方案三:通過外部方法調(diào)回來
這個是我看到網(wǎng)友提供的一種方法,又想用注解饼丘,又想自調(diào)用,那么可以參考編程式事務的方式來實現(xiàn)辽话。
@Component
public class TransactionComponent {
public interface Callback<T>{
T run() throws Exception;
}
public interface CallbackWithOutResult {
void run() throws Exception;
}
// 帶返回參數(shù)
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
@Nullable
public <T> T doTransactional(Callback<T> callback) throws Exception {
return callback.run();
}
// 無返回參數(shù)
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
@Nullable
public void doTransactionalWithOutResult(CallbackWithOutResult callbackWithOutResult) throws Exception {
callbackWithOutResult.run();
}
}
這樣通過 TransactionComponent 調(diào)用內(nèi)部方法肄鸽,就可以解決失效問題了。
@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();
}
}
總結(jié)
本文總結(jié)了比較常見的幾種事務失效的場景油啤,以及一些解決方案典徘,不一定很全。你還遇到了哪些我沒提到的場景益咬,歡迎分享逮诲,有不足之處,也歡迎指正。
END
往期推薦