Spring及SpringBoot事务
1. 事务简介
事务管理是企业开发,或是多增删改操作当中必不可少的技术,主要用来保证数据的一致性,例如,在一个接口中,一次执行A(增)、B(删)、C(改)三个操作,当执行完AB操作之后突然出现了异常,导致C无法继续操作,即AB操作成功,C没有成功。此时,事务管理派上了用场,它可以保证ABC三个操作,要么都成功,要么都不成功
。只有这么两个结果。
2. 事务的四大特性
事务有四大特性(ACID )
-
原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败。成功则所有的数据库操作都生效,失败则所有的数据库操作都不生效。 -
一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另一个一致性状态。最经典的例子就是甲乙每人有100块钱,他们之间无论如何转账,总金额都应该为200元。 -
隔离性(Isolation)
多个用户并发访问数据库时,数据库为每一个用户开启的事务。且碧云不能被其他事务的操作干扰,即多个并发事务之间要相互隔离。我们常说的锁就是用于解决隔离性的一种机制,而锁的不同的粒度,使得事务有不同的隔离级别
-
持久性(Durability)
一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的。即当系统或介质发生故障时,已提交事务的更新也不能丢失。
3. Spring的事务
Spring并不直接管理
事务,而是提供了许多内置事务管理器实现,常用的有JpaTransactionManager、DataSourceTransactionManager、HibernateTransactionManager和JdoTransactionManager等。Spring支持的事务有两类,一是编程式事务(也称编码式事务),二是声明式的事务。
3.1 编程式事务:
编程式事务管理是指将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。(用代码的方式显示的取实现,有点类似我们常写的JDBC代码)
3.2 声明式事务
声明式事务管理是指将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。(使用注解的方式,直接在方法上加@Transactional注解即可)
4. Spring事务的隔离级别
隔离级别 | ISOLATION | 描述 | 等级 |
---|---|---|---|
默认级别 | DEFAULT | 数据库的默认隔离级别,ORACLE(读已提交) MySQL(可重复读),其余4个与JDBC的隔离级别分别对应。 | ②③ |
读未提交 | READ_UNCOMITTED | 读取尚未提交的更改。可能导致脏读、幻读或不可重复读,这个是事务最低的隔离级别。 | ① |
读已提交 | READ_COMMITED | 从已经提交的并发事务读取。可防止脏读,但幻读和不可重复读仍可能会发生 | ② |
可重复读 | REPEATABLE_READ | 在事务执行期间会锁定该事务以任何方式引用的所有行。因此,如果在同一个事务中发出同一个SELECT语句两次或更多次,那么产生的结果数据集总是相同的。可防止脏读和不可重复读,但仍然有幻读的风险。据我实地测试,MySQL5.6在加上事务控制后,并不会发生幻读,官方可能已经修复了,所以不必执着于此 具体情况如下图 | ③ |
串行化 | SERLALIZABLE | 执行完一个事务及其子事务后才执行其它的事务和子事务,完全符合ACID原则。该隔离级别最慢,但确保不发生脏读、不可重复读和幻读,实际场景中几乎用不到 | ④ |
5. 事务并发导致的问题
问题 | Problem | 说明 |
---|---|---|
脏读 | Dirty reads | 一个事务读取了另一个事务更改但尚未提交的数据。而且在更改后被回滚了,此时第一个事务获取的数据就是无效的。我们称之为脏读。即读取到无效的垃圾数据,所以是称之为脏读 |
不可重复读 | Nonrepeatable read | 一个事务执行两次或两次以上的相同查询时,第二个事务在两次查询的间隔更新了数据,导致每次都得到不同的数据,即经不起一次又一次的考验,称之为不可重复。 |
幻读 | Phantom read | 一个事务第一次读取了M条数据,另一个并发事务插入了N条数据时。第一个事务再次读取时出现查询到了M+N条数据。好比你前一眼地球还在,一眨眼地球就不见了,很魔幻,所以称为幻读。 |
6. Spring事务的传播(Propagation)
在实际操作中,很多时候为了代码复用,减少开发量,或者某些迫不得已的原因,需要在事务方法中调用另一个事务方法,那多个事务是如何传递的了?下面来了解一下七种事务传播:
行为 | 翻译(不准哦) | 解释 |
---|---|---|
PROPAGATION_REQUIRED | 必需的 | 这个是默认的,如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。 |
PROPAGATION_SUPPORTS | 支持当前事务 | 如果当前有事务,支持当前事务;如果当前没有事务,则以非事务方式执行。 |
PROPAGATION_MANDATORY | 强制的 | 如果当前有事务,则支持当前事务;如果没有事务,则抛出异常 |
PROPAGATION_REQUIRES_NEW | 需要新建的 | 如果当前有事务,则挂起当前事务;如果没有事务,则新建一个事务 |
PROPAGATION_NOT_SUPPORTED | 不支持事务的 | 如果当前有事务,则挂起事务;如果没有事务,则以非事务的方式执行 |
PROPAGATION_NEVER | 不支持事务(程度更高) | 如果当前存在事务,则抛出异常;如果不存在事务,则以非事务的方式执行。 |
PROPAGATION_NESTED | 嵌套的 | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则新建一个事务 |
事务挂起
:A,B是带事务的方法(有多个数据库操作),A中调用B,将A挂起,意思是B中出现任何事务异常都和A无关,比如B中出现数据库操作失败,此时A并不会回滚。
7. Spring/SpringBoot注解式事务使用
Spring的事务很简单,一般直接在置于public
方法上即可:
7.1 最简单的事务处理:
@Transactional
public Object transtionalManage() {
// db操作1
// db操作2
// db操作3
return "SUCCESS";
}
7.2 限定异常情况的回滚
/**
*发生运行异常时回滚
*/
@Transactional(rollbackFor = { RuntimeException.class })
public Object transtionalManage() {
// db操作1
// db操作2
// db操作3
return "SUCCESS";
}
7.3 可能在事务中,包含其他事务的操作时
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {FileNotFoundException.class,RuntimeException.class})
public Object transtionalManage() {
// db操作1
// db操作2
// db操作3
return "SUCCESS";
}
8 注解事务使用的注意事项
- 事务注解一定不要加在类上,即使Spring支持这么做。
- 事务注解加在方法上时,该方法必须为
public
不然事务失效。 - 当一个接口中除了有M个查询SQL语句,若再无其他SQL语句,此时没必要加事务注解。