除了基于 XML 的事务配置的声明式方法外,你还可以使用基于注解的方法。直接在 Java 源代码中声明事务语义,使声明更接近于受影响的代码。没有太多的过度耦合的危险,因为无论如何,要以事务方式使用的代码几乎总是以这种方式部署。

:::info 标准的 javax.transaction.Transactional注解也被支持,可以直接替代 Spring 自己的注解。更多细节请参考 JTA 1.2 文档。 :::

使用 @Transactional注解所带来的易用性最好用一个例子来说明,这将在下面的文字中解释。考虑一下下面的类定义:

  1. // 我们想使之成为事务性的服务类。
  2. @Transactional
  3. public class DefaultFooService implements FooService {
  4. @Override
  5. public Foo getFoo(String fooName) {
  6. // ...
  7. }
  8. @Override
  9. public Foo getFoo(String fooName, String barName) {
  10. // ...
  11. }
  12. @Override
  13. public void insertFoo(Foo foo) {
  14. // ...
  15. }
  16. @Override
  17. public void updateFoo(Foo foo) {
  18. // ...
  19. }
  20. }

如上所述,在类的层面上使用,该注解为声明类(以及它的子类)的所有方法指出了一个默认值。另外,每个方法都可以被单独注解。关于Spring 认为哪些方法是事务性的,请参见方法的可见性和 @Transactional(本文后面部分)。请注意,类级注解并不适用于类层次结构中的祖先类;在这种情况下,继承的方法需要在本地重新声明,以便参与子类级注解

当像上面这样的 POJO 类被定义为 Spring 上下文中的 Bean 时,你可以通过 @Configuration类中的 @EnableTransactionManagement注解使 Bean 实例具有事务性。详细内容请参见 javadoc

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

  1. <!-- from the file 'context.xml' -->
  2. <?xml version="1.0" encoding="UTF-8"?>
  3. <beans xmlns="http://www.springframework.org/schema/beans"
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5. xmlns:aop="http://www.springframework.org/schema/aop"
  6. xmlns:tx="http://www.springframework.org/schema/tx"
  7. xsi:schemaLocation="
  8. http://www.springframework.org/schema/beans
  9. https://www.springframework.org/schema/beans/spring-beans.xsd
  10. http://www.springframework.org/schema/tx
  11. https://www.springframework.org/schema/tx/spring-tx.xsd
  12. http://www.springframework.org/schema/aop
  13. https://www.springframework.org/schema/aop/spring-aop.xsd">
  14. <!-- this is the service object that we want to make transactional -->
  15. <bean id="fooService" class="x.y.service.DefaultFooService"/>
  16. <!-- 启动基于注解的事务行为配置 -->
  17. <!-- 任然需要一个 TransactionManager -->
  18. <tx:annotation-driven transaction-manager="txManager"/>
  19. <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  20. <!-- (this dependency is defined somewhere else) -->
  21. <property name="dataSource" ref="dataSource"/>
  22. </bean>
  23. <!-- other <bean/> definitions here -->
  24. </beans>

:::info 如果你想接入的 TransactionManager 的 bean 名称为 transactionManager,你可以在 <tx:annotation-driven/> 标签中省略 transaction-manager 属性。如果你想依赖注入的 TransactionManager Bean 有任何其他的名字,你必须使用 transaction-manager 属性,就像前面的例子那样。 :::

反应式事务性方法使用反应式返回类型,与命令式编程安排形成对比,如下表所示:

  1. // the reactive service class that we want to make transactional
  2. @Transactional
  3. public class DefaultFooService implements FooService {
  4. @Override
  5. public Publisher<Foo> getFoo(String fooName) {
  6. // ...
  7. }
  8. @Override
  9. public Mono<Foo> getFoo(String fooName, String barName) {
  10. // ...
  11. }
  12. @Override
  13. public Mono<Void> insertFoo(Foo foo) {
  14. // ...
  15. }
  16. @Override
  17. public Mono<Void> updateFoo(Foo foo) {
  18. // ...
  19. }
  20. }

请注意,对于返回的 Publisher,在 Reactive Streams 取消信号方面有特殊的考虑。请参阅 使用 TransactionOperator 下的 取消信号 部分 以了解更多细节。


方法的可见性和 @Transactional

当你用 Spring 的标准配置使用事务代理时,你应该只对具有 public可见性的方法应用 @Transactional 注解。如果你确实用 @Transactional注解来注解 protected、private 的或包可见的方法,则不会产生错误,但被注解的方法不会表现出配置的事务性设置。如果你需要注解非 public 的方法,请考虑下一段中关于基于类的代理的提示,或者考虑使用 AspectJ 编译时或加载时编织(后面描述)。

当在 @Configuration 类中使用 @EnableTransactionManagement 时,通过注册一个自定义的 transactionAttributeSource bean,也可以使基于类的代理的 protected 或包可见的方法成为事务性的,就像下面这个例子一样。然而,请注意,基于接口的代理中的事务性方法必须始终是 public 的,并定义在被代理的接口中

  1. /**
  2. * 注册一个自定义的 AnnotationTransactionAttributeSource,并设置
  3. * publicMethodsOnly 标志设置为 false,以使支持
  4. * 在基于类的代理中,支持 protected 或 package-private @Transactional 的方法
  5. * 基于类的代理。
  6. *
  7. * @see ProxyTransactionManagementConfiguration#transactionAttributeSource()
  8. */
  9. @Bean
  10. TransactionAttributeSource transactionAttributeSource() {
  11. return new AnnotationTransactionAttributeSource(false);
  12. }

Spring TestContext 框架默认支持非私有的 @Transactional 测试方法。例子见 测试章节中的事务管理


你可以将 @Transactional 注解应用于接口定义、接口上的方法、类定义或类上的方法。然而,仅仅存在 @Transactional 注解并不足以激活事务性行为。@Transactional 注解只是元数据,它可以被一些具有 @Transactional 意识的运行时基础设施所消费,这些基础设施可以使用元数据来配置具有事务行为的适当的 bean。在前面的例子中,<tx:annotation-driven/>元素开启了事务行为。

:::tips Spring 团队建议你只用 @Transactional 注解来注解具体类(以及具体类的方法),而不是注解接口。你当然可以将 @Transactional 注解放在接口(或接口方法)上,但这只在你使用基于接口的代理时才能发挥作用。Java 注解不从接口继承的事实意味着,如果你使用基于类的代理(proxy-target-class=”true”)或基于织构的方面(mode=”aspectj”),事务设置不会被代理和织构基础设施识别,对象也不会被包裹在事务代理中。 ::: :::info 在代理模式下(这是默认的),只有通过代理进来的外部方法调用被拦截。这意味着自我调用(实际上,目标对象内的方法调用目标对象的另一个方法)在运行时不会导致实际的事务,即使被调用的方法被标记为 @Transactional。另外,代理必须被完全初始化以提供预期的行为,所以你不应该在初始化代码中依赖这个特性—例如,在 @PostConstruct 方法中。 :::

如果你希望自我调用也被事务包裹,请考虑使用 AspectJ 模式(见下表的模式属性)。在这种情况下,首先就不存在代理。相反,目标类被编织(也就是说,它的字节码被修改)以支持任何种类的方法上的 @Transactional 运行时行为

XML Attribute Annotation Attribute Default 描述
transaction-manager N/A (see TransactionManagementConfigurer javadoc) transactionManager 要使用的事务管理器的名称。只有当事务管理器的名称不是 transactionManager 时才需要,如前面的例子。
mode mode proxy 默认模式(proxy)通过使用 Spring 的 AOP 框架(遵循代理语义,如前所述,仅适用于通过代理进入的方法调用)来处理要代理的注释豆。另一种模式(aspectj)则是用Spring 的 AspectJ 事务切面来编织受影响的类,修改目标类的字节码以适用于任何类型的方法调用。AspectJ 织入需要在 classpath 中加入 spring-aspects.jar,并启用加载时织入(或编译时织入)。(参见 Spring 配置,了解如何设置加载时织构的细节)。
proxy-target-class proxyTargetClass false 仅适用于代理模式。控制为带有 @Transactional注 解的类创建何种类型的事务代理。如果 proxy-target-class 属性被设置为 true,就会创建基于类的代理。如果 proxy-target-class 是 false 的,或者该属性被省略,那么就会创建基于 JDK 接口的标准代理。(参见代理机制以详细了解不同的代理类型)。
order order Ordered.LOWEST_PRECEDENCE 定义应用于用 @Transactional 注解的 bean 的事务 advice 的顺序。(关于 AOP advice 的排序规则的更多信息,请参见 advice 排序)。没有指定排序意味着 AOP 子系统决定 advice 的顺序。

:::info 处理 @Transactional 注解的默认建议模式是 proxy,它只允许通过代理拦截调用。同一类中的本地调用不能通过这种方式被拦截。对于更高级的拦截模式,可以考虑切换到 aspectj 模式,并结合编译时或加载时织入。 ::: :::info proxy-target-class 属性控制了为带有 @Transactional 注解的类创建何种类型的事务代理。如果 proxy-target-class 被设置为 true,就会创建基于类的代理。如果 proxy-target-class 为 false,或者省略该属性,则创建基于 JDK 接口的标准代理。(关于不同代理类型的讨论,请参见代理机制)。 ::: :::info @EnableTransactionManagement 和 <tx:annotation-driven/>只在定义它们的同一应用上下文中的 bean 上寻找 @Transactional。这意味着,如果你把注解驱动的配置放在 DispatcherServlet 的 WebApplicationContext 中,它只在你的控制器而不是服务中检查@Transactional Bean。更多信息请参见 MVC。 :::

在评估一个方法的事务性设置时,最派生的位置优先考虑。在下面的例子中,DefaultFooService 类在类的层次上被注解了只读事务的设置,但是同一类中 updateFoo(Foo)方法上的 @Transactional 注解优先于在类层次上定义的事务设置:

  1. @Transactional(readOnly = true)
  2. public class DefaultFooService implements FooService {
  3. public Foo getFoo(String fooName) {
  4. // ...
  5. }
  6. // 这些设置对该方法有优先权
  7. @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  8. public void updateFoo(Foo foo) {
  9. // ...
  10. }
  11. }

@Transactional 设置

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

  • 传播设置为 PROPAGATION_REQUIRED。
  • 隔离级别是 ISOLATION_DEFAULT。
  • 事务是读-写的。
  • 事务超时默认为底层事务系统的默认超时,如果不支持超时,则默认为无。
  • 任何 RuntimeException 都会触发回滚,而任何未检查的 Exception 都不会回滚。

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

Property Type Description
value String 可选的限定词,指定要使用的事务管理器。
propagation enum: Propagation 可选的传播设置。
isolation enum: Isolation 可选的隔离级别。仅适用于 REQUIRED 或 REQUIRES_NEW 的传播值。
timeout int (单位秒) 可选的事务超时。仅适用于 REQUIRED 或 REQUIRES_NEW 的传播值。
readOnly boolean 读写事务与只读事务。只适用于 REQUIRED 或 REQUIRES_NEW 的值。
rollbackFor 类对象的数组,必须是 Throwable 子类 可选的异常类数组,必须引起回滚。
rollbackForClassName 字符串类名数组,必须是 Throwable 子类 可选的异常类名称数组,必须引起回滚。
noRollbackFor 类对象的数组,必须是 Throwable 子类 可选的异常类数组,不得引起回滚。
noRollbackForClassName 字符串类名数组,必须是 Throwable 子类 可选的异常类名数组,不得引起回滚。
label 字符串标签数组,用于为事务添加表达式描述。 标签可以由事务管理器评估,以便将特定于实现的行为与实际事务联系起来。

目前,你不能显式地控制事务的名称,这里的 「名称」是指出现在事务监视器(如果适用)(例如,WebLogic 的事务监视器)和日志输出中的事务名称。对于声明式事务,事务名总是 全限定的类名 + . + 事务 advice 类的方法名。例如,如果 BusinessService 类的handlePayment(.)方法启动了一个事务,那么事务的名称将是:com.example.BusinessService.handlePayment

使用 @Transactional 的多个事务管理器

大多数 Spring 应用程序只需要一个事务管理器,但也可能出现在一个应用程序中需要多个独立事务管理器的情况。你可以使用@Transactional 注解的 value 或 transactionManager 属性来选择性地指定要使用的事务管理器的身份。这可以是 bean 的名字,也可以是事务管理器 bean 的限定词值。例如,使用限定符符号,你可以在应用程序上下文中将以下 Java 代码与以下事务管理器 Bean 声明结合起来:

  1. public class TransactionalService {
  2. @Transactional("order")
  3. public void setSomething(String name) { ... }
  4. @Transactional("account")
  5. public void doSomething() { ... }
  6. @Transactional("reactive-account")
  7. public Mono<Void> doSomethingReactive() { ... }
  8. }
  1. <tx:annotation-driven/>
  2. <bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  3. ...
  4. <qualifier value="order"/>
  5. </bean>
  6. <bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  7. ...
  8. <qualifier value="account"/>
  9. </bean>
  10. <bean id="transactionManager3" class="org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager">
  11. ...
  12. <qualifier value="reactive-account"/>
  13. </bean>

在这种情况下,TransactionalService 上的各个方法在不同的事务管理器下运行,由订单、账户和反应式账户限定符来区分。如果没有找到特别合格的 TransactionManager Bean,默认的 <tx:annotation-driven> 目标 bean 名称 transactionManager 仍然被使用。

自定义组合式注解

如果你发现你在许多不同的方法上重复使用相同的属性与 @Transactional,Spring 的元注解 支持让你为你的特定用例定义自定义组成注解。例如,考虑下面的注解定义:

  1. @Target({ElementType.METHOD, ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Transactional(transactionManager = "order", label = "causal-consistency")
  4. public @interface OrderTx {
  5. }
  6. @Target({ElementType.METHOD, ElementType.TYPE})
  7. @Retention(RetentionPolicy.RUNTIME)
  8. @Transactional(transactionManager = "account", label = "retryable")
  9. public @interface AccountTx {
  10. }

前面的注解让我们把上一节的例子写成如下:

  1. public class TransactionalService {
  2. @OrderTx
  3. public void setSomething(String name) {
  4. // ...
  5. }
  6. @AccountTx
  7. public void doSomething() {
  8. // ...
  9. }
  10. }

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