zl程序教程

您现在的位置是:首页 >  其他

当前栏目

透彻的掌握 Spring 中 @transactional 的使用

2023-04-18 12:54:02 时间

透彻的掌握 Spring 中 @transactional 的使用

事务管理是应用系统开发中必不可少的一部分。Spring 为事务管理提供了丰富的功能支持。

声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于 @Transactional 注解的方式。注释配置是目前流行的使用方式。

一、@Transactional 注解管理事务的实现步骤

使用 @Transactional 注解管理事务的实现步骤分为两步。

第一步,在 xml 配置文件中添加事务配置信息。

<tx:annotation-driven />

<bean id="transactionManager" 
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

第二步,将 @Transactional 注解添加到合适的方法上,并设置合适的属性信息。

@Transactional 注解的属性信息

属性名

说明

name

当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。

propagation

事务的传播行为,默认值为 REQUIRED。

isolation

事务的隔离度,默认值采用 DEFAULT。

timeout

事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

read-only

指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

rollback-for

用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。

no-rollback-for

抛出 no-rollback-for 指定的异常类型,不回滚事务。

除此以外,@Transactional 注解也可以添加到类级别上。当把 @Transactional 注解放在类级别时,表示所有该类的公共方法都配置相同的事务属性信息。

方法级别的事务属性信息会覆盖类级别的相关配置信息。

@Transactional 注解的标注于类上:

@Transactional(propagation= Propagation.SUPPORTS,readOnly=true)
@Service(value ="employeeService")
public class EmployeeService

二、注解方式的事务使用注意事项

当您对 Spring 的基于注解方式的实现步骤和事务内在实现机制有较好的理解之后,就会更好的使用注解方式的事务管理,避免当系统抛出异常,数据不能回滚的问题。

1. 正确的设置@Transactional 的 propagation 属性

本来期望目标方法进行事务管理,但若是错误的配置这三种 propagation,事务将不会发生回滚。

  1. TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  2. TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  3. TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

2. 正确的设置 @Transactional 的 rollbackFor 属性

默认情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常)或者 Error,则 Spring 将回滚事务;除此之外的异常,Spring 都不会回滚事务!

如果在事务中抛出其他类型的异常,并期望 Spring 能够回滚事务,可以指定 rollbackFor。例:

@Transactional(propagation= Propagation.REQUIRED, rollbackFor= MyException.class)

若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。

3. @Transactional 只能应用到 public 方法才有效

只有 @Transactional 注解应用到 public 方法,才能进行事务管理。

Spring AOP 会检查目标方法的修饰符是不是 public,若不是 public,就不会获取 @Transactional 的属性配置信息,最终会造成不会用 TransactionInterceptor 来拦截该目标方法进行事务管理。

4. 避免 Spring 的 AOP 的自调用问题

在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。

若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法,有 @Transactional 注解的方法的事务被忽略,不会发生回滚。

@Service
public class OrderService {
    private void insert() {
        insertOrder();
    }

    @Transactional
    public void insertOrder() {
        //insert log info
        //insertOrder
        //updateAccount
    }
}

insertOrder 尽管有@Transactional 注解,但它被内部方法 insert 调用,事务被忽略,出现异常事务不会发生回滚。

2. 三大问题

数据库事务的正确执行的 4 个基本要素是 原子性(「A」tommicity)、一致性(「C」onsistency)、隔离性(「I」solation) 和 持久性(「D」urability)。

原子性 : 整个事务中的操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节,仅完成一部分。

一致性 : 指一个事务可以改变数据库状态。食物必须始终保持系统处于一致状态,不管任何给定时间有多少并发事务。

隔离性 : 之两个事务之间的隔离级别。

持久性 : 在事务完成以后,该事务对数据库所做的更改便持久保存在数据库中,并不会被回滚。

三大问题从 严重 到 轻度 以此如下:

「脏读问题」

时刻

事务一(老公)

事务二(老婆)

T1

查询余额,显示10k

——

T2

——

查询余额,显示 10k

T3

——

网购 1千,显示 9k

T4

请客吃饭开销 1k,显示余额 8k

——

T5

提交事务

——

T6

——

回滚事务

T7

——

最终余额 8k

所谓脏读,指的就是读到了“脏”数据,即一个事务读取到了另一个事务未提交的数据。

「不可重复读问题」

时刻

事务一(老公)

事务二(老婆)

T1

查询余额,显示10k

——

T2

——

查询余额,显示 10k

T3

——

网购,开销 1k,余额 9k

T4

请客吃饭,预计开销 2k

——

T5

——

网购,开销 8k,余额 1k

T6

——

提交事务

T7

吃完买单,显示余额1k,不够付账。

——

不可重复读,指的是理论上的同一条数据,重复读取,居然会不一样,不具备可重复性。

「幻读问题」

时刻

事务一(老公)

事务二(老婆)

T1

——

查询信用卡消费记录,显示 10 条记录

T2

网购

——

T3

提交事务

——

T4

——

打印消费记录,有11条记录

幻读,和不可重复读类似,第二次读取的数据相较于第一次居然发生了变化,仿佛看到了幻觉。

不可重复读 和 幻读 有一定的相似性,都是指(在本人未改变的情况下)第二次读取的数据,与第一次读取结果不一样。不过它们描述的侧重点(及造成的影响程度)不一样。

  • 不可重复读问题,强调的是某一条数据的内容在“我”两次读取间,发生了改变。(因为 update 语句)
  • 幻读问题,强调的整个数据的数据总量,在“我”两次读取间,发生了改变。(因为 insert / delete 语句)
  • 幻读问题 造成的危害要小于 不可重复读问题。

3. 四个隔离级别

隔离级别表示:「当“我”操作这张表时,“其他人”对这张表还有多大的操作权限」 。“我”的隔离级别越高,其他人的权利就越小,那么“他”要执行他想要执行的操作而没有权限时,那就只能 「等」“我”操作完 。

数据库领域有四个隔离级别(注意,这并非 Java 中特有的概念),针对于上述三大问题,四个隔离级别,从 解决不了任何问题 到 解决所有问题,每一级多解决一个问题。

隔离级别

解决问题

备注

READ_UNCOMMITTED

解决不了任何问题

——

READ_COMMITTED

可以解决脏读问题

——

REPEATABLE_READ

可以解决不可重复读问题

包括解决脏读问题

SERIALIZABLE

解决幻读问题

包括解决不可重复读和脏读问题

4. 传播机制

传播行为

含义

备注

REQUIRED

当方法调用时,如果不存在当前事务,那么就创建事务;如果之前已经存在了事物,那么就沿用之前的事务。

默认值

SUPPORTS

当方法调用时,如果不存在当前事务,就不启用事务;如果当前启用事务,那么就沿用当前事务。

——

MANATORY

方法必须在事务内运行。

如果不存在当前事务,则直接抛出异常。

REQUIRES_NEW

无论是否存在当前事务,方法都会在新的事务中运行

总是开启一个新事务,执行本方法。

NOT_SUPPORTED

不支持事务,不存在当前事务也不会创建新事务;如果存在当前事务则挂起它,直到方法结束后才恢复当前事务

适用于那些不支持事务的数据库和SQL语句

NEVER

不支持事务。

MANATORY 的“反面”,如果存在当前事务,就直接抛出异常。

NESTED

嵌套事务。REQUIRES_NEW 的高级版

支持当前事务中使用保存点(savepoint),可以回滚到保存点;如果当前事务没有保存点,则完全等价于 REQUIRES_NEW

毫无疑问,最常用的是 REQUIRED,其次是 REQUIRES_NEW 。它们是最常见的业务处理方式,其它方式都是用于处理特定的业务。

传播机制本质上描述的是:在一个整体行为中,一个部分行为的失败,会不会对整体行为造成影响,以及造成何种影响。