浅谈Spring中的事务及事务传播机制

一、为什么需要事务?

事务定义:将一组操作封装成一个执行单元(封装到一起),要么全部成功,要么全部失败

二、为什么要用事务?

比如转账分为两个操作:
第一步操作: A账户 - 100元。
第二步操作:B账户 + 100元。

如果没有事务,第一步操作执行成功了,第二步操作执行失败了,那么A账户平白无故的100元就“人间蒸发"了。而如果使用事务就可以解决这个问题,让这一组操作要么一起成功, 要么一起失败。

三、MySQL中的事务使用(回顾)

事务在MySQL有3个重要的操作:开启事务、提交事务、回滚事务,它们对应的操作命令如下:
(1)、开启事务
start transaction (mysql5 使用此命令)
begin transaction(mysql8 使用此命令)
(2)、提交事务
commit
(3)、回滚事务
rollback

四、SpringBoot项目中事务的实现

其实通过代码的方式实现事务,还是通过注解的方式实现声明式事务,就好比我们现实生活中的手动挡汽车以及自动挡汽车。

事务管理器 dataSourceTransactionManager,这个对象属于内置对象,随着Spring的启动而启动。这个内置对象就是Spring约定大于配置的体现,还体现出了Spring的自动注入(自动装配??)

4.1、通过代码的方式手动实现事务

模板:

@RestController
public class UserController{
	@Resource
	private UserService userService;
	//JDBC 事务管理器
	@Resource
	private DataSourceTransactionManager dataSourceTransactionManager;
	//定义事务属性
	@Resource
	private TransactionDefinition transactionDefinition;
	@RequestMapping("/save")
	public Object save(User user){
	//开启事务  transactionStatus:事务
	TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
	//插入数据库
	int result = userService.save(user);
	//提交事务
	dataSourceTransactionManager.commit(transactionStatus)//回滚事务
	//dataSourceTransactionManager.rollback(transactionStatus);
	return result;
	}
}

项目例子:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

其实通过代码手动实现事务有5个步骤:
两个关键对象:
在这里插入图片描述

三个关键命令:
在这里插入图片描述

4.2、通过注解的方法实现声明式事务

通过注解 @Transactional 实现。
(1)、该注解可以添加在类上或者方法上,添加在类上说明当前类里的所有public方法需要自动的提交和开启事务,添加在方法上说明当前方法需要自动的提交和开启事务。这个@Transactional 注解加在非public方法上是不生效的。
(2)、在方法执行前自动开启事务,在方法执行完(没有任何异常),会自动提交事务,但是如果方法在执行期间出现异常,则会回滚事务。
在这里插入图片描述
(3)、使用注解实现声明式事务时,方法中有异常,但是如果方法中使用了try catch,那么即使方法执行过程中出现异常,事务也不会回滚
在这里插入图片描述
在这里插入图片描述
那为什么代码中加了 try catch 之后,程序出现异常,事务也不会自动回滚呢??其实是因为代码中加了try catch ,Spring框架会自动认为开发者已经发现了程序的异常了,此时Spring框架就不会再处理了,而是会交给开发者自己来处理,因此事务就不会自动回滚了。

2种办法解决代码中加了try catch,导致事务不会回滚的问题:
(1)、将异常继续抛出:(不建议)
在这里插入图片描述
(2)、使用代码手动回滚事务
第一种办法我们捕获了异常,但是又把异常抛出去了,多少有点在做无用功了,所以大多数是使用第2种方法。

捕获到的异常该打印还是打印出来,然后再使用事务切面手动回滚事务。使用事务切面先得到当前的事务,然后再将事务进行回滚:
在这里插入图片描述

一般开发中大部分还是使用注解实现声明式的事务,因为使用代码手动实现事务是一件比较麻烦的事情。

但是一些情况下还是要使用代码手动实现事务,比如一些Servlet项目上,由于注解@Transactional 是由Spring框架提供的,因此Servlet是没办法使用的。

4.3、注解@Transactional的参数

在这里插入图片描述
事务隔离级别 isolation 有什么作用呢??可以解决MySQL中的脏读、幻读、不可重复读问题

其实Spring中有事务是依托mysql中的事务去实现的,他们都是互通的。

五、注解 @Transactional工作原理

@Transactional 是基于AOP实现的,AOP又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用JDK动态代理,如果目标对象没有实现接口,会使用CGLIB动态代理。那怎么验证这个事呢?
在这里插入图片描述
执行程序后网页显示的报错,出现了CGLIB:
在这里插入图片描述
那为什么会出现一堆 CGLIB??是因为事务执行时依赖AOP思想,而AOP是使用CGLIB的动态代理,动态代理在执行程序时报错了:
在这里插入图片描述

@Transactional 在开始执行业务之前,通过代理先开启事务,在目标方法执行成功之后再提交事务。如果中途遇到异常则回滚事务。

@Transactional 实现思路预览:
在这里插入图片描述
@Transactional 执行细节:
在这里插入图片描述

六、Spring中事务的隔离级别

6.1、事务特性

a.原子性
   一个事务(transaction) 中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback) 到事务开始前的状态,即相当于这个事务从来没有执行过一样。

b.一致性
   在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

c.持久性
   事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

d.隔离性
   数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。 事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable) 。

上面四个属性,可以简称为 ACID :
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)
持久性(Durability)

这4种特性中,只有 隔离性(隔离级别) 可以设置。

6.2、为什么要设置事务的隔离级别?

用来保障多个事务并发执行时更加可控,更符合操作者的预期。

那怎么样才是可控?

就像疫情爆发时,我们会将确诊感染人员直接隔离于酒店,将间接接触人员(接触感染者但未确诊人员)隔离在家里,即针对不同的人群,采取不同的隔离级别,这种为了预防传染病的隔离机制就和事务的隔离级别类似,都是采取某种行动让某个事件变得更可控而事务的隔离级别就是为了防止其他事务影响当前事务执行的一种策略。

6.3、Spring中5种事务隔离级别

Spring种含有5种事务隔离级别,前4种都和mysql中的4种事务隔离级别一致:
(1)、isolation.read_uncommitted:读未提交,可以读取到未提交的事务,存在脏读现象。
(2)、isolation.read_committed:读已提交,可以读取到已经提交的事务,解决了脏读,但存在不可重复读现象。
(3)、isolation.repeatable_read:可重复读(mysql默认事务隔离级别),解决了不可重复读,但存在幻读。
(4)、isolation.serializable:序列化(串行化),可以解决所有并发问题(脏读、幻读、不可重复读),但性能太低,很少使用。

多了一种事务隔离级别是:
isolation.default:以连接的数据库的事务隔离级别为主。

6.4、Spring中设置事务隔离级别

Spring中事务隔离级别设置可以通过 @Transactional 中的 isolation 属性进行设置,具体操作如下所示:
在这里插入图片描述

6.4.1、mysql中4种事务隔离级别(回顾)

(1)、 read uncommitted :读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,而未提交的数据可能会发生回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读。

(2)、read commited :读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据,因此它不会有脏读问题。但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间下进行相同的SQL查询,可能会得到不同的结果,这种现象叫做不可重复读

(3)、repeatable read:可重复读,是MySQL的默认事务隔离级别,它能确保同一事务多次查询的结果一致。但也会有新的问题,比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因)。明明在事务中查询不到这条信息,但自己就是插入不进去,这就叫幻读(Phantom Read)。
在这里插入图片描述

(4)、serializable:序列化(串行化),事务最高隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读、不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多。

在这里插入图片描述
脏读:一个事务读取到了另一个事务修改的数据之后,后一个事务又进行了回滚操作,从而导致第一个事务读取的数据是错误的。

不可重复读:一个事务两次查询得到的结果不同,因为在两次查询中间,有另一个事务把数据修改了。

幻读:一个事务两次查询中得到的结果集不同,因为在两次查询中另一个事务有新增了一部分数据。

七、Spring的事务的传播机制

7.1、什么是事务传播机制?

Spring事务传播机制定义了:多个包含了事务的方法在相互调用时,事务是如何在这些方法间进行传递的。

7.2、为什么需要事务传播机制?

事务隔离级别是保证多个并发事务执行的可控性,而事务传播机制是保证一个事务在多个调用方法间的可控性。

举个例子:疫情期间,有不同的隔离方式(譬如居家隔离或酒店隔离),这些隔离方式都是为了保证疫情的可控性。但其实每个人在隔离的过程中,还会经历许多环节,譬如酒店隔离这种方式,需要负责 人员运送、物品运送、消杀居住区域、定时核算检查、定时送餐送药等诸多环节,事务传播机制也是这样,就是要保证一个事务在传递过程中的可控性,对于本例来说就是保证每一个人员在隔离过程中是可控的。

事务隔离级别:解决的是多个事务同时调用一个数据库的问题。
在这里插入图片描述

在这里插入图片描述

其实上面这幅事务隔离级别的图有点:一个小项目,初期规模小业务简单,那么每一个小项目只需要一个开发人员单独完成就行了,但是事务传播机制这幅图,就像:一个小项目变成了一个业务复杂功能巨大的大项目时,这个大项目就会被拆分成多个模块,每个小模块由不同的开发人员负责。

7.3、Spring事务传播机制的7种模式

(1)、Propagation.required:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事目务,则创建一个新的事务。
在这里插入图片描述

(2)、Propagation.supports:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
在这里插入图片描述

(3)、Propagation.mandatory:(mandatory: 强制性)如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
在这里插入图片描述

(4)、Propagation.requires_new:表示创建一个新的事务, 如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.requireds_new修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
在这里插入图片描述

(5)、Propagation.not_supported:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
在这里插入图片描述

(6)、Propagation.never:以非事务方式运行,如果当前存在事务,则抛出异常。
在这里插入图片描述
(7)、Propagation.nested:(nested:嵌套、加入)如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于Propagation_required。
在这里插入图片描述

以上7种传播行为,可以根据是否支持当前事务分为以下3类:
在这里插入图片描述
上述演示的项目例子

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值