spring事务

事务简介

事务在逻辑上是一组操作,要么执行,要不都不执行。主要是针对数据库而言的,比如说 MySQL。
为了保证事务是正确可靠的,在数据库进行写入或者更新操作时,就必须得表现出 ACID 的 4 个重要特性:

  1. 原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  2. 一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。
  3. 事务隔离(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
  4. 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

需要格外注意的是:事务能否生效,取决于数据库引擎是否支持事务,MySQL 的 InnoDB 引擎是支持事务的,但 MyISAM 就不支持。

spring的事务支持

Spring 支持两种事务方式,分别是编程式事务和声明式事务,后者最常见,通常情况下只需要一个 **@Transactional **就搞定了(代码侵入性降到了最低),就像这样:

在这里插入图片描述

编程式事务

编程式事务是指将事务管理代码嵌入嵌入到业务代码中,来控制事务的提交和回滚。
你比如说,使用 TransactionTemplate 来管理事务:
在这里插入图片描述
再比如说,使用 TransactionManager 来管理事务:
在这里插入图片描述
就编程式事务管理而言,Spring 更推荐使用 TransactionTemplate。
在编程式事务中,必须在每个业务操作中包含额外的事务管理代码,就导致代码看起来非常的臃肿,但对理解 Spring 的事务管理模型非常有帮助。

声明式事务

声明式事务将事务管理代码从业务方法中抽离了出来,以声明式的方式来实现事务管理,对于开发者来说,声明式事务显然比编程式事务更易用、更好用。
当然了,要想实现事务管理和业务代码的抽离,就必须得用到 Spring 当中的AOP,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
声明式事务虽然优于编程式事务,但也有不足,声明式事务管理的粒度是方法级别,而编程式事务是可以精确到代码块级别的。
@Transactional注解:

它是声明式事务管理编程中使用的注解,放在接口实现类或接口实现方法上,并且只对public方法才起作用。只读的接口不需要事务管理,防止影响系统性能。

@Transactional 实质是使用了 JDBC 的事务来进行事务控制的,实现原理:

事务开始时,通过AOP机制,生成一个代理connection对象,并将其放入 DataSource 实例的某个与 DataSourceTransactionManager 相关的某处容器中。在接下来的整个事务中,客户代码都应该使用该 connection 连接数据库,执行所有数据库命令。[不使用该 connection 连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚](物理连接 connection 逻辑上新建一个会话session;DataSource 与 TransactionManager 配置相同的数据源)

事务结束时,回滚在第1步骤中得到的代理 connection 对象上执行的数据库命令,然后关闭该代理 connection 对象。(事务结束后,回滚操作不会对已执行完毕的SQL操作命令起作用)

要使用事务,我们只需要在需要事务的类或方法上使用@Transactional 注解即可,当注解在类上的时候意味着此类的所有public方法都是开启事务的。被注解的方法都成为一个事务整体,同一个事务内共享一个数据库连接,所有操作同时发生。如果在事务内部执行过程中发生了异常,则事务整体会自动进行回滚。

事务管理模型

在这里插入图片描述
该接口有两个子接口,分别是编程式事务接口ReactiveTransactionManager 和声明式事务接口 PlatformTransactionManager。我们来重点说说 PlatformTransactionManager,该接口定义了 3 个接口方法:
在这里插入图片描述
通过 PlatformTransactionManager 这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
参数 TransactionDefinition 和 @Transactional 注解是对应的,比如说 @Transactional 注解中定义的事务传播行为、隔离级别、事务超时时间、事务是否只读等属性,在 TransactionDefinition 都可以找得到。
返回类型 TransactionStatus 主要用来存储当前事务的一些状态和数据,比如说事务资源(connection)、回滚状态等。
TransactionDefinition如下:
在这里插入图片描述
Transactional注解如下:
在这里插入图片描述
@Transactional 注解中的 propagation 对应 TransactionDefinition 中的 getPropagationBehavior,默认值为 Propagation.REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED)。
@Transactional 注解中的 isolation 对应 TransactionDefinition 中的 getIsolationLevel,默认值为 DEFAULT(TransactionDefinition.ISOLATION_DEFAULT)。
@Transactional 注解中的 timeout 对应 TransactionDefinition 中的 getTimeout,默认值为TransactionDefinition.TIMEOUT_DEFAULT。
@Transactional 注解中的 readOnly 对应 TransactionDefinition 中的 isReadOnly,默认值为 false。

事务传播行为

1.REQUIRED
@Transactional(propagation = Propagation.REQUIRED)
spring中的默认事务传播行为就是它。如果业务方法执行时已经在一个事务中,则加入当前事务,

否则重新开启一个事务。外层事务提交了,内层才会提交。内/外只要有报错,他俩会一起回滚。

只要内层方法报错抛出异常,即使外层有try-catch,该事务也会回滚。

因为内外层方法在同一个事务中,内层只要抛出了异常,这个事务就会被设置成rollback-only,即使外层try-catch内层的异常,该事务也会回滚。

2.REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)

支持事务。每次都是创建一个新事物,如果当前已经在事务中了,会挂起当前事务。内层事务结束,内层就提交了,不用等着外层一起提交。

外层报错回滚,不影响内层。内层报错回滚,外层try-catch内层的异常,外层不会回滚。

内层报错回滚,然后又会抛出异常,外层如果没有捕获处理内层抛出来的这个异常,外层还是会回滚的。

3.NESTED
@Transactional(propagation = Propagation.NESTED)

支持事务。如果当前事务存在,那么在嵌套的事务中执行,内层事务结束,要等着外层一起提交。如果当前事务不存在,则表现跟REQUIRED一样。

这个直接说,如果外层报错回滚,内层也会跟着回滚。

如果只是内层回滚,不影响外层。这个内层回滚不影响外层的特性是有前提的,否则内外都回滚。

内层是NESTED模式下,外层要try-catch内层的异常,外层才不会回滚。而内层是REQUIRED模式的话,即使外层try-catch内层异常,外层同样会回滚的。

4.SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)

支持事务。当前有事务就支持使用当前事务,若当前不存在事务,以非事务的方式执行。内层事务结束,要等着外层一起提交。

5.MANDATORY
@Transactional(propagation = Propagation.MANDATORY)

支持事务,如果业务方法执行时已经在一个事务中,则加入当前事务。否则抛出异常。内层事务结束,要等着外层一起提交。

6.NOT_SUPPORTED
@Transactional(propagation = Propagation.NOT_SUPPORTED)

不支持事务,以非事务的方式执行,若当前存在事务,则把当前事务挂起,等方法执行完毕后,事务恢复进行。

若A是事务执行,B(NOT_SUPPORTED非事务执行)B在A尚未提交前再操作同一条记录,会产生死锁,A、B不可操作同一条记录。

7.NEVER
@Transactional(propagation = Propagation.NEVER)

不支持事务。如果当前已经在一个事务中了,抛出异常。

事务并发执行带来的问题

1、脏读

脏读就是指当A事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,B事务也访问这个数据,然后使用了这个数据。这时候如果事务A回滚,那么B事务读到的数据是不被承认的。

2、不可重复读(重点在修改,体现在值不同)

指在A事务内,多次读同一数据。在A事务还没有结束时,B事务也访问该同一数据。那么,在A事务中的两次读数据之间,由于B事务的修改,那么A事务两次读到的的数据可能是不一样的。这样就发生了在A事务内两次读到的数据是不一样的。

3、幻读(重点在增加或删除,体现在记录数不同)

是指当事务不是独立执行时发生的一种现象,例如A事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,B事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作A事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

事务隔离级别

当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性。隔离级别越高,数据库的并发性能就越差。
事务之间有不同的隔离级别,不同的隔离级别可以解决并发事务所带来的问题。在SpringBoot中,事务的隔离级别定义为@Transactional 注解中的属性@Transactional(isolation = Isolation.DEFAULT)

int ISOLATION_DEFAULT = -1;//默认的隔离级别,使用当前数据库的隔离级别。会是后面四种隔离级别的其中一种

int ISOLATION_READ_UNCOMMITTED = 1;//读未提交;
int ISOLATION_READ_COMMITTED = 2; //读已提交;
int ISOLATION_REPEATABLE_READ = 4; //重复读;
int ISOLATION_SERIALIZABLE = 8; //串行化

第一种隔离级别:Read uncommitted(读未提交)
在该隔离级别下,所有事务都可以看到其它未提交事务的执行结果。即在该级别下,事务的修改即便没有提交,对其他事务也都是可见的,可能出现脏读、不可重复读、幻读。

第二种隔离级别:Read committed(读提交)
该隔离级别满足了隔离的简单定义,一个事务只能看见已经提交事务所做的改变。这是Oracle数据库默认的事务隔离级别。避免了脏读,可能出现不可重复读、幻读。

第三种隔离级别:Repeatable read(可重复读取)
可以确保同一个事务在多次读取同样的数据时,返回同样的结果。这是MySQL数据库默认的事务隔离级别。这样避免了不可重复读和脏读,但是有时可能会出现幻读。

第四种隔离级别:Serializable(可序化)
它通过强制事务排序,使事务一个一个的进行,事务之间不可能再存在相互冲突,从而解决幻读问题。

事务的超时时间

事务超时**timeout **,也就是指一个事务所允许执行的最长时间,如果在超时时间内还没有完成的话,就自动回滚。
假如事务的执行时间格外的长,由于事务涉及到对数据库的锁定,就会导致长时间运行的事务占用数据库资源。

事务的只读属性

事务的只读属性readOnly, 如果一个事务只是对数据库执行读操作,那么该数据库就可以利用事务的只读属性,采取优化措施,适用于多条数据库查询操作中。
为什么一个查询操作还要启用事务支持呢?
这是因为 MySql(innodb)默认对每一个连接都启用了 autocommit 模式,在该模式下,每一个发送到 MySql 服务器的 SQL 语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务。
那如果我们给方法加上了 @Transactional 注解,那这个方法中所有的 SQL 都会放在一个事务里。否则,每条 SQL 都会单独开启一个事务,中间被其他事务修改了数据,都会实时读取到。
有些情况下,当一次执行多条查询语句时,需要保证数据一致性时,就需要启用事务支持。否则上一条 SQL 查询后,被其他用户改变了数据,那么下一个 SQL 查询可能就会出现不一致的状态。

事务的回滚策略

**回滚策略rollbackFor **,用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。默认情况下,事务只在出现运行时异常(Runtime Exception)时回滚,以及 Error,出现检查异常(checked exception,需要主动捕获处理或者向上抛出)时不回滚。

如果你想要回滚特定的异常类型的话,可以这样设置:
在这里插入图片描述
可以通过添加@Transactional(rollbackFor=Exception.class) 来对所有异常进行回滚。

事务的不回滚策略

**不回滚策略noRollbackFor **,用于指定不触发事务回滚的异常类型,可以指定多个异常类型。

@Transaction失效场景

检查你方法是不是public的(只有Public方法才能开启事务);
检查你的异常类型是不是unchecked异常(抛出的是unchecked类型(RuntimeException级别)的异常,默认事务回滚不会生效。);
检查数据库引擎是否支持事务,如果是MySQL,注意表要使用支持事务的引擎,比如innodb,如果是myisam,事务是不起作用的;
检查异常是不是被catch住了(当异常被捕获后,并且没有再抛出,那么事务是不会回滚的。);
检查是否在事务中新开启了一个线程(因为spring实现事务的原理是通过ThreadLocal把数据库连接绑定到当前线程中,新开启一个线程获取到的连接就不是同一个了。);
检查类有没有被Spring管理(方法被标注了@Transactional,但是类没有注解,没有被Spring管理也不会生效);
类内部未开启事务的方法调用了开启事务的方法;
rollbackFor指定事务回滚的异常类型;
同个类中的调用被@transaction修饰的方法,会失效,因为只有当事务方法被当前类以外的代码调用,才会由spring生成的代理对象来管理。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值