跳到主要内容
版本:7.0.3

使用 @Transactional

Hunyuan 7b 中英对照 Using @Transactional Using @Transactional

除了基于XML的声明式事务配置方法外,您还可以使用基于注解的方法。直接在Java源代码中声明事务语义,可以使这些声明更接近于相关的代码。这样几乎不会存在不必要的耦合风险,因为本来就打算用于事务处理的代码几乎总是以这种方式部署的。

备注

标准的 jakarta.transactionTransactional 注解也可以作为 Spring 自有注解的替代品直接使用。详情请参阅 JTA 文档。

@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) {
// ...
}
}

如上所述,在类级别使用该注解时,它表示声明该类的所有方法(及其子类)的默认行为。或者,也可以对每个方法分别进行注释。有关Spring认为哪些方法是事务性的更多细节,请参见方法可见性。请注意,类级别的注解不适用于类层次结构中的父类;在这种情况下,继承的方法需要在本地重新声明才能参与子类级别的注解。

当像上面这样的POJO类在Spring上下文中被定义为bean时,你可以通过在@Configuration类中使用@EnableTransactionManagement注解来使该bean实例具备事务处理功能。有关详细信息,请参阅javadoc

在XML配置中,<tx:annotation-driven/>标签提供了类似的便利性:

<!-- 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"/> // <1>

<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>
  • 使bean实例具有事务性的那一行。

提示

如果您想要注入的TransactionManager的bean名称为transactionManager,则可以省略<tx:annotation-driven/>标签中的transaction-manager属性。如果您想要依赖注入的TransactionManagerbean具有其他名称,则必须使用transaction-manager属性,如前面的例子所示。

反应式事务性方法使用反应式返回类型(reactive return types),这与命令式编程(imperative programming)的安排不同,如下例所示:

// the reactive service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

@Override
public Publisher<Foo> getFoo(String fooName) {
// ...
}

@Override
public Mono<Foo> getFoo(String fooName, String barName) {
// ...
}

@Override
public Mono<Void> insertFoo(Foo foo) {
// ...
}

@Override
public Mono<Void> updateFoo(Foo foo) {
// ...
}
}

请注意,对于返回的Publisher,在处理Reactive Streams的取消信号时需要有一些特殊的考虑。有关更多详细信息,请参阅“使用TransactionalOperator”下的Cancel Signals部分。

备注

代理模式下的方法可见性与 @Transactional
@Transactional 注解通常用于 public 可见性的方法上。从 Spring 6.0 开始,对于基于类的代理,protected 或包内可见(package-visible)的方法也可以默认设置为事务性。需要注意的是,基于接口的代理中的事务性方法必须始终为 public 并且定义在被代理的接口中。对于这两种类型的代理,只有通过代理进入的外部方法调用才会被拦截。

如果你希望在不同类型的代理中对方法可见性进行一致处理(在 Spring 5.3 之前这是默认设置),可以考虑指定 publicMethodsOnly

/**
* 注册一个自定义的 AnnotationTransactionAttributeSource,并将 `publicMethodsOnly` 标志设置为 true,以便始终忽略非公共方法。
* @see ProxyTransactionManagementConfiguration#transactionAttributeSource()
*/
@Bean
TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource(true);
}

Spring TestContext Framework 默认也支持非私有的 @Transactional 测试方法。有关示例,请参阅测试章节中的 事务管理

你可以将 @Transactional 注解应用于接口定义、接口上的方法、类定义或类上的方法。然而,仅仅有 @Transactional 注解并不足以激活事务行为。@Transactional 注解只是一种元数据,可以被相应的运行时基础设施所使用,这些基础设施会利用这种元数据来配置具有事务行为的相关组件(beans)。在前面的例子中,<tx:annotation-driven/> 元素在运行时启用了实际的事务管理功能。

提示

Spring团队建议你在具体类的方法上添加@Transactional注解,而不是依赖于接口中的带注解的方法。尽管从Spring 5.0开始,后一种方法对于基于接口的代理和目标类代理也是有效的。但由于Java注解不会从接口继承,因此在使用AspectJ模式时,接口声明的注解仍然不会被织入(weaving)机制识别,从而导致切面(aspect)无法被应用。其结果是,你的事务注解可能会被默默地忽略:在测试回滚场景之前,你的代码可能看起来“正常工作”。

备注

在代理模式(默认模式)下,只有通过代理传入的外部方法调用才会被拦截。这意味着自我调用(实际上是目标对象中的某个方法调用目标对象的另一个方法)在运行时不会引发实际的事务,即使被调用的方法被标记为@Transactional。此外,为了实现预期的行为,代理必须完全初始化,因此你不应该在初始化代码中依赖这一特性——例如,在@PostConstruct方法中。

如果你也期望自我调用(self-invocations)能够被事务所包裹,可以考虑使用 AspectJ 模式(请参见下表中的 mode 属性)。在这种情况下,一开始就不会有代理(proxy)存在。相反,目标类(target class)的字节码会被直接修改(woven),以便在任何类型的方法上支持 @Transactional 的运行时行为。

表1. 注解驱动的事务设置

XML属性注解属性默认值描述
transaction-managerN/A(参见TransactionManagementConfigurer的Javadoc)transactionManager要使用的事务管理器的名称。仅当事务管理器的名称不是transactionManager时才需要指定,如前面的示例所示。
modemodeproxy默认模式(proxy)通过使用Spring的AOP框架来处理被注解的Bean以创建代理(遵循前面讨论的代理语义,仅适用于通过代理进入的方法调用)。另一种模式(aspectj)则是将受影响的类与Spring的AspectJ事务方面(AspectJ transaction aspect)进行编织,修改目标类的字节码以适用于任何类型的方法调用。使用AspectJ编织需要在类路径中包含spring-aspects.jar,并且需要启用加载时编织(load-time weaving)或编译时编织(compile-time weaving)。(有关如何设置加载时编织的详细信息,请参见Spring配置。)
proxy-target-classproxyTargetClassfalse仅适用于proxy模式。控制为带有@Transactional注解的类创建哪种类型的事务代理。如果将proxy-target-class属性设置为true,则会创建基于类的代理;如果proxy-target-classfalse或省略该属性,则会创建标准的基于JDK接口的代理。(有关不同代理类型的详细信息,请参见代理机制。)
orderorderOrdered.LOWEST_PRECEDENCE定义应用于带有@Transactional注解的Bean的事务方面的顺序。(有关AOP方面排序规则的更多信息,请参见方面排序。)如果没有指定顺序,则由AOP子系统决定方面的顺序。
备注

处理@Transactional注解的默认建议模式是proxy,该模式仅允许通过代理进行调用拦截。同一类内的局部调用无法通过这种方式被拦截。若需要更高级的拦截模式,可以考虑结合编译时或加载时织入(weaving)技术使用aspectj模式。

备注

proxy-target-class属性用于控制为标有@Transactional注解的类创建哪种类型的事务代理。如果proxy-target-class被设置为true,则会创建基于类的代理;如果proxy-target-class被设置为false或该属性被省略,则会创建标准的JDK接口基代理。(有关不同代理类型的讨论,请参见代理机制。)

备注

@EnableTransactionManagement<tx:annotation-driven/> 仅会在定义它们的相同应用程序上下文中的 Bean 上查找 @Transactional 注解。这意味着,如果你在 DispatcherServletWebApplicationContext 中使用注解驱动的配置,那么它只会检查控制器(controllers)中的 @Transactional Bean,而不会检查服务(services)中的。更多信息请参见 MVC

在评估方法的事务设置时,最具体的(即派生程度最高的)设置会优先被应用。以下示例中,DefaultFooService 类在类级别上被标注为只读事务的设置,但同一类中的 updateFoo(Foo) 方法上的 @Transactional 注解会优先于类级别上定义的事务设置。

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

public Foo getFoo(String fooName) {
// ...
}

// these settings have precedence for this method
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
// ...
}
}

@Transactional 设置

@Transactional 注解是一种元数据,它指定一个接口、类或方法必须具有事务语义(例如,“当调用此方法时,启动一个全新的只读事务,并挂起任何现有事务”)。@Transactional 的默认设置如下:

  • 传播设置为 PROPAGATION_REQUIRED.

  • 隔离级别为 ISOLATION_DEFAULT.

  • 该事务是读写事务。

  • 事务超时默认使用底层事务系统的默认超时设置;如果不支持超时,则不设置超时。

  • 任何 RuntimeExceptionError 引发回滚,而任何经过检查的 Exception 则不会引发回滚。

您可以更改这些默认设置。下表总结了@Transactional注解的各个属性:

表2. @Transactional 设置

属性类型描述
valueString可选限定符,用于指定要使用的事务管理器。
transactionManagerStringvalue的别名。
labelString类型的标签数组为事务添加更具描述性的标签。事务管理器可以解析这些标签,将实现特定的行为与实际事务关联起来。
propagationenum: Propagation可选的事务传播设置。
isolationenum: Isolation可选的事务隔离级别。仅适用于REQUIREDREQUIRES_NEW的传播值。
timeoutint(以秒为单位)可选的事务超时时间。仅适用于REQUIREDREQUIRES_NEW的传播值。
timeoutStringString(以秒为单位)以字符串形式指定timeout的超时时间的替代方式(例如,作为占位符)。
readOnlyboolean读写事务与只读事务的区分。仅适用于REQUIREDREQUIRES_NEW的值。
rollbackForClass类型的对象数组必须继承自Throwable的异常类型数组,这些异常类型会导致事务回滚。
rollbackForClassName异常名称模式数组必须导致事务回滚的异常名称模式数组。
noRollbackForClass类型的对象数组必须继承自Throwable的异常类型数组,这些异常类型不会导致事务回滚。
noRollbackForClassName异常名称模式数组不会导致事务回滚的异常名称模式数组。
提示

有关回滚规则语义、模式以及基于模式的回滚规则可能出现的意外匹配情况的警告的更多详细信息,请参阅回滚规则

备注

从6.2版本开始,你可以全局更改默认的回滚行为——例如,通过@EnableTransactionManagement(rollbackOn=ALL_EXCEPTIONS)来设置,这样在事务中抛出的所有异常(包括任何检查型异常)都会触发回滚。对于进一步的自定义,AnnotationTransactionAttributeSource提供了一个addDefaultRollbackRule(RollbackRuleAttribute)方法来定制默认规则。

请注意,特定于事务的回滚规则会覆盖默认行为,但对于未指定的异常仍会保留所选的默认设置。Spring的@Transactional注解以及JTA的jakarta.transactionTransactional注解也是如此。

除非你依赖于具有提交行为的EJB风格的业务异常,否则建议将rollbackOn设置为ALL_EXCEPTIONS,以便在发生(可能是意外的)检查型异常时也能保证一致的回滚语义。此外,在Kotlin应用程序中,由于根本不会强制使用检查型异常,也建议进行这样的设置。

目前,你无法显式控制事务的名称,这里的“名称”指的是在事务监控器和日志输出中显示的事务名称。对于声明式事务,事务名称总是事务建议类的完全限定类名加上.再加上方法名。例如,如果BusinessService类的handlePayment(..)方法启动了一个事务,那么该事务的名称就是com.example.BusinessService.handlePayment

带有@Transactional的多个事务管理器

大多数Spring应用程序只需要一个事务管理者,但在某些情况下,你可能希望在同一个应用程序中使用多个独立的事务管理者。你可以使用@Transactional注解的valuetransactionManager属性来可选地指定要使用的事务管理者的身份。这可以是bean的名称,也可以是事务管理者bean的限定符值。例如,使用限定符表示法,你可以将以下Java代码与应用程序上下文中的以下事务管理者bean声明结合起来:

public class TransactionalService {

@Transactional("order")
public void setSomething(String name) { ... }

@Transactional("account")
public void doSomething() { ... }

@Transactional("reactive-account")
public Mono<Void> doSomethingReactive() { ... }
}

以下列表显示了Bean的声明:

<tx:annotation-driven/>

<bean id="transactionManager1" class="org.springframework.jdbc.support.JdbcTransactionManager">
...
<qualifier value="order"/>
</bean>

<bean id="transactionManager2" class="org.springframework.jdbc.support.JdbcTransactionManager">
...
<qualifier value="account"/>
</bean>

<bean id="transactionManager3" class="org.springframework.data.r2dbc.connection.R2dbcTransactionManager">
...
<qualifier value="reactive-account"/>
</bean>

在这种情况下,TransactionalService上的各个方法分别在不同的事务管理器下运行,这些事务管理器通过orderaccountreactive-account这些限定符来区分。如果没有找到特定限定的TransactionManager bean,那么默认的<tx:annotation-driven>目标bean名称transactionManager仍然会被使用。

提示

如果同一类上的所有事务方法都使用相同的限定符,可以考虑声明一个类型级别的 org.springframework.beans.factory.annotation.Qualifier 注解。如果该限定符的值与某个特定事务管理器的限定符值(或 bean 名称)匹配,那么在没有在 @Transactional 上直接指定限定符的情况下,就会使用这个事务管理者来处理事务。

这样的类型级别限定符可以声明在具体的类上,同时也会适用于基类中的事务定义。这实际上会覆盖任何未指定限定符的基类方法的默认事务管理者选择。

最后但同样重要的是,这种类型级别的 bean 限定符可以有多种用途。例如,当其值为 “order” 时,它可以用于自动装配(用于标识订单仓库),也可以用于事务管理器的选择,只要自动装配的目标 bean 以及相关的事务管理者定义都声明了相同的限定符值即可。这样的限定符值只需在一组类型匹配的 bean 内是唯一的,而不需要作为一个唯一的标识符使用。

自定义组合注释

如果你发现自己在许多不同的方法上反复使用带有@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() {
// ...
}
}

在前面的例子中,我们使用了语法来定义事务管理器限定符和事务标签,但我们也可以包含传播行为、回滚规则、超时以及其他特性。