學習筆記-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
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...