Spring事务和事务传播机制

事务是⼀组操作的集合, 是⼀个不可分割的操作.

事务会把所有的操作作为⼀个整体, ⼀起向数据库提交或者是撤销操作请求. 所以这组操作要么同时成功, 要么同时失败.

事务的操作主要有三步:

1. 开启事start transaction/ begin (⼀组操作前开启事务)

2. 提交事务: commit (这组操作全部成功, 提交事务)

3. 回滚事务: rollback (这组操作中间任何⼀个操作出现异常, 回滚事务)

Spring 中事务的实现

Spring 中的事务操作分为两类:

1. 编程式事务(⼿动写代码操作事务).

Spring ⼿动操作事务和上⾯ MySQL 操作事务类似, 有 3 个重要操作步骤:

开启事务(获取事务)

提交事务

回滚事务

SpringBoot 内置了两个对象:

1. DataSourceTransactionManager 事务管理器. ⽤来获取事务(开启事务), 提交或回滚事务

2. TransactionDefinition 是事务的属性, 在获取事务的时候需要将

TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus

import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RestController
public class UserController {
 // JDBC 事务管理器
 @Autowired
 private DataSourceTransactionManager dataSourceTransactionManager;
 // 定义事务属性
 @Autowired
 private TransactionDefinition transactionDefinition;
 @Autowired
 private UserService userService;
 @RequestMapping("/registry")
 public String registry(String name,String password){
 // 开启事务
 TransactionStatus transactionStatus = dataSourceTransactionManager
 .getTransaction(transactionDefinition);
 //⽤⼾注册
 userService.registryUser(name,password);
 //提交事务
 dataSourceTransactionManager.commit(transactionStatus);
 //回滚事务
 //dataSourceTransactionManager.rollback(transactionStatus);
 return "注册成功";
 }
}

2. 声明式事务(利⽤注解⾃动开启和提交事务).

声明式事务的实现很简单

两步操作:

1. 添加依赖

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-tx</artifactId>
</dependency>

2. 在需要事务的⽅法上添加 @Transactional 注解就可以实现了. ⽆需⼿动开启事务和提交事

务, 进⼊⽅法时⾃动开启事务, ⽅法执⾏完会⾃动提交事务, 如果中途发⽣了没有处理的异常会⾃动

回滚事务.

我们来看代码实现:

@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 "注册成功";
 }
}
@Transactional 作⽤

@Transactional 可以⽤来修饰⽅法或类:

修饰⽅法时: 只有修饰public ⽅法时才⽣效(修饰其他⽅法时不会报错, 也不⽣效)[推荐]

修饰类时: 对 @Transactional 修饰的类中所有的 public ⽅法都⽣效

 @Transactional 详解

我们主要学习 @Transactional 注解当中的三个常⻅属性:

1. rollbackFor: 异常回滚属性. 指定能够触发事务回滚的异常类型. 可以指定多个异常类型

2. Isolation: 事务的隔离级别. 默认值为 Isolation.DEFAULT

3. propagation: 事务的传播机制. 默认值为 Propagation.REQUIRED

rollbackFor

@Transactional 默认只在遇到运⾏时异常和Error时才会回滚, ⾮运⾏时异常不回滚. 即
Exception的⼦类中, 除了RuntimeException及其⼦类
如果我们需要所有异常都回滚, 需要来配置 @Transactional 注解当中的 rollbackFor 属性, 通
rollbackFor 这个属性指定出现何种异常类型时事务进⾏回滚.
@Transactional(rollbackFor = Exception.class)
@RequestMapping("/r2")
public String r2(String name,String password) throws IOException {
 //⽤⼾注册
 userService.registryUser(name,password);
 log.info("⽤⼾数据插⼊成功");
 if (true){
 throw new IOException();
 }
 return "r2";
}

Isolation

1. 读未提交(READ UNCOMMITTED): 读未提交, 也叫未提交读. 该隔离级别的事务可以看到其他事务中未提交的数据.

因为其他事务未提交的数据可能会发⽣回滚, 但是该隔离级别却可以读到, 我们把该级别读到的数

据称之为脏数据, 这个问题称之为脏读.

2. 读提交(READ COMMITTED): 读已提交, 也叫提交读. 该隔离级别的事务能读取到已经提交事务的数据,

该隔离级别不会有脏读的问题.但由于在事务的执⾏中可以读取到其他事务提交的结果, 所以在不

同时间的相同 SQL 查询可能会得到不同的结果, 这种现象叫做不可重复读

3. 可重复读(REPEATABLE READ): 事务不会读到其他事务对已有数据的修改, 即使其他事务已提交. 也就可以确保同⼀事务多次查询的结果⼀致, 但是其他事务新插⼊的数据, 是可以感知到的. 这也就引发了幻读问题. 可重复读, 是 MySQL 的默认事务隔离级别.

⽐如此级别的事务正在执⾏时, 另⼀个事务成功的插⼊了某条数据, 但因为它每次查询的结果都是

⼀样的, 所以会导致查询不到这条数据, ⾃⼰重复插⼊时⼜失败(因为唯⼀约束的原因). 明明在事务

中查询不到这条信息,但⾃⼰就是插⼊不进去, 这个现象叫幻读.

4. 串⾏化(SERIALIZABLE): 序列化, 事务最⾼隔离级别. 它会强制事务排序, 使之不会发⽣冲突, 从⽽解决了脏读, 不可重复读和幻读问题, 但因为执⾏效率低, 所以真正使⽤的场景并不多

Spring 事务隔离级别

Spring 中事务隔离级别有5 种:

1. Isolation.DEFAULT : 以连接的数据库的事务隔离级别为主.

2. Isolation.READ_UNCOMMITTED : 读未提交, 对应SQL标准中 READ UNCOMMITTED

3. Isolation.READ_COMMITTED : 读已提交,对应SQL标准中 READ COMMITTED

4. Isolation.REPEATABLE_READ : 可重复读, 对应SQL标准中 REPEATABLE READ

5. Isolation.SERIALIZABLE : 串⾏化, 对应SQL标准中 SERIALIZABLE

Spring 事务传播机制

事务传播机制就是: 多个事务⽅法存在调⽤关系时, 事务是如何在这些⽅法间进⾏传播的.

⽐如有两个⽅法A, B都被 @Transactional 修饰, A⽅法调⽤B⽅法

A⽅法运⾏时, 会开启⼀个事务. 当A调⽤B时, B⽅法本⾝也有事务, 此时B⽅法运⾏时, 是加⼊A的事务, 还是创建⼀个新的事务呢?

这个就涉及到了事务的传播机制.

⽽事务传播机制解决的是⼀个事务在多个节点(⽅法)中传递的问题

事务的传播机制有哪些

@Transactional 注解⽀持事务传播机制的设置, 通过 propagation 属性来指定传播⾏为.

Spring 事务传播机制有以下 7 种:

1. Propagation.REQUIRED : 默认的事务传播级别. 如果当前存在事务, 则加⼊该事务. 如果当前没

有事务, 则创建⼀个新的事务.

2. Propagation.SUPPORTS : 如果当前存在事务, 则加⼊该事务. 如果当前没有事务, 则以⾮事务的Spring 事务传播机制使⽤和各种场景演⽰⽅式继续运⾏.

3. Propagation.MANDATORY :强制性. 如果当前存在事务, 则加⼊该事务. 如果当前没有事务, 则

抛出异常.

4. Propagation.REQUIRES_NEW : 创建⼀个新的事务. 如果当前存在事务, 则把当前事务挂起. 也

就是说不管外部⽅法是否开启事务, Propagation.REQUIRES_NEW 修饰的内部⽅法都会新开

启⾃⼰的事务, 且开启的事务相互独⽴, 互不⼲扰.

5. Propagation.NOT_SUPPORTED : 以⾮事务⽅式运⾏, 如果当前存在事务, 则把当前事务挂起(不⽤).

6. Propagation.NEVER : 以⾮事务⽅式运⾏, 如果当前存在事务, 则抛出异常.

7. Propagation.NESTED : 如果当前存在事务, 则创建⼀个事务作为当前事务的嵌套事务来运⾏.

如果当前没有事务, 则该取值等价于 PROPAGATION_REQUIRED

Spring 事务传播机制使⽤和各种场景演⽰

REQUIRED(加⼊事务)

看下⾯代码实现:
1. ⽤⼾注册, 插⼊⼀条数据
2. 记录操作⽇志, 插⼊⼀条数据(出现异常)
观察 propagation = Propagation.REQUIRED 的执⾏结果
@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";
 }
}
对应的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,"⽤⼾注册");
 }
}

运⾏程序, 发现数据库没有插⼊任何数据.

流程描述:

1. p1 ⽅法开始事务

2. ⽤⼾注册, 插⼊⼀条数据 (执⾏成功) (和p1 使⽤同⼀个事务)

3. 记录操作⽇志, 插⼊⼀条数据(出现异常, 执⾏失败) (和p1 使⽤同⼀个事务)

4. 因为步骤3出现异常, 事务回滚. 步骤2和3使⽤同⼀个事务, 所以步骤2的数据也回滚了.

REQUIRES_NEW(新建事务)

将上述UserService 和LogService 中相关⽅法事务传播机制改为

Propagation.REQUIRES_NEW

运⾏程序, 发现⽤⼾数据插⼊成功了, ⽇志表数据插⼊失败.

LogService ⽅法中的事务不影响 UserService 中的事务.

当我们不希望事务之间相互影响时, 可以使⽤该传播⾏为

NEVER (不⽀持当前事务, 抛异常)

修改UserService 中对应⽅法的事务传播机制为 Propagation. NEVER
程序执⾏报错, 没有数据插⼊

NESTED(嵌套事务)

将上述UserService 和LogService 中相关⽅法事务传播机制改为 Propagation.NESTED

运⾏程序, 发现没有任何数据插⼊.

流程描述:

1. Controller 中p1 ⽅法开始事务

2. UserService ⽤⼾注册, 插⼊⼀条数据 (嵌套p1事务)

3. LogService 记录操作⽇志, 插⼊⼀条数据(出现异常, 执⾏失败) (嵌套p1事务, 回滚当前事务, 数

据添加失败)

4. 由于是嵌套事务, LogService 出现异常之后, 往上找调⽤它的⽅法和事务, 所以⽤⼾注册也失败

了.

5. 最终结果是两个数据都没有添加

NESTED和REQUIRED 有什么区别?

NESTED和REQUIRED区别 :

整个事务如果全部执⾏成功, ⼆者的结果是⼀样的.

如果事务⼀部分执⾏成功, REQUIRED加⼊事务会导致整个事务全部回滚. NESTED嵌套事务可以实现局部回滚, 不会影响上⼀个⽅法中执⾏的结果.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值