文章目录
- [1. Spring 事务管理简介](#1. Spring 事务管理简介)
- [2. @Transactional 注解属性信息](#2. @Transactional 注解属性信息)
- [3. @Transactional 简单使用](#3. @Transactional 简单使用)
- [4. @Transactional 使用注意事项](#4. @Transactional 使用注意事项)
-
- [4.1 正确指定事务回滚的异常类型](#4.1 正确指定事务回滚的异常类型)
-
- [4.1.1 Java 异常继承体系](#4.1.1 Java 异常继承体系)
- [4.2 `@Transactional` 注解应用在 `public` 方法或类上才有效](#4.2
@Transactional
注解应用在public
方法或类上才有效) - [4.3 正确设置 `@Transactional` 的 `propagation` 属性。](#4.3 正确设置
@Transactional
的propagation
属性。) -
- [4.3.1 事务的传播行为](#4.3.1 事务的传播行为)
- [4.3.2 `TransactionDefinition` 和 `Propagation`](#4.3.2
TransactionDefinition
和Propagation
)
- [5. Spring 事务失效的场景](#5. Spring 事务失效的场景)
-
- [5.1 @Transactional 注解使用在非 public 方法上,导致事务失效](#5.1 @Transactional 注解使用在非 public 方法上,导致事务失效)
- [5.2 propagation 参数设置错误,导致事务失效](#5.2 propagation 参数设置错误,导致事务失效)
- [5.3 rollbackFor 参数设置错误,导致事务失效](#5.3 rollbackFor 参数设置错误,导致事务失效)
- [5.4 在同一个类中,方法之间的相互调用导致事务失效](#5.4 在同一个类中,方法之间的相互调用导致事务失效)
- [5.5 异常被捕获导致事务失效](#5.5 异常被捕获导致事务失效)
- [5.6 数据库引擎不支持事务](#5.6 数据库引擎不支持事务)
1. Spring 事务管理简介
事务管理是应用系统开发中必不可少的一部分,Spring 为事务管理提供了丰富的功能。Spring 的事务管理分为编程式和声明式两种方式。声明式事务管理可以使业务代码逻辑不受污染,将业务逻辑和事务处理解耦。声明式事务也有两种方式:一种是在配置文件(xml)中做相关的事务规则声明,一种是基于注解 @Transactional
的方式。
事务是数据库的概念,Spring 本身是没有事务的,Spring 的事务是借助 AOP,通过动态代理的方式实现。
在我们要操作数据库的时候,实际是 Spring 通过动态代理进行功能拓展,在我们的代码操作数据库之前通过数据库客户端打开数据库事务,如果代码执行完毕没有异常信息或者是没有 Spring 要捕获的异常信息时,再通过数据库客户端提交事务;如果有异常信息或者是有 Spring 要捕获的异常信息,再通过数据库客户端程序回滚事务,从而控制数据库事务。
Spring 基于注解使用事务主要有两步:
- 在主配置类上声明
@EnableTransactionManagement
注解,表示开启声明式事务; - 在访问数据库的方法上添加
@Transactional
注解。
@Transactional
注解也可以用在类上,表示该类的所有公共方法都配置相同的事务属性。如果类和方法都配置了@Transactional
,应用程序会用方法级别的事务属性来管理事务。
2. @Transactional 注解属性信息
属性名 | 说明 |
---|---|
name | 当配置文件中有多个 TransactionManager时,可以用该属性指定选择哪个事务管理器 |
propagation | 事务的传播行为,默认值:REQUIRED |
isolation | 事务的隔离度,默认值:DEFAULT |
timeout | 事务的超时时间,默认值 -1。如果超过该时间但事务未完成,则回滚该事务 |
readOnly | 指定事务为只读事务,默认值 false。指定当前事务为只读事务后,当前事务不能进行写操作,否则报错 |
rollbackFor | 指定能够触发事务回滚的异常类型,如果有多个异常类型要指定,用逗号分隔 |
noRollbackFor | 抛出该参数指定的异常类型,不回滚事务 |
3. @Transactional 简单使用
如下有一个保存数据的方法,加上 @Transactional
注解,运行时抛出运行时异常之后,事务会自动回滚,数据不会插入数据库。
java
@Override
@Transactional
public boolean save() {
boolean res = stateRepository.save();
if(true) {
throw new RuntimeException();
}
return res;
}
-
如果去掉
@Transactional
注解,save()
就是一个普通的没有事务的方法,执行时还是会抛出异常,但是会插入数据。 -
如果加上
@Transactional
注解,但是在方法内使用try-catch
捕获异常,则事务不会回滚,数据会插入成功。java@Override @Transactional public boolean save() { boolean res = stateRepository.save(); try { if(true) { throw new RuntimeException(); } }catch (Exception e){ e.printStackTrace(); } return res; }
4. @Transactional 使用注意事项
4.1 正确指定事务回滚的异常类型
默认情况下,@Transactional
会在运行时异常及其子类异常时回滚事务,其他类型的异常 Spring 不会帮我们回滚事务。如果要在指定类型的异常时回滚事务,需要使用 rollbackFor
指定异常类型,如 @Transacrional(rollbackFor = Exception.class)
。
4.1.1 Java 异常继承体系
最顶层的异常类型是可抛出异常 Throwable
,有 Error
和 Exception
两个子类。
Error
表示严重的错误(如 OOM);Exception
分为 运行时异常(RuntimeException
)和非运行时异常;- 非运行时异常是检查异常(
checked exceptions
),编译阶段就能检查出来,编码时需要try-catch
或抛出异常。 Error
和运行时异常是非检查异常(unchecked exceptions
),编译阶段不会检查。
4.2 @Transactional
注解应用在 public
方法或类上才有效
只有 @Transactional
注解应用到 public
方法,才能进行事务管理。
4.3 正确设置 @Transactional
的 propagation
属性。
propagation
属性用于指定事务的传播行为,当 propagation
参数指定为 SUPPORTS
、NOT_SUPPORTED
、NEVER
时,事务将不会发生回滚。
4.3.1 事务的传播行为
事务的传播行为是指,多个事务方法相互调用时,事务如何在这些方法之间传播。
Spring 支持一下 7 种事务传播行为,定义在枚举类 Propagation
:
传播类型 | 说明 |
---|---|
REQUIRED(默认值) | 支持当前事务,如果有就加入当前事务中。如果当前方法没有事务,则新建一个事务 |
SUPPORTS | 支持当前事务,如果有就加入当前事务中。如果当前方法没有事务,则以非事务方式执行 |
MANDATORY | 支持当前事务,如果有就加入当前事务中。如果当前方法没有事务,就抛出异常 |
REQUIRES_NEW | 如果当前存在事务,就把当前事务挂起,然后新建一个事务;如果当前方法不存在事务,就新建一个事务 |
NOT_SUPPORTED | 以非事务方式执行,如果当前方法存在事务就挂起当前事务,执行完后恢复事务 |
NEVER | 以非事务方式执行,如果当前方法存在事务,则抛出异常 |
NESTED | 如果当前存在事务,则嵌套在当前事务中。如果当前没有事务,则新建一个事务自己执行。(嵌套事务使用保存点作为回滚点,内部事务回滚不影响外部事务的提交,但是外部事务的回滚会把内部事务一起回滚。) |
4.3.2 TransactionDefinition
和 Propagation
TransactionDefinition 和 Propagation 是 Spring 框架中两个不同的概念,它们在事务管理中起着不同的作用。
- TransactionDefinition 是一个接口,它定义了事务的基本属性,如隔离级别、传播行为、超时时间等。它允许您为事务设置各种属性,并将这些属性应用于事务管理器。
- Propagation是一个枚举,它定义了事务的传播行为。事务传播行为确定了当一个事务方法被另一个事务方法调用时会发生什么。它控制了事务如何与现有事务相关联,以及是否创建新的事务。
- 总之,TransactionDefinition 是关于定义事务属性的,而 Propagation 是关于控制事务传播行为的。它们在 Spring 框架的事务管理中发挥着重要作用,使开发人员能够精细地控制事务的行为。
5. Spring 事务失效的场景
5.1 @Transactional 注解使用在非 public 方法上,导致事务失效
5.2 propagation 参数设置错误,导致事务失效
propagation
参数设置为 SUPPORTS
、NOT_SUPPORTED
、NEVER
时,@Transactional
注解就不会产生效果。
5.3 rollbackFor 参数设置错误,导致事务失效
- Spring 默认在方法抛出未检查异常或 Error 时回滚事务,其他异常类型不会回滚;
- 如果要在方法抛出其他异常时也能回滚事务,需要
rollbackFor
参数指定异常类型。
5.4 在同一个类中,方法之间的相互调用导致事务失效
如下,在 StateService 中有两个保存方法:
java
@Override
@Transactional
public boolean save() {
save2();
boolean res = stateRepository.save();
if(true) {
throw new RuntimeException();
}
return res;
}
private boolean save2() {
StateDAO stateDAO = new StateDAO();
stateDAO.setWorkspaceKey("space1");
return stateRepository.saveState(stateDAO);
}
save()
方法首先调用了 save2()
方法,然后 save()
方法抛出异常,导致事务回滚,两条数据都不会插入数据库。
现在我们想实现:save()
方法抛异常不影响 save2()
方法插入数据。@Transactional
的 propagation
参数可以设置事务的传播行为,我们可以尝试给 save2()
方法设置 propagation = REQUIRES_NEW
去实现。如下:
java
@Override
@Transactional(propagation = Propagation.REQUIRED)
public boolean save() {
save2();
boolean res = stateRepository.save();
if(true) {
throw new RuntimeException();
}
return res;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
private boolean save2() {
StateDAO stateDAO = new StateDAO();
stateDAO.setWorkspaceKey("space1");
return stateRepository.saveState(stateDAO);
}
但是,运行之后,发现 save2()
方法的数据也没有插入数据库。
原因:
在默认的代理模式下,只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。在同一个类的两个方法直接调用,不会被 Spring 的事务拦截器,也就是说
save2()
方法的@Transactional
注解是失效的。
为解决该问题,可以新建一个 service 类,提供 save2()
方法。然后在 save()
方法中调用这个外部的 save2()
方法。
5.5 异常被捕获导致事务失效
事务通知只有捕获到了目标抛出的异常,才能进行回滚处理;如果目标自己处理掉异常,事务通知无法知悉。
如:
java
@Override
@Transactional
public boolean save() {
boolean res = planBaselineStateRepository.save();
try {
if(true) {
throw new RuntimeException();
}
}catch (Exception e){
e.printStackTrace();
}
return res;
}
5.6 数据库引擎不支持事务
- InnoDB 引擎支持事务,是 MySQL 默认的引擎;
- MyIsam 引擎不支持事务。