当前位置:   article > 正文

Spring 事务

Spring 事务

本篇主要介绍Spring中事务相关的内容。

一、什么是事务?

在操作数据库中,我们通常会遇到这样一种场景,对于一组操作,要么全部成功,要么全部失败。在数据库中,专门对这样一组操作进行了定义,那就是事务。事务具有以下四个特性:

  • 原子性:对于一个事务,只有两个执行结果,要么成功要么失败。
  • 一致性:事务执行前后数据库都是处于一个正确的状态,也就是事务对数据库的影响是符合预期的
  • 持久化:事务的执行会直接对硬盘里的数据进行修改
  • 隔离性:不同事务之间相互隔离的程度

对于事务,通常有以下三个操作:

  • start(开启事务):开启一个事务
  • rollback(回滚事务):回滚一个事务(使数据库回到开启事务前的状态)
  • commit(提交事务):提交事务对于数据库的影响,真正对数据库进行修改 

二、Spring中的事务

在spring中,对于事务的操作的操作进行了重新封装,也就是我们可以在spring中使用事务相关的操作。

在 spring中使用事务,由于涉及到数据库相关的操作,需要引入mysql和mybatis等相关依赖,并配置与连接数据库相关的配置,具体配置项如下:

在spring中开使用事务有两种方式,一种是使用编程式事务,另一种则是使用声明式事务。具体如下:

编程式事务

使用编程式事务,通常需要使用到spring boot中内置的两个对象:

在这里有一个User类,其对应的表已在数据库中创建:

 

接下来我们通过编程式事务的方式来向数据库中插入 一条数据,代码如下:

通过 postman来访问一下这里的接口:

通过数据库可以发现事务已经生效了。

然后我们再来试试不提交的情况,代码如下:

 再通过postMan来访问一下:

 通过查询数据库可以发现事务并没有生效,admin并没有被插入

最后我们再来看一下回滚的情况

 通过postMan访问 通过数据库还是可以发现admin还是没有成功插入:

通过上述过程可以发现如果不提交事务,事务就不会真的生效,那这两次没有提交的操作,真的没有去操作数据吗,其实不然。例如,我们在去通过postMan来插入一个admin2,并且这次不回滚且提交事务。

然后我们来查看一下数据库:

 可以发现数据插入成功了,但id并不连续,由此可以推断出不提交事务或者回滚事务也是会去操作数据库的,但操作未提交或者回滚被抵消了。

通过前面的代码可以发现一个编程式事务存在一个问题,那就是写起来它麻烦了,因此,通常情况下我们对不使用编程式事务,而是使用另一种比较简单的声明式事务。

声明式事务

声明式事务的使用非常简单,只需要在方法上加一个注解即可开启事务(也可以在类上加,一味类中的全部方法都添加该注解),代码如下:

然后我们通过postMan来访问一下:

 通过日志可以发现事务提交了

并且数据库里的数据库也发生了更新

 由此可见声明式事务会自动帮我们创建事务并提交。那声明式事务何时会回滚呢?声明式事务通常会在发生异常时回滚,例如我们在代码里加上一个算术异常,具体如下:

访问一下看下效果:

 通过日志可以发现日志并没有被提交就被关闭了

然后我们再看数据库中的数据,可以发现,并没有我们的admin5

那是不是所有异常都会触发回滚呢?其实不然,声明式事务默认只有在发生运行时异常时和错误时会发生回滚 ,例如我们这里抛出一个IO异常(在方法后面进行声明):

通过postMan来访问:

通过日志可以发现,事务仍然提交了:

 我们也可以将发生触发回滚的异常通过rollbackfor来进行指定,具体如下:

这里我们设置成了所有异常都触发回滚,此时我们再以上面抛出IO异常的代码来新增数据,可以发现这次日志没有提交,而是进行了回滚

 有时候,对于发生的一些异常我们并不想让其回滚,也不想因为一个异常而修改整个发生回滚的异常的类型,此时我们可以考虑使用try-catch来将这个异常catch住,这样@Transcation就感知不到异常的发生也就无法回滚,如果我们只想在发生该异常时进行一些额外处理,我们可以在处理后进行手动回滚,具体如下:

通过这行代码也可以大致推断出@Transcation其实是为加了注解的方法创建了一个切面(Aspect),在切面里封装了前面编程式事务的内容从而实现事务。

三、事务的隔离级别

在数据库中,对于事务的隔离性按照从低到高的顺序进行了分级,分别为:

  • READ_UNCOMMITED(读未提交):在进行读操作时,可以读到其它事务还未提交的内容。
  • READ_COMMITED(读已提交):在进行读操作时,可以读到其它事务已经提交的内容。
  • REAPEATABLE_READ(可重复读):在进行读操作时只能读到开启事务前的数据库中的数据,对于已经提交的事务修改的数据是读不到,但能够感知到其它事务进行数据插入。
  • SERIALIZABLE(串行化):对所有事务进行排序,按顺序一个一个执行事务。是最高的隔离级别

在这四个隔离级别中前三个隔离级别会发生一些问题,具体如下:

  • 读未提交:由于该级别能读到其它事务未提交的内容,但其它事务可能会发生回滚,因此使事务读到了不该存在的脏数据,从而导致脏读问题。
  • 读已提交:该级别只能够读到其它事务已提交的内容,因此避免了脏读问题,但又引入了新的问题,由于能读到已提交的事务的内容,但如果两个已提交的事务都修改了同一个数据,就可能导致当前事务两次完全一样的读操作,读到的却是两种不同的数据,从而导致不可重复读的问题。
  • 可重复读:该级别由于只能读到事务开启前的内容,对事务开启后其它事务的修改是无感知的,因次也就避免了脏读和不可重复读的问题,但由于能够感知到其它数据库的插入操作,就会导致明明已经感知到数据库的变化了,但去查又查不到变化这种幻读的问题。

 而使用串行化由于并没有并发执行的事务,所以不存在上述问题。

在spring中也设置了事务的隔离级别,具体如下:

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 

对于隔离级别,我们同样可以在@Transcation注解里进行设置,例如我们想将隔离级别改为可重复读,代码如下:

 修改完成之后,通过该方法创建的事务的隔离级别就是可重复读了。如果我们不去自己设置隔离级别,则使用默认的隔离级别,也就是使用数据库中使用的隔离级别。

四、事务的传播机制

在使用事务时通常有可能会出现在开启的一个事务中又开启了一个事务的情况,例如,我们将前面的代码里在封装一层,具体如下:

封装的service层 

像这种事务在开启事务的情况称为事务的传播。

对于事务的传播spring 设置了不同的传播机制,具体如下:

  • Propageation.REQUIRED:默认的传播机制,如果当前已存在事务,就加入该事务,如果没有,则自己创建一个新事务
  • Propageation.SUPPORTS:如果当前存在事务,则加入该事务,如果当前不存在事务,已非事务的方式执行
  • Propageation.MANDATORY:强制性,如果当前存在事务,则加入该事务,如果当前没有事务则抛出异常
  • Propagation.REQUIRS_NEW:创建一个新的事务,如果当前存在事务,就把当前事务挂起,也就是说不管外部方法有没有开启事务,在这个机制下这个方法都会开启一个新的事务,新旧事务互不干扰
  • Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起(不用)
  • Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常
  • Propagation.NESTED:如果当前存在事务,则创建一个新的事务嵌套在事务中,如果不存在则新建一个事务。

 在spring中我们可以通过设置propagation参数来设置当前事务使用哪种传播机制,具体如下:

下面我们来演示一下在调用方法和被调用方法使用REQUIRED 、REQUIRED_NEW、NESTED这几种传播机制会发生的效果。然后我们来创建一个算术异常来控制是否回滚 。

首先我们来看一下不回滚的情况:
调用方法

被调用方法

 两个方法都开启了事务

被调用方法使用REQUIRED机制:

访问结果:

此时只有一个事务提交

使用REQUIRED_NEW:

 访问结果:

可以发现在原本事务的 基础上开启了一个新的事务.并且新旧事务都提交了。

使用 NETSTED

访问结果:

 可以发现这种机制只有一个提交,因为创建的新事务嵌套在了旧事务里,所以一起提交了

我们在来看一下发生回滚的情况

代码如下:

使用REQUIRED:

访问结果:

 可以发现事务回滚了且没有事务提交,因为在这种机制下新旧方法都使用的一个事务,一旦里面有一个方法里出现了异常,两个方法都得回滚。

使用REQUIRED_NEW的情况

访问结果

这种机制下由于两个方法处于不同的事务中,因此某一个事务中出现异常回滚,并不会影响另一个事务的运行,因此新事务回滚了,旧事务仍然提交了

使用NETSTED的情况

访问结果 

这种机制下新事务会嵌套在旧事务里,并保存开启新事务前的一个状态,当新事务发生回滚时,回滚到保存的这个状态点,而不是回到旧事务开启时的状态因此旧的事务不会被回滚而是继续提交了。

其它机制的描述比较直观,这里就不多赘述了。 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/490889
推荐阅读
相关标签
  

闽ICP备14008679号