除了基于 XML 的事务配置的声明式方法外,你还可以使用基于注解的方法。直接在 Java 源代码中声明事务语义,使声明更接近于受影响的代码。没有太多的过度耦合的危险,因为无论如何,要以事务方式使用的代码几乎总是以这种方式部署。
:::info
标准的 javax.transaction.Transactional
注解也被支持,可以直接替代 Spring 自己的注解。更多细节请参考 JTA 1.2 文档。
:::
使用 @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 认为哪些方法是事务性的,请参见方法的可见性和 @Transactional(本文后面部分)。请注意,类级注解并不适用于类层次结构中的祖先类;在这种情况下,继承的方法需要在本地重新声明,以便参与子类级注解。
当像上面这样的 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"/>
<!-- 启动基于注解的事务行为配置 -->
<!-- 任然需要一个 TransactionManager -->
<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>
:::info
如果你想接入的 TransactionManager 的 bean 名称为 transactionManager,你可以在 <tx:annotation-driven/>
标签中省略 transaction-manager 属性。如果你想依赖注入的 TransactionManager Bean 有任何其他的名字,你必须使用 transaction-manager 属性,就像前面的例子那样。
:::
反应式事务性方法使用反应式返回类型,与命令式编程安排形成对比,如下表所示:
// 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 取消信号方面有特殊的考虑。请参阅 使用 TransactionOperator 下的 取消信号 部分 以了解更多细节。
方法的可见性和 @Transactional
当你用 Spring 的标准配置使用事务代理时,你应该只对具有 public
可见性的方法应用 @Transactional 注解。如果你确实用 @Transactional
注解来注解 protected、private 的或包可见的方法,则不会产生错误,但被注解的方法不会表现出配置的事务性设置。如果你需要注解非 public 的方法,请考虑下一段中关于基于类的代理的提示,或者考虑使用 AspectJ 编译时或加载时编织(后面描述)。
当在 @Configuration 类中使用 @EnableTransactionManagement 时,通过注册一个自定义的 transactionAttributeSource bean,也可以使基于类的代理的 protected 或包可见的方法成为事务性的,就像下面这个例子一样。然而,请注意,基于接口的代理中的事务性方法必须始终是 public 的,并定义在被代理的接口中。
/**
* 注册一个自定义的 AnnotationTransactionAttributeSource,并设置
* publicMethodsOnly 标志设置为 false,以使支持
* 在基于类的代理中,支持 protected 或 package-private @Transactional 的方法
* 基于类的代理。
*
* @see ProxyTransactionManagementConfiguration#transactionAttributeSource()
*/
@Bean
TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource(false);
}
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 注解优先于在类层次上定义的事务设置:
@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
// ...
}
// 这些设置对该方法有优先权
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
// ...
}
}
@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 声明结合起来:
public class TransactionalService {
@Transactional("order")
public void setSomething(String name) { ... }
@Transactional("account")
public void doSomething() { ... }
@Transactional("reactive-account")
public Mono<Void> doSomethingReactive() { ... }
}
<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>
在这种情况下,TransactionalService 上的各个方法在不同的事务管理器下运行,由订单、账户和反应式账户限定符来区分。如果没有找到特别合格的 TransactionManager Bean,默认的 <tx:annotation-driven>
目标 bean 名称 transactionManager 仍然被使用。
自定义组合式注解
如果你发现你在许多不同的方法上重复使用相同的属性与 @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() {
// ...
}
}
在前面的例子中,我们使用语法来定义事务管理器限定符和事务标签,但我们也可以包括传播行为、回滚规则、超时和其他功能。