1.什么是事务(为什么要有事务)
事务就是将一组操作封装成一个执行单元(封装到一起),要么一起成功,要么一起失败。
在打账的情景上,A向B转账200 元,A的账户-200.B的账号+200,但是如果是一些特殊情况,A的账号-200之后,但是B账号并没有加上这个200元子的话,就会出现很大的问题,而出现这一大问题的原因,就是没有将转账这个业务变成事务。这里也体现了事务的原子性一大特征。
2.事务的实现
手动、自动的实现俩种方式。手动就是编程的方式(手写代码),另一种自动就是声明式(注解)。
2.1 手动的实现:
2.2 注解实现
注解实现极为简单就是在方法前加上个 @Transactional 注解就ok 了,如果方法报错,就将所有已经进行的操作进行回滚,如果没有错误,那就正常执行不进行回滚。
@Transactional 可以⽤来修饰⽅法或类:
修饰⽅法时:需要注意只能应⽤到 public ⽅法上,否则不⽣效。推荐此种⽤法。
修饰类时:表明该注解对该类中所有的 public ⽅法都⽣效
2.3 @Transactional原理
@Transactional 是基于 AOP 实现的,AOP ⼜是使⽤动态代理实现的。如果⽬标对象实现了接⼝,默认情况下会采⽤ JDK 的动态代理,如果⽬标对象没有实现了接⼝,会使⽤ CGLIB 动态代理。
@Transactional 在开始执⾏业务之前,通过代理先开启事务,在执⾏成功之后再提交事务。如果中途遇到的异常,则回滚事务。
常见的参数设置:
3.事务的隔离级别
Spring事务管理框架支持数据库的四种隔离级别,可以在@Transactional注解中设置isolation属性来指定所需的隔离级别。具体隔离级别如下:
-
DEFAULT:使用默认隔离级别,由底层数据源管理器决定。
-
READ_UNCOMMITTED(读未提交):事务可以读取到其他事务修改但未提交的数据,可能会导致脏读问题的出现。
-
READ_COMMITTED(读已提交):事务只能读取到其他事务已经提交的数据,可以避免脏读问题,但仍可能会出现不可重复读和幻读问题。
-
REPEATABLE_READ(可重复读):事务在执行期间可以重复读取相同的记录,并保证读到的是事务开始时的状态。可以避免脏读和不可重复读问题,但仍可能会出现幻读问题。
-
SERIALIZABLE(串行化):事务需要串行执行,完全避免了并发问题的出现,但同时也影响了数据库的并发性能。
事务的隔离级别是指在并发环境下多个事务之间隔离的程度,主要是为了避免各种并发问题的出现,如脏读、不可重复读和幻读等。
常见的事务隔离级别有以下四种:
-
READ UNCOMMITTED(读未提交):事务可以读取到其他事务修改但未提交的数据,可能会导致脏读问题的出现。
-
READ COMMITTED(读已提交):事务只能读取到其他事务已经提交的数据,可以避免脏读问题,但仍可能会出现不可重复读和幻读问题。
-
REPEATABLE READ(可重复读):事务在执行期间可以重复读取相同的记录,并保证读到的是事务开始时的状态。可以避免脏读和不可重复读问题,但仍可能会出现幻读问题。
-
SERIALIZABLE(串行化):事务需要串行执行,完全避免了并发问题的出现,但同时也影响了数据库的并发性能。
下面详细介绍各种隔离级别的特点和应用场景:
-
READ UNCOMMITTED(读未提交):该隔离级别下,由于事务可以读取到其他事务尚未提交的数据,因此可能会出现脏读问题。该隔离级别通常不应用于生产环境,仅用于特定的性能测试或调试场景。
-
READ COMMITTED(读已提交):该隔离级别下,事务只能读取到其他事务已经提交的数据,可以避免脏读问题。但由于非锁定读取,依然可能会出现不可重复读和幻读问题。
-
REPEATABLE READ(可重复读):该隔离级别下,事务在执行期间可以重复读取相同的记录,并保证读到的是事务开始时的状态。可以避免脏读和不可重复读问题,但仍可能会出现幻读问题。MySQL的默认隔离级别即为REPEATABLE READ。
-
SERIALIZABLE(串行化):该隔离级别下,事务需要串行执行,完全避免了并发问题的出现。但同时也影响了数据库的并发性能,通常应用于对数据安全要求极高的场景,如银行、财务等系统。
● 脏读:⼀个事务读取到了另⼀个事务修改的数据之后,后⼀个事务⼜进⾏了回滚操作,从⽽导致第⼀个事务读取的数据是错误的
● 不可重复读:⼀个事务两次查询得到的结果不同,因为在两次查询中间,有另⼀个事务把数据修改了。
● 幻读:⼀个事务两次查询中得到的结果集不同,因为在两次查询中另⼀个事务有新增了⼀部分数据
4. 事务失效的场景
4.1 try catch
在try catch 将错误代码包裹之后,catch里面不加任何操作的话,是会出现问题的,出现这个现象的原因是,因为Spring中事务是会自动识别错误代码的,但是你如果加上了try catch的话就说明你是知道这里有错的,Spring就不会帮你识别了,也就不会自动的进行回滚操作了。
可以看到并没有实现事务回滚操作。
4.1解决方案:
1.你把这个错误抛出不就行了嘛 2.通过代码的方式手动回滚
第一种就是throw就行,第二种方式要讲一下:
4.2 非public修饰方法
SpringBoot事务失效的场景有哪些? | Javaᶜⁿ 面试突击 (javacn.site)
注解只能在public修饰的方法使用。
4.3 timeout超时
注解上是可以设置超时时间的,如果设置的时间比较小,而执行代码的时间大于设置的时间,就会导致本来要插入的数据没法正常插入。
4.4 数据库不支持事务
4.5 调用类内部的@Transactional方法
本来是要进行回滚的,但实际情况是报错,但是数据还是存入到数据库中的。
5. Spring事务的传播机制
Spring事务传播机制是指多个事务在互相调用的情况下,如何管理这些事务的提交和回滚。
Spring 提供了七种事务传播行为,分别是:
- REQUIRED(默认传播行为):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
- MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- REQUIRESNEW:创建一个新的事务,如果当前存在事务,则挂起该事务。
- NOTSUPPORTED:以非事务方式执行操作,如果当前存在事务,则挂起该事务。
- NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
- NESTED:如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则创建一个新的事务。
著作权归 JavaCN.site 所有 原文链接:https://www.javacn.site/spring/propagation.html
我们通过支持当前事务和不支持当前事务和嵌套事务分为三类:
5.1 支持当前事务
REQUIRED
当前有事务就加入,没有就创建新的。
SUPPORTS
有事务就加入,没有就以非事务的方式执行。
MANDATORY
有事务就加入,没有就抛异常
5.2 不支持当前事务
REQUIRESNEW
创建一个新事务,如果有事务就将这个事务挂起
NOTSUPPORTED
以非事务的方式执行,如果有事务就挂机这个事务
NEVER
以非事务的方式执行,如果有事务就抛异常
5.3 嵌套事务
NESTED
如果当前是有事务就嵌套进这个事务中,如果没有事务就创建一个新的事务。
我们创建一个业务场景来简单理解一下这里的事务传播机制:
我们先在controller层调用UserController类里面的UserService(service层),在user的service层有俩个操作:一是调用usermapper的add,另一个是调用了log的service层(进行logmapper的add)。
嵌套事务(NESTED)和加⼊事务(REQUIRED )的区别:
整个事务如果全部执⾏成功,⼆者的结果是⼀样的。
如果事务执⾏到⼀半失败了,那么加⼊事务整个事务会全部回滚;⽽嵌套事务会局部回滚,不会影
响上⼀个⽅法中执⾏的结果.
嵌套事务只所以能够实现部分事务的回滚,是因为事务中有⼀个保存点(savepoint)的概念,嵌套事务进⼊之后相当于新建了⼀个保存点,⽽滚回时只回滚到当前保存点,因此之前的事务是不受影响的