Spring_事務

在mysql階段的文章中,已經介紹過事務了。本篇文章是對mysql事務的總結和對使用Spring框架來實現事務操作的講解。

事務回顧

什么是事務

事務時一組操作的集合,是一個不可分割的操作。

事務會把所有操作作為一個整體,一起向數據庫提交或者撤銷操作請求。所以這組操作要么同時成功,要么同時失敗。

為什么需要事務

我們在程序開發的時候,會有事務的需求。

比如:轉賬操作。

第一步:A:-100元

第二步:B:+100元

事務的操作

事務的操作主要有三步:

1、開啟事務:start transaction(一組操作開啟事務)

2、提交事務:commit(這組操作全部成功,提交事務)

3、回滾事務:rollback(這組操作中間任何一個操作出現異常,回滾事務)

Spring中事務的實現

Spring中的事務實現操作分為兩類:

1、編程式事務(手動寫代碼操作事務)

2、聲明式事務(利用注解自動開啟和提交事務)

假設現在有需求:用戶注冊,注冊時在日志表中插入一條操作記錄。

數據準備:

DROP TABLE
IFEXISTS user_info;
CREATE TABLE user_info (`id` INT NOT NULL AUTO_INCREMENT,`user_name` VARCHAR ( 128 ) NOT NULL,`password` VARCHAR ( 128 ) NOT NULL,`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now() ON UPDATE now(),PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARACTER 
SET = utf8mb4 COMMENT = '日志表';-- 操作日志表
DROP TABLE
IFEXISTS log_info;
CREATE TABLE log_info (`id` INT PRIMARY KEY auto_increment,`user_name` VARCHAR ( 128 ) NOT NULL,`op` VARCHAR ( 256 ) NOT NULL,`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now() ON UPDATE now() 
) DEFAULT charset 'utf8mb4';

代碼準備:

1、創建項目,引入SpringWeb,Mybatis,mysql等依賴

2、配置文件

spring:datasource:url: jdbc:mysql://127.0.0.1:3306/trans_test?characterEncoding=utf8&useSSL=falseusername: rootpassword: 5028driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration: # 配置打印 MyBatis?志log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true #配置駝峰?動轉換

實體類:

Userinfo:

@Data
public class Userinfo {private Integer id;private String userName;private String password;private Date createTime;private Date updateTime;
}

Loginfo:

@Data
public class LogInfo {private Integer id;private String userName;private String op;private Date createTime;private Date updateTime;
}

Mapper:

UserinfoMapper:

@Mapper
public interface UserinfoMapper {@Insert("insert into user_info(user_name,password)values (#{userName},#{password})")Integer insert(String userName,String password);
}

LoginfoMapper:

@Mapper
public interface LoginfoMapper {@Insert("insert into log_info(user_name,op) values (#{userName},#{op})")Integer insertLog(String name,String op);
}

Service:

UserService:

@Service
public class UserService {@Autowiredprivate UserinfoMapper userinfoMapper;public Integer insert(String userName,String password){return userinfoMapper.insert(userName,password);}
}

LogService:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;public Integer login(String userName,String op){return loginfoMapper.insertLog(userName,op);}
}

Controller:

UserController:

@RequestMapping("/user")
@RestController
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/r1")public boolean login(String userName,String password){userService.insert(userName,password);return true;}
}

Spring編程式事務(了解)

Spring手動操作事務有三個操作步驟:

  • 開啟事務
  • 提交事務
  • 回滾事務

SpringBoot內置了兩個對象:

  1. DataSourceTransactionManager 事務管理器,用來開啟、提交或回滾事務
  2. TransactionDefinition是事務的屬性,在獲取事務的時候需要將TransactionDefinition傳遞進去從而獲得一個事務TransactionStatus

下面是代碼實現:

@RequestMapping("/user")
@RestController
public class UserController {@Autowiredprivate UserService userService;//JDBC事務管理器@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;//定義事務屬性@Autowiredprivate TransactionDefinition transactionDefinition;@RequestMapping("/r1")public boolean login(String userName,String password){//開啟事務TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);//用戶注冊userService.insert(userName,password);//提交事務dataSourceTransactionManager.commit(transactionStatus);//回滾事務
//        dataSourceTransactionManager.rollback(transactionStatus);return true;}
}

使用postMan進行測試:

提交事務:

回滾事務:

刷新之后數據庫的數據并沒有增加:

Spring聲明式事務@Transactional

聲明式事務只要在需要事務的方法上添加@Transactional注解就可以實現了。無需手動開啟事務和提交事務,進入方法時自動開啟事務,方法執行完會自動提交事務,如果中途發生了沒有處理的異常會自動回滾事務。

代碼實現:


@RequestMapping("/trans")
@RestController
public class TransController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/r1")public boolean login(String userName,String password){//用戶注冊userService.insert(userName,password);return true;}
}

日志:

刷新數據庫,發現數據插入成功:

修改程序,使它出現異常:

@RequestMapping("/trans")
@RestController
public class TransController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/r1")public boolean login(String userName,String password){//用戶注冊userService.insert(userName,password);//制造異常int a = 10/0;return true;}
}

重新測試:

日志:

刷新數據庫,發現并沒有數據插入:

對比日志:

那如果我們需要讓它發生異常時不發生回滾呢?

此時我們可以使用try-catch將異常捕獲住,代碼修改如下:

@RequestMapping("/trans")
@RestController
public class TransController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/r1")public boolean login(String userName,String password){//用戶注冊userService.insert(userName,password);try {//制造異常int a = 10/0;} catch (Exception e) {e.getMessage();}return true;}
}

重新測試:

日志:

數據庫:

那我們如果在異常捕獲后需要事務進行回滾呢?有以下兩種方式:

1、重新拋出異常

@RequestMapping("/trans")
@RestController
public class TransController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/r1")public boolean login(String userName,String password){//用戶注冊userService.insert(userName,password);try {//制造異常int a = 10/0;} catch (Exception e) {//將異常重新拋出throw e;}return true;}
}

測試:

2、手動回滾事務

使用TransationAspectSupport.currentTransactionStatus()得到當前事務,并使用setRollbackOnly設置setRollbackOnly。

代碼:

@RequestMapping("/trans")
@RestController
public class TransController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/r1")public boolean login(String userName,String password){//用戶注冊userService.insert(userName,password);try {//制造異常int a = 10/0;} catch (Exception e) {//手動回滾TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return true;}
}

測試:

@Transactional詳解

1、rollbackFor

上面我們已經知道了,@Transactional注解會開始事務并且自動提交/回滾事務。

我們將異常類型改為IOException再進行測試:

    @Transactional@RequestMapping("/r2")public boolean login2(String userName,String password) throws IOException {//用戶注冊userService.insert(userName,password);if(true){throw new IOException();}return true;}

測試:

此時我們發現雖然程序已經拋出異常,但是事務仍然提交了:

數據庫也新增了一條數據:

咦?這是為什么呢?不是拋出異常后,事務就應該自動回滾嗎?

這是因為事務回滾的默認是遇到運行時異常進行回滾,我們上面的算數異常就屬于運行時異常的子類。因此,能夠正常進行回滾。

如何解決呢?

通過@Transactional中的rollbackfor屬性進行解決:

    @Transactional(rollbackFor = Exception.class)@RequestMapping("/r2")public boolean login2(String userName,String password) throws IOException {//用戶注冊userService.insert(userName,password);if(true){throw new IOException();}return true;}

測試:

可以看到事務發生了回滾:

數據庫:

對上面內容的總結:

事務隔離級別

回顧Mysql事務隔離級別

1、讀未提交:讀未提交,也叫未提交讀。該隔離級別的事務可以看到其他事務中未提交的事務。

因為其他事務未提交的數據可能會發生回滾,但是該隔離級別卻可以讀到,我們把該級別讀到的數據稱為臟數據,這個問題稱之為臟讀

臟讀問題:

2、讀提交:讀已提交,也叫提交讀,該隔離級別的事務能讀取到已提交事務的數據。

該隔離級別不會有臟讀問題,但由于在事務執行中可以讀取到其他事務提交的結果,所以在不同的時間的相同sql查詢可能會得到不同的結果,這種現象叫做不可重復讀(前后多次讀取,數據內容不一致)

不可重復讀:

3、可重復讀(mysql默認的隔離級別):事務不會讀到其他事務對已有數據的修改,即使其他事務已經提交,也可以確保同一事務多次查詢結果一致,但是其他事務新插入的數據,是可以感知到的,這也就引發了幻讀問題。

此隔離級別事務執行時,另一個事務成功插入了某條數據,但因為它每次查詢的結果都是一樣的(修改能查詢到是因為它涉及到了表中的所有數據行),所以會導致查詢不到這條數據,這個現象稱為幻讀(前后多次讀取,數據總量不同)

幻讀:

4、串行化:序列化,事務最高隔離級別。它會強制事務排序,使之不會發生沖突,從而解決了臟讀,不可重復讀和幻讀問題,但因為執行效率第,所以真正使用的場景并不多。

Spring事務隔離級別

Spring中的事務隔離級別有5種:

1、Isolation.DEFAULT:以連接的數據庫的事務隔離級別為主。

2、Isolation.READ_UNCOMMITTED:讀未提交,對應SQL標準的READ UNCOMMITTED。

3、Isolation.READ_COMITTED:讀已提交,對應SQL標準中的READ COMMITTED。

4、Isolation.REPEATABLE_READ:可重復讀,對應SQL標準中REPEATABLE READ。

5、Isolation.SERIALIZABLE:串行化,對應SQL標準中的SERIALIZABLE。

我們可以通過@Transactional中的islation屬性設置事務隔離級別:

//設置為讀已提交@Transactional(isolation = Isolation.READ_COMMITTED)@RequestMapping("/r3")public boolean login3(String userName,String password) throws IOException {//用戶注冊userService.insert(userName,password);if(true){throw new IOException();}return true;}

Spring事務傳播機制

事務傳播機制:是多個事務方法存在調用關系時,事務時如何在這些方法間進行傳播的。

比如:Controller中的方法A調用Service中的方法B,它們都是被@Transactional修飾。A方法運行時,會開啟事務,當A調用B時,B方法本身也有事務,此時方法B運行時,是加入A的事務還是在創建一個新的事務呢?

這就涉及到了事務的傳播機制。

打個比方,公司的流程管理:

執行任務之前需要先寫執行文檔,任務執行結束,再寫總結匯報。

此時A部門有一項工作是和B部門一起干的,此時B部門是直接使用A部門的文檔,還是新建一個文檔呢?

事務隔離級別解決的是多個事務同時調用一個數據庫的問題:

而事務傳播機制解決的是一個事務再多個方法中傳遞的問題

事務傳播級別有哪些

@Transactional注解支持事務傳播機制的設置,通過propagation屬性來指定傳播行為。

Spring事務傳播機制有以下七種:

1、Propagtion.REQUIRED:默認的事務傳播級別。如果當前存在事務,則加入該事務。如果沒有事務,則創建一個新的事務。

2、Propagtion.SUPPORTS:如果當前存在事務,則加入該事務。如果當前沒有事務,則以非事務的方式繼續運行。

3、Propagtion.MANDATORY:強制性。如果當前存在事務,則加入該事務。如果當前沒有事務,則拋出異常

4、Propagation.REQUIRES_NEW:創建一個新的事務,如果當前存在事務,則把當前事務掛起,也就是說不管外部方法是否開啟事務,Propagation.REQUIRES_NEW修飾的內部方法都會開啟新的事務且開啟的事務相互獨立,互不干擾。

5、Propagtion.NOT_SUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起(不使用)。

6、Propagtion.NEVER:以非事務方式運行,如果當前存在事務,則拋出異常。

7、Propagtion.NESTED:如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行。如果當前沒有事務則該取值等價于Propagtion.REQUIRED。

舉例記憶:

事務傳播機制場景演示

此時,用戶注冊不僅要在用戶表中添加數據,在日志表中也需要進行登記。

REQUIRE

Controller:


@RequestMapping("/user2")
@RestController
public class UserController2 {@Autowiredprivate UserService userService;@Autowiredprivate LogService logService;@Transactional@RequestMapping("/register")public boolean register(String userName,String password){/*** 用戶表和注冊表的插入理應再Service完成* 此處為了方便,直接在Controller中完成*/if(!StringUtils.hasLength(userName)||!StringUtils.hasLength(password)){return false;}Integer result = userService.insert(userName,password);System.out.println("result:"+result);//插入日志表Integer insert = logService.insert(userName, "用戶注冊");System.out.println("insert:"+insert);return true;}
}

Service:

LogService:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.REQUIRED)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);return result;}
}

UserSerVice:

@Service
public class UserService {@Autowiredprivate UserInfoMapper mapper;@Transactional(propagation = Propagation.REQUIRED)public Integer insert(String userName, String password) {return mapper.insert(userName,password);}
}

mapper:

UserinfoMapper:


@Mapper
public interface UserInfoMapper {@Insert("insert into user_info (user_name,password) values (#{userName},#{password})")Integer insert(String userName,String password);}

LoginfoMapper:

@Mapper
public interface LoginfoMapper {@Insert("insert into log_info(user_name,op) values (#{userName},#{op}) ")Integer insertLog(String userName,String op);
}

我們嘗試在其中的一個Service中制造異常:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.REQUIRED)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);//制造異常int a = 10/0;return result;}
}

測試:

從日志上可以看出,事務發生了回滾:

總結:

REQUIRE_NEW

修改Service代碼即可:

UserService:

@Service
public class UserService {@Autowiredprivate UserInfoMapper mapper;@Transactional(propagation = Propagation.REQUIRES_NEW)public Integer insert(String userName, String password) {return mapper.insert(userName,password);}
}

LogService:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.REQUIRES_NEW)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);//制造異常int a = 10/0;return result;}
}

再次進行測試,通過日志可以看到,日志表的事務發生了回滾,而用戶表的事務提交了:

總結:

NEVER

UserService:

@Service
public class UserService {@Autowiredprivate UserInfoMapper mapper;@Transactional(propagation = Propagation.NEVER)public Integer insert(String userName, String password) {return mapper.insert(userName,password);}
}

LogService:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.NEVER)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);return result;}
}

這里我們不制造異常,但是讓Controller存在事務進行測試:

可以看到仍然報了500:

NESTED

UserService:

@Service
public class UserService {@Autowiredprivate UserInfoMapper mapper;@Transactional(propagation = Propagation.NESTED)public Integer insert(String userName, String password) {return mapper.insert(userName,password);}
}

LogService:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.NESTED)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);return result;}
}

測試沒有異常的情況:

事務得到了提交:

測試有異常發生的情況:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.NESTED)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);int a = 10/0;return result;}
}

事務回滾:

看起來NESTED傳播機制好像跟REQUIRE機制沒什么區別:但實際上NESTED可以實現部分回滾,使得其他事務能夠被提交。

部分回滾:


@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.NESTED)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);try {int a = 10/0;} catch (Exception e) {//部分回滾TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return result;}
}

重新測試:

事務得到提交:

總結:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/92153.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/92153.shtml
英文地址,請注明出處:http://en.pswp.cn/web/92153.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

事務管理介紹

為什么要用事務管理在我們同時操作兩個或更多個數據庫時,可能因為網絡等各方面原因導致中間出現異常。造成像對第一個數據庫的操作成功了,但是對第二個數據庫的操作沒有成功。這樣數據的完整性就被破壞了。事務:是一組操作的集合,…

Android 之 ViewBinding 實現更安全、高效的視圖綁定

??一、配置說明????作用位置??需在模塊級 build.gradle或 build.gradle.kts文件的 android {}塊內添加:android {buildFeatures {viewBinding true // Kotlin DSL 語法} }android {buildFeatures {viewBinding true // Groovy 語法} }??生成規則??為每…

全球首款Java專用AI開發助手實測:一句話生成完整工程代碼——飛算 JavaAI

🌟 嗨,我是Lethehong!🌟🌍 立志在堅不欲說,成功在久不在速🌍🚀 歡迎關注:👍點贊??留言收藏🚀🍀歡迎使用:小智初學計算機…

Shader開發(七)創建第一個Shader項目

在前面的章節中,我們已經了解了Shader的基本概念和渲染管線的工作原理。現在,是時候動手實踐了!本章將帶您一步步創建第一個Shader項目,開啟真正的Shader開發之旅。 為什么選擇openFrameworks? 與其他文章不同&#x…

IAR軟件中測量函數執行時間

通常在調試代碼中需要直到某個函數或者某段代碼的實際執行時間,在IAR中可以直接借助軟件提供的工具來計算代碼執行時間。 第一種方法 進入仿真調試界面,在需要測量的代碼前面打斷點。工具欄中選擇 ST-LINK — Data Log Summary在 Data Log Summary 窗口中…

Java 字節碼文件(.class)的組成詳解

文章目錄基礎信息常量池字段方法屬性字節碼文件內容說明案例文件基本信息類的基本信息常量池字段信息構造方法實例方法主方法源文件信息字節碼文件由五部分組成,分別是基礎信息、常量池、字段、方法、屬性。案例: public class Main implements Interfa…

C++之vector類的代碼及其邏輯詳解 (下)

1. insert()這個就是在指定位置插入一個元素,首先計算要插入的這個位置和開頭之間的距離,接著判斷那個_finish 有沒有碰到_endofstorage 或者_endofstorage 是不是為0,如果滿足條件,那就進行擴容,然后接著重新計算距離…

【自動化測試】Python Selenium 自動化測試元素定位專業教程

1. 引言:元素定位在 Selenium 中的核心地位 元素定位是 Selenium 自動化測試的基礎,所有用戶交互操作(如點擊、輸入、選擇)都依賴于準確識別頁面元素。Selenium WebDriver 提供了多種定位策略,從簡單的 ID 定位到復雜…

通用代碼自用

多文件上傳public int save(Role role, RequestParam("nfile") MultipartFile nfile, HttpServletRequest request) {System.out.println(nfile.getOriginalFilename());String path request.getSession().getServletContext().getRealPath("/upload");Fi…

生成式AI如何顛覆我們的工作和生活

原問題: ?你覺得生成式AI未來會如何改變普通人的工作和生活?? 做過一個對比國外和國內工業化產品制造的簡單調研,類似一款定制化的臺燈或者語音音響,從零到原型實物, 美國至少需要20萬美刀,國內成本大概…

K8S、Docker安全漏洞靶場

1 介紹 一個脆弱基礎設施自動化構建框架,主要用于快速、自動化搭建從簡單到復雜的脆弱云原生靶機環境。 1.1 項目的緣起 在研究漏洞時,我們經常會發現“環境搭建”這一步驟本身就會占用大量的時間,與之相比,真正測試PoC、ExP的時間可能非常短。由于許多官方鏡像在國內的…

使用Nginx部署前后端分離項目

使用Nginx部署前后端分離項目:用戶中心系統實踐指南 部署前的關鍵準備 在正式部署前,務必確保前后端在生產環境能正常運行: 前端:測試所有API請求路徑和生產環境配置后端:驗證數據庫連接、環境變量和外部服務集成完整流…

當前就業形勢下,軟件測試工程師職業發展與自我提升的必要性

軟件測試行業正處于深刻變革期,2025年的市場已超越400億美元規模,預計2027年將增長7% 。在這個技術驅動、效率至上的時代,測試工程師若想保持競爭力,必須主動擁抱變革,系統性提升技能。通過深入分析行業現狀與人才需求…

java 之 繼承

一、繼承 1.1 、什么是繼承? 繼承就是把所有的類的公共部分(相同的成員)提取出來,放到一個類中繼承需要使用 extends 關鍵字 public class Animal{ public String name; } public class Dog extends Animal{}Dog 是 An…

強化應急通信生命線:遨游三防平板、衛星電話破局極端災害救援

暴雨傾盆,山洪咆哮,城市陷入內澇。今年進入汛期以來,我國廣東、福建、河南、陜西、京津冀等地相繼遭遇暴雨、洪澇、山洪等災害,道路損毀、基站斷網、電力中斷等次生問題為應急響應帶來嚴峻挑戰。如何保障極端場景下的通信暢通&…

【Linux系統】進程間通信:命名管道

1. 匿名管道的限制匿名管道存在以下核心限制:僅限親緣關系進程:只能用于父子進程等有血緣關系的進程間通信(如通過 fork() 創建的子進程)。單向通信:數據只能單向流動(一端寫,另一端讀&#xff…

Python Day24 多線程編程:核心機制、同步方法與實踐案例

一、線程事件對象(threading.Event)threading.Event 用于實現線程間的通信,可讓一個線程通知其他線程終止任務,核心是通過 “事件觸發” 機制協調線程行為。核心方法:創建事件對象:event threading.Event(…

007 前端( JavaScript HTML DOM+Echarts)

一.html dom運用查找html元素的三種方式通過 id 找到 HTML 元素通過標簽名找到 HTML 元素通過類名找到 HTML 元素1.通過 id 找到 HTML 元素<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>msf的網頁</title> <…

實習文檔背誦

實習內容:1.定時任務與數據補全:基于 XXL-JOB 實現分布式定時任務調度&#xff0c;補全近半年歷史操作日志數據&#xff0c;有效解決因網絡異常導致的數據缺失問題。業務場景&#xff1b;集團的4a日志半年內沒有同步&#xff0c;這邊需要把日志數據同步到集團上首先先評估每天的…

分布式CAP定理

CAP 定理在一個分布式系統中&#xff0c;以下三個特性不可能同時完全滿足&#xff0c;最多只能滿足其中兩個&#xff1a;C&#xff08;Consistency&#xff0c;一致性&#xff09;&#xff1a;所有節點在同一時間看到的數據是完全一致的&#xff08;即更新操作成功并返回后&…