拿下 Spring 事务

如题所述

第1个回答  2022-08-10

事务是数据库操作的最基本单元,是逻辑上的一组操作,要么都成功,要么都失败。是一个不可分割的工作单元。

事务具有 4 个特性:原子性、一致性、隔离性】持久性,简称为 ACID 特性。

举例:银行转账。小明给小红转 100 元。小明需要减少余额 100,小红需要增加余额 100。这是两个操作,需要一起成功。如果在小明转账成功之后发生了异常,就会出现小明 减 100 余额,但是小红并没有加 100 余额。就会造成钱丢失的情况。这是绝对不允许的。伪代码如下:

Spring 支持 2 种事务管理方式。

Spring 并不会直接管理事务,而是通过事务管理器对事务进行管理的。

Spring 提供了一个 PlatformTransactionManager 接口,这个接口被称为 Spring 的事务管理器,其源码如下:

该接口的源码很简单。这个接口针对不同的框架提供了不同的实现类,如下:

注意:这些实现类,需要导入对应的依赖才能看到。该接口中还有两个对象,分别是 TransactionDefinition 和 TransactionStatus。

方法说明如下:

方法说明如下:

方法说明如下:

有一个默认的抽象实现 AbstractTransactionStatus ,对 TransactionExecution、savepoint、SavepointManager 有具体的实现逻辑,代码有点多,就不贴了,但是非常好理解。 对 TransactionExecution、savepoint、SavepointManager 有具体的实现逻辑,代码有点多,就不贴了,但是非常好理解。 DefaultTransactionStatus 又继承了 AbstractTransactionStatus,继续进行扩充。

事务传播行为指的是,多事务方法之间进行调用时,这个过程中事务应该如何进行管理。例如,事务方法 A 在调用事务方法 B 时,B 方法是在调用者 A 方法的事务中运行呢,还是为自己开启一个新事务运行,这就是由事务方法 B 的事务传播行为决定的。

事务方法:能让数据库表数据发生改变的方法,例如新增、删除、修改数据的方法。

根据上面的描述,我们可以将行为分为三大类。

事务有一个特性为隔离性,多事务操作之间不会产生影响。但如果不考虑隔离性,则会产生三个读问题:脏读、不可重复读、虚(幻)读。

我们先来看看不使用事务会发生什么情况。创建名为 aopxml 的包。

在类中提供两个方法,一个张三增加金额,一个李四减金额。

项目结构如下:

控制台出现异常

再来查看数据库数据,可以发现张三的金额增加了,但是李四的金额没有减。银行哭死!!! 所以我们需要引入 Spring 事务,解决上述出现的问题。

配置的事务管理器实现为 DataSourceTransactionManager,是 JDBC 和 MBatis 的PlatformTransactionManager 接口实现。

配置事务通知,指定所需要使用的事务管理器以及指定事务作用的方法和该事务属性。

transaction-manage 参数的默认值就是 transactionManager,如果事务管理器 id 与其一致,则可以不用指定。 元素包含多个属性参数,可以为某个或某些方法(name 属性指定的方法)定义事务属性,如下表所示:

如上写法就对 transfer 方法进行了事务管理。就不会出现小明减少余额,而小红没有增加余额的情况,发生了异常就进行回滚。

使用注解方式就不会有上面如此琐碎的配置了。再重新创建名为 txannon 包,将 xml 方式使用到的 entity、dao、service 相关代码 copy 过来。

使用 EnableTransactionManagement 注解开启事务。

相当于tx:annotation-driven 标签。

可以不需要在配置切入点和切面了。

在需要添加事务的方法上添加 @Transactional 注解,表明该方法需要进行事务管理。

@Transactional 这个注解可以添加到类上面,也可以添加方法上面。如果把这个注解添加到类上面,这个类里面所有的方法都添加事务,如果把这个注解添加方法上面,则是为这个方法添加事务。

Transactional 这个注解里面可以配置很多事务相关参数。

基本用法会了,现在就来看看事务的传播行为,这是Spring事务中难以理解的一块,因为它的场景很多。

如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行

当前方法必须启动新事务,并在它自己的事务内运行。如果有事务正在运行,应该将它挂起。

reduce 方法行为修改为 Propagation.REQUIRES_NEW 。transfer 方法创建新事务,然后调用 reduce 方法,reduce 方法会将 transfer 方法的事务挂起,并创建属于 reduce 方法的事务。所以在该例子中会创建两个事务。由于有两个事务,那事务的回滚就出现了几种情况。

transfer 方法进行的操作不会回滚,reduce 方法的操作会回滚。

如果当前存在事务(主事务),则创建一个新事务作为当前事务的嵌套事务(子事务)来运行;如果当前没有事务,则该取值等价于 REQUIRED。

transfer 方法发生异常并回滚,会导致 reduce 方法 同时回滚。

transfer 方法进行的操作不会回滚,reduce 方法的操作会回滚。 注意 :transfer 方法需要进行 catch,不然 transfer 方法也会回滚。

主事务方法异常回滚时,会同时回滚子事务。而子事务可以单独异常回滚,可以不影响主事务和其他子事务(前提是需要处理掉子事务的异常)

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

由于 transfer 方法没有事务,在启动时就会抛出异常,如下:

如果有事务在运行,当前的方法就在这个事务内运行;如果当前没有事务,则以非事务的方式运行。

由于 transfer 方法没有事务,所以 reduce 方法也不会创建事务,发生了异常也不会进行回滚。

以非事务方式运行,如果当前存在事务,则把当前事务挂起。

transfer 方法有事务,但 reduce 方法传播行为是 NOT_SUPPORTED,所以会将 transfer 方法事务挂起,reduce 方法以非事务的方式运行。

所以图片例子会出现 transfer 方法进行的操作会回滚,reduce 方法的操作不会回滚。

以非事务方式运行,如果当前存在事务,则抛出异常。

由于 transfer 方法有事务,在启动时就会抛出异常,如下:

上面一直在说遇到异常就回滚,那是遇到所有异常都会回滚吗?不是的,默认情况下,Spring 事务只有遇到 RuntimeException 以及 Error 时才会回滚,在遇到检查型异常时是不会回滚的,比如 IOException、TimeoutException。

那如果想在发生检查型异常时也进行回滚呢,可以使用 rollbackFor 属性进行如下配置:

那同理,如果遇到某个异常,不想进行回滚,使用 noRollbackFor 属性配置如下: