學習筆記-Spring事務

来源:https://www.cnblogs.com/Andl-Liu/archive/2023/05/23/17422805.html
-Advertisement-
Play Games

學習的文章 [小姐姐非要問我:spring編程式事務是啥? (qq.com)](https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648936779&idx=2&sn=a6255c7d436a62af380dfa6b326fd4e7&chk ...


學習的文章

小姐姐非要問我:spring編程式事務是啥? (qq.com)

一文搞懂什麼是事務 - 知乎 (zhihu.com)

阿裡3面:Spring聲明式事務連環炮,讓我措手不及。。 (qq.com)

帶你讀懂Spring 事務——事務的傳播機制 - 知乎 (zhihu.com)

spring 事務失效的 12 種場景_事務什麼時候失效_hanjq_code的博客-CSDN博客

什麼是事務

  • 事務是併發操作的單位

  • 是用戶定義的操作序列

  • 事務如果成功,則會提交

  • 事務如果失敗,則會回滾

事務的四大特性

  • 原子性

    • 事務中操作,要麼不做,要麼都做
  • 持久性

    • 一個事務一旦提交,它對資料庫的改變是永久的
  • 一致性

    • 事務讓資料庫從一個一致性狀態轉移到另一個一致性狀態

    • 比如

      • 事務前,A有50,B有50,總共有100

      • 事務中,A送給B20

      • 事務後,A有30,B有70,總共還是有100

  • 隔離性

    • 一個事務的執行不能被其他事務所干擾

    • 分成不同的等級

事務併發訪問導致的數據問題

臟讀

讀到了修改但還沒有提交的數據

  • A事務修改了某條記錄的欄位c,但還沒有提交

  • B事務在此時讀取了欄位c

  • A事務發生了回滾,欄位c恢復了修改前的狀態

  • 但B事務持有的還是修改後的狀態

不可重覆讀

某個事務在執行過程中,兩次讀同一個條記錄,但結果不一樣

  • A事務讀取了記錄c,值為10

  • B事務修改了記錄c,值為0

  • A事務再次讀取記錄c,值為0

幻讀

某個事務在執行過程中,前後兩次讀取記錄,數據總量不一樣

  • A事務統計了表c中的記錄總數,結果為10

  • B事務刪除了表c中的5條記錄

  • A事務再次統計了表c中的記錄總數,結果為5

和不可重覆讀的區別在於,幻讀針對的是記錄條數,不可重覆讀針對的是記錄內容

事務的隔離級別

針對事務併發訪問時出現的問題,設置了四種事務的隔離級別

讀未提交

可以的讀取還沒有提交的數據

沒有限制,三種問題都有可能發生

讀已提交

只能讀取已經提交了的數據

不會發生臟讀,但是會發生不可重覆讀和幻讀

可重覆讀

一個事務前後讀取的同一條記錄的結果必須一致

不會發生臟讀和不可重覆讀,但會發生幻讀

mysql中預設的事務隔離級別

串列化

所有的事務必須依次執行

不會發生臟讀、不可重讀讀和幻讀

效率比較低

Spring事務的使用方法

Spring分為兩種控制事務的方法

  • 編程式事務

    • 方法1:通過PlatformTransactionManager控制事務

    • 方法2:通過TransactionTemplate控制事務

  • 聲明式事務

    • 常用

編程式事務的使用

使用PlatformTransactionManager

@Test
public void test1() throws Exception {
    //定義一個數據源
    org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
    dataSource.setUsername("root");
    dataSource.setPassword("root123");
    dataSource.setInitialSize(5);
    //定義一個JdbcTemplate,用來方便執行資料庫增刪改查
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    //1.定義事務管理器,給其指定一個數據源(可以把事務管理器想象為一個人,這個人來負責事務的控制操作)
    PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
    //2.定義事務屬性:TransactionDefinition,TransactionDefinition可以用來配置事務的屬性信息,比如事務隔離級別、事務超時時間、事務傳播方式、是否是只讀事務等等。
    TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
    //3.開啟事務:調用platformTransactionManager.getTransaction開啟事務操作,得到事務狀態(TransactionStatus)對象
    TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
    //4.執行業務操作,下麵就執行2個插入操作
    try {
        System.out.println("before:" + jdbcTemplate.queryForList("SELECT * from t_user"));
        jdbcTemplate.update("insert into t_user (name) values (?)", "test1-1");
        jdbcTemplate.update("insert into t_user (name) values (?)", "test1-2");
        //5.提交事務:platformTransactionManager.commit
        platformTransactionManager.commit(transactionStatus);
    } catch (Exception e) {
        //6.回滾事務:platformTransactionManager.rollback
        platformTransactionManager.rollback(transactionStatus);
    }
    System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}

使用TransactionTemplate

@Test
public void test1() throws Exception {
    //定義一個數據源
    org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
    dataSource.setUsername("root");
    dataSource.setPassword("root123");
    dataSource.setInitialSize(5);
    //定義一個JdbcTemplate,用來方便執行資料庫增刪改查
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    //1.定義事務管理器,給其指定一個數據源(可以把事務管理器想象為一個人,這個人來負責事務的控制操作)
    PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
    //2.定義事務屬性:TransactionDefinition,TransactionDefinition可以用來配置事務的屬性信息,比如事務隔離級別、事務超時時間、事務傳播方式、是否是只讀事務等等。
    DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
    transactionDefinition.setTimeout(10);//如:設置超時時間10s
    //3.創建TransactionTemplate對象
    TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager, transactionDefinition);
    /**
     * 4.通過TransactionTemplate提供的方法執行業務操作
     * 主要有2個方法:
     * (1).executeWithoutResult(Consumer<TransactionStatus> action):沒有返回值的,需傳遞一個Consumer對象,在accept方法中做業務操作
     * (2).<T> T execute(TransactionCallback<T> action):有返回值的,需要傳遞一個TransactionCallback對象,在doInTransaction方法中做業務操作
     * 調用execute方法或者executeWithoutResult方法執行完畢之後,事務管理器會自動提交事務或者回滾事務。
     * 那麼什麼時候事務會回滾,有2種方式:
     * (1)transactionStatus.setRollbackOnly();將事務狀態標註為回滾狀態
     * (2)execute方法或者executeWithoutResult方法內部拋出異常
     * 什麼時候事務會提交?
     * 方法沒有異常 && 未調用過transactionStatus.setRollbackOnly();
     */
    transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {
        @Override
        public void accept(TransactionStatus transactionStatus) {
            jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-1");
            jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-2");

        }
    });
    System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}

聲明式事務的使用

  • 在配置類上使用@EnableTransactionManagement

    • Springboot可以加在啟動類上
  • 定義事務管理器

    • @Bean
      public PlatformTransactionManager transactionManager(DataSource dataSource) {
          return new DataSourceTransactionManager(dataSource);
      }
      
    • springboot中有預設的事務管理器

  • 在需要事務的目標上加上@Transaction註解

    • 作用位置

      • 註意:@Transaction只對public方法有效

      • @Transacion放在介面上,介面的所有實現類中所有的public方法都自動加上事務

      • @Transaction放在類上,當前類以及其下無限級子類中的public方法都被加上事務

      • @Transaction放在public方法上,方法被加上事務

    • 屬性

      • "transactionManager"或"value"

        • 指定事務管理器的bean對象

        • 為空的話,預設按類型獲取

      • “propagation”

        • 指定事務的傳播類型

        • 預設為REQUIRED

      • “rollbackFor”

        • 自定義回滾異常

事務的傳播類型

  • REQUIRED

    • 如果當前有事務,則加入當前事務

    • 如果當前沒有事務,則自己新建一個事務

  • SUPPORTS

    • 如果當前有事務,則加入當前事務

    • 如果當前沒有事務,則以非事務方式執行

  • MANDATORY

    • 如果當前有事務,則加入當前事務

    • 如果當前沒有事務,則拋出異常

  • REQUIERES_NEW

    • 如果當前有事務,則將該事務掛起,另外新創建一個事務

    • 如果當前沒有事務,則新創建一個事務

  • NOT_SUPPORTED

    • 如果當前有事務,則將該事務掛起,以非事務方式執行

    • 如果當前沒有事務,則以非事務方式執行

  • NEVER

    • 如果當前有事務,則拋出異常

    • 如果當前沒有事務,以非事務方式執行

  • NESTED

    • 如果當前有事務,則在當前事務中嵌套一個事務執行

    • 如果當前沒有事務,則新建一個事務執行

事務失效或回滾異常的12種情況

事務失效

  • 訪問許可權問題

    • 事務只能對public的方法方法生效
  • 方法用final或static修飾

    • spring事務是使用動態代理的方式實現的

    • 如果加了final或public方法,則方法無法被代理

  • 方法內部調用

    • @Service
      public class UserService {
       
          @Autowired
          private UserMapper userMapper;
       
        
          public void add(UserModel userModel) {
              userMapper.insertUser(userModel);
              updateStatus(userModel);
          }
       
          @Transactional
          public void updateStatus(UserModel userModel) {
              doSameThing();
          }
      }
      
    • 在add方法中是通過this來調用updateStatus方法的,沒有通過代理

    • 解決方法:

      • 1.註入自己

        • @Servcie
          public class ServiceA {
             @Autowired
             prvate ServiceA serviceA;
           
             public void save(User user) {
                   queryData1();
                   queryData2();
                   serviceA.doSave(user);
             }
           
             @Transactional(rollbackFor=Exception.class)
             public void doSave(User user) {
                 addData1();
                 updateData2();
              }
           }
          
      • 2.通過AopContext.currentProxy()獲取代理對象

        • @Servcie
          public class ServiceA {
           
             public void save(User user) {
                   queryData1();
                   queryData2();
                   ((ServiceA)AopContext.currentProxy()).doSave(user);
             }
           
             @Transactional(rollbackFor=Exception.class)
             public void doSave(User user) {
                 addData1();
                 updateData2();
              }
           }
          
  • 未被spring管理

    • 忘了給類添加註釋了

    • 沒有被放到IOC容器中

  • 多線程調用

    • spring事務是通過資料庫連接來實現的

    • 在多線程中,每一個線程用的都是不同的資料庫連接

  • 表不支持事務

    • MyISAM引擎的表不支持事務
  • 未開啟事務

    • springboot需要在啟動類上加上@EnableTransactionManagement的註解

    • 傳統spring項目需要在applicationContext.xml中進行相關的配置

事務回滾異常

  • 使用了錯誤的傳播特性

  • 手動捕獲了異常

    • 如果想要spring事務能夠正常回滾,必須拋出它能夠處理的異常

    • 使用try/catch將異常捕獲,將導致事務不會回滾

  • 拋得異常類型不正確

    • spring事務,預設情況下只會回滾RuntimeException或Error
  • 自定義回滾異常不匹配

    • 比如定義了BusinessException,但拋出的是SqlException
  • 嵌套事務回滾範圍多了

    • public class UserService {
       
          @Autowired
          private UserMapper userMapper;
       
          @Autowired
          private RoleService roleService;
       
          @Transactional
          public void add(UserModel userModel) throws Exception {
              userMapper.insertUser(userModel);
              roleService.doOtherThing();
          }
      }
       
      @Service
      public class RoleService {
       
          @Transactional(propagation = Propagation.NESTED)
          public void doOtherThing() {
              System.out.println("保存role表數據");
          }
      }
      
    • roleService.doOtherThing()如果發生回滾,它拋出的異常沒有被處理,會繼續往上級拋

    • add()在捕獲到向上拋的異常後也會發生回滾

    • 應該手動進行捕獲

      • @Slf4j
        @Service
        public class UserService {
         
            @Autowired
            private UserMapper userMapper;
         
            @Autowired
            private RoleService roleService;
         
            @Transactional
            public void add(UserModel userModel) throws Exception {
         
                userMapper.insertUser(userModel);
                try {
                    roleService.doOtherThing();
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
        

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

-Advertisement-
Play Games
更多相關文章
  • 本文使用的是巴法雲 你也可以使用其他的物聯網平臺 並且 也不一定是小愛 比如小度啊 等等其他的一下應該也是可以實現的 調到java裡面之後 剩下的事情大家就可以想幹嘛就幹嘛了 ...
  • ## 一、安裝laradock ### 1. 如果有laravel項目並使用git,可以用git submodule將laradock克隆到laravel根目錄,方便後續管理 ```git submodule add https://github.com/laradock/laradock.git` ...
  • #### 錯誤: 找不到或無法載入主類 jar ##### 問題描述: 在使用springboot框架對項目打包後,手動使用命令java -jar 包名啟動jar包,報錯:錯誤: 找不到或無法載入主類 jar。 網上找了各辦法,都是加maven插件,打成可執行jar包 ``` org.springf ...
  • [TOC](Nett的概念及體繫結構) # 第一章 Java網路編程 最早期的 Java API(java.net)只支持由本地系統套接字型檔提供的所謂的阻塞函數,像下麵的那樣 ```java //創建一個新的 ServerSocket,用以監聽指定埠上的連接請求 ServerSocket serv ...
  • 在筆者上一篇文章`《驅動開發:內核MDL讀寫進程記憶體》`簡單介紹瞭如何通過MDL映射的方式實現進程讀寫操作,本章將通過如上案例實現遠程進程反彙編功能,此類功能也是ARK工具中最常見的功能之一,通常此類功能的實現分為兩部分,內核部分只負責讀寫位元組集,應用層部分則配合反彙編引擎對位元組集進行解碼,此處我們... ...
  • ## Spring Boot 3.1 正式發佈 大家好,我是R哥。 上一篇:[Spring Boot 3.0 正式發佈,王炸!!](https://mp.weixin.qq.com/s/p-rDuyNv68hQvwRBrm5KWA) Spring Boot 3.0 發佈半年左右,Spring Boo ...
  • 有時間,我們在搭建微服務時,總希望拿一個比較單純的,沒有污染其它代碼的項目來從頭開始做,今天我們來建設一個最簡單的,gateway項目,它被註冊到nacos里,路由配置也存到nacos里,動態實現更新配置功能。 # 依賴配置 > 版本:com.alibaba.cloud:spring-cloud-s ...
  • 摘要:常用於消除雜訊的圖像平滑方法包括三種線性濾波(均值濾波、方框濾波、高斯濾波)和兩種非線性濾波(中值濾波、雙邊濾波),本文將詳細講解三種線性濾波方法。 本文分享自華為雲社區《[Python從零到壹] 五十五.圖像增強及運算篇之圖像平滑(均值濾波、方框濾波、高斯濾波)》,作者:eastmount。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...