探索spring事務(wù)失效之謎

1. spring 事務(wù)失效 描述

工作中經(jīng)常使用spring聲明式事務(wù)鲁豪,使用起來很方便耘子,只需要在需要添加事務(wù)的方法上面添加一個@Transaction注解即可,對開發(fā)人員來說完全是透明的實現(xiàn)。spring使用AOP來支持聲明式事務(wù),會根據(jù)事務(wù)屬性疮绷,自動在方法調(diào)用之前決定是否開啟一個事務(wù),并在方法執(zhí)行之后決定事務(wù)提交或回滾事務(wù)嚣潜。在使用過程中冬骚,遇到過一種情況:就是同一個類中無事務(wù)方法調(diào)用有事務(wù)方法會導(dǎo)致有事務(wù)方法不生效,起始不僅僅是spring事務(wù)這種情況會失效,Spring AOP 這種情況下也是不起作用的郑原,下面就來探索下這種情況的原因唉韭。

事務(wù)實效情況圖示.png

2 動態(tài)代理模擬spring 事務(wù)實現(xiàn)

spring 的聲明式事務(wù),其根本實現(xiàn)原理就是動態(tài)代理的方式,下面采用java JDK動態(tài)代理的方式來模擬spring 事務(wù)的實現(xiàn)犯犁,然后展示spring 事務(wù)失效的現(xiàn)象属愤。

2.1 定義接口

Tx 是 Transaction的縮寫。

public interface TxService {
 /**
  * 事務(wù)方法
  */
 void txM();
 /**
  * 無事務(wù)方法
  */
 void noTxM();
}

2.2 模擬事務(wù)失效

  @Test
    public void testTx(){
        //被代理對象
        TxServiceImpl target = new TxServiceImpl();
        //代理對象
        TxService proxyInstance =(TxService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), new TxInvocationHandler(target));
        System.out.println("測試1:執(zhí)行無事務(wù)方法調(diào)用有事務(wù)方法");
        proxyInstance.noTxM();
        System.out.println("-------------分割線------------");
        System.out.println("測試2:執(zhí)行有事務(wù)方法");
        proxyInstance.txM();
    }

    /**
     * 攔截 目標(biāo)方法酸役,進(jìn)行事務(wù)管理
     */
    class TxInvocationHandler implements InvocationHandler{
        /**
         * 被代理類實列
         */
        private Object target;

        public TxInvocationHandler(Object target){
            this.target = target;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //判斷是否是事務(wù)方法
            boolean isTxMethod = isTxMethod(method,target);
            //獲取數(shù)據(jù)庫連接
            System.out.println("獲取數(shù)據(jù)庫連接");
            //事務(wù)開啟
            startTx(isTxMethod);
            Object result ;
            try {
                //執(zhí)行被代理類對象方法
                result = method.invoke(target, args);
                //事務(wù)提交
                commitTx(isTxMethod);
                return result;
            }catch (Exception e){
                //事務(wù)回滾
                rollBack(isTxMethod);
                throw new Exception(e);
            }finally {
                //關(guān)閉數(shù)據(jù)庫連接
                closeConnection();
            }
        }

        /**
         * 關(guān)閉數(shù)據(jù)庫連接
         */
        private void closeConnection() {
            System.out.println("關(guān)閉數(shù)據(jù)庫連接");
        }

        /**
         * 判斷是否是 一個事務(wù)方法
         */
        private boolean isTxMethod(Method method,Object target) {
            try {
                Transactional annotation = AnnotationUtils.findAnnotation(method, Transactional.class);
                if(annotation == null){
                    Method tagetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
                    annotation = AnnotationUtils.findAnnotation(tagetMethod, Transactional.class);
                }
                return annotation != null;
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
            return false;
        }
        /**
         * 提交事務(wù)
         */
        private void commitTx(boolean isTxMethod) {
            if(isTxMethod){
                System.out.println(":事務(wù)提交");
            }
        }
        /**
         * 回滾事務(wù)
         */
        private void rollBack(boolean isTxMethod) {
            if(isTxMethod){
                System.out.println(":事務(wù)回滾");
            }
        }
        /**
         * 根據(jù)條件事務(wù)開啟
         */
        private void startTx(boolean isTxMethod) {
            if(isTxMethod){
                System.out.println(":開啟事務(wù)");
            }
        }
    }

2.3 執(zhí)行testTx()結(jié)果

從結(jié)果可以看出測試1 在執(zhí)行有事務(wù)方法是并沒有開啟事務(wù)住诸。

***測試1:執(zhí)行無事務(wù)方法調(diào)用有事務(wù)方法:
獲取數(shù)據(jù)庫連接
執(zhí)行無事務(wù)方法調(diào)用有事務(wù)方法->(
執(zhí)行事務(wù)方法
)
關(guān)閉數(shù)據(jù)庫連接
----------------------分割線-----------------------
***測試2:執(zhí)行有事務(wù)方法:
獲取數(shù)據(jù)庫連接
:開啟事務(wù)
執(zhí)行事務(wù)方法
:事務(wù)提交
關(guān)閉數(shù)據(jù)庫連接

3 查看動態(tài)代理字節(jié)碼文件

按照保存java 動態(tài)代理生成的字節(jié)碼文件介紹的方法獲取到動態(tài)代理生成的字節(jié)碼文件驾胆。

3.1 動態(tài)代理類中noTxM方法的實現(xiàn)

1.生成的動態(tài)代理類繼承了 Proxy 類 并且實現(xiàn)了TxService接口
2.Proxyh屬性 就是 InvacationHandler實現(xiàn)類,既是我們的TxInvocationHandler
3.接口方法的實現(xiàn)都是 調(diào)用了 InvocationHandlerinvoke方法。invoke方法的實現(xiàn)就是實現(xiàn)事務(wù)管理的地方贱呐。

public final class $Proxy extends Proxy implements TxService { 
 //省略 屬性 其他方法
  public final void noTxM() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
}

3.2 代理類UML圖

JDK動態(tài)代理UML圖.jpg

3.3 繪制調(diào)用時序圖

通過 字節(jié)碼文件可以繪制一下調(diào)用時序圖(無事務(wù)方法調(diào)用有事務(wù)方法):


無事務(wù)方法調(diào)用有事務(wù)方法.png

4 事務(wù)失效原因分析

  1. 項目中丧诺,通過spring的 自動注入 獲取到 TxService類型的對象,其實是一個動態(tài)代理類增強(qiáng)對象$Proxy奄薇。
  2. 在執(zhí)行TxService.noTxM()方法時驳阎,實際執(zhí)行的$Proxy.noTxM(),接著會執(zhí)行InvocationHandler.invoke 方法。
  3. 在invoke方法內(nèi)判斷noTxM()并不是一個事務(wù)方法馁蒂,所以事務(wù)并沒有開啟呵晚。
  4. 在事務(wù)沒有開啟的情況下,執(zhí)行被代理類對象target .noTxM() ,在noTxM()內(nèi)執(zhí)行txM()函數(shù) ,InvocationHandler.invoke()并不會再次執(zhí)行,所以也就不會為txM()開啟事務(wù)了沫屡。
  5. 如果noTxM()調(diào)用txM()時饵隙,能使用代理類對象調(diào)用,事務(wù)就不會失效。
  6. 這種情況不僅僅存在于事務(wù)管理中沮脖,同樣Spring AOP 也會有這種情況金矛,因為事務(wù)是基于AOP 實現(xiàn)的。

5 解決方案

  1. 通過@Resouse@Autowired 注入自己:這樣獲取到的就是代理類對象勺届。

    @Service
    public class TxServiceImpl implements TxService {
    
        @Autowired
        private TxService txService;
    
        @Transactional
        @Override
        public void txM() {
            System.out.println("執(zhí)行事務(wù)方法");
        }
        @Override
        public void noTxM() {
            System.out.println("執(zhí)行無事務(wù)方法調(diào)用有事務(wù)方法->(");
            txService.txM();
            System.out.println(")");
        }
    }
    
  2. AopContext.currentProxy()方式
    spring boot 項目 需要在啟動類添加@EnableAspectJAutoProxy(exposeProxy = true)驶俊,spring 項目需要配置<aop:aspectj-autoproxy proxy-target-class="true"expose-proxy="true"/> ,否則會拋異常免姿。

     @Override
     public void noTxM() {
         System.out.println("執(zhí)行無事務(wù)方法調(diào)用有事務(wù)方法->(");
         TxService txService = (TxService) AopContext.currentProxy();
         txService.txM();
         System.out.println(")");
     }
    
  3. 從Spring IOC 容器中獲取到代理類對象

@Override
  public void noTxM() {
      System.out.println("執(zhí)行無事務(wù)方法調(diào)用有事務(wù)方法->(");
      TxService txService = SpringContextUtil.getBean(TxService.class);
      txService.txM();
      System.out.println(")");
  }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末废睦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子养泡,更是在濱河造成了極大的恐慌,老刑警劉巖奈应,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澜掩,死亡現(xiàn)場離奇詭異,居然都是意外死亡杖挣,警方通過查閱死者的電腦和手機(jī)肩榕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惩妇,“玉大人株汉,你說我怎么就攤上這事「柩辏” “怎么了乔妈?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長氓皱。 經(jīng)常有香客問我路召,道長勃刨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任股淡,我火速辦了婚禮身隐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘唯灵。我一直安慰自己贾铝,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布埠帕。 她就那樣靜靜地躺著垢揩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪搞监。 梳的紋絲不亂的頭發(fā)上水孩,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機(jī)與錄音琐驴,去河邊找鬼俘种。 笑死,一個胖子當(dāng)著我的面吹牛绝淡,可吹牛的內(nèi)容都是我干的宙刘。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼牢酵,長吁一口氣:“原來是場噩夢啊……” “哼悬包!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起馍乙,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤布近,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后丝格,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撑瞧,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年显蝌,在試婚紗的時候發(fā)現(xiàn)自己被綠了预伺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡曼尊,死狀恐怖酬诀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情骆撇,我是刑警寧澤瞒御,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站神郊,受9級特大地震影響葵腹,放射性物質(zhì)發(fā)生泄漏高每。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一践宴、第九天 我趴在偏房一處隱蔽的房頂上張望鲸匿。 院中可真熱鬧,春花似錦阻肩、人聲如沸带欢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乔煞。三九已至,卻和暖如春柒室,著一層夾襖步出監(jiān)牢的瞬間渡贾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工雄右, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留空骚,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓擂仍,卻偏偏與公主長得像囤屹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子逢渔,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理肋坚,服務(wù)發(fā)現(xiàn),斷路器肃廓,智...
    卡卡羅2017閱讀 134,601評論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,748評論 6 342
  • 廣州總在下雨智厌。 前幾天下班在校園的長凳上坐了好久,到了天完全黑了才動身回去盲赊。騎著車在校道上峦剔,就下起雨來。起初還是小...
    公民劉澤遠(yuǎn)閱讀 206評論 0 0
  • 呼角钩,寫字也看心情。 腦袋里面蹦出來的西亂的詞句呻澜。
    簡小取閱讀 266評論 16 3
  • 最近我又是忙成狗的節(jié)奏羹幸。 除了上班時脊髓,人像陀螺一樣,一直不停旋轉(zhuǎn)之外栅受,我還在下班后給自己搞了各種事将硝,包括讀書恭朗、背單...
    作家格格閱讀 388評論 0 4