3️⃣事务回滚
上一节概述了如何在应用程序中以声明的方式为类(通常是服务层类)指定事务设置的基础知识。 本节描述如何以简单的声明式方式控制事务的回滚。
重点:
在其默认配置中,Spring框架的事务基础结构代码只在运行时、未检查的异常情况下标记事务进行回滚。 也就是说,当抛出的异常是’ RuntimeException ‘的实例或子类时。 (默认情况下,’ Error '实例也会导致回滚)。 事务方法抛出的已检查异常不会导致默认配置的回滚。
您还可以准确地配置哪些“Exception”类型将事务标记为回滚。 下面的XML代码片段演示了如何为一个已检查的、特定于应用程序的“Exception”类型配置回滚:
<tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/> <tx:method name="*"/> </tx:attributes> </tx:advice>
如果您不想在抛出异常时回滚事务,您还可以指定“无回滚规则”。 下面的例子告诉Spring框架的事务基础架构,即使面对InstrumentNotFoundException`,也要提交相应的事务:
<tx:advice id="txAdvice"> <tx:attributes> <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/> <tx:method name="*"/> </tx:attributes> </tx:advice>
当Spring Framework的事务,捕获异常并参考配置的回滚规则来决定是否将事务标记为回滚时,最强匹配规则胜出。 因此,在以下配置的情况下,除了InstrumentNotFoundException之外的任何异常都会导致事务的回滚:
<tx:advice id="txAdvice"> <tx:attributes> <tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/> </tx:attributes> </tx:advice>
4️⃣<tx:advice/> 设置
本节总结了通过使用<tx:advice/>
标记可以指定的各种事务设置。 默认的<tx:advice/>
设置是:
传播行为是REQUIRED。
隔离级别为 DEFAULT。
事务处于可读写状态。
事务超时默认为底层事务系统的默认超时,如果不支持超时,则为none。
任何RuntimeException触发回滚,而任何选中的Exception不会。
您可以更改这些默认设置。 下表总结了嵌套在<tx:advice/>和<tx:attributes/>标签中的<tx:method/>标签的各种属性:
属性 | Required? | 默认值 | 描述 |
name |
Yes | 要与事务属性相关联的方法名。 通配符()字符可用于将相同的事务属性设置与许多方法相关联(例如,’ get ‘、’ handle* ‘、’ on*Event '等等)。 |
propagation |
No | REQUIRED |
事务传播行为。 |
isolation |
No | DEFAULT |
事务隔离级别。 仅适用于’ REQUIRED ‘或’ REQUIRES_NEW '的传播设置。 |
timeout |
No | -1 | 事务超时(秒)。 仅适用于传播’ REQUIRED ‘或’ REQUIRES_NEW '。 |
read-only |
No | false | 读写事务与只读事务。 只适用于’ REQUIRED ‘或’ REQUIRES_NEW '。 |
rollback-for |
No | 触发回滚的“Exception”实例的逗号分隔列表。 例如,“com.foo.MyBusinessException, ServletException”。 |
no-rollback-for |
No | 不触发回滚的“Exception”实例的逗号分隔列表。 例如,“com.foo.MyBusinessException, ServletException”。 |
5️⃣使用 @Transactional
除了事务配置的基于xml的声明性方法外,还可以使用基于注解的方法。 直接在Java源代码中声明事务语义使声明更接近受影响的代码。 不存在过多耦合的危险,因为要以事务方式使用的代码几乎总是以这种方式部署的。
使用’ @Transactional '注解所提供的易用性可以用一个示例进行最好的说明,下面的文本将对此进行解释。 考虑以下类定义:
// the service class that we want to make transactional @Transactional public class DefaultFooService implements FooService { @Override public Foo getFoo(String fooName) { // ... } @Override public Foo getFoo(String fooName, String barName) { // ... } @Override public void insertFoo(Foo foo) { // ... } @Override public void updateFoo(Foo foo) { // ... } }
在如上所述的类级别上使用,注解指示声明类(及其子类)的所有方法的默认值。 或者,每个方法都可以单独注解。 请注意,类级注解并不适用于类层次结构中的祖先类; 在这种情况下,继承的方法需要在本地重新声明,以便参与子类级别的注解。
当上述POJO类被定义为Spring上下文中的bean时,您可以通过“@Configuration”类中的“@EnableTransactionManagement”注解使bean实例具有事务性。
在XML配置中,<tx:annotation-driven transaction-manager="txManager"/>标签提供了类似的便利:
<!-- from the file 'context.xml' --> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- this is the service object that we want to make transactional --> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- enable the configuration of transactional behavior based on annotations --> <!-- a TransactionManager is still required --> <tx:annotation-driven transaction-manager="txManager"/> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- (this dependency is defined somewhere else) --> <property name="dataSource" ref="dataSource"/> </bean> <!-- other <bean/> definitions here --> </beans>
如果要连接的’ TransactionManager ‘的bean名称为’ TransactionManager ‘,则可以省略’ tx:annotation-driven/ ‘标记中的’ transaction-manager ‘属性。 如果您想要依赖注入的’ TransactionManager ’ bean有任何其他名称,您必须使用’ transaction-manager '属性,如前面的示例所示。
注意:
(1)当你在Spring的标准配置中使用事务性代理时,你应该【只把@Transactional注解应用到public 的方法上】。如果使用’ @Transactional ‘注解’ protected ‘、’ private’或包可见的方法,则不会引发错误,但已注解的方法中事务不会生效。
(2)Spring团队建议只使用【 @Transactional】注解来注解具体的类(以及具体类的方法),而不是注解接口。当然,您可以将’ @Transactional '注解放在接口(或接口方法)上,但只有当您使用基于接口的代理时,它才会发挥作用。Java注解的事实并不意味着继承接口,如果使用基于类的代理(proxy-target-class = " true ")或weaving-based方面('模式=“aspectj”),事务设置不认可的代理和编织的基础设施,和对象不是包在一个事务代理。
(3)在代理模式(这是默认的)中,只有通过代理进入的外部方法调用会被拦截。这意味着,即使被调用的方法被标记为【@Transactional】,自调用(实际上是目标对象中的一个方法调用目标对象的另一个方法)在运行时也不会产生事务。
6️⃣@Transactional的设置
【@Transactional】注解是元数据,它指定接口、类或方法必须具有事务性语义(例如,“在调用此方法时启动一个全新的只读事务,暂停任何现有事务”)。 默认的【@Transactiona】设置如下:
传播设置为 PROPAGATION_REQUIRED。
隔离级别为 ISOLATION_DEFAULT。
事务处于可读写状态。
事务超时默认为底层事务系统的默认超时,如果不支持超时,则为none。
任何RuntimeException触发回滚,而任何选中的Exception不会。
您可以更改这些默认设置。 下表总结了@Transactional注解的各种属性:
特质 | 类型 | 描述 |
value | String | 指定要使用的事务管理器的可选限定符。 |
propagation | enum: Propagation | 可选的传播环境。 |
isolation | enum: Isolation | 可选的隔离级别。 仅适用于REQUIRED 或 REQUIRES_NEW的传播值。 |
timeout | int(以秒为粒度) | 可选的事务超时。 仅适用于 REQUIRED 或 REQUIRES_NEW的传播值。 |
readOnly | boolean | 读写事务与只读事务。 只适用于 REQUIRED 或 REQUIRES_NEW的值。 |
rollbackFor | Class 对象的数组,它必须派生自Throwable. | 必须导致回滚的异常类的可选数组。 |
rollbackForClassName | 类名数组。 类必须派生自Throwable. | 必须导致回滚的异常类名称的可选数组。 |
noRollbackFor | Class 对象的数组,它必须派生自Throwable. | 不能导致回滚的异常类的可选数组。 |
noRollbackForClassName | String类名数组,它必须派生自 Throwable. | 异常类名称的可选数组,该数组必须不会导致回滚。 |
label | 数组String标签,用于向事务添加富有表现力的描述。 | 事务管理器可以评估标签,以将特定于实现的行为与实际事务关联起来。 |
目前,您不能显式地控制事务的名称,其中“name”指出现在事务监视器(例如,WebLogic的事务监视器)和日志输出中的【事务名称】。 对于声明性事务,事务名总是完全限定类名+ ‘.’ +事务通知类的方法名。 例如,如果’ BusinessService ‘类的’ handlePayment(…) '方法启动了一个事务,事务的名称将是: com.example.BusinessService.handlePayment。
7️⃣带 @Transactional的多个事务管理器
大多数Spring应用程序只需要一个事务管理器,但是在某些情况下,您可能希望在一个应用程序中有多个独立的事务管理器。 您可以使用’ @Transactional '注解的【value】或【transactionManager】属性来指定要使用的【transactionManage】的标识。 这可以是bean名,也可以是事务管理器bean的限定符值。 例如,使用限定符表示法,您可以在应用程序上下文中将下列Java代码与下列事务管理器bean声明组合起来:
下面的例子定义了三个事务管理器:
<tx:annotation-driven/> <bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> ... <qualifier value="order"/> </bean> <bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> ... <qualifier value="account"/> </bean> <bean id="transactionManager3" class="org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager"> ... <qualifier value="reactive-account"/> </bean>
下面的例子中,不同的事务使用了不同的事务管理器:
public class TransactionalService { @Transactional("order") public void setSomething(String name) { ... } @Transactional("account") public void doSomething() { ... } @Transactional("reactive-account") public Mono<Void> doSomethingReactive() { ... } }
在这种情况下,【TransactionalService】上的各个方法在单独的事务管理器下运行,通过’ order ‘、’ account ‘和’ reactive-account ‘限定符进行区分。 如果没有找到特别限定的’ transactionManager ’ bean,则仍然使用默认的<tx:annotation-driven>中定义的bean。
8️⃣自定义注解组成
如果您发现在许多不同的方法上重复使用带有【@Transactional】的相同属性,【Spring的元注解支持】允许您为特定的用例定义自定义的组合注解。 例如,考虑以下注解定义:
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Transactional(transactionManager = "order", label = "causal-consistency") public @interface OrderTx { } @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Transactional(transactionManager = "account", label = "retryable") public @interface AccountTx { }
前面的注解让我们将上一节中的示例编写为如下所示:
public class TransactionalService { @OrderTx public void setSomething(String name) { // ... } @AccountTx public void doSomething() { // ... } }
在前面的示例中,我们使用语法来定义事务管理器限定符和事务标签,但是我们还可以包括传播行为、回滚规则、超时和其他特性。
五、事务传播
事务的传播通常发生在【一个service层中的方法】调用【其他service层中的方法】,虽然我们并不推荐这么做,但是的确会存在一个【大的业务】包含多个【小业务】,大业务和小业务都可独立运行的场景。
比如【销售】中可以包含【增加积分】的操作,而【加积分也可以独立运行】(比如某天搞活动,给老用户直接冲积分),其中销售的方法会有事务,加积分的方法也会有事务,当销售的方法调用加积分的方法时,加积分的【小事务】就被传播到了销售这个【大事务】当中。
当事务发生传播时,一般有以下几种解决方案:
传播行为 | 含义 |
PROPAGATION_REQUIRED | 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务 |
PROPAGATION_SUPPORTS | 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行 |
PROPAGATION_MANDATORY | 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常 |
PROPAGATION_REQUIRED_NEW | 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_NOT_SUPPORTED | 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_NEVER | 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常 |
PROPAGATION_NESTED | 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在, |
隔离级别 | 含义 |
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 |
ISOLATION_READ_COMMITTED | 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 |
ISOLATION_REPEATABLE_READ | 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生 |
ISOLATION_SERIALIZABLE | 最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的 |
六、在编程式和声明式事务管理之间进行选择
只有在有少量事务操作的情况下,编程式事务管理通常是一个好主意。 例如,如果您有一个web应用程序,它只需要为某些更新操作使用事务,那么您可能不希望使用Spring或任何其他技术来设置事务代理。 在这种情况下,使用TransactionTemplate可能是一种很好的方法。 只有使用事务管理的编程方法才能显式地设置事务名称。
另一方面,如果应用程序有许多事务操作,则声明式事务管理通常是值得的。 它使事务管理远离业务逻辑,并且不难配置。 当使用Spring框架而不是EJB CMT时,声明性事务管理的配置成本大大降低了。