最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring事務(wù)aftercommit原理及實踐

 更新時間:2023年09月11日 11:19:36   作者:土豆肉絲蓋澆飯  
這篇文章主要為大家介紹了Spring事務(wù)aftercommit原理及實踐,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

題引示例

sql

CREATE TABLE `goods` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `good_id` varchar(20) DEFAULT NULL,
  `num` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `goods_good_id_index` (`good_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

java示例

        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","");

        //part1
        conn.setAutoCommit(false);
        Statement statement = conn.createStatement();
        statement.execute("INSERT INTO test.goods ( good_id, num) VALUES ( 'sku123', 0);");

        conn.commit();

        //part2
        statement = conn.createStatement();
        statement.execute("INSERT INTO test.goods ( good_id, num) VALUES ( 'sku123', 0);");

        conn.setAutoCommit(true);

        //part3
        try {
            statement = conn.createStatement();
            statement.execute("INSERT INTO test.goods ( good_id, num) VALUES ( 'sku123', 0);");
            int i = 1/0;
        }catch (Exception ex){
            System.out.println("there is an error");
        }
        conn.setAutoCommit(true);

        //part4
        conn.setAutoCommit(false);
        try {
            statement = conn.createStatement();
            statement.execute("INSERT INTO test.goods ( good_id, num) VALUES ( 'sku123', 0);");
            int i = 1/0;
        }catch (Exception ex){
            System.out.println("there is an error");
        }
        conn.setAutoCommit(true);

你舉得這4段代碼都提交了嗎,為什么?

如果你知道這個知識點(diǎn),那么本文對于你來說很容易理解。

一個知識點(diǎn)

首先,上面4段代碼都會提交成功。

主要的知識點(diǎn)是, autocommit 的狀態(tài)切換時,會對自動提交之前執(zhí)行的內(nèi)容。

看下這個方法的注釋就知道了。

他這邊說,如果事務(wù)執(zhí)行過程中,如果 autocommit 狀態(tài)改變了,會提交之前的事務(wù)。

額,這有個邏輯上的問題,如果autocommit本身就是true,我們的語句不是直接就提交了么,那這個描述應(yīng)該改成從false改成true的時候。

其實這段注釋還有前半段。

針對DML和DDL語句,autocommit=true的情況下,statement是立刻提交的。

而對于select語句,要等到關(guān)聯(lián)的result set被關(guān)閉,對于存儲過程....

而這個知識點(diǎn)太偏了,懂的朋友了解下,告訴我是啥..

所以我們這邊的知識點(diǎn)嚴(yán)謹(jǐn)點(diǎn)來說就是: 對于DDL和DML語句,當(dāng)autocommit從false切換為true時,事務(wù)會自動提交。

spring事務(wù)中的aftercommit

afterCommit是Spring事務(wù)機(jī)制中的一個回調(diào)鉤子,用于在事務(wù)提交后做一些操作。

我們可以這么使用它

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
    public void afterCommit() {
        //...執(zhí)行一些操作
    }
});

也可以通過@TransactionalEventListener間接使用它,它的底層原理就是上面這段代碼

@TransactionalEventListener
public void handleEvent(Event event){
    //...執(zhí)行一些操作
}

重點(diǎn)是在事務(wù)提交后執(zhí)行一些操作,也就是我題目中conn.commit()之后再執(zhí)行一些操作。

這個時候存在一個問題,如果這個操作是數(shù)據(jù)庫相關(guān)的操作,會不會被提交。

根據(jù)我文章開篇的代碼,你肯定就知道答案就是會提交,但是是autocommit的切換導(dǎo)致的提交。

額,其實并不是,對比2個常用db框架,Mybatis和JPA(Hibernate),Mybatis會提交,而Hibernate會丟失。

在afercomit的注釋中,他也警告我們了,在aftercommit中做數(shù)據(jù)庫操作可能不會被提交。如果你要做數(shù)據(jù)庫操作,你需要在一個新的事務(wù)中,可以使用PROPAGATION_REQUIRES_NEW隔離級別。

源碼中的NOTE要仔細(xì)的看?。『苤攸c(diǎn)

在不創(chuàng)建新事務(wù)的前提下,為什么對于Mybatis和JPA在aftercommit中執(zhí)行操作,一個提交,一個不提交?開始我們的源碼解析。

源碼解析

Spring Transaction的核心邏輯封裝在TransactionAspectSupportinvokeWithinTransaction方法中,而核心流程中重要的三個操作,獲取/提交/回滾事務(wù),由PlatformTransactionManager來實現(xiàn)。

public interface PlatformTransactionManager extends TransactionManager {
    
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
            throws TransactionException;
    
    void commit(TransactionStatus status) throws TransactionException;
    
    void rollback(TransactionStatus status) throws TransactionException;
}

PlatformTransactionManager使用了策略模式和模板方法模式,它的子類AbstractPlatformTransactionManager又對上面三個方法做了抽象,暴露了一一系列鉤子方法讓子類實現(xiàn)。

最常用的子類就是DataSourceTransactionManager和HibernateTransactionManager,分別對應(yīng)Mybatis和JPA框架。

本文講解的aftercommit同步鉤子在AbstractPlatformTransactionManager的processCommit中被觸發(fā)。

回顧我們上面展示的場景,我們在一個事務(wù)里,注冊了一個aftercommit鉤子,并且aftercommit里面,也會再次操作數(shù)據(jù)庫,執(zhí)行dml操作。

private void processCommit(DefaultTransactionStatus status) throws TransactionException {
        try {
            //...
             else if (status.isNewTransaction()) {
                    if (status.isDebug()) {
                        logger.debug("Initiating transaction commit");
                    }
                    unexpectedRollback = status.isGlobalRollbackOnly();
                    //...假設(shè)事務(wù)提交成功
                    doCommit(status);
             }
            try {
                triggerAfterCommit(status);
            }
            finally {
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
            }
        }
        finally {
            cleanupAfterCompletion(status);
        }

在第一個事務(wù)doCommit成功,他會通過triggerAfterCommit觸發(fā)它的aftercommit鉤子邏輯,進(jìn)行下一次事務(wù)操作,但是此時的Transaction還沒有釋放,并且它也不是newTransaction了。

為什么不是newTransaction,見以下代碼

private TransactionStatus handleExistingTransaction(
    TransactionDefinition definition, Object transaction, boolean debugEnabled)
    throws TransactionException {
    //...
    return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}

因為 status.isNewTransaction() 不成立,所以 doCommit(status); 不會執(zhí)行。

doCommit中會進(jìn)行什么操作?

對于DataSourceTransactionManager,就是調(diào)用了Connection的commit方法,對事務(wù)進(jìn)行提交。

protected void doCommit(DefaultTransactionStatus status) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    Connection con = txObject.getConnectionHolder().getConnection();
    if (status.isDebug()) {
        logger.debug("Committing JDBC transaction on Connection [" + con + "]");
    }
    try {
        con.commit();
    }
    catch (SQLException ex) {
        throw new TransactionSystemException("Could not commit JDBC transaction", ex);
    }
}

雖然錯失了doCommit這個機(jī)會,但是在cleanupAfterCompletion(status);方法

private void cleanupAfterCompletion(DefaultTransactionStatus status) {
    status.setCompleted();
    if (status.isNewSynchronization()) {
        TransactionSynchronizationManager.clear();
    }
    if (status.isNewTransaction()) {
        doCleanupAfterCompletion(status.getTransaction());
    }
    if (status.getSuspendedResources() != null) {
        if (status.isDebug()) {
            logger.debug("Resuming suspended transaction after completion of inner transaction");
        }
        Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
        resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
    }
}

在doCleanupAfterCompletion的邏輯中,注意doCleanupAfterCompletion也是一個鉤子,這個邏輯也由DataSourceTransactionManager實現(xiàn)

protected void doCleanupAfterCompletion(Object transaction) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;

    // Remove the connection holder from the thread, if exposed.
    if (txObject.isNewConnectionHolder()) {
        TransactionSynchronizationManager.unbindResource(obtainDataSource());
    }

    // Reset connection.
    Connection con = txObject.getConnectionHolder().getConnection();
    try {
        if (txObject.isMustRestoreAutoCommit()) {
            con.setAutoCommit(true);
        }
        DataSourceUtils.resetConnectionAfterTransaction(
            con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
    }
    catch (Throwable ex) {
        logger.debug("Could not reset JDBC Connection after transaction", ex);
    }

    if (txObject.isNewConnectionHolder()) {
        if (logger.isDebugEnabled()) {
            logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
        }
        DataSourceUtils.releaseConnection(con, this.dataSource);
    }

    txObject.getConnectionHolder().clear();
}

調(diào)用到了 con.setAutoCommit(true);間接了提交了事務(wù)

然后我們再來看看HibernateTransactionManager對這個兩個方法的實現(xiàn)

HibernateTransactionManager#doCommit

protected void doCommit(DefaultTransactionStatus status) {
    HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
    Transaction hibTx = txObject.getSessionHolder().getTransaction();
    Assert.state(hibTx != null, "No Hibernate transaction");
    if (status.isDebug()) {
        logger.debug("Committing Hibernate transaction on Session [" +
                     txObject.getSessionHolder().getSession() + "]");
    }

    try {
        //看這里
        hibTx.commit();
    }
    catch (org.hibernate.TransactionException ex) {
        // assumably from commit call to the underlying JDBC connection
        throw new TransactionSystemException("Could not commit Hibernate transaction", ex);
    }
    catch (HibernateException ex) {
        // assumably failed to flush changes to database
        throw convertHibernateAccessException(ex);
    }
    catch (PersistenceException ex) {
        if (ex.getCause() instanceof HibernateException) {
            throw convertHibernateAccessException((HibernateException) ex.getCause());
        }
        throw ex;
    }
}

HibernateTransactionManager#doCleanupAfterCompletion

protected void doCleanupAfterCompletion(Object transaction) {
    HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
    // Remove the session holder from the thread.
    if (txObject.isNewSessionHolder()) {
        TransactionSynchronizationManager.unbindResource(obtainSessionFactory());
    }
    // Remove the JDBC connection holder from the thread, if exposed.
    if (getDataSource() != null) {
        TransactionSynchronizationManager.unbindResource(getDataSource());
    }
    Session session = txObject.getSessionHolder().getSession();
    if (this.prepareConnection && isPhysicallyConnected(session)) {
        // We're running with connection release mode "on_close": We're able to reset
        // the isolation level and/or read-only flag of the JDBC Connection here.
        // Else, we need to rely on the connection pool to perform proper cleanup.
        try {
            Connection con = ((SessionImplementor) session).connection();
            Integer previousHoldability = txObject.getPreviousHoldability();
            if (previousHoldability != null) {
                con.setHoldability(previousHoldability);
            }
            DataSourceUtils.resetConnectionAfterTransaction(
                con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
        }
        catch (HibernateException ex) {
            logger.debug("Could not access JDBC Connection of Hibernate Session", ex);
        }
        catch (Throwable ex) {
            logger.debug("Could not reset JDBC Connection after transaction", ex);
        }
    }
    if (txObject.isNewSession()) {
        if (logger.isDebugEnabled()) {
            logger.debug("Closing Hibernate Session [" + session + "] after transaction");
        }
        SessionFactoryUtils.closeSession(session);
    }
    else {
        if (logger.isDebugEnabled()) {
            logger.debug("Not closing pre-bound Hibernate Session [" + session + "] after transaction");
        }
        if (txObject.getSessionHolder().getPreviousFlushMode() != null) {
            session.setFlushMode(txObject.getSessionHolder().getPreviousFlushMode());
        }
        if (!this.allowResultAccessAfterCompletion && !this.hibernateManagedSession) {
            disconnectOnCompletion(session);
        }
    }
    txObject.getSessionHolder().clear();
}

docommit里的邏輯還是用到了底層connection的commit,而在doCleanupAfterCompletion中,沒有見到設(shè)置autocommit的身影。

所以在JPA中你在aftercommit中進(jìn)行dml操作是會丟失的。

另外一個點(diǎn)是,如果你在aftercommit進(jìn)行了事務(wù)操作,但是中間發(fā)生了異常,比如2條insert語句后,發(fā)生了異常,這兩條insert會不會回滾?

答案是不會

回顧processCommit方法

private void processCommit(DefaultTransactionStatus status) throws TransactionException {
        try {
            //...
             else if (status.isNewTransaction()) {
                    if (status.isDebug()) {
                        logger.debug("Initiating transaction commit");
                    }
                    unexpectedRollback = status.isGlobalRollbackOnly();
                    //...假設(shè)事務(wù)提交成功
                    doCommit(status);
             }
            try {
                triggerAfterCommit(status);
            }
            finally {
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
            }

        }
        finally {
            cleanupAfterCompletion(status);
        }
}

我們的aftercommit在triggerAfterCommit執(zhí)行,這個方法里面拋出了異常,因為沒有catch,異常會往上傳遞,在cleanupAfterCompletion里也沒有處理異常,但是對于mybatis來講,它改變了autocommit狀態(tài),所以更改被提交了。這是一個你想不到的坑。

最佳實踐

  • aftercommit或者說是transactionlistener,最好不要有dml操作
  • 一但aftercommit中有事務(wù)操作,存在的風(fēng)險是,一致性得不到保證,異常不會讓這部分的事務(wù)回滾

demo

寫了一個工程,用于測試mybatis和jpa中對于aftercommit中執(zhí)行dml操作是否會提交

地址 https://github.com/shengchaojie/spring_tx_aftercommit_problem

參考資料 http://m.fzitv.net/article/279094.htm

以上就是Spring事務(wù)aftercommit原理及實踐的詳細(xì)內(nèi)容,更多關(guān)于Spring事務(wù)aftercommit原理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • JAVA異常處理機(jī)制之throws/throw使用情況

    JAVA異常處理機(jī)制之throws/throw使用情況

    這篇文章主要介紹了JAVA異常處理機(jī)制之throws/throw使用情況的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • 基于java SSM springboot實現(xiàn)景區(qū)行李寄存管理系統(tǒng)

    基于java SSM springboot實現(xiàn)景區(qū)行李寄存管理系統(tǒng)

    這篇文章主要介紹了基于java SSM springboot實現(xiàn)的景區(qū)行李寄存管理系統(tǒng),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-08-08
  • springboot使用maven實現(xiàn)多環(huán)境運(yùn)行和打包問題

    springboot使用maven實現(xiàn)多環(huán)境運(yùn)行和打包問題

    這篇文章主要介紹了springboot使用maven實現(xiàn)多環(huán)境運(yùn)行和打包問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • Java如何實現(xiàn)內(nèi)存緩存

    Java如何實現(xiàn)內(nèi)存緩存

    內(nèi)存緩存(Memory?caching)是一種常見的緩存技術(shù),它利用計算機(jī)的內(nèi)存存儲臨時數(shù)據(jù),以提高數(shù)據(jù)的讀取和訪問速度,本文就來和大家聊聊Java如何實現(xiàn)內(nèi)存緩存吧
    2023-08-08
  • java程序員自己的圖片轉(zhuǎn)文字OCR識圖工具分享

    java程序員自己的圖片轉(zhuǎn)文字OCR識圖工具分享

    這篇文章主要介紹了java程序員自己的圖片轉(zhuǎn)文字OCR識圖工具,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • SpringBoot如何自定義starter

    SpringBoot如何自定義starter

    這篇文章主要介紹了SpringBoot如何自定義starter,Springboot的出現(xiàn)極大的簡化了開發(fā)人員的配置,而這之中的一大利器便是springboot的starter,starter是springboot的核心組成部分,下面來看看集體引用過程吧
    2022-01-01
  • java web SpringMVC后端傳json數(shù)據(jù)到前端頁面實例代碼

    java web SpringMVC后端傳json數(shù)據(jù)到前端頁面實例代碼

    本篇文章主要介紹了java web SpringMVC后端傳json數(shù)據(jù)到前端頁面實例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下。
    2017-03-03
  • Maven的國內(nèi)鏡像(快速解決jar下載過慢的問題)

    Maven的國內(nèi)鏡像(快速解決jar下載過慢的問題)

    下面小編就為大家?guī)硪黄狹aven的國內(nèi)鏡像(快速解決jar下載過慢的問題)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06
  • Mybatis中typeAliases的使用

    Mybatis中typeAliases的使用

    這篇文章主要介紹了Mybatis中typeAliases的使用,需要的朋友可以參考下
    2017-08-08
  • SpringMVC的@InitBinder參數(shù)轉(zhuǎn)換代碼實例

    SpringMVC的@InitBinder參數(shù)轉(zhuǎn)換代碼實例

    這篇文章主要介紹了SpringMVC的@InitBinder參數(shù)轉(zhuǎn)換代碼實例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-09-09

最新評論

尤溪县| 越西县| 盖州市| 呼图壁县| 栾城县| 鲁甸县| 巢湖市| 和政县| 佛冈县| 手游| 武威市| 富蕴县| 神农架林区| 华亭县| 交城县| 赤壁市| 鄂托克前旗| 南江县| 清涧县| 泰宁县| 黑河市| 石家庄市| 上思县| 茶陵县| 林州市| 前郭尔| 金塔县| 河源市| 武山县| 松桃| 米易县| 连江县| 肃南| 泸溪县| 南平市| 岗巴县| 衢州市| 大渡口区| 南岸区| 晋州市| 六安市|