Spring事務(wù)和事務(wù)傳播機(jī)制操作大全
一、事務(wù)
1.1、什么是事務(wù)
事務(wù)是一組操作的集合, 是一個(gè)不可分割的操作.
事務(wù)會(huì)把所有的操作作為一個(gè)整體, 一起向數(shù)據(jù)庫提交或者是撤銷操作請求. 所以這組操作要么同時(shí)成功,要么同時(shí)失敗。
1.2、為什么需要事務(wù)
我們在進(jìn)行程序開發(fā)時(shí), 也會(huì)有事務(wù)的需求.
比如轉(zhuǎn)賬操作: 第一步:A 賬戶 -100 元. 第二步:B 賬戶 +100 元. 如果沒有事務(wù),第?步執(zhí)行成功了, 第?步執(zhí)行失敗了, 那么A 賬戶的100 元就平白無故消失了. 如果使 用事務(wù)就可以解決這個(gè)問題, 讓這?組操作要么?起成功, 要么一起失敗.
比如秒殺系統(tǒng),
第一步: 下單成功
第二步: 扣減庫存
下單成功后, 庫存也需要同步減少. 如果下單成功, 庫存扣減失敗, 那么就會(huì)造成下單超出的情況. 所以就需要把這兩步操作放在同一個(gè)事務(wù)中. 要么一起成功, 要么一起失敗。
1.3、事務(wù)的操作
事務(wù)的操作主要有三步:
1. 開啟事start transaction/ begin (?組操作前開啟事務(wù))
2. 提交事務(wù): commit (這組操作全部成功, 提交事務(wù))
3. 回滾事務(wù): rollback (這組操作中間任何?個(gè)操作出現(xiàn)異常, 回滾事務(wù))
-- 開啟事務(wù) start transaction; -- 提交事務(wù) commit; -- 回滾事務(wù) rollback;
二、Spring中事務(wù)的實(shí)現(xiàn)
Spring 中的事務(wù)操作分為兩類:
- 編程式事務(wù)(手動(dòng)寫代碼操作事務(wù)).
- 聲明式事務(wù)(利用注解自動(dòng)開啟和提交事務(wù)).
在了解事務(wù)之前,我們先準(zhǔn)備數(shù)據(jù)和數(shù)據(jù)的訪問代碼:
需求: 用戶注冊, 注冊時(shí)在日志表中插入?條操作記錄:
數(shù)據(jù)準(zhǔn)備:
-- 創(chuàng)建數(shù)據(jù)庫 DROP DATABASE IF EXISTS trans_test; CREATE DATABASE trans_test DEFAULT CHARACTER SET utf8mb4; -- ??表 DROP TABLE IF EXISTS 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 IF EXISTS 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';
代碼準(zhǔn)備:
1.創(chuàng)建項(xiàng)目 spring-trans, 引?Spring Web, Mybatis, mysql等依賴
2 . 配置文件:
spring: datasource: url: jdbc:mysql://127.0.0.1:3306/trans_testcharacterEncoding=utf8&useSSL=false username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver mybatis: configuration: # 配置打印 MyBatis?志 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true #配置駝峰?動(dòng)轉(zhuǎn)換
3.實(shí)體類
@Data
public class UserInfo {
private Integer id;
private String userName;
private String password;
private Date createTime;
private Date updateTime;
}
@Data
public class LogInfo {
private Integer id;
private String userName;
private String op;
private Date createTime;
private Date updateTime;
}
4.Mapper
@Mapper
public interface UserInfoMapper {
@Insert("insert into user_info(`user_name`,`password`)values(#{name},#
{password})")
Integer insert(String name, String password);
}
@Mapper
public interface LogInfoMapper {
@Insert("insert into log_info(`user_name`,`op`)values(#{name},#{op})")
Integer insertLog(String name, String op);
}5.Service
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
public void registryUser(String name, String password) {
//插???信息
userInfoMapper.insert(name, password);
}
}
@Slf4j
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
public void insertLog(String name, String op) {
//記錄??操作
logInfoMapper.insertLog(name, "?戶注冊");
}
}6.Controller
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/registry")
public String registry(String name, String password) {
//??注冊
userService.registryUser(name, password);
return "注冊成功";
}
}2.1、Spring編程式事務(wù)
Spring 手動(dòng)操作事務(wù)和上面 MySQL 操作事務(wù)類似, 有 3 個(gè)重要操作步驟:
• 開啟事務(wù)(獲取事務(wù))
• 提交事務(wù)
• 回滾事務(wù)
SpringBoot 內(nèi)置了兩個(gè)對象:
- DataSourceTransactionManager 事務(wù)管理器. 用來獲取事務(wù)(開啟事務(wù)), 提交或回滾事務(wù)的
- TransactionDefinition 是事務(wù)的屬性, 在獲取事務(wù)的時(shí)候需要將TransactionDefinition 傳遞進(jìn)去從?獲得?個(gè)事務(wù) TransactionStatus。
我們還是根據(jù)代碼的實(shí)現(xiàn)來學(xué)習(xí):
@RequestMapping("/user")
@RestController
public class UserController {
// JDBC 事務(wù)管理器
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
// 定義事務(wù)屬性
@Autowired
private TransactionDefinition transactionDefinition;
@Autowired
private UserService userService;
@RequestMapping("/registry")
public String registry(String name, String password) {
// 開啟事務(wù)
TransactionStatus transactionStatus = dataSourceTransactionManager
.getTransaction(transactionDefinition);
//??注冊
userService.registryUser(name, password);
//提交事務(wù)
dataSourceTransactionManager.commit(transactionStatus);
//回滾事務(wù)
//dataSourceTransactionManager.rollback(transactionStatus);
return "注冊成功";
}
}運(yùn)?程序之后,用postman測試接口,觀看數(shù)據(jù)庫結(jié)果數(shù)據(jù)插入成功。

觀察事務(wù)回滾:
//回滾事務(wù) dataSourceTransactionManager.rollback(transactionStatus);
觀察數(shù)據(jù)庫, 雖然程序返回"注冊成功", 但數(shù)據(jù)庫并沒有新增數(shù)據(jù).
以上代碼雖然可以實(shí)現(xiàn)事務(wù), 但操作也很繁瑣, 有沒有更簡單的實(shí)現(xiàn)?法呢? 接下來我們學(xué)習(xí)聲明式事務(wù)。
2.2、Spring 聲明式事務(wù) @Transactional
聲明式事務(wù)的實(shí)現(xiàn)很簡單, 只需要在需要事務(wù)的放法上添加 @Transactional 注解就可以實(shí)現(xiàn)了. 無需手動(dòng)開啟事務(wù)和提交事務(wù), 進(jìn)入方法時(shí)自動(dòng)開啟事務(wù), 方法執(zhí)行完會(huì)自動(dòng)提交事務(wù), 如果中途發(fā)生了 沒有處理的異常會(huì)自動(dòng)回滾事務(wù).
我們來看代碼實(shí)現(xiàn):
@RequestMapping("/trans")
@RestController
public class TransactionalController {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/registry")
public String registry(String name, String password) {
//??注冊
userService.registryUser(name, password);
return "注冊成功";
}
}運(yùn)?程序, 發(fā)現(xiàn)數(shù)據(jù)插?成功.
修改程序, 使之出現(xiàn)異常
@Slf4j
@RequestMapping("/trans")
@RestController
public class TransactionalController {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/registry")
public String registry(String name, String password) {
//??注冊
userService.registryUser(name, password);
log.info("??數(shù)據(jù)插?成功");
//強(qiáng)制程序拋出異常
int a = 10 / 0;
return "注冊成功";
}
}運(yùn)行程序: 發(fā)現(xiàn)雖然日志顯示數(shù)據(jù)插?成功, 但數(shù)據(jù)庫卻沒有新增數(shù)據(jù), 事務(wù)進(jìn)行了回滾.

@Transactional 作?
@Transactional 可以用來修飾方法或類:
• 修飾方法時(shí): 只有修飾public方法時(shí)才生效(修飾其他方法時(shí)不會(huì)報(bào)錯(cuò), 也不生效)
• 修飾類時(shí): 對 @Transactional 修飾的類中所有的 public 方法都生效
方法/類被 @Transactional 注解修飾時(shí), 在目標(biāo)方法執(zhí)行開始之前, 會(huì)?動(dòng)開啟事務(wù), ?法執(zhí)行結(jié)束 之后, 自動(dòng)提交事務(wù).
如果在方法執(zhí)行過程中, 出現(xiàn)異常, 且異常未被捕獲, 就進(jìn)行事務(wù)回滾操作.
如果異常被程序捕獲, 方法就被認(rèn)為是成功執(zhí)行, 依然會(huì)提交事務(wù).
修改上述代碼, 對異常進(jìn)?捕獲:
@Transactional
@RequestMapping("/registry")
public String registry(String name, String password) {
//??注冊
userService.registryUser(name, password);
log.info("??數(shù)據(jù)插?成功");
//對異常進(jìn)?捕獲
try {
//強(qiáng)制程序拋出異常
int a = 10 / 0;
} catch (Exception e) {
e.printStackTrace();
}
return "注冊成功";
}運(yùn)?程序, 發(fā)現(xiàn)雖然程序出錯(cuò)了, 但是由于異常被捕獲了, 所以事務(wù)依然得到了提交. 如果需要事務(wù)進(jìn)?回滾, 有以下兩種方式:
- 重新拋出異常
@Transactional
@RequestMapping("/registry")
public String registry(String name, String password) {
//??注冊
userService.registryUser(name, password);
log.info("??數(shù)據(jù)插?成功");
//對異常進(jìn)?捕獲
try {
//強(qiáng)制程序拋出異常
int a = 10 / 0;
} catch (Exception e) {
//將異常重新拋出去
throw e;
}
return "注冊成功";
}- 手動(dòng)回滾事務(wù)
使用 TransactionAspectSupport.currentTransactionStatus() 得到當(dāng)前的事務(wù), 并 使? setRollbackOnly 設(shè)置 setRollbackOnly。
@Transactional
@RequestMapping("/registry")
public String registry(String name, String password) {
//??注冊
userService.registryUser(name, password);
log.info("??數(shù)據(jù)插?成功");
//對異常進(jìn)?捕獲
try {
//強(qiáng)制程序拋出異常
int a = 10 / 0;
} catch (Exception e) {
// ?動(dòng)回滾事務(wù)
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return "注冊成功";
}三、@Transactional 詳解
通過上面的代碼, 我們學(xué)習(xí)了 @Transactional 的基本使用. 接下來我們學(xué)習(xí) @Transactional
注解的使用細(xì)節(jié). 我們主要學(xué)習(xí) @Transactional 注解當(dāng)中的三個(gè)常見屬性:
- rollbackFor: 異?;貪L屬性. 指定能夠觸發(fā)事務(wù)回滾的異常類型. 可以指定多個(gè)異常類型
- Isolation: 事務(wù)的隔離級(jí)別. 默認(rèn)值為 Isolation.DEFAULT
- propagation: 事務(wù)的傳播機(jī)制. 默認(rèn)值為 Propagation.REQUIRED
3.1、rollbackFor
@Transactional 默認(rèn)只在遇到運(yùn)行時(shí)異常和Error時(shí)才會(huì)回滾, 非運(yùn)行時(shí)異常不回滾. 即Exception的子類中, 除了RuntimeException及其子類。
如果我們需要所有異常都回滾, 需要來配置 @Transactional 注解當(dāng)中的 rollbackFor 屬性, 通過 rollbackFor 這個(gè)屬性指定出現(xiàn)何種異常類型時(shí)事務(wù)進(jìn)行回滾。
@Transactional(rollbackFor = Exception.class)
@RequestMapping("/r2")
public String r2(String name, String password) throws IOException {
//??注冊
userService.registryUser(name, password);
log.info("??數(shù)據(jù)插?成功");
if (true) {
throw new IOException();
}
return "r2";
}
運(yùn)行程序后,發(fā)現(xiàn)雖然程序拋出了異常, 但是事務(wù)依然進(jìn)?了提交.
結(jié)論:
• 在Spring的事務(wù)管理中,默認(rèn)只在遇到運(yùn)行時(shí)異常RuntimeException和Error時(shí)才會(huì)回滾.
• 如果需要回滾指定類型的異常, 可以通過rollbackFor屬性來指定.
3.2事務(wù)隔離級(jí)別
3.2.1、MySQL 事務(wù)隔離級(jí)別
SQL 標(biāo)準(zhǔn)定義了四種隔離級(jí)別, MySQL 全都?持. 這四種隔離級(jí)別分別是:
- 讀未提交(READ UNCOMMITTED): 讀未提交, 也叫未提交讀. 該隔離級(jí)別的事務(wù)可以看到其他事務(wù)中 未提交的數(shù)據(jù). 因?yàn)槠渌聞?wù)未提交的數(shù)據(jù)可能會(huì)發(fā)生回滾, 但是該隔離級(jí)別卻可以讀到, 我們把該級(jí)別讀到的數(shù) 據(jù)稱之為臟數(shù)據(jù), 這個(gè)問題稱之為臟讀.
2 . 讀提交(READ COMMITTED): 讀已提交, 也叫提交讀. 該隔離級(jí)別的事務(wù)能讀取到已經(jīng)提交事務(wù)的數(shù) 據(jù),該隔離級(jí)別不會(huì)有臟讀的問題.但由于在事務(wù)的執(zhí)?中可以讀取到其他事務(wù)提交的結(jié)果, 所以在不 同時(shí)間的相同 SQL 查詢可能會(huì)得到不同的結(jié)果, 這種現(xiàn)象叫做不可重復(fù)讀
3 . 可重復(fù)讀(REPEATABLE READ): 事務(wù)不會(huì)讀到其他事務(wù)對已有數(shù)據(jù)的修改, 即使其他事務(wù)已提交. 也 就可以確保同?事務(wù)多次查詢的結(jié)果?致, 但是其他事務(wù)新插?的數(shù)據(jù), 是可以感知到的. 這也就引 發(fā)了幻讀問題. 可重復(fù)讀, 是 MySQL 的默認(rèn)事務(wù)隔離級(jí)別. 比如此級(jí)別的事務(wù)正在執(zhí)行時(shí), 另?個(gè)事務(wù)成功的插?了某條數(shù)據(jù), 但因?yàn)樗看尾樵兊慕Y(jié)果都是?樣的, 所以會(huì)導(dǎo)致查詢不到這條數(shù)據(jù), 自己重復(fù)插入時(shí)又失敗(因?yàn)槲?約束的原因). 明明在事務(wù)中查詢不到這條信息,但自己就是插入不進(jìn)去, 這個(gè)現(xiàn)象叫幻讀.
4 . 串行化(SERIALIZABLE): 序列化, 事務(wù)最高隔離級(jí)別. 它會(huì)強(qiáng)制事務(wù)排序, 使之不會(huì)發(fā)生沖突, 從而解決了臟讀, 不可重復(fù)讀和幻讀問題, 但因?yàn)閳?zhí)行效率低, 所以真正使用的場景并不多.
3.2.2、Spring 事務(wù)隔離級(jí)別
Spring 中事務(wù)隔離級(jí)別有5 種:
- Isolation.DEFAULT : 以連接的數(shù)據(jù)庫的事務(wù)隔離級(jí)別為主.
2 . Isolation.READ_UNCOMMITTED : 讀未提交, 對應(yīng)SQL標(biāo)準(zhǔn)中 READ UNCOMMITTED
3 . Isolation.READ_COMMITTED : 讀已提交,對應(yīng)SQL標(biāo)準(zhǔn)中 READ COMMITTED
4 . Isolation.REPEATABLE_READ : 可重復(fù)讀, 對應(yīng)SQL標(biāo)準(zhǔn)中 REPEATABLE READ
5 . Isolation.SERIALIZABLE : 串?化, 對應(yīng)SQL標(biāo)準(zhǔn)中 SERIALIZABLE
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
private final int value;
private Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
Spring 中事務(wù)隔離級(jí)別可以通過 @Transactional 中的 isolation 屬性進(jìn)行設(shè)置。
@Transactional(isolation = Isolation.READ_COMMITTED)
@RequestMapping("/r3")
public String r3(String name,String password) throws IOException {
//... 代碼省略
return "r3";
}
3.3、Spring 事務(wù)傳播機(jī)制
3.3.1、什么是事務(wù)傳播機(jī)制?
事務(wù)傳播機(jī)制就是: 多個(gè)事務(wù)方法存在調(diào)用關(guān)系時(shí), 事務(wù)是如何在這些方法間進(jìn)行傳播的?
比如有兩個(gè)方法A, B都被 @Transactional 修飾, A方法調(diào)用B方法 A方法運(yùn)行時(shí), 會(huì)開啟?個(gè)事務(wù). 當(dāng)A調(diào)用B時(shí), B方法本身也有事務(wù), 此時(shí)B方法運(yùn)行時(shí), 是加入A的事務(wù), 還 是創(chuàng)建一個(gè)新的事務(wù)呢? 這個(gè)就涉及到了事務(wù)的傳播機(jī)制。
比如公司流程管理
執(zhí)行任務(wù)之前, 需要先寫執(zhí)行?檔, 任務(wù)執(zhí)行結(jié)束, 再寫總結(jié)匯報(bào)

此時(shí)A部門有?項(xiàng)工作, 需要B部門的支援, 此時(shí)B部門是直接使用A部門的?檔, 還是新建一個(gè)文檔呢?
事務(wù)隔離級(jí)別解決的是多個(gè)事務(wù)同時(shí)調(diào)用一個(gè)數(shù)據(jù)庫的問題:

而事務(wù)傳播機(jī)制解決的是?個(gè)事務(wù)在多個(gè)節(jié)點(diǎn)(?法)中傳遞的問題:
3.3.2、事務(wù)的傳播機(jī)制有哪些
@Transactional 注解支持事務(wù)傳播機(jī)制的設(shè)置, 通過 propagation 屬性來指定傳播?為.Spring 事務(wù)傳播機(jī)制有以下 7 種:
- Propagation.REQUIRED : 默認(rèn)的事務(wù)傳播級(jí)別. 如果當(dāng)前存在事務(wù), 則加入該事務(wù). 如果當(dāng)前沒有事務(wù), 則創(chuàng)建一個(gè)新的事務(wù).
2 . Propagation.SUPPORTS : 如果當(dāng)前存在事務(wù), 則加入該事務(wù). 如果當(dāng)前沒有事務(wù), 則以非事務(wù)的方式繼續(xù)運(yùn)行.
3 . Propagation.MANDATORY :強(qiáng)制性. 如果當(dāng)前存在事務(wù), 則加?該事務(wù). 如果當(dāng)前沒有事務(wù), 則 拋出異常.
4 . Propagation.REQUIRES_NEW : 創(chuàng)建?個(gè)新的事務(wù). 如果當(dāng)前存在事務(wù), 則把當(dāng)前事務(wù)掛起. 也 就是說不管外部?法是否開啟事務(wù), Propagation.REQUIRES_NEW 修飾的內(nèi)部方法都會(huì)新開 啟??的事務(wù), 且開啟的事務(wù)相互獨(dú)立, 互不干擾.
5 . Propagation.NOT_SUPPORTED : 以非事務(wù)方式運(yùn)行, 如果當(dāng)前存在事務(wù), 則把當(dāng)前事務(wù)掛起(不用).
6 . Propagation.NEVER : 以非事務(wù)方式運(yùn)行, 如果當(dāng)前存在事務(wù), 則拋出異常.
7 . Propagation.NESTED : 如果當(dāng)前存在事務(wù), 則創(chuàng)建?個(gè)事務(wù)作為當(dāng)前事務(wù)的嵌套事務(wù)來運(yùn)行. 如果當(dāng)前沒有事務(wù), 則該取值等價(jià)于 PROPAGATION_REQUIRED .
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
private final int value;
private Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}3.3.3、Spring 事務(wù)傳播機(jī)制使用和各種場景演示
對于以上事務(wù)傳播機(jī)制,我們重點(diǎn)關(guān)注以下兩個(gè)就可以了:
- REQUIRED(默認(rèn)值)
- REQUIRES_NEW
3.3.3.1 REQUIRED(加入事務(wù))
看下?代碼實(shí)現(xiàn):
- 用戶注冊, 插入一條數(shù)據(jù)
- 記錄操作日志, 插入一條數(shù)據(jù)(出現(xiàn)異常)
觀察 propagation = Propagation.REQUIRED 的執(zhí)行結(jié)果:
@RequestMapping("/propaga")
@RestController
public class PropagationController {
@Autowired
private UserService userService;
@Autowired
private LogService logService;
@Transactional(propagation = Propagation.REQUIRED)
@RequestMapping("/p1")
public String r3(String name, String password) {
//??注冊
userService.registryUser(name, password);
//記錄操作?志
logService.insertLog(name, "??注冊");
return "r3";
}
}對應(yīng)的UserService和LogService都添加上 @Transactional(propagation =
Propagation.REQUIRED)。
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void registryUser(String name, String password) {
//插???信息
userInfoMapper.insert(name, password);
}
}
@Slf4j
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void insertLog(String name, String op) {
int a = 10 / 0;
//記錄??操作
logInfoMapper.insertLog(name, "??注冊");
}
}運(yùn)行程序, 發(fā)現(xiàn)數(shù)據(jù)庫沒有插入任何數(shù)據(jù). 流程描述:
- p1 方法開始事務(wù)
- 用戶注冊, 插入一條數(shù)據(jù) (執(zhí)行成功) (和p1 使用同?個(gè)事務(wù))
- 記錄操作日志, 插入一條數(shù)據(jù)(出現(xiàn)異常, 執(zhí)行失敗) (和p1 使用同?個(gè)事務(wù))
- 因?yàn)椴襟E3出現(xiàn)異常, 事務(wù)回滾. 步驟2和3使用同?個(gè)事務(wù), 所以步驟2的數(shù)據(jù)也回滾了.
3.3.3.2、REQUIRES_NEW(新建事務(wù))
將上述UserService 和LogService 中相關(guān)?法事務(wù)傳播機(jī)制改為Propagation.REQUIRES_NEW。
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void registryUser(String name, String password) {
//插???信息
userInfoMapper.insert(name, password);
}
}
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertLog(String name, String op) {
int a = 10 / 0;
//記錄??操作
logInfoMapper.insertLog(name, "??注冊");
}
}運(yùn)行程序, 發(fā)現(xiàn)用戶數(shù)據(jù)插入成功了, 日志表數(shù)據(jù)插入失敗.
LogService 方法中的事務(wù)不影響 UserService 中的事務(wù).
當(dāng)我們不希望事務(wù)之間相互影響時(shí), 可以使用該傳播行為.
3.3.3、NEVER (不支持當(dāng)前事務(wù), 拋異常)
修改UserService 中對應(yīng)?法的事務(wù)傳播機(jī)制為 Propagation.NEVER
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.NEVER)
public void registryUser(String name, String password) {
//插???信息
userInfoMapper.insert(name, password);
}
}程序執(zhí)行報(bào)錯(cuò), 沒有數(shù)據(jù)插入.
3.3.3.4、NESTED(嵌套事務(wù))
將上述UserService 和LogService 中相關(guān)方法事務(wù)傳播機(jī)制改為 Propagation.NESTED。
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public void registryUser(String name, String password) {
//插???信息
userInfoMapper.insert(name, password);
}
}
@Slf4j
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public void insertLog(String name, String op) {
int a = 10 / 0;
//記錄??操作
logInfoMapper.insertLog(name, "??注冊");
}
}運(yùn)?程序, 發(fā)現(xiàn)沒有任何數(shù)據(jù)插入. 流程描述:
- Controller 中p1 方法開始事務(wù)
- UserService 用戶注冊, 插入一條數(shù)據(jù) (嵌套p1事務(wù))
- LogService 記錄操作日志, 插入一條數(shù)據(jù)(出現(xiàn)異常, 執(zhí)行失敗) (嵌套p1事務(wù), 回滾當(dāng)前事務(wù), 數(shù)據(jù)添加失敗)
- 由于是嵌套事務(wù), LogService 出現(xiàn)異常之后, 往上找調(diào)用它的方法和事務(wù), 所以用戶注冊也失敗了.
- 最終結(jié)果是兩個(gè)數(shù)據(jù)都沒有添加
p1事務(wù)可以認(rèn)為是父事務(wù), 嵌套事務(wù)是子事務(wù). 父事務(wù)出現(xiàn)異常, 子事務(wù)也會(huì)回滾, 子事務(wù)出現(xiàn)異常, 如果不進(jìn)行處理, 也會(huì)導(dǎo)致父事務(wù)回滾.
3.3.3.5 NESTED和REQUIRED 有什么區(qū)別?
我們在 LogService 進(jìn)?當(dāng)前事務(wù)回滾, 修改 LogService 代碼如下:
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public void insertLog(String name, String op) {
try {
int a = 10 / 0;
} catch (Exception e) {
//回滾當(dāng)前事務(wù)
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
//記錄??操作
logInfoMapper.insertLog(name, "??注冊");
}
}重新運(yùn)行程序, 發(fā)現(xiàn)用戶表數(shù)據(jù)添加成功, 日志表添加失敗.
LogService 中的事務(wù)已經(jīng)回滾, 但是嵌套事務(wù)不會(huì)回滾嵌套之前的事務(wù), 也就是說嵌套事務(wù)可以實(shí)現(xiàn)部分事務(wù)回滾。
對?REQUIRED
把 NESTED 傳播機(jī)制改為 REQUIRED, 修改代碼如下:
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void registryUser(String name, String password) {
//插???信息
userInfoMapper.insert(name, password);
}
}
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void insertLog(String name, String op) {
try {
int a = 10 / 0;
} catch (Exception e) {
//回滾當(dāng)前事務(wù)
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
//記錄??操作
logInfoMapper.insertLog(name, "??注冊");
}
}重新運(yùn)行程序, 發(fā)現(xiàn)用戶表和日志表的數(shù)據(jù)添加都失敗了.
REQUIRED 如果回滾就是回滾所有事務(wù), 不能實(shí)現(xiàn)部分事務(wù)的回滾. (因?yàn)閷儆谕?個(gè)事務(wù))
NESTED和REQUIRED區(qū)別:
• 整個(gè)事務(wù)如果全部執(zhí)行成功, ?者的結(jié)果是?樣的.
• 如果事務(wù)?部分執(zhí)行成功, REQUIRED加入事務(wù)會(huì)導(dǎo)致整個(gè)事務(wù)全部回滾. NESTED嵌套事務(wù)可以實(shí)現(xiàn)局部回滾, 不會(huì)影響上一個(gè)方法中執(zhí)行的結(jié)果.
嵌套事務(wù)之所以能夠?qū)崿F(xiàn)部分事務(wù)的回滾, 是因?yàn)槭聞?wù)中有?個(gè)保存點(diǎn)(savepoint)的概念, 嵌套事務(wù) 進(jìn)入之后相當(dāng)于新建了?個(gè)保存點(diǎn), 而滾回時(shí)只回滾到當(dāng)前保存點(diǎn)。
REQUIRED 是加入到當(dāng)前事務(wù)中, 并沒有創(chuàng)建事務(wù)的保存點(diǎn), 因此出現(xiàn)了回滾就是整個(gè)事務(wù)回滾, 這就是嵌套事務(wù)和加入事務(wù)的區(qū)別
總結(jié)
- Spring中使用事務(wù), 有兩種方式: 編程式事務(wù)(手動(dòng)操作)和聲明式事務(wù). 其中聲明式事務(wù)使用較多,在方法上添加 @Transactional 就可以實(shí)現(xiàn)了
- 通過 @Transactional(isolation = Isolation.SERIALIZABLE) 設(shè)置事務(wù)的隔離級(jí)別. Spring 中的事務(wù)隔離級(jí)別有 5 種
- 通過 @Transactional(propagation = Propagation.REQUIRED) 設(shè)置事務(wù)的傳播機(jī)制, Spring 中的 事務(wù)傳播級(jí)別有 7 種, 重點(diǎn)關(guān)注 REQUIRED (默認(rèn)值) 和 REQUIRES_NEW
以上就是本文全部內(nèi)容,感謝各位能夠看到最后,如有問題,歡迎各位大佬在評論區(qū)指正,希望大家可以有所收獲!創(chuàng)作不易,希望大家多多支持!
到此這篇關(guān)于Spring事務(wù)和事務(wù)傳播機(jī)制操作大全的文章就介紹到這了,更多相關(guān)spring事務(wù)傳播機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot+echarts實(shí)現(xiàn)可視化
這篇文章主要為大家詳細(xì)介紹了Springboot+echarts實(shí)現(xiàn)可視化,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12
java工具類之實(shí)現(xiàn)java獲取文件行數(shù)
這篇文章主要介紹了一個(gè)java工具類,可以取得當(dāng)前項(xiàng)目中所有java文件總行數(shù),代碼行數(shù),注釋行數(shù),空白行數(shù),需要的朋友可以參考下2014-03-03
Maven多模塊項(xiàng)目調(diào)試與問題排查的完整指南
在現(xiàn)代企業(yè)級(jí)Java開發(fā)中,Maven多模塊項(xiàng)目因其清晰的代碼組織,依賴管理和高效的構(gòu)建流程已成為主流架構(gòu)模式,本文深入剖析多模塊項(xiàng)目的四大核心痛點(diǎn)解決方案,感興趣的小伙伴可以跟隨小編一起了解下2025-06-06
MyBatis通用Mapper實(shí)現(xiàn)原理及相關(guān)內(nèi)容
今天小編就為大家分享一篇關(guān)于MyBatis通用Mapper實(shí)現(xiàn)原理及相關(guān)內(nèi)容,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12
Java實(shí)現(xiàn)表單提交(支持多文件同時(shí)上傳)
本文介紹了Java、Android實(shí)現(xiàn)表單提交(支持多文件同時(shí)上傳)的方法,具有一定的參考價(jià)值,下面跟著小編一起來看下吧2017-01-01
Spring Boot實(shí)現(xiàn)熱部署的實(shí)例方法
在本篇文章里小編給大家整理的是關(guān)于Spring Boot實(shí)現(xiàn)熱部署的實(shí)例方法和實(shí)例,需要的朋友們可以參考下。2020-02-02

