Spring4.x 手動(dòng)事務(wù)七嫌,監(jiān)聽處理未關(guān)閉事務(wù)的幾點(diǎn)思路灰蛙,文末彩蛋

簡書 慢黑八
轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處键兜,謝謝岖赋!
如果讀完覺得有收獲的話,歡迎點(diǎn)贊加關(guān)注

背景

由于某項(xiàng)目獨(dú)特的特色需要手動(dòng)開啟事務(wù)鞭莽。然而坊秸,在手動(dòng)開啟事務(wù)后,事務(wù)能否正常結(jié)束 commit or rollback 就出現(xiàn)了各式各樣的不確定情況澎怒。如果commit or rollback未執(zhí)行或執(zhí)行失敗褒搔,將會(huì)導(dǎo)致該事務(wù)持有的數(shù)據(jù)庫連接無法正常歸還到連接池中。高并發(fā)場(chǎng)景下的現(xiàn)象就是連接池中的可用連接越來越少喷面,最后導(dǎo)致獲取連接超時(shí)的異常星瘾。

以下為手動(dòng)事務(wù)工具類
@Service
public class TransactionTool {

    //spring注入事務(wù)管理對(duì)象
    @Resource(name = "transactionManager")
    private PlatformTransactionManager transManager ;
      
    public TransactionStatus getTransSatus(int propagate) {

        // TransactionStatus.
        // TransactionDefinition
        // 事務(wù)定義
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        // 傳播范圍
        def.setPropagationBehavior(propagate);

        TransactionStatus transactionStatus = transManager.getTransaction(def);
        return transactionStatus;
    }
}

下面是開啟事務(wù)的業(yè)務(wù)處理邏輯
@Service
public class BizService{
    @Autowired
    TransactionTool transactionTool;

    public void bizMethod(){
        //以下代碼手動(dòng)開啟事務(wù)
        TransactionStatus transactionStatus = null;

        try{
            transactionStatus = TransactionTool.getTransaction(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW);
            // ..業(yè)務(wù)邏輯
            transactionManager.commit(transactionStatus);
        }catch(Exception e){
            transactionManager.rollback(transactionStatus);
        }finally{
            //略掉一些分庫分表的特殊處理
        }
    }
}
主要導(dǎo)致事務(wù)沒有正常結(jié)束的三種場(chǎng)景
  • 場(chǎng)景 1、處理業(yè)務(wù)邏輯時(shí)惧辈,拋出的是Error而不是Exception琳状,catch接不住,導(dǎo)致rollback不能正常執(zhí)行盒齿,這也意味著事務(wù)無法正衬畛眩回滾困食,造成連接泄露。
  • 場(chǎng)景 2翎承、處理業(yè)務(wù)邏輯時(shí)硕盹,未執(zhí)行到commitreturn了,這樣也會(huì)導(dǎo)致了該事務(wù)沒有正常結(jié)束叨咖,connection沒有正常歸還連接池,造成泄露瘩例。
  • 場(chǎng)景3、同一個(gè)方法中事務(wù)雙開甸各,雙關(guān)垛贤,按照以下順序執(zhí)行
    開啟事務(wù)1(requires_new)-> 然后開事務(wù)2(requires_new) -> 之后提交事務(wù)1(commit) -> 在提交事務(wù)2(commit)
    事務(wù)上下文狀態(tài)切換如下:
    TS=TransactionStatus ???? TE=TransactionEvent ??? ? T=Transaction
步驟 事務(wù)操作 TransactionSynchronizationManager 掛起\執(zhí)行
1 TS1=getTransaction(REQUIRES_NEW)
publish TE1
T1(con1)、TE1 掛起 NULL
2 TS2=getTransaction(REQUIRES_NEW)
publish TE2
T2(con2)痴晦、TE2 掛起T1南吮,TE1
3 commit(TS1) TE2執(zhí)行琳彩,同步器清理T2
解掛步驟1掛起的null事務(wù)資源
執(zhí)行T1.commit成功
con1歸還連接池
4 commit(TS2) 當(dāng)前事務(wù)資源為null導(dǎo)致同步器
事件處理出現(xiàn)異常誊酌,導(dǎo)致con2
不能正常歸還到連接池,造成
連接泄露
執(zhí)行 T2.commit失敗
con2泄露

在開啟事務(wù)1的時(shí)候掛起的事務(wù)資源為空露乏,在commit事務(wù)1的之后碧浊,會(huì)解掛當(dāng)前線程的事務(wù)資源為:null,提交事務(wù)2時(shí)候瘟仿,如果當(dāng)前線程的事務(wù)資源為null箱锐,會(huì)拋空指針異常,最后在解綁資源unbindResource()的時(shí)候拋出以下代碼塊中的IllegalStateException異常(遺憾的是劳较,該異常被spring框架捕獲后沒有打印出來)驹止。最終導(dǎo)致事務(wù)2持有的連接不能正常釋放。TransactionEvent 會(huì)在事務(wù)結(jié)束的時(shí)候執(zhí)行當(dāng)前TransactionSynchronizationManager線程本地變量中的synchronizations事件观蜗。

public static Object unbindResource(Object key) throws IllegalStateException {
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    Object value = doUnbindResource(actualKey);
    if (value == null) {
        throw new IllegalStateException(
                "No value for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
    }
    return value;
}

以上3中情況臊恋,在自動(dòng)事務(wù)@Transactional的處理邏輯中都不會(huì)出現(xiàn)。首先spring-tx都進(jìn)行了統(tǒng)一封裝充分考慮了非正常的可以墓捻。其次抖仅,在嵌套事務(wù)雙開的時(shí)候,都是先開的事務(wù)后關(guān)砖第。所以撤卢,手動(dòng)事務(wù)一定要遵循先開的事務(wù)后關(guān)這個(gè)原則

監(jiān)控解決未關(guān)閉事務(wù)的幾個(gè)思路
  • 思路1:采用spring的ApplicationEventPublisher的事件發(fā)布監(jiān)聽機(jī)制梧兼。
    訂閱@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)事務(wù)完成階段的監(jiān)聽放吩,對(duì)“一定時(shí)間內(nèi)”未關(guān)閉的事件進(jìn)行預(yù)警,發(fā)現(xiàn)后整改羽杰。
  • 思路2:在finally中對(duì)事務(wù)進(jìn)行統(tǒng)一關(guān)閉渡紫。
    調(diào)整catch的范圍瞭稼,從Exception修改為Throwable捕捉到所有Exception 或者Error的情況,把commit移動(dòng)到finally中腻惠。commit的前置條件是transactionStatus!=null&&transactionStatus.isNewTransaction() && !transactionStatus.isCompleted()环肘,這樣會(huì)對(duì)所有 新建的且未完成的 事務(wù)進(jìn)行commit。如果小伙伴覺得思路2改動(dòng)方式比較激進(jìn)集灌,想暫時(shí)先觀察一下那些服務(wù)存在 事務(wù)未正常結(jié)束 的情況悔雹,可以參考思路3
@Service
public class BizService{
    @Autowired
    TransactionTool transactionTool;

    public void bizMethod(){
        //以下代碼手動(dòng)開啟事務(wù)
        TransactionStatus transactionStatus = null;

        try{
            transactionStatus = TransactionTool.getTransaction(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW);
            // ..業(yè)務(wù)邏輯
        }catch(Throwable t){
            transactionManager.rollback(transactionStatus);
        }finally{
            //try..catch內(nèi)容可提煉成公共方法
            try {
                if (transactionStatus != null && transactionStatus.isNewTransaction() 
                        && !transactionStatus.isCompleted()) {
                    //TODO: arms日志輸出 堆棧相關(guān)信息
                    transactionManager.commit(transactionStatus);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            //略掉一些分庫分表的特殊處理
        }
    }
}
  • 思路3: 在finally中檢查未完成的事物并進(jìn)行預(yù)警欣喧。
    預(yù)警的前提條件是transactionStatus!=null&&transactionStatus.isNewTransaction() && !transactionStatus.isCompleted()腌零,這樣會(huì)對(duì)所有 新建的且未完成的 事務(wù)進(jìn)行預(yù)警日志信息輸出。該思路在finally中增加try..catch塊進(jìn)行檢查唆阿,對(duì)應(yīng)用程序改動(dòng)影響較小益涧。
    需要注意的是:這種方式仍然監(jiān)控不到上文中場(chǎng)景3連接泄露的問題,如果想解決場(chǎng)景3的問題驯鳖,需要從TransactionStatus中獲取事務(wù)對(duì)象闲询,抽取ConnectionHolder中的數(shù)據(jù)庫Connection,用conn.isClosed()來判斷連接是否已經(jīng)關(guān)閉浅辙。另外還需要修改DataSourceTransactionManager源碼扭弧,把內(nèi)部類DataSourceTransactionObject的訪問修飾符從private修改為public
    參考如下代碼:
@Service
public class BizService{
    @Autowired
    TransactionTool transactionTool;

    public void bizMethod(){
        //以下代碼手動(dòng)開啟事務(wù)
        TransactionStatus transactionStatus = null;

        try{
            transactionStatus = TransactionTool.getTransaction(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW);
            // ..業(yè)務(wù)邏輯
            transactionManager.commit(transactionStatus);
        }catch(Throwable t){
            transactionManager.rollback(transactionStatus);
        }finally{
            //try..catch內(nèi)容可提煉成公共方法
            try {
                if (transactionStatus != null && transactionStatus.isNewTransaction()) {
                    if(!transactionStatus.isCompleted()) {
                        // arms日志輸出 堆棧相關(guān)信息
                        System.out.println("事務(wù)未結(jié)束原因[事務(wù)-未完成]");
                        printStackTrace(Thread.currentThread().getStackTrace());
                    }else {
                        Connection conn = null;
                        DefaultTransactionStatus defaultTransactionStatus = (DefaultTransactionStatus)transactionStatus;
                        if(defaultTransactionStatus.getTransaction().getClass().getClassLoader() == DataSourceTransactionObject.class.getClassLoader()) {
                            conn = ((DataSourceTransactionObject)defaultTransactionStatus.getTransaction()).getConnectionHolder().getConnection();  
                            if(conn != null && conn.isClosed()==false) {
                                System.out.println("事務(wù)未結(jié)束原因[連接-未關(guān)閉]");
                                printStackTrace(Thread.currentThread().getStackTrace());
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            //略掉一些分庫分表的特殊處理
        }
    }
}
接下來說下上面三種思路的可行性

[X ] 思路1记舆,不可行
[ok] 思路2鸽捻,可行
[ok] 思路3為過渡監(jiān)控性的解決方案,可行
[ok] 思路2+思路3為最終解決方案泽腮,可行

思路1中御蒲,基于spring事件的發(fā)布訂閱模式會(huì)存在什么問題?

使用spring的ApplicationEventPublisher的事件發(fā)布監(jiān)聽機(jī)制诊赊。
訂閱@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
事務(wù)完成階段**的監(jiān)聽厚满,對(duì)“一定時(shí)間內(nèi)”未關(guān)閉的事件進(jìn)行預(yù)警,發(fā)現(xiàn)后整改豪筝。
1痰滋、改造TransactionTool在執(zhí)行getTransSatus方法時(shí)調(diào)用publishTransactionEvent(transactionStatus , propagate)發(fā)布包含transactionId 的 "新事務(wù)事件" ,然后把需要監(jiān)控的事務(wù)事件存放在aliveTransactionMap中 续崖。

@Service
public class TransactionTool {
    private AtomicLong transactionId = new AtomicLong(0);
    // transcatioId,BizTransactionEvent 存儲(chǔ)存活的事務(wù)事件
    public static ConcurrentHashMap<String, BizTransactionEvent> aliveTransactionMap = 
        new ConcurrentHashMap<String, BizTransactionEvent>();

    //spring注入事務(wù)管理對(duì)象
    @Resource(name = "transactionManager")
    private PlatformTransactionManager transManager ;

    @Autowired
    private ApplicationEventPublisher publisher;
    public TransactionStatus getTransSatus(int propagate) {

        // TransactionStatus.
        // TransactionDefinition
        // 事務(wù)定義
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        // 傳播范圍
        def.setPropagationBehavior(propagate);

        TransactionStatus transactionStatus = transManager.getTransaction(def);
        // 增加事務(wù)監(jiān)聽
        publishTransactionEvent(transactionStatus , propagate);
        return transactionStatus;
    }

    public void publishEvent(long tid,int propagate) {
        long temp = tid;
        StackTraceElement[] stackTraceElementArray = Thread.currentThread().getStackTrace();
        if(stackTraceElementArray.length>2) {
            if(transactionId.longValue() == Long.MAX_VALUE) {
                transactionId.compareAndSet(Long.MAX_VALUE, 0);
            }
            BizTransactionEvent bizTransactionEvent = new BizTransactionEvent();
            bizTransactionEvent.setTransactionId(""+temp);
            bizTransactionEvent.setTransactionName(stackTraceElementArray[3].getClassName()+":"
                    +stackTraceElementArray[3].getMethodName()+":"+stackTraceElementArray[3].getLineNumber());
            bizTransactionEvent.setCurrentTimeMillis(System.currentTimeMillis());
            bizTransactionEvent.setStackTraceElement(stackTraceElementArray);
            bizTransactionEvent.setPropagate(propagate);
            System.out.println("[NEWTX"+bizTransactionEvent.getTransactionId()+"]"+bizTransactionEvent.toString());
            publisher.publishEvent(bizTransactionEvent);
            //在這里處理新建的事務(wù)操作敲街,可以放入一個(gè)map中
            TransactionTool.aliveTransactionMap.put(bizTransactionEvent.getTransactionId(), bizTransactionEvent);
        }
    }
}

2、增加事物事件類1BizTransactionEvent 严望,事務(wù)監(jiān)聽類BizTransactionEventListener多艇,通過事務(wù)commit時(shí)候,同步調(diào)用標(biāo)有注解@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)afterCompletion方法把aliveTransactionMap中transactionId對(duì)應(yīng)的事務(wù)事件刪掉像吻。

事務(wù)事件監(jiān)聽類

@Component
public class BizTransactionEventListener {
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
    public void afterCompletion(PayloadApplicationEvent<BizTransactionEvent> event) {
        System.out.println("[NEWTX" + event.getPayload().getTransactionId() + "-REMOVE]  " + event.toString()
                + "Duration:" + (System.currentTimeMillis() - event.getPayload().getCurrentTimeMillis()) + "ms");
        TransactionTool.aliveTransactionMap.remove(event.getPayload().getTransactionId());
    }
}

事務(wù)事件類

public class BizTransactionEvent {
    
    private static final int STACK_TRACE_ELEMENT_DEEP = 4;
    private String transactionId;
    private String transactionName;
    private StackTraceElement[] stackTraceElement;
    private long currentTimeMillis;
    private int propagate;
    
    
    public String getTransactionName() {
        return transactionName;
    }
    public void setTransactionName(String transactionName) {
        this.transactionName = transactionName;
    }

    
    public int getPropagate() {
        return propagate;
    }
    public void setPropagate(int propagate) {
        this.propagate = propagate;
    }
    public String getTransactionId() {
        return transactionId;
    }
    public void setTransactionId(String transactionId) {
        this.transactionId = transactionId;
    }

    public long getCurrentTimeMillis() {
        return currentTimeMillis;
    }
    public void setCurrentTimeMillis(long currentTimeMillis) {
        this.currentTimeMillis = currentTimeMillis;
    }

    public StackTraceElement[] getStackTraceElement() {
        return stackTraceElement;
    }
    public void setStackTraceElement(StackTraceElement[] stackTraceElement) {
        this.stackTraceElement = stackTraceElement;
    }
    @Override
    public String toString() {
        return "BizTransactionEvent [transactionId=" + transactionId + ", transactionName=" + transactionName
                + ", stackTraceElement=" + Arrays.toString(Arrays.copyOf(stackTraceElement, STACK_TRACE_ELEMENT_DEEP))
                + ", currentTimeMillis=" + currentTimeMillis + ", propagate=" + propagate + "]";
    }
}

3峻黍、我們可以通過監(jiān)控aliveTransactionMap中的事務(wù)事件存活時(shí)間來尋找發(fā)現(xiàn)事務(wù)未關(guān)閉的業(yè)務(wù)代碼复隆。

代碼略...

4、我們看下以下邏輯中問題出在哪:

@Service
public class BizService{
    @Autowired
    TransactionTool transactionTool;

    @Transactional 
    public void bizMethod(){
        //以下代碼手動(dòng)開啟事務(wù)
        TransactionStatus transactionStatus1 = null;
        TransactionStatus transactionStatus2 = null;
        try{
            transactionStatus1 = TransactionTool.getTransaction(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW);
            // ..業(yè)務(wù)邏輯
            //transactionManager.commit(transactionStatus1);
        }catch(Exception){
            transactionManager.rollback(transactionStatus1);
        }finally{
            //略掉一些分庫分表的特殊處理
        }

        try{
            transactionStatus2 = TransactionTool.getTransaction(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW);
            // ..業(yè)務(wù)邏輯
            transactionManager.commit(transactionStatus2);
        }catch(Exception){
            transactionManager.rollback(transactionStatus2);
        }finally{
            //略掉一些分庫分表的特殊處理
        }
    }
}

事務(wù)上下文狀態(tài)切換如下:
TS=TransactionStatus ???? TE=TransactionEvent ??? ? T=Transaction

步驟 事務(wù)操作 TransactionSynchronizationManager 掛起\執(zhí)行
1 @Transactional TS0=getTransaction(REQUIRESD) T0(con0) 掛起 NULL
2 TS1=getTransaction(REQUIRES_NEW)
publish TE1
T1(con1)姆涩、TE1 掛起T0
3 commit(TS1)被注掉了挽拂,不執(zhí)行 . con1連接泄露
4 TS2=getTransaction(REQUIRES_NEW)
publish TE2
T2(con1)、TE2 掛起T1骨饿、TE1
5 commit(TS2) TE2執(zhí)行亏栈,同步器清理T2
解掛步驟4的T1、TE1
執(zhí)行T2.commit成功
con2歸還連接池
6 commit(TS0) TE1執(zhí)行宏赘,同步器清理T1
解掛步驟2的T0
執(zhí)行 T0.commit成功
con0歸還連接池

這種方式的最大問題在于绒北,程序執(zhí)行完成后,當(dāng)前線程在事務(wù)同步器中仍存在解掛的事務(wù)資源(T0)察署,并且事務(wù)commit(TS1)沒有執(zhí)行闷游,TE1卻被正常執(zhí)行了,同時(shí)aliveTransactionMap中的TE1被移除了贴汪,失去了后續(xù)的監(jiān)控基礎(chǔ)脐往。
所以對(duì)于手動(dòng)事務(wù)來說,思路1比較失敗

文末彩蛋:簡述手動(dòng)Spring事務(wù)處理邏輯

spring-tx嘶是、spring-jdbc中比較重要的四個(gè)關(guān)鍵處理類:

  • AbstractPlatformTransactionManager:事務(wù)核心處理類钙勃,開啟事務(wù)蛛碌,掛起/恢復(fù)聂喇,釋放資源等功能
  • DataSourceTransactionManager:數(shù)據(jù)庫操作都有這個(gè)類來完成,例如:setAutoCommit蔚携,commit希太,rollback
  • TransactionSynchronizationManager:這里的TransactionSynchronizationManager都是以線程為單位來記錄相關(guān)的資源息。resources中記錄了酝蜒,key為datasource誊辉,value為ConnectionHolder的map結(jié)構(gòu)信息。上文中publisher.publishEvent(bizTransactionEvent)會(huì)把事務(wù)事件到synchronizations中亡脑,后續(xù)事務(wù)在提交的時(shí)候會(huì)執(zhí)行synchronizations中的事件堕澄。
  • DefaultTransactionStatus:存放當(dāng)前事務(wù),掛起的事務(wù)資源霉咨,事務(wù)定義等內(nèi)容蛙紫。

自動(dòng)事務(wù)cglib代理可參考TransactionAspectSupport

spring事務(wù)

在事務(wù)處理的過程中參考如下步驟,偷個(gè)懶不畫時(shí)序圖了途戒,大家按照序號(hào)坑傅,腦補(bǔ)一下

[package:spring-tx]AbstractPlatformTransactionManager
1、首先調(diào)用getTransaction()方法喷斋,獲取連接唁毒,獲取當(dāng)前事務(wù)狀態(tài)
4蒜茴、調(diào)用handleExistingTransaction()處理已存在的事務(wù)

  • 如果是REQUIRES_NEW就要掛起當(dāng)前存在事務(wù)、創(chuàng)建新事務(wù)把掛起的事務(wù)資源放入新事務(wù)中浆西,并且切換TransactionSynchronizationManager的本地線程變量為新事務(wù)相關(guān)內(nèi)容粉私,解綁當(dāng)前事務(wù)資源。
  • 如果是NESTED則需要?jiǎng)?chuàng)建保存點(diǎn)
  • 如果是REQUIRED近零,創(chuàng)建新把newTransaction設(shè)定為false毡鉴。

5、掛起資源SuspendedResourcesHolder結(jié)構(gòu)與TransactionSynchronizationManager相同秒赤,用于解掛時(shí)恢復(fù)TransactionSynchronizationManager中的本地線程變量猪瞬。
7、調(diào)用prepareSynchronization方法入篮,初始化當(dāng)前線程的事務(wù)同步管理器陈瘦,設(shè)置Threadlocal相關(guān)內(nèi)容,并反回新的TransactionStatus對(duì)象潮售。


以下為事務(wù)提交后的操作
8痊项、調(diào)用commit方法提交事務(wù)。這里會(huì)調(diào)用processCommit方法酥诽,在這個(gè)方法中會(huì)調(diào)用事務(wù)事件監(jiān)聽邏輯鞍泉。通過ApplicationListenerMethodTransactionalAdapter處理各個(gè)不同階段的transactionEvent,需要注意的是待處理的transactionEvent是從TransactionSynchronizationManager.getSynchronizations()當(dāng)前的本地線程變量中獲取的肮帐。
9咖驮、cleanupAfterCompletion設(shè)置事務(wù)狀態(tài)為完成,清理當(dāng)前線程TransactionSynchronizationManager資源训枢,解綁connection資源托修,設(shè)置autocommit=true。還原connection屬性恒界,回并且把連接歸還給連接池睦刃。
10、調(diào)用resume()方法還原掛起的資源十酣,繼續(xù)執(zhí)行涩拙。

[package:spring-jdbc]DataSourceTransactionManager
2、調(diào)用doGetTransaction() 獲取事務(wù)對(duì)象DataSourceTransactionObject
3耸采、檢索綁定到當(dāng)前線程(TransactionSynchronizationManager)的資源(ConnectionHolder)兴泥,把ConnectionHolder放入DataSourceTransactionObject中
6、調(diào)用dobegin開啟事務(wù)con.setAutoCommit(false);并且修改transactionActive為true洋幻。如果連接資源為空則獲取新的連接郁轻,并且在TransactionSynchronizationManager進(jìn)行資源綁定。
8.1、調(diào)用doCommit提交事務(wù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末好唯,一起剝皮案震驚了整個(gè)濱河市竭沫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌骑篙,老刑警劉巖蜕提,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異靶端,居然都是意外死亡谎势,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門杨名,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脏榆,“玉大人,你說我怎么就攤上這事台谍⌒胛梗” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵趁蕊,是天一觀的道長坞生。 經(jīng)常有香客問我,道長掷伙,這世上最難降的妖魔是什么是己? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮任柜,結(jié)果婚禮上卒废,老公的妹妹穿的比我還像新娘。我一直安慰自己乘盼,他們只是感情好升熊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绸栅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪页屠。 梳的紋絲不亂的頭發(fā)上粹胯,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音辰企,去河邊找鬼风纠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛牢贸,可吹牛的內(nèi)容都是我干的竹观。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼臭增!你這毒婦竟也來了懂酱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤誊抛,失蹤者是張志新(化名)和其女友劉穎列牺,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拗窃,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瞎领,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了随夸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片九默。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖宾毒,靈堂內(nèi)的尸體忽然破棺而出荤西,到底是詐尸還是另有隱情,我是刑警寧澤伍俘,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布邪锌,位于F島的核電站,受9級(jí)特大地震影響癌瘾,放射性物質(zhì)發(fā)生泄漏觅丰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一妨退、第九天 我趴在偏房一處隱蔽的房頂上張望妇萄。 院中可真熱鬧,春花似錦咬荷、人聲如沸冠句。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽懦底。三九已至,卻和暖如春罕扎,著一層夾襖步出監(jiān)牢的瞬間聚唐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國打工腔召, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留杆查,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓臀蛛,卻偏偏與公主長得像亲桦,于是被迫代替她去往敵國和親崖蜜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

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

  • java事務(wù)的處理 轉(zhuǎn) https://www.cnblogs.com/Bonker/p/5417967.html...
    小小的Jobs閱讀 1,394評(píng)論 0 1
  • 前面我們講到了Spring在進(jìn)行事務(wù)邏輯織入的時(shí)候客峭,無論是事務(wù)開始豫领,提交或者回滾,都會(huì)觸發(fā)相應(yīng)的事務(wù)事件桃笙。本文首先...
    AI喬治閱讀 1,734評(píng)論 0 1
  • 這部分的參考文檔涉及數(shù)據(jù)訪問和數(shù)據(jù)訪問層和業(yè)務(wù)或服務(wù)層之間的交互氏堤。 Spring的綜合事務(wù)管理支持覆蓋很多細(xì)節(jié),然...
    竹天亮閱讀 1,038評(píng)論 0 0
  • http://liuxing.info/2017/06/30/Spring%20AMQP%E4%B8%AD%E6%...
    sherlock_6981閱讀 15,923評(píng)論 2 11
  • IOC和DI是什么搏明? Spring IOC 的理解鼠锈,其初始化過程? BeanFactory 和 FactoryBe...
    justlpf閱讀 3,474評(píng)論 1 21