事务定义:
将一组操作封装成一个执行单元,要么全部成功,要么全部失败。
事务的意义:
当执行某个操作,例如支付操作时(分为先将钱从个人账户扣除和将他人账户新增两个操作),如果这两个操作不能同时成功或者失败,那么就会出现财产问题。而使用事务就能够很好的解决这个问题。
Spring 中的事务操作分为两类:
Spring 手动操作事务分为三个步骤:
SpringBoot 内置了两个对象,可以用来处理事务:
具体代码实现如下:
@RestController
@RequestMapping("user")
public class UserController {
// JDBC 事务管理器
@Resource
private DataSourceTransactionManager dataSourceTransactionManager;
// 定义事务属性
@Resource
private TransactionDefinition transactionDefinition;
@RequestMapping("/test1")
public String test1(){
// 开启事务
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
// 执行数据库操作
// 提交事务
dataSourceTransactionManager.commit(transaction);
// 回滚事务
dataSourceTransactionManager.rollback(transaction);
return "测试完成!";
}
}
声明式事务的实现相较于手动实现要简单很多,只需要在需要添加事务的方法上加上 @Transactional 注解就可以,无需手动开启事务和提交事务,进入方法时会自动开启事务,方法执行完成会自动提交事务,如果中途发生了没有处理的异常就会自动回滚事务。
具体代码实现如下:
@RestController
@RequestMapping("user")
public class UserController {
@Transactional
@RequestMapping("/test2")
public String test2(){
// 执行数据库操作
return "测试完成!";
}
}
当我们在执行完数据库操作后,有一个语句出现异常,通过 @Transactional 注解就能进行回滚。
通过上文我们了解到,在需要的方法上添加 @Transactional 注解,就能自动开启事务。接下来将会具体了解下 @Transactional 的使用细节。
@Fransactional 可以用来修饰方法或类:
| 参数 | 作用 |
|---|---|
| value | 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。 |
| transactionManager | 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。 |
| propagation | 事务的传播行为,默认值为 Propagation.REQUIRED。 |
| isolation | 事务的隔离级别,默认值为 Isolation.DEFAULT。 |
| timeout | 事务的超时时间,默认值为-1(表示没有超时时间)。如果超过该超时时间显示但事务还没有完成,则自动回滚事务。 |
| readOnly | 指定事务是否为只读事务,默认值为 false。为了忽略那些不需要事务的方法,比如读取数据,可以设置为 true。 |
| rollbackFor | 用于指定能够被触发事务回滚的异常类型,可以指定多个异常类型。 |
| rollbackForClassName | 用于指定能够被触发事务回滚的异常类型,可以指定多个异常类型。 |
| noRollbackFor | 抛出异常的类型,不回滚事务,也可以指定多个异常类型。 |
| noRollbackForClassName | 抛出异常的类型,不回滚事务,也可以指定多个异常类型。 |
注意: rollbackFor 是将指定的非运行时异常进行回滚。
@Transactional 在异常被捕获的情况下,不会进行事务的自动回滚。
注意: 默认情况下,Spring 中的事务如果遇到运行时异常,事务是会进行回滚的,但遇到非运行时异常,事务不会自动回滚。可以设置 rollbackFor 来解决非运行时异常不会被回滚的问题。
示例代码如下:
@RequestMapping("/test3")
@Transactional
public String test3(@RequestParam String username, @RequestParam String pwd) {
// 插⼊数据库
int result = userService.addUser(username, pwd);
try {
// 执⾏了异常代码
int i = 10 / 0;
} catch (Exception e) {
}
return "测试完成!";
}
以上代码虽然出现了算数异常,但是由于主动捕获了,因此不会进行事物的回滚,数据库中会插入该条数据。
如果要解决出现异常事务不能自动回滚的问题,以下提供两种解决方案:
方案一:对于捕获的异常,事务是不会自动回滚的,因此可以在捕获异常后主动将该异常重新抛出。示例代码如下:
@RequestMapping("/test3")
@Transactional
public String test3(@RequestParam String username, @RequestParam String pwd) {
// 插⼊数据库
int result = userService.addUser(username, pwd);
try {
// 执⾏了异常代码
int i = 10 / 0;
} catch (Exception e) {
// 将异常重新抛出
throw e;
}
return "测试完成!";
}
方案二(推荐):手动去回滚事务,可以通过方法 TransactionAspectSupport.currentTransactionStatus() 得到当前的事务,然后设置回滚方法 setRollbackOnly 就可以实现回滚。示例代码如下:
@RequestMapping("/test3")
@Transactional
public String test3(@RequestParam String username, @RequestParam String pwd) {
// 插⼊数据库
int result = userService.addUser(username, pwd);
try {
// 执⾏了异常代码
int i = 10 / 0;
} catch (Exception e) {
// 手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return "测试完成!";
}
@Transactional 是基于 AOP 实现的,AOP 又是使用了动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理;如果目标对象没有实现接口,会使用 CGLIB 动态代理。@Transactional 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果中途遇到异常,则回滚事务。
@Transactional 实现思路:![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RV6KNVJa-1659875752214)(C:/Users/bbbbbge/Pictures/接单/1659762882156.png)]](https://1000bd.com/contentImg/2022/08/11/215431240.png)
@Transactional 具体执行细节:![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8lp3mFmG-1659875752215)(C:/Users/bbbbbge/Pictures/接单/1659763621378.png)]](https://1000bd.com/contentImg/2022/08/11/215431414.png)
事务有4大特性,简称为 ACID,分别如下:
原子性(Atomicity):
一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束再在某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像事务从来没有执行一样。
一致性(Consistency):
在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
持久性(Isolation):
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
隔离性(Durability):
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)、串行化(serializable)
在这四种特性中,只有隔离性可以设置,通过设置事务的隔离级别可以用来保障多个并发事务执行更加可控,更符合操作者的预期,是防止其它的事务影响当前事务执行的一种策略。
| 事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交 | √ | √ | √ |
| 读已提交 | × | √ | × |
| 可重复度 | × | × | √ |
| 串行化 | × | × | × |
在数据库中可以通过以下 SQL 查询全局事务隔离级别和当前连接的事务隔离级别:
select @@global.tx_isolation,@@tx_isolation;
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KX40FKS8-1659875752215)(C:/Users/bbbbbge/Pictures/接单/1659804676761.png)]](https://1000bd.com/contentImg/2022/08/11/215431623.png)
Spring 中事务隔离级别可以通过 @Transactional 的 Isolation 属性进行设置。
Spring 事务传播机制定义了多个包含了事务的方法在相互调用时,事务是如何在这些方法之间进行传递的。
嵌套事务和加入事务的区别: