就這庭呜?Spring 事務失效場景及解決方案

小明:靚仔滑进,我最近遇到了很邪門的事。

靚仔:哦疟赊?說來聽聽郊供。

小明:上次看了你的文章《就這?一篇文章讓你讀懂 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

往期推薦

就這梅鹦?一篇文章讓你讀懂 Spring 事務

SpringBoot+Redis 實現(xiàn)消息訂閱發(fā)布

最詳細的圖文解析Java各種鎖(終極篇)

常見代碼重構(gòu)技巧裆甩,你一定用得上

圖文詳解 23 種設計模式

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市齐唆,隨后出現(xiàn)的幾起案子嗤栓,更是在濱河造成了極大的恐慌,老刑警劉巖箍邮,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茉帅,死亡現(xiàn)場離奇詭異,居然都是意外死亡锭弊,警方通過查閱死者的電腦和手機堪澎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來味滞,“玉大人樱蛤,你說我怎么就攤上這事√胰” “怎么了刹悴?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長攒暇。 經(jīng)常有香客問我土匀,道長,這世上最難降的妖魔是什么形用? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任就轧,我火速辦了婚禮,結(jié)果婚禮上田度,老公的妹妹穿的比我還像新娘妒御。我一直安慰自己,他們只是感情好镇饺,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布乎莉。 她就那樣靜靜地躺著,像睡著了一般奸笤。 火紅的嫁衣襯著肌膚如雪惋啃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天监右,我揣著相機與錄音边灭,去河邊找鬼。 笑死健盒,一個胖子當著我的面吹牛绒瘦,可吹牛的內(nèi)容都是我干的称簿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼惰帽,長吁一口氣:“原來是場噩夢啊……” “哼憨降!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起善茎,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤券册,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后垂涯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烁焙,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年耕赘,在試婚紗的時候發(fā)現(xiàn)自己被綠了骄蝇。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡操骡,死狀恐怖九火,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情册招,我是刑警寧澤岔激,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站是掰,受9級特大地震影響虑鼎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜键痛,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一炫彩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧絮短,春花似錦江兢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至席里,卻和暖如春叔磷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胁勺。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留独旷,地道東北人署穗。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓寥裂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親案疲。 傳聞我的和親對象是個殘疾皇子封恰,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容