項目中如何配置事務

項目中使用事務有好幾種方式,本文章的項目都是使用的Spring,如果你使用的是JDBC編程,那麼請看這個

事務管理是應用系統開發中必不可少的一部分。Spring 爲事務管理提供了豐富的功能支持。Spring 事務管理分爲 編程式聲明式 的兩種方式。

  • 編程式:指的是通過編碼方式實現事務,看這個

  • 聲明式:基於 AOP, 將具體業務邏輯與事務處理解耦

    聲明式事務管理使業務代碼邏輯不受污染, 因此在實際使用中聲明式事務用的比較多(所以本篇文章只講聲明式)。

聲明式事務有兩種方式配置:

  • 基於XML配置(本篇文章不講,請自行百度
  • 基於 @Transactional 註解(本篇文章使用此方式

一、SSM

配置:

spring-mybatis.xml

<!-- 配置事務管理器 -->
<bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 注入數據庫連接池 -->
    <property name="dataSource" ref="dataSource"/>
</bean>

<!-- 使用基於註解方式配置事務 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

使用:

20191104111456.png

只需在 Service 的類或方法上面寫 @Transactional 註解即可, @Transactional 中的屬性,下面會詳細講解。

二、SpringBoot

開啓事務:

Application 類上面打個 @EnableTransactionManagement 註解即可。如下:

20191104111747.png

@EnableTransactionManagement有兩個屬性可以看看:

  • proxyTargetClass 默認false(標準的 JDK 基於接口的代理)

    該屬性用於控制代理是基於接口的還是基於類被創建 設置爲 true 表示使用基於子類實現的代理(CGLIB),設置爲 false 表示使用基於接口實現的代理

  • mode 默認PROXY

    該屬性表示是使用哪種事務切面,有 PROXYASPECTJ,想要更深入瞭解,可以看看源碼

使用也是在 Service 上面加 @Transactional 註解即可。

三、我們在使用 @Transactional 註解的時候,需要注意的一些問題:

配置好了,先彆着急去用,先來看看有哪些需要我們注意的。

1、默認配置下 Spring 只會回滾運行時、未檢查異常(繼承自 RuntimeException 的異常)或者 Error,但是我們可以配置 rollbackFor 來指定我們要處理的異常(下面會講)

2、 @Transactional 註解只能應用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 註解,它也不會報錯, 但是這個被註解的方法將不會展示已配置的事務設置。

3、 @Transactional 註解可以被應用於接口定義和接口方法、類定義和類的 public 方法上,但是Spring團隊建議在具體的類(或類的方法)上使用 @Transactional 註解,而不要使用在類所要實現的任何接口上(因爲 Spring 的聲明式事務是默認基於 SpringAOP 實現,而 SpringAOP 默認是使用 java 動態代理實現(基於接口實現),如果此時將代理改爲cglib(基於子類實現),在接口上面加註解就沒用了,所以爲了保持兼容註解最好都寫到實現類方法上)

4、如果 @Transactional 註解被寫在 Service 的類上面,則表示類中所有的方法都被事務管理,但是有些查詢方法是不需要事務管理,那麼可以這樣做(爲什麼這樣,看完下面就明白了):

/**
 * 獲取用戶列表
 * <br/>
 * 查詢不需要事務
 */
@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
public PageInfo<User> listUsers(Page<User> page) {
    PageHelper.startPage(page.getPageNum(), page.getPageSize());
    List<User> users = userMapper.listUsers();
    return new PageInfo<>(users);
}

5、有些時候我們需要手動回滾事務,那麼,有兩種方法:

  1. service層處理事務,那麼service中的方法中不做異常捕獲,或者在catch語句中最後增加throw new RuntimeExcetpion()語句,以便讓Aop捕獲異常再去回滾,並且在service上層(webservice客戶端,view層Controller)要繼續捕獲這個異常並處理

  2. 在service層方法的catch語句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();語句,手動回滾,這樣上層就無需去處理異常(現在項目的做法)

四、@Transactional 詳解

@Transactional 中的屬性:

1、value、transactionManager

這兩個作用一樣,當配置了多個事務管理器時,可以使用該屬性指定選擇哪個事務管理器

2、propagation

事務的傳播行爲,默認值爲 Propagation.REQUIRED

可選的值有:

  • Propagation.REQUIRED

    如果當前存在事務,則加入該事務,如果當前不存在事務,則創建一個新的事務。

  • Propagation.SUPPORTS

    如果當前存在事務,則加入該事務;如果當前不存在事務,則以非事務的方式繼續運行。

  • Propagation.MANDATORY

    如果當前存在事務,則加入該事務;如果當前不存在事務,則拋出異常。

  • Propagation.REQUIRES_NEW

    重新創建一個新的事務,如果當前存在事務,暫停當前的事務。

  • Propagation.NOT_SUPPORTED

    以非事務的方式運行,如果當前存在事務,暫停當前的事務。

  • Propagation.NEVER

    以非事務的方式運行,如果當前存在事務,則拋出異常。

  • Propagation.NESTED

    Propagation.REQUIRED 效果一樣。

3、isolation

事務的隔離級別,默認值爲 Isolation.DEFAULT

可選的值有:

  • Isolation.DEFAULT

    使用底層數據庫默認的隔離級別。(MySQl 默認:Repeatable read)

  • Isolation.READ_UNCOMMITTED

    (讀未提交):最低級別,任何情況都無法保證。

  • Isolation.READ_COMMITTED

    (讀已提交):可避免髒讀的發生。

  • Isolation.REPEATABLE_READ

    (可重複讀):可避免髒讀、不可重複讀的發生

  • Isolation.SERIALIZABLE

    (串行化):可避免髒讀、不可重複讀、幻讀的發生。

4、timeout

事務的超時時間,默認值爲-1,表示事務超時將依賴於底層事務系統,

如果超過該時間限制但事務還沒有完成,則自動回滾事務。

5、readOnly

指定事務是否爲只讀事務,默認值爲 false;爲了忽略那些不需要事務的方法,比如讀取數據,可以設置 read-only 爲 true。

6、rollbackFor

需要觸發回滾的異常定義,可定義多個,默認任何RuntimeException都將導致事務回滾,而任何Checked Exception將不導致事務回滾

7、noRollbackFor

拋出指定的異常類型,不回滾事務,也可以指定多個異常類型。

下面重點講講 Propagation.REQUIRES_NEW 這種傳播行爲:

注:

下面有幾個使用案例,如果你也想打印詳細日誌,你需要下面這樣配置:

需要控制檯打印MyBatis執行SQL日誌的這樣配置:

# ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ MyBatis 配置 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ #
mybatis:
    configuration:
        # spring boot集成mybatis的方式打印sql:https://blog.csdn.net/qq_22194659/article/details/81120712
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

需要看到事務的創建詳細日誌的這樣配置:

# ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 日誌 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ #
# 默認情況下,spring boot從控制檯打印出來的日誌級別只有ERROR, WARN 還有INFO,如果你想要打印debug級別的日誌,可以通過配置debug=true
debug: true
logging:
    # 配置logging.level.*來具體輸出哪些包的日誌級別
    level:
        root: info
        org.springframework.web: debug
        # 打開 jdbc 事務執行日誌
        org.springframework.jdbc.datasource: debug

先來看一段代碼:

package com.blog.www.service;

import com.blog.www.domain.entity.User;
import com.blog.www.domain.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 事務測試服務
 */
@Transactional
@Service
public class TransactionTestService {

    @Autowired
    private UserMapper userMapper;

    public void test1() {
        test2();

        User user = new User();
        user.setAge((short)1);
        user.setUserName("lei");
        user.setPassword("212121");
        user.setDeleted(false);
        userMapper.insert(user);

        throw new RuntimeException("測試事務回滾");
    }


    public void test2() {
        User user = new User();
        user.setAge((short)2);
        user.setUserName("tom");
        user.setPassword("2121DSASDAS");
        user.setDeleted(false);
        userMapper.insert(user);
    }

}

這個Service裏面有兩個方法,使用test1去調用test2,在test2和test1都執行完之後,拋一個異常,結果是兩條數據都回滾了,因爲我們在類上面加了 @Transactional 註解,此註解傳播行爲默認是:Propagation.REQUIRED,所以此時test1和test2事務是同一個。

可以看看執行日誌:

20191104214631.png
20191104214701.png

從日誌可以看出,只創建了一個事務,事務也正常回滾了。

那麼,如果現在有這樣的一個需求:test1拋異常僅僅只回滾test1,test2不受影響,如何實現?

有人可能會想到,在test2上面加個 @Transactional 註解,設置傳播行爲爲:Propagation.REQUIRES_NEW,這樣test2就會創建新的事務,真的是這樣麼?我們試試

修改上面的代碼爲:

package com.blog.www.service;

import com.blog.www.domain.entity.User;
import com.blog.www.domain.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * 事務測試服務
 */
@Transactional
@Service
public class TransactionTestService {

    @Autowired
    private UserMapper userMapper;

    public void test1() {
        test2();

        User user = new User();
        user.setAge((short)1);
        user.setUserName("lei");
        user.setPassword("212121");
        user.setDeleted(false);
        userMapper.insert(user);

        throw new RuntimeException("測試事務回滾");
    }


    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void test2() {
        User user = new User();
        user.setAge((short)2);
        user.setUserName("tom");
        user.setPassword("2121DSASDAS");
        user.setDeleted(false);
        userMapper.insert(user);
    }

}

查看執行日誌:

20191104220114.png

從日誌可以看出,兩個方法還是處於同一個事務中,兩條數據都回滾了。

爲什麼會出現這種情況?

這就得看看 Spring 官方文檔

20191104220457.png

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.

大概意思:

在默認的代理模式下,只有目標方法由外部調用,才能被 Spring 的事務攔截器攔截。在同一個類中的兩個方法直接調用,是不會被 Spring 的事務攔截器攔截。

就像上面的 test1 方法直接調用了同一個類中的 test2 方法,test2 方法不會被 Spring 的事務攔截器攔截。可以使用 AspectJ 取代 Spring AOP 代理來解決這個問題,但是這裏暫不討論。

爲了解決這個問題,我們可以新建一個類,將 test2 放入新建的類,如下:

test1修改後:

package com.blog.www.service;

import com.blog.www.domain.entity.User;
import com.blog.www.domain.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 事務測試服務
 */
@Transactional
@Service
public class TransactionTest1Service {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private TransactionTest2Service transactionTest2Service;

    public void test1() {
        transactionTest2Service.test2();

        User user = new User();
        user.setAge((short) 1);
        user.setUserName("lei");
        user.setPassword("212121");
        user.setDeleted(false);
        userMapper.insert(user);

        throw new RuntimeException("測試事務回滾");
    }

}

test2修改後:

package com.blog.www.service;

import com.blog.www.domain.entity.User;
import com.blog.www.domain.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * 事務測試服務
 */
@Transactional
@Service
public class TransactionTest2Service {

    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void test2() {
        User user = new User();
        user.setAge((short)2);
        user.setUserName("tom");
        user.setPassword("2121DSASDAS");
        user.setDeleted(false);
        userMapper.insert(user);
    }

}

查看執行日誌:

20191104221726.png
20191104221357.png

從日誌可以清晰的看出,在執行test2時,test1的事務被掛起,test2重新創建了一個新的事務,此時,再去看看數據庫:

20191104221943.png

只有test1回滾了,test2數據正常插入。

還有一個比較常見的場景,就是在循環體內使用事務,比如,現在需要循環插入1000條數據,需要保證中途有一條插入失敗,只回滾失敗的那條,前面已經執行的不受影響。

這個和上面差不多,只需要把循環執行的方法單獨放入一個類中,將事務的傳播行爲設置成 Propagation.REQUIRES_NEW 即可。

測試代碼如下:

package com.blog.www.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 事務測試服務
 */
@Transactional
@Service
@Slf4j
public class TransactionTest1Service {

    @Autowired
    private TransactionTest2Service transactionTest2Service;

    public void test1() {
        for (int i = 0; i < 10; i++) {
            transactionTest2Service.test2(i);
            if (i == 5) {
                // 模擬拋異常讓 AOP 處理事務回滾,這裏拋異常回滾,只能保證已經執行了的不會回滾,程序會停止執行
                throw new RuntimeException("測試循環體內事務");
                // 推薦使用手動回滾事務,手動回滾不影響程序繼續執行
                // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            }
        }
    }
}
package com.blog.www.service;

import com.blog.www.domain.entity.User;
import com.blog.www.domain.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * 事務測試服務
 */
@Transactional
@Service
public class TransactionTest2Service {

    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void test2(int i) {
        User user = new User();
        user.setAge((short)2);
        user.setUserName("tom" + i);
        user.setPassword("2121DSASDAS");
        user.setDeleted(false);
        userMapper.insert(user);
    }
}

執行日誌如下:

2019-11-04 22:48:09.996 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Creating new transaction with name [com.blog.www.service.TransactionTest1Service.test1]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2019-11-04 22:48:09.996 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@29abba49] for JDBC transaction
2019-11-04 22:48:09.996 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@29abba49] to manual commit
2019-11-04 22:48:09.997 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Suspending current transaction, creating new transaction with name [com.blog.www.service.TransactionTest2Service.test2]
2019-11-04 22:48:09.997 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] for JDBC transaction
2019-11-04 22:48:09.998 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] to manual commit
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6a5de853]
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] will be managed by Spring
==>  Preparing: insert into user (deleted, age, user_name, password, create_time, update_time) values (?, ?, ?,?, ?, ?) 
==> Parameters: false(Boolean), 2(Short), tom0(String), 2121DSASDAS(String), null, null
<==    Updates: 1
==>  Preparing: SELECT LAST_INSERT_ID() 
==> Parameters: 
<==    Columns: LAST_INSERT_ID()
<==        Row: 52
<==      Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6a5de853]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6a5de853]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6a5de853]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6a5de853]
2019-11-04 22:48:10.005 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Initiating transaction commit
2019-11-04 22:48:10.005 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc]
2019-11-04 22:48:10.007 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] after transaction
2019-11-04 22:48:10.008 DEBUG 576 --- [nio-8085-exec-2] o.s.jdbc.datasource.DataSourceUtils      : Returning JDBC Connection to DataSource
2019-11-04 22:48:10.008 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Resuming suspended transaction after completion of inner transaction
2019-11-04 22:48:10.008 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Suspending current transaction, creating new transaction with name [com.blog.www.service.TransactionTest2Service.test2]
2019-11-04 22:48:10.008 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] for JDBC transaction
2019-11-04 22:48:10.008 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] to manual commit
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@af833aa]
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] will be managed by Spring
==>  Preparing: insert into user (deleted, age, user_name, password, create_time, update_time) values (?, ?, ?,?, ?, ?) 
==> Parameters: false(Boolean), 2(Short), tom1(String), 2121DSASDAS(String), null, null
<==    Updates: 1
==>  Preparing: SELECT LAST_INSERT_ID() 
==> Parameters: 
<==    Columns: LAST_INSERT_ID()
<==        Row: 53
<==      Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@af833aa]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@af833aa]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@af833aa]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@af833aa]
2019-11-04 22:48:10.016 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Initiating transaction commit
2019-11-04 22:48:10.016 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc]
2019-11-04 22:48:10.019 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] after transaction
2019-11-04 22:48:10.019 DEBUG 576 --- [nio-8085-exec-2] o.s.jdbc.datasource.DataSourceUtils      : Returning JDBC Connection to DataSource
2019-11-04 22:48:10.019 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Resuming suspended transaction after completion of inner transaction
2019-11-04 22:48:10.020 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Suspending current transaction, creating new transaction with name [com.blog.www.service.TransactionTest2Service.test2]
2019-11-04 22:48:10.020 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] for JDBC transaction
2019-11-04 22:48:10.020 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] to manual commit
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25ca9b0f]
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] will be managed by Spring
==>  Preparing: insert into user (deleted, age, user_name, password, create_time, update_time) values (?, ?, ?,?, ?, ?) 
==> Parameters: false(Boolean), 2(Short), tom2(String), 2121DSASDAS(String), null, null
<==    Updates: 1
==>  Preparing: SELECT LAST_INSERT_ID() 
==> Parameters: 
<==    Columns: LAST_INSERT_ID()
<==        Row: 54
<==      Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25ca9b0f]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25ca9b0f]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25ca9b0f]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25ca9b0f]
2019-11-04 22:48:10.028 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Initiating transaction commit
2019-11-04 22:48:10.029 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc]
2019-11-04 22:48:10.031 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] after transaction
2019-11-04 22:48:10.031 DEBUG 576 --- [nio-8085-exec-2] o.s.jdbc.datasource.DataSourceUtils      : Returning JDBC Connection to DataSource
2019-11-04 22:48:10.031 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Resuming suspended transaction after completion of inner transaction
2019-11-04 22:48:10.032 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Suspending current transaction, creating new transaction with name [com.blog.www.service.TransactionTest2Service.test2]
2019-11-04 22:48:10.032 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] for JDBC transaction
2019-11-04 22:48:10.032 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] to manual commit
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66ddaba0]
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] will be managed by Spring
==>  Preparing: insert into user (deleted, age, user_name, password, create_time, update_time) values (?, ?, ?,?, ?, ?) 
==> Parameters: false(Boolean), 2(Short), tom3(String), 2121DSASDAS(String), null, null
<==    Updates: 1
==>  Preparing: SELECT LAST_INSERT_ID() 
==> Parameters: 
<==    Columns: LAST_INSERT_ID()
<==        Row: 55
<==      Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66ddaba0]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66ddaba0]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66ddaba0]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66ddaba0]
2019-11-04 22:48:10.037 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Initiating transaction commit
2019-11-04 22:48:10.037 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc]
2019-11-04 22:48:10.039 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] after transaction
2019-11-04 22:48:10.039 DEBUG 576 --- [nio-8085-exec-2] o.s.jdbc.datasource.DataSourceUtils      : Returning JDBC Connection to DataSource
2019-11-04 22:48:10.040 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Resuming suspended transaction after completion of inner transaction
2019-11-04 22:48:10.040 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Suspending current transaction, creating new transaction with name [com.blog.www.service.TransactionTest2Service.test2]
2019-11-04 22:48:10.040 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] for JDBC transaction
2019-11-04 22:48:10.040 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] to manual commit
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@535d39c7]
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] will be managed by Spring
==>  Preparing: insert into user (deleted, age, user_name, password, create_time, update_time) values (?, ?, ?,?, ?, ?) 
==> Parameters: false(Boolean), 2(Short), tom4(String), 2121DSASDAS(String), null, null
<==    Updates: 1
==>  Preparing: SELECT LAST_INSERT_ID() 
==> Parameters: 
<==    Columns: LAST_INSERT_ID()
<==        Row: 56
<==      Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@535d39c7]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@535d39c7]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@535d39c7]
2019-11-04 22:48:10.046 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Initiating transaction rollback
2019-11-04 22:48:10.046 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Rolling back JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc]
2019-11-04 22:48:10.048 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] after transaction
2019-11-04 22:48:10.048 DEBUG 576 --- [nio-8085-exec-2] o.s.jdbc.datasource.DataSourceUtils      : Returning JDBC Connection to DataSource
2019-11-04 22:48:10.049 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Resuming suspended transaction after completion of inner transaction
2019-11-04 22:48:10.049 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Initiating transaction rollback
2019-11-04 22:48:10.049 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Rolling back JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@29abba49]
2019-11-04 22:48:10.050 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@29abba49] after transaction
2019-11-04 22:48:10.050 DEBUG 576 --- [nio-8085-exec-2] o.s.jdbc.datasource.DataSourceUtils      : Returning JDBC Connection to DataSource
2019-11-04 22:48:10.051 DEBUG 576 --- [nio-8085-exec-2] .m.m.a.ExceptionHandlerExceptionResolver : Resolving exception from handler [public void com.blog.www.controller.TransactionTestController.testRollBack()]: java.lang.RuntimeException: 測試循環體內事務
2019-11-04 22:48:10.052 DEBUG 576 --- [nio-8085-exec-2] .m.m.a.ExceptionHandlerExceptionResolver : Invoking @ExceptionHandler method: public com.blog.www.bean.common.Response com.blog.www.web.GlobalExceptionHandNew.handleException(java.lang.Exception)
2019-11-04 22:48:10.055 ERROR 576 --- [nio-8085-exec-2] com.blog.www.web.GlobalExceptionHandNew  : 服務內部異常!測試循環體內事務

java.lang.RuntimeException: 測試循環體內事務
	at com.blog.www.service.TransactionTest2Service.test2(TransactionTest2Service.java:30) ~[classes/:na]
	at com.blog.www.service.TransactionTest2Service$$FastClassBySpringCGLIB$$ae552b84.invoke(<generated>) ~[classes/:na]
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:747) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294) ~[spring-tx-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at com.blog.www.service.TransactionTest2Service$$EnhancerBySpringCGLIB$$d21401df.test2(<generated>) ~[classes/:na]
	at com.blog.www.service.TransactionTest1Service.test1(TransactionTest1Service.java:21) ~[classes/:na]
	at com.blog.www.service.TransactionTest1Service$$FastClassBySpringCGLIB$$4673fea5.invoke(<generated>) ~[classes/:na]
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:747) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294) ~[spring-tx-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at com.blog.www.service.TransactionTest1Service$$EnhancerBySpringCGLIB$$55d8f7e2.test1(<generated>) ~[classes/:na]
	at com.blog.www.controller.TransactionTestController.testRollBack(TransactionTestController.java:23) ~[classes/:na]
	at sun.reflect.GeneratedMethodAccessor188.invoke(Unknown Source) ~[na:na]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:45005) ~[na:1.8.0_151]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_151]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:877) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:783) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:635) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) [tomcat-embed-websocket-8.5.32.jar:8.5.32]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109) [spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at com.blog.www.web.filter.xss.XssFilter.doFilter(XssFilter.java:31) [classes/:na]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at com.blog.www.web.filter.i18n.I18nFilter.doFilter(I18nFilter.java:77) [classes/:na]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:123) [druid-1.1.10.jar:1.1.10]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:90) [spring-boot-actuator-2.0.4.RELEASE.jar:2.0.4.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:155) [spring-boot-actuator-2.0.4.RELEASE.jar:2.0.4.RELEASE]
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:123) [spring-boot-actuator-2.0.4.RELEASE.jar:2.0.4.RELEASE]
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:108) [spring-boot-actuator-2.0.4.RELEASE.jar:2.0.4.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) [spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.core.StandardContextValve.__invoke(StandardContextValve.java:96) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:41002) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:800) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1471) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_151]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_151]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.32.jar:8.5.32]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_151]

再看看數據庫:

20191104225210.png

可以發現前面插入的不受影響。

五、@Transactional 事務實現機制

AOP 代理後的方法調用執行流程:

20191104222443.png

在應用系統調用聲明瞭 @Transactional 的目標方法時,Spring Framework 默認使用 AOP 代理,在代碼運行時生成一個代理對象,根據 @Transactional 的屬性配置信息,這個代理對象決定該聲明 @Transactional 的目標方法是否由攔截器 TransactionInterceptor 來使用攔截,在 TransactionInterceptor 攔截時,會在目標方法開始執行之前創建並加入事務,並執行目標方法的邏輯, 最後根據執行情況是否出現異常,利用抽象事務管理器 AbstractPlatformTransactionManager 操作數據源 DataSource 提交或回滾事務。

Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 兩種,以 CglibAopProxy 爲例,對於 CglibAopProxy,需要調用其內部類的 DynamicAdvisedInterceptor 的 intercept 方法。對於 JdkDynamicAopProxy,需要調用其 invoke 方法。

20191104222225.png

事務管理的框架是由抽象事務管理器 AbstractPlatformTransactionManager 來提供的,而具體的底層事務處理實現,由 PlatformTransactionManager 的具體實現類來實現,如事務管理器 DataSourceTransactionManager。不同的事務管理器管理不同的數據資源 DataSource,比如 DataSourceTransactionManager 管理 JDBC 的 Connection。

20191104222314.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章