Spring源碼解析——@Transactional註解的聲明式事物介紹

来源:https://www.cnblogs.com/tyson03/archive/2023/10/14/17764353.html
-Advertisement-
Play Games

正文 面的幾個章節已經分析了spring基於@AspectJ的源碼,那麼接下來我們分析一下Aop的另一個重要功能,事物管理。最全面的Java面試網站 事務的介紹 1.資料庫事物特性 原子性 多個資料庫操作是不可分割的,只有所有的操作都執行成功,事物才能被提交;只要有一個操作執行失敗,那麼所有的操作都 ...


正文

面的幾個章節已經分析了spring基於@AspectJ的源碼,那麼接下來我們分析一下Aop的另一個重要功能,事物管理。最全面的Java面試網站

事務的介紹

1.資料庫事物特性

  • 原子性
    多個資料庫操作是不可分割的,只有所有的操作都執行成功,事物才能被提交;只要有一個操作執行失敗,那麼所有的操作都要回滾,資料庫狀態必須回覆到操作之前的狀態
  • 一致性
    事物操作成功後,資料庫的狀態和業務規則必須一致。例如:從A賬戶轉賬100元到B賬戶,無論資料庫操作成功失敗,A和B兩個賬戶的存款總額是不變的。
  • 隔離性
    當併發操作時,不同的資料庫事物之間不會相互干擾(當然這個事物隔離級別也是有關係的)
  • 持久性
    事物提交成功之後,事物中的所有數據都必須持久化到資料庫中。即使事物提交之後資料庫立刻崩潰,也需要保證數據能能夠被恢復。

2.事物隔離級別

當資料庫併發操作時,可能會引起臟讀、不可重覆讀、幻讀、第一類丟失更新、第二類更新丟失等現象。

  • 臟讀
    事物A讀取事物B尚未提交的更改數據,並做了修改;此時如果事物B回滾,那麼事物A讀取到的數據是無效的,此時就發生了臟讀。
  • 不可重覆讀
    一個事務執行相同的查詢兩次或兩次以上,每次都得到不同的數據。如:A事物下查詢賬戶餘額,此時恰巧B事物給賬戶里轉賬100元,A事物再次查詢賬戶餘額,那麼A事物的兩次查詢結果是不一致的。
  • 幻讀
    A事物讀取B事物提交的新增數據,此時A事物將出現幻讀現象。幻讀與不可重覆讀容易混淆,如何區分呢?幻讀是讀取到了其他事物提交的新數據,不可重覆讀是讀取到了已經提交事物的更改數據(修改或刪除)

對於以上問題,可以有多個解決方案,設置資料庫事物隔離級別就是其中的一種,資料庫事物隔離級別分為四個等級,通過一個表格描述其作用。

隔離級別 臟讀 不可重覆讀 幻象讀
READ UNCOMMITTED 允許 允許 允許
READ COMMITTED 臟讀 允許 允許
REPEATABLE READ 不允許 不允許 允許
SERIALIZABLE 不允許 不允許 不允許

3.Spring事物支持核心介面

  • TransactionDefinition-->定義與spring相容的事務屬性的介面
public interface TransactionDefinition {
    // 如果當前沒有事物,則新建一個事物;如果已經存在一個事物,則加入到這個事物中。
    int PROPAGATION_REQUIRED = 0;
    // 支持當前事物,如果當前沒有事物,則以非事物方式執行。
    int PROPAGATION_SUPPORTS = 1;
    // 使用當前事物,如果當前沒有事物,則拋出異常。
    int PROPAGATION_MANDATORY = 2;
    // 新建事物,如果當前已經存在事物,則掛起當前事物。
    int PROPAGATION_REQUIRES_NEW = 3;
    // 以非事物方式執行,如果當前存在事物,則掛起當前事物。
    int PROPAGATION_NOT_SUPPORTED = 4;
    // 以非事物方式執行,如果當前存在事物,則拋出異常。
    int PROPAGATION_NEVER = 5;
    // 如果當前存在事物,則在嵌套事物內執行;如果當前沒有事物,則與PROPAGATION_REQUIRED傳播特性相同
    int PROPAGATION_NESTED = 6;
    // 使用後端資料庫預設的隔離級別。
    int ISOLATION_DEFAULT = -1;
    // READ_UNCOMMITTED 隔離級別
    int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
    // READ_COMMITTED 隔離級別
    int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
    // REPEATABLE_READ 隔離級別
    int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
    // SERIALIZABLE 隔離級別
    int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
    // 預設超時時間
    int TIMEOUT_DEFAULT = -1;
    // 獲取事物傳播特性
    int getPropagationBehavior();
    // 獲取事物隔離級別
    int getIsolationLevel();
    // 獲取事物超時時間
    int getTimeout();
    // 判斷事物是否可讀
    boolean isReadOnly();
    // 獲取事物名稱
    @Nullable
    String getName();
}
  1. Spring事物傳播特性表:
傳播特性名稱 說明
PROPAGATION_REQUIRED 如果當前沒有事物,則新建一個事物;如果已經存在一個事物,則加入到這個事物中
PROPAGATION_SUPPORTS 支持當前事物,如果當前沒有事物,則以非事物方式執行
PROPAGATION_MANDATORY 使用當前事物,如果當前沒有事物,則拋出異常
PROPAGATION_REQUIRES_NEW 新建事物,如果當前已經存在事物,則掛起當前事物
PROPAGATION_NOT_SUPPORTED 以非事物方式執行,如果當前存在事物,則掛起當前事物
PROPAGATION_NEVER 以非事物方式執行,如果當前存在事物,則拋出異常
PROPAGATION_NESTED 如果當前存在事物,則在嵌套事物內執行;如果當前沒有事物,則與PROPAGATION_REQUIRED傳播特性相同
  1. Spring事物隔離級別表:
事務隔離級別 臟讀 不可重覆讀 幻讀
讀未提交(read-uncommitted)
不可重覆讀(read-committed)
可重覆讀(repeatable-read)
串列化(serializable)

MySQL預設的事務隔離級別為 可重覆讀repeatable-read

  3.PlatformTransactionManager-->Spring事務基礎結構中的中心介面

分享一份大彬精心整理的大廠面試手冊,包含電腦基礎、Java基礎、多線程、JVM、資料庫、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分散式、微服務、設計模式、架構、校招社招分享等高頻面試題,非常實用,有小伙伴靠著這份手冊拿過位元組offer~

需要的小伙伴可以自行下載

http://mp.weixin.qq.com/s?__biz=Mzg2OTY1NzY0MQ==&mid=2247485445&idx=1&sn=1c6e224b9bb3da457f5ee03894493dbc&chksm=ce98f543f9ef7c55325e3bf336607a370935a6c78dbb68cf86e59f5d68f4c51d175365a189f8#rd

public interface PlatformTransactionManager {
    // 根據指定的傳播行為,返回當前活動的事務或創建新事務。
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
    // 就給定事務的狀態提交給定事務。
    void commit(TransactionStatus status) throws TransactionException;
    // 執行給定事務的回滾。
    void rollback(TransactionStatus status) throws TransactionException;
}

Spring將事物管理委托給底層的持久化框架來完成,因此,Spring為不同的持久化框架提供了不同的PlatformTransactionManager介面實現。列舉幾個Spring自帶的事物管理器:

事物管理器 說明
org.springframework.jdbc.datasource.DataSourceTransactionManager 提供對單個javax.sql.DataSource事務管理,用於Spring JDBC抽象框架、iBATIS或MyBatis框架的事務管理
org.springframework.orm.jpa.JpaTransactionManager 提供對單個javax.persistence.EntityManagerFactory事務支持,用於集成JPA實現框架時的事務管理
org.springframework.transaction.jta.JtaTransactionManager 提供對分散式事務管理的支持,並將事務管理委托給Java EE應用伺服器事務管理器
  • TransactionStatus-->事物狀態描述
  1. TransactionStatus介面
public interface TransactionStatus extends SavepointManager, Flushable {
    // 返回當前事務是否為新事務(否則將參與到現有事務中,或者可能一開始就不在實際事務中運行)
    boolean isNewTransaction();
    // 返回該事務是否在內部攜帶保存點,也就是說,已經創建為基於保存點的嵌套事務。
    boolean hasSavepoint();
    // 設置事務僅回滾。
    void setRollbackOnly();
    // 返回事務是否已標記為僅回滾
    boolean isRollbackOnly();
    // 將會話刷新到數據存儲區
    @Override
    void flush();
    // 返回事物是否已經完成,無論提交或者回滾。
    boolean isCompleted();
}
  1. SavepointManager介面
public interface SavepointManager {
    // 創建一個新的保存點。
    Object createSavepoint() throws TransactionException;
    // 回滾到給定的保存點。
    // 註意:調用此方法回滾到給定的保存點之後,不會自動釋放保存點,
    // 可以通過調用releaseSavepoint方法釋放保存點。
    void rollbackToSavepoint(Object savepoint) throws TransactionException;
    // 顯式釋放給定的保存點。(大多數事務管理器將在事務完成時自動釋放保存點)
    void releaseSavepoint(Object savepoint) throws TransactionException;
}

Spring編程式事物

CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
  `balance` int(11) DEFAULT NULL COMMENT '賬戶餘額',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='--賬戶表'
  • 實現
 import org.apache.commons.dbcp.BasicDataSource;
 import org.springframework.dao.DataAccessException;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.datasource.DataSourceTransactionManager;
 import org.springframework.transaction.TransactionDefinition;
 import org.springframework.transaction.TransactionStatus;
 import org.springframework.transaction.support.DefaultTransactionDefinition;
 
 import javax.sql.DataSource;

 public class MyTransaction {
 
     private JdbcTemplate jdbcTemplate;
     private DataSourceTransactionManager txManager;
     private DefaultTransactionDefinition txDefinition;
     private String insert_sql = "insert into account (balance) values ('100')";
 
     public void save() {
 
         // 1、初始化jdbcTemplate
         DataSource dataSource = getDataSource();
         jdbcTemplate = new JdbcTemplate(dataSource);
 
         // 2、創建物管理器
         txManager = new DataSourceTransactionManager();
         txManager.setDataSource(dataSource);
 
         // 3、定義事物屬性
         txDefinition = new DefaultTransactionDefinition();
         txDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
 
         // 3、開啟事物
         TransactionStatus txStatus = txManager.getTransaction(txDefinition);
 
         // 4、執行業務邏輯
         try {
             jdbcTemplate.execute(insert_sql);
             //int i = 1/0;
             jdbcTemplate.execute(insert_sql);
             txManager.commit(txStatus);
         } catch (DataAccessException e) {
             txManager.rollback(txStatus);
             e.printStackTrace();
         }
 
     }
 
     public DataSource getDataSource() {
         BasicDataSource dataSource = new BasicDataSource();
         dataSource.setDriverClassName("com.mysql.jdbc.Driver");
         dataSource.setUrl("jdbc:mysql://localhost:3306/my_test?useSSL=false&useUnicode=true&characterEncoding=UTF-8");
         dataSource.setUsername("root");
         dataSource.setPassword("dabin1991@");
         return dataSource;
     }
 
 }
  • 測試類及結果
public class MyTest {
    @Test
    public void test1() {
        MyTransaction myTransaction = new MyTransaction();
        myTransaction.save();
    }
}

運行測試類,在拋出異常之後手動回滾事物,所以資料庫表中不會增加記錄。

基於@Transactional註解的聲明式事物

其底層建立在 AOP 的基礎之上,對方法前後進行攔截,然後在目標方法開始之前創建或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。通過聲明式事物,無需在業務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中做相關的事務規則聲明(或通過等價的基於標註的方式),便可以將事務規則應用到業務邏輯中。

  • 介面
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Transactional(propagation = Propagation.REQUIRED)
public interface AccountServiceImp {
    void save() throws RuntimeException;
}
  • 實現
import org.springframework.jdbc.core.JdbcTemplate;

public class AccountServiceImpl implements AccountServiceImp {

    private JdbcTemplate jdbcTemplate;

    private static String insert_sql = "insert into account(balance) values (100)";


    @Override
    public void save() throws RuntimeException {
        System.out.println("==開始執行sql");
        jdbcTemplate.update(insert_sql);
        System.out.println("==結束執行sql");

        System.out.println("==準備拋出異常");
        throw new RuntimeException("==手動拋出一個異常");
    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}
  • 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--開啟tx註解-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!--事物管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--數據源-->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/my_test?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
        <property name="username" value="root"/>
        <property name="password" value="dabin1991@"/>
    </bean>

    <!--jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--業務bean-->
    <bean id="accountService" class="com.dabin.aop.AccountServiceImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

</beans>
  • 測試
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {

    @Test
    public void test1() {
        // 基於tx標簽的聲明式事物
        ApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml");
        AccountServiceImp studentService = ctx.getBean("accountService", AccountServiceImp.class);
        studentService.save();
    }
}
  • 測試
==開始執行sql
==結束執行sql
==準備拋出異常

java.lang.RuntimeException: ==手動拋出一個異常

    at com.lyc.cn.v2.day09.AccountServiceImpl.save(AccountServiceImpl.java:24)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)

測試方法中手動拋出了一個異常,Spring會自動回滾事物,查看資料庫可以看到並沒有新增記錄。

註意:預設情況下Spring中的事務處理只對RuntimeException方法進行回滾,所以,如果此處將RuntimeException替換成普通的Exception不會產生回滾效果。

下一篇我們分析基於@Transactional註解的聲明式事物的的源碼實現。

最後給大家分享一個Github倉庫,上面有大彬整理的300多本經典的電腦書籍PDF,包括C語言、C++、Java、Python、前端、資料庫、操作系統、電腦網路、數據結構和演算法、機器學習、編程人生等,可以star一下,下次找書直接在上面搜索,倉庫持續更新中~

Github地址

如果訪問不了Github,可以訪問碼雲地址。

碼雲地址


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 具體以電子商務網站為例, 展示web應用的架構演變過程。 1.0時代 這個時候是一個web項目里包含了所有的模塊,一個資料庫里包含了所需要的所有表,這時候網站訪問量增加時,首先遇到瓶頸的是應用伺服器連接數,比如tomcat連接數不能無限增加,線程數上限受進程記憶體大小、CPU內核數等因素影響,當線程數 ...
  • 目錄 1. Maven簡介 2. Maven下載安裝 3. Maven項目創建 4. Maven項目導入依賴 內容 Maven簡介 Maven是什麼 Maven 是 Apache 下的一個純 Java 開發的開源項目。基於項目對象模型(縮寫:POM)概念,Maven利用一個中央信息片斷能管理一個項目 ...
  • 本篇文章深入探討了Go語言的泛型特性,從其基礎概念到高級用法,並通過實戰示例展示了其在實際項目中的應用。 關註【TechLeadCloud】,分享互聯網架構、雲服務技術的全維度知識。作者擁有10+年互聯網服務架構、AI產品研發經驗、團隊管理經驗,同濟本復旦碩,復旦機器人智能實驗室成員,阿裡雲認證的資 ...
  • 目錄 1. MySQL準備 2. JDBC項目 3. JDBC新增 4. JDBC查詢 5. JDBC修改 6. JDBC刪除 內容 MySQL準備 新建表t_person CREATE TABLE `t_person` ( `id` int(11) NOT NULL AUTO_INCREMENT ...
  • 錯誤也可以理解為異常,代表應用程式在執行過程中的發生了非預期的行為,常見異常比如有空指針、數組越界、網路超時、IO異常等,Dart語言也支持拋出和捕獲異常。和Java不同的是,Dart語言只有未檢測異常。Dart中的異常需要被捕獲並被處理,否則可能導致程式退出…… ...
  • 目錄 1. JDBC簡介 2. JDBC項目 3. JDBC的導入 4. JDBC的使用 內容 JDBC簡介 什麼是JDBC JDBC的全稱是Java資料庫連接(Java Database connect),它是一套用於執行SQL語句的Java API。應用程式可通過這套API連接到關係資料庫,並使 ...
  • 字典用於存儲鍵值對形式的數據。字典是一個有序、可更改的集合,不允許重覆。從 Python 3.7 版本開始,字典是有序的。在 Python 3.6 及更早版本中,字典是無序的。字典用花括弧編寫,具有鍵和值: 示例,創建並列印一個字典: thisdict = { "brand": "Ford", "m ...
  • 1、 安裝 pip install openpyxl 想要在文件中插入圖片文件,需要安裝pillow,安裝文件:PIL-fork-1.1.7.win-amd64-py2.7.exe · font(字體類):字型大小、字體顏色、下劃線等 · fill(填充類):顏色等 · border(邊框類):設置單元 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...