事務(wù)—【01】Spring事務(wù)管理介紹以及SpringBoot+Druid+MyBatis單數(shù)據(jù)源事務(wù)管理實現(xiàn)

前置知識

  • 簡單介紹 詳解自行g(shù)oogle.

事務(wù)是什么?

  • 事務(wù)是一種可靠、一致的方式,訪問和操作數(shù)據(jù)庫中的程序單元

事務(wù)的特性

  • 原子性:要么做,要么不做
  • 一致性:一致性是指事務(wù)必須使數(shù)據(jù)庫從一個一致性狀態(tài)變換到另一個一致性狀態(tài)
  • 持久性:持久性是指一個事務(wù)一旦被提交了,那么對數(shù)據(jù)庫中的數(shù)據(jù)的改變就是永久性的。
  • 隔離性:不同的事務(wù)操作的互相不干擾

并發(fā)事務(wù)的問題

  1. 臟讀:臟讀是指在一個事務(wù)處理過程里讀取了另一個未提交的事務(wù)中的數(shù)據(jù),針對對某一項數(shù)據(jù)
  2. 不可重復(fù)讀:一個事務(wù)范圍內(nèi)多次查詢卻返回了不同的數(shù)據(jù)值,是由于在查詢間隔,被另一個事務(wù)修改并提交了。
  3. 幻讀:是指在事務(wù)執(zhí)行過程中多次查詢出來的數(shù)據(jù)數(shù),不一致,比如通過where篩選第二次比第一次多幾條別的事務(wù)新插入的數(shù)據(jù),幻讀針對的是一批數(shù)據(jù)整體

事務(wù)的隔離級別

  • READ_UNCOMMITTED 讀未提交:可以讀到?jīng)]有沒有提交事務(wù)做出的數(shù)據(jù)修改內(nèi)容(隔離級別最低,并發(fā)性能高)
  • READ_COMMITTED 讀已提交:只能讀到事務(wù)以及提交的數(shù)據(jù)(鎖定正在讀取的行)
  • REPEATABLE_READ 可重復(fù)讀:保證在一個事務(wù)中 多次讀同樣的數(shù)據(jù)是一致的。不會被其他事務(wù)所影響(鎖定所讀取的所有行)
  • SERIALIZABLE 可串行化:事務(wù)串行化順序執(zhí)行,

Spring事務(wù)

  • Spring提供了統(tǒng)一的事務(wù)API來支持不同的資源類型
  • Spring也提供了聲明式事務(wù)管理的方式,與業(yè)務(wù)代碼解耦
  • 很容易和Spring生態(tài)框架整合
  • 支持多資源的事務(wù)管理與同步

1. 事務(wù)抽象

1.1 PlatformTransactionManager

  • 事務(wù)管理器:主要提供提交操作、回滾操作以及根據(jù)事務(wù)定義獲取事務(wù)狀態(tài)的統(tǒng)一接口
public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
            throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;

}

1.2 TransactionDefinition

  • 事務(wù)定義:事務(wù)的一些基礎(chǔ)信息,如超時時間、隔離級別、傳播屬性等

事務(wù)傳播機制,也就是在事務(wù)在多個方法的調(diào)用中是如何傳遞的;如:兩個service的方法都是事務(wù)操作,一個事務(wù)方法調(diào)用另一個事務(wù)方法如何進(jìn)行處理?


public interface TransactionDefinition {

    // 事務(wù)的傳播機制
    // 如果調(diào)用方有事務(wù)則使用調(diào)用方的事務(wù),如果不存在事務(wù)則創(chuàng)建一個事務(wù)
    int PROPAGATION_REQUIRED = 0;

    // 跟隨調(diào)用方,如果調(diào)用方有 那就用,調(diào)用方?jīng)]有那就不用
    int PROPAGATION_SUPPORTS = 1;

    // 調(diào)用方的方法必須運行在一個事務(wù)中,不存在事務(wù)則拋出異常
    int PROPAGATION_MANDATORY = 2;

    // 不管調(diào)用方是否有事務(wù)執(zhí)行,自己都要起一個新事務(wù)。把原先的事務(wù)掛起,這個只在jta的事務(wù)管理器中起作用(事務(wù)是不支持嵌套的)
    int PROPAGATION_REQUIRES_NEW = 3;

    // 即使調(diào)用方有事務(wù),我也要在非事務(wù)中執(zhí)行,把原先的事務(wù)掛起
    int PROPAGATION_NOT_SUPPORTED = 4; 

    // 絕不在事務(wù)里面執(zhí)行
    int PROPAGATION_NEVER = 5;

    // 嵌套事務(wù)(實際是不支持的,是利用存盤點來實現(xiàn),把調(diào)用方之前執(zhí)行的存盤點,然后類似于再開啟一個事務(wù)執(zhí)行,執(zhí)行完畢后恢復(fù)存盤點,繼續(xù)執(zhí)行。JDBC3.0以上支持)
    int PROPAGATION_NESTED = 6;

    // 以下定義的是事務(wù)的隔離機制
    // 根據(jù)數(shù)據(jù)庫的默認(rèn)的隔離機制而定
    int ISOLATION_DEFAULT = -1;

    // 讀未提交
    int ISOLATION_READ_UNCOMMITTED = 1;  // same as java.sql.Connection.TRANSACTION_READ_UNCOMMITTED;

    // 讀已提交
    int ISOLATION_READ_COMMITTED = 2;  // same as java.sql.Connection.TRANSACTION_READ_COMMITTED;

    // 可重復(fù)讀
    int ISOLATION_REPEATABLE_READ = 4;  // same as java.sql.Connection.TRANSACTION_REPEATABLE_READ;

    // 串行化
    int ISOLATION_SERIALIZABLE = 8;  // same as java.sql.Connection.TRANSACTION_SERIALIZABLE;


    // 默認(rèn)超時時間,以數(shù)據(jù)庫設(shè)定為準(zhǔn)
    int TIMEOUT_DEFAULT = -1;

    // 獲取事務(wù)的傳播屬性
    default int getPropagationBehavior() {
        return PROPAGATION_REQUIRED;
    }

    // 獲取事務(wù)的隔離級別
    default int getIsolationLevel() {
        return ISOLATION_DEFAULT;
    }

    // 獲取事務(wù)超時時間
    default int getTimeout() {
        return TIMEOUT_DEFAULT;
    }

    // 事務(wù)是否只讀。
    default boolean isReadOnly() {
        return false;
    }

    // 獲取事務(wù)的名稱
    @Nullable
    default String getName() {
        return null;
    }

    // 返回一個默認(rèn)事務(wù)定義
    static TransactionDefinition withDefaults() {
        return StaticTransactionDefinition.INSTANCE;
    }

}

1.3 TransactionStatus

  • 事務(wù)狀態(tài):事務(wù)的一些狀態(tài)信息,如是否是一個新的事務(wù)、是否已被標(biāo)記為回滾
// Savepoiont就是在Nested這種傳播機制中提供保存點機制來實現(xiàn)嵌套事務(wù),出錯的時候可以選擇恢復(fù)到保存點
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
    // 是否有保存點
    boolean hasSavepoint();

    @Override
    void flush();
}
public interface TransactionExecution {
    // 是否是一個新事務(wù)
    boolean isNewTransaction();

    // 設(shè)置事務(wù)回滾
    void setRollbackOnly();
    
    // 事務(wù)是否回滾
    boolean isRollbackOnly();
    
    // 獲取事務(wù)是否完成 
    boolean isCompleted();

}

2. PlatformTransactionManager常見的實現(xiàn)

  • DataSourceTransactionManager (用于JDBC Template、MyBatis等)
  • JpaTransactionManager (用于data-jpa、hibernate等)
  • JmsTransactionManager (用于消息中間件等)
  • JtaTransactionManager (主要用于分布式事務(wù))

3. Spring單數(shù)據(jù)源事務(wù)實際案例

3.1 環(huán)境說明:

3.2 實例代碼

  • 此案例額基于轉(zhuǎn)賬業(yè)務(wù)實現(xiàn) PlatformTransactionManager則是DataSourceTransactionManager
@Slf4j
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDAO accountDAO;
    @Autowired
    PlatformTransactionManager transactionManager;
    /**
     * 聲明式事務(wù)
     * propagation = Propagation.REQUIRED (默認(rèn)值就是REQUIRED) 如果調(diào)用方有事務(wù)就直接使用調(diào)用方的事務(wù),如果沒有就新建一個事務(wù)
     * transactionManager = "transactionManager" 也是默認(rèn)值
     * isolation= Isolation.DEFAULT 隔離級別
     * 還有timeout等參數(shù) 可自行查看Transactional的源碼 里面都有說明
     * @param sourceAccountId 源賬戶
     * @param targetAccountId 目標(biāo)賬戶
     * @param amount 金額
     * @return 操作結(jié)果信息
     */
    @Override
    @Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED, rollbackFor = Exception.class, isolation= Isolation.DEFAULT)
    public String transferAnnotation(Long sourceAccountId, Long targetAccountId, BigDecimal amount) {
        AccountDO sourceAccountDO = accountDAO.selectByPrimaryKey(sourceAccountId);
        AccountDO targetAccountDO = accountDAO.selectByPrimaryKey(targetAccountId);
        if (null == sourceAccountDO || null == targetAccountDO) {
            return "轉(zhuǎn)入或者轉(zhuǎn)出賬戶不存在";
        }
        if (sourceAccountDO.getBalance().compareTo(amount) < 0) {
            return "轉(zhuǎn)出賬戶余額不足";
        }
        sourceAccountDO.setBalance(sourceAccountDO.getBalance().subtract(amount));
        accountDAO.updateByPrimaryKeySelective(sourceAccountDO);
        // error("annotation error!");
        targetAccountDO.setBalance(targetAccountDO.getBalance().add(amount));
        accountDAO.updateByPrimaryKeySelective(targetAccountDO);
        return "轉(zhuǎn)賬成功!";
    }

    @Override
    public String transferCode(Long sourceAccountId, Long targetAccountId, BigDecimal amount) {
        TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        // 獲取事務(wù) 開始業(yè)務(wù)執(zhí)行
        TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
        try {
            AccountDO targetAccountDO = accountDAO.selectByPrimaryKey(targetAccountId);
            AccountDO sourceAccountDO = accountDAO.selectByPrimaryKey(sourceAccountId);
            if (null == sourceAccountDO || null == targetAccountDO) {
                return "轉(zhuǎn)入或者轉(zhuǎn)出賬戶不存在";
            }
            error("code error");
            if (sourceAccountDO.getBalance().compareTo(amount) < 0) {
                return "轉(zhuǎn)出賬戶余額不足";
            }
            sourceAccountDO.setBalance(sourceAccountDO.getBalance().subtract(amount));
            targetAccountDO.setBalance(targetAccountDO.getBalance().add(amount));
            accountDAO.updateByPrimaryKeySelective(sourceAccountDO);
            accountDAO.updateByPrimaryKeySelective(sourceAccountDO);
            // 提交事務(wù)
            transactionManager.commit(transaction);
            return "轉(zhuǎn)賬成功!";
        } catch (Exception e) {
            log.error("轉(zhuǎn)賬發(fā)生錯誤,開始回滾,source: {}, target: {}, amount: {}, errMsg: {}",
                    sourceAccountId, targetAccountId, amount, e.getMessage());
            // 報錯回滾
            transactionManager.rollback(transaction);
        }
        return "轉(zhuǎn)賬失敗";
    }

    @Override
    public List<AccountDO> listAll() {
        return accountDAO.selectAll();
    }


    private static void error(String msg) {
        throw new RuntimeException(msg);
    }
}

3.3 Transactional注解實現(xiàn)事務(wù)

  • 使用注解式事務(wù),Spring會利用代理實現(xiàn)一個代理類,

  • 然后從上下文中得到事務(wù)管理器,開啟一個事務(wù)后執(zhí)行業(yè)務(wù)代碼,

  • 如果滿足你設(shè)置的回滾異常條件,就執(zhí)行rollback

  • 我們調(diào)用的時候是直接調(diào)用的Service的方法

  • 但是Spring在實現(xiàn)的時候,實際是通過AOP Proxy(AOP 代理服務(wù))來調(diào)用Transaction Advisor(做事務(wù)管理的)然后來處理調(diào)用注解式事務(wù)的service方法


  • 我們通過接口/api/account/transfer/annotation?source=1&target=2&amount=123 觸發(fā)賬戶1轉(zhuǎn)給賬戶2 123塊錢,轉(zhuǎn)賬操作
  • 日志信息(開啟DEBUG日志):
o.s.web.servlet.DispatcherServlet        : GET "/api/account/transfer/annotation?source=1&target=2&amount=123", parameters={masked}
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to io.ilss.transaction.onedatasource.web.AccountController#transferAnnotation(Long, Long, String)
o.s.j.d.DataSourceTransactionManager     : Creating new transaction with name [io.ilss.transaction.onedatasource.service.impl.AccountServiceImpl.transferAnnotation]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; 'transactionManager',-java.lang.Exception
o.s.j.d.DataSourceTransactionManager     : Acquired Connection [com.mysql.jdbc.JDBC4Connection@185f0a96] for JDBC transaction
o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [com.mysql.jdbc.JDBC4Connection@185f0a96] to manual commit
org.mybatis.spring.SqlSessionUtils       : Creating a new SqlSession
org.mybatis.spring.SqlSessionUtils       : Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
o.m.s.t.SpringManagedTransaction         : JDBC Connection [com.mysql.jdbc.JDBC4Connection@185f0a96] will be managed by Spring
i.i.t.o.d.AccountDAO.selectByPrimaryKey  : ==>  Preparing: select id, nickname, username, `password`, balance, create_time, update_time from account where id = ?
i.i.t.o.d.AccountDAO.selectByPrimaryKey  : ==> Parameters: 1(Long)
i.i.t.o.d.AccountDAO.selectByPrimaryKey  : <==      Total: 1
org.mybatis.spring.SqlSessionUtils       : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils       : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42] from current transaction
i.i.t.o.d.AccountDAO.selectByPrimaryKey  : ==>  Preparing: select id, nickname, username, `password`, balance, create_time, update_time from account where id = ?
i.i.t.o.d.AccountDAO.selectByPrimaryKey  : ==> Parameters: 2(Long)
i.i.t.o.d.AccountDAO.selectByPrimaryKey  : <==      Total: 1
org.mybatis.spring.SqlSessionUtils       : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils       : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42] from current transaction
i.i.t.o.d.A.updateByPrimaryKeySelective  : ==>  Preparing: update account SET nickname = ?, username = ?, `password` = ?, balance = ?, create_time = ?, update_time = ? where id = ?
i.i.t.o.d.A.updateByPrimaryKeySelective  : ==> Parameters: 小一(String), xiaoyi(String), 123456(String), 877.00(BigDecimal), 2020-01-09T17:04:28(LocalDateTime), 2020-01-09T17:44:33(LocalDateTime), 1(Long)
i.i.t.o.d.A.updateByPrimaryKeySelective  : <==    Updates: 1
org.mybatis.spring.SqlSessionUtils       : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils       : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42] from current transaction
i.i.t.o.d.A.updateByPrimaryKeySelective  : ==>  Preparing: update account SET nickname = ?, username = ?, `password` = ?, balance = ?, create_time = ?, update_time = ? where id = ?
i.i.t.o.d.A.updateByPrimaryKeySelective  : ==> Parameters: 小二(String), xiaoer(String), 123456(String), 223.00(BigDecimal), 2020-01-09T17:04:40(LocalDateTime), 2020-01-09T17:04:40(LocalDateTime), 2(Long)
i.i.t.o.d.A.updateByPrimaryKeySelective  : <==    Updates: 1
org.mybatis.spring.SqlSessionUtils       : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils       : Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils       : Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils       : Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
o.s.j.d.DataSourceTransactionManager     : Initiating transaction commit
o.s.j.d.DataSourceTransactionManager     : Committing JDBC transaction on Connection [com.mysql.jdbc.JDBC4Connection@185f0a96]
o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection [com.mysql.jdbc.JDBC4Connection@185f0a96] after transaction
m.m.a.RequestResponseBodyMethodProcessor : Using 'text/html', given [text/html, application/xhtml+xml, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] and supported [text/plain, */*, text/plain, */*, application/json, application/*+json, application/json, application/*+json]
m.m.a.RequestResponseBodyMethodProcessor : Writing ["轉(zhuǎn)賬成功!"]
o.s.web.servlet.DispatcherServlet        : Completed 200 OK

  • 成功執(zhí)行過程
  1. GET 請求到 /api/account/transfer/annotation接口 然后調(diào)用service方法
  2. 根據(jù)寫的Transactional注解的設(shè)置 創(chuàng)建一個新事務(wù)
  3. 選用JDBC連接 然后創(chuàng)建 SqlSession
  4. 為SqlSession注冊事務(wù)同步
  5. SQL操作
  6. 然后把事務(wù)提交
  7. 最后釋放資源。完成方法調(diào)用
  8. 接口返回200
  • 通過日志可看出我們使用MyBatis+Druid 配置數(shù)據(jù)源 事務(wù)管理實際是使用的DataSourceTransactionManager.
  • 雖然代碼中指定了transactionManager,但實際卻沒有增加任何Bean注冊或者其他有關(guān)DataSource的事務(wù)管理器代碼。這個地方是因為Spring幫你做了,我設(shè)置transactionManager是因為自動裝配的transactionManager名字就是這個。寫出來提醒一下有這個配置,這個配置顧名思義就是配置對這個事務(wù)的管理器實現(xiàn)。簡而言之就是PlatformTransactionManager的指定。后面多數(shù)據(jù)源的時候我們會通過指定不同的transactionManager來實現(xiàn)事務(wù)管理。
  • 如果報錯會是怎么樣呢?
  • 先看日志(開啟DEBUG日志):
o.s.web.servlet.DispatcherServlet        : GET "/api/account/transfer/annotation?source=1&target=2&amount=123", parameters={masked}
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to io.ilss.transaction.onedatasource.web.AccountController#transferAnnotation(Long, Long, String)
o.s.j.d.DataSourceTransactionManager     : Creating new transaction with name [io.ilss.transaction.onedatasource.service.impl.AccountServiceImpl.transferAnnotation]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; 'transactionManager',-java.lang.Exception
o.s.j.d.DataSourceTransactionManager     : Acquired Connection [com.mysql.jdbc.JDBC4Connection@1b6963ec] for JDBC transaction
o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [com.mysql.jdbc.JDBC4Connection@1b6963ec] to manual commit
org.mybatis.spring.SqlSessionUtils       : Creating a new SqlSession
org.mybatis.spring.SqlSessionUtils       : Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
o.m.s.t.SpringManagedTransaction         : JDBC Connection [com.mysql.jdbc.JDBC4Connection@1b6963ec] will be managed by Spring
i.i.t.o.d.AccountDAO.selectByPrimaryKey  : ==>  Preparing: select id, nickname, username, `password`, balance, create_time, update_time from account where id = ?
i.i.t.o.d.AccountDAO.selectByPrimaryKey  : ==> Parameters: 1(Long)
i.i.t.o.d.AccountDAO.selectByPrimaryKey  : <==      Total: 1
org.mybatis.spring.SqlSessionUtils       : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
org.mybatis.spring.SqlSessionUtils       : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58] from current transaction
i.i.t.o.d.AccountDAO.selectByPrimaryKey  : ==>  Preparing: select id, nickname, username, `password`, balance, create_time, update_time from account where id = ?
i.i.t.o.d.AccountDAO.selectByPrimaryKey  : ==> Parameters: 2(Long)
i.i.t.o.d.AccountDAO.selectByPrimaryKey  : <==      Total: 1
org.mybatis.spring.SqlSessionUtils       : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
org.mybatis.spring.SqlSessionUtils       : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58] from current transaction
i.i.t.o.d.A.updateByPrimaryKeySelective  : ==>  Preparing: update account SET nickname = ?, username = ?, `password` = ?, balance = ?, create_time = ?, update_time = ? where id = ?
i.i.t.o.d.A.updateByPrimaryKeySelective  : ==> Parameters: 小一(String), xiaoyi(String), 123456(String), 754.00(BigDecimal), 2020-01-09T17:04:28(LocalDateTime), 2020-01-09T17:44:33(LocalDateTime), 1(Long)
i.i.t.o.d.A.updateByPrimaryKeySelective  : <==    Updates: 1
org.mybatis.spring.SqlSessionUtils       : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
org.mybatis.spring.SqlSessionUtils       : Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
org.mybatis.spring.SqlSessionUtils       : Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
o.s.j.d.DataSourceTransactionManager     : Initiating transaction rollback
o.s.j.d.DataSourceTransactionManager     : Rolling back JDBC transaction on Connection [com.mysql.jdbc.JDBC4Connection@1b6963ec]
o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1b6963ec] after transaction
o.s.web.servlet.DispatcherServlet        : Failed to complete request: java.lang.RuntimeException: annotation error!
o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is 
java.lang.RuntimeException: annotation error!
                            .......
o.a.c.c.C.[Tomcat].[localhost]           : Processing ErrorPage[errorCode=0, location=/error]
o.s.web.servlet.DispatcherServlet        : "ERROR" dispatch for GET "/error?source=1&target=2&amount=123", parameters={masked}
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)
o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, text/html;q=0.8]
o.s.web.servlet.DispatcherServlet        : Exiting from "ERROR" dispatch, status 500

  • 失敗執(zhí)行過程
  1. GET 請求到 /api/account/transfer/annotation接口 然后調(diào)用service方法
  2. 根據(jù)寫的Transactional注解的設(shè)置 創(chuàng)建一個新事務(wù)
  3. 選用JDBC連接 然后創(chuàng)建 SqlSession
  4. 為SqlSession注冊事務(wù)同步
  5. SQL操作:可以從日志看到,執(zhí)行到第一個跟新操作發(fā)生錯誤
  6. 直接回滾Rolling back JDBC transaction on Connection后打印錯誤日志。后面的第二個更新操作也就沒了
  7. 方法異常退出,接口500

3.4 編程實現(xiàn)事務(wù)管理

  • 編程式事務(wù)主要步驟
  1. 創(chuàng)建事務(wù)定義TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
  2. 設(shè)置事務(wù)屬性:如傳播屬性、隔離屬性等
  3. 通過TransactionManager開啟事務(wù)
  4. 執(zhí)行業(yè)務(wù)代碼
  5. 提交事務(wù) / 處理回滾
  • 此實例的編程式事務(wù)的執(zhí)行基本和聲明式事務(wù)相同,有興趣可以自行下載代碼自己實現(xiàn)。

// todo: 下一篇多數(shù)據(jù)源事務(wù)管理


關(guān)于我

  • 坐標(biāo)杭州,普通本科在讀,計算機科學(xué)與技術(shù)專業(yè),20年6月畢業(yè),瘋狂找工作中。。。。
  • 目前處于菜鳥階段,各位大佬輕噴,小弟正在瘋狂學(xué)習(xí)。
  • 歡迎大家和我交流鴨?。?!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容