使用 @Transactional
@Transactional
除了基于 XML 的声明式事务配置方法外,您还可以使用基于注解的方法。直接在 Java 源代码中声明事务语义使得声明更接近于受影响的代码。由于事务性代码几乎总是以这种方式部署,因此不会存在过多的耦合风险。
标准的 jakarta.transaction.Transactional
注解也受支持,可以作为 Spring 自有注解的直接替代品。更多详情请参阅 JTA 文档。
通过使用 @Transactional
注解所带来的易用性,最好通过一个例子来说明,这将在下文中进行解释。考虑以下类定义:
- Java
- Kotlin
// 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) {
// ...
}
}
// the service class that we want to make transactional
@Transactional
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Foo {
// ...
}
override fun getFoo(fooName: String, barName: String): Foo {
// ...
}
override fun insertFoo(foo: Foo) {
// ...
}
override fun 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
属性。如果你想要依赖注入的 TransactionManager
bean 的名称是其他任何名称,那么你必须使用 transaction-manager
属性,如前面的示例所示。
响应式事务方法使用响应式返回类型,与命令式编程安排形成对比,如下列表示例所示:
- Java
- Kotlin
// 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) {
// ...
}
}
// the reactive service class that we want to make transactional
@Transactional
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Flow<Foo> {
// ...
}
override fun getFoo(fooName: String, barName: String): Mono<Foo> {
// ...
}
override fun insertFoo(foo: Foo): Mono<Void> {
// ...
}
override fun updateFoo(foo: Foo): Mono<Void> {
// ...
}
}
请注意,返回的 Publisher
在响应式流取消信号方面有特殊注意事项。有关更多详细信息,请参阅“使用事务操作符”(TransactionalOperator)下的“取消信号”部分。
方法可见性与代理模式下的 @Transactional
@Transactional
注解通常用于具有 public
可见性的方法。从 6.0 版本开始,默认情况下,基于类的代理也可以使 protected
或包可见的方法具有事务性。请注意,基于接口的代理中的事务方法必须始终是 public
的,并且必须在代理接口中定义。对于这两种代理,只有通过代理传入的外部方法调用才会被拦截。
如果您希望在不同类型的代理之间对方法可见性进行一致的处理(这是 5.3 版本之前的默认行为),请考虑指定 publicMethodsOnly
:
/**
* 注册一个自定义的 AnnotationTransactionAttributeSource,
* 并将 publicMethodsOnly 标志设置为 true,以一致地忽略非公共方法。
* @see ProxyTransactionManagementConfiguration#transactionAttributeSource()
*/
@Bean
TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource(true);
}
Spring TestContext Framework 默认也支持非私有的 @Transactional
测试方法。有关示例,请参阅测试章节中的事务管理。
你可以将 @Transactional
注解应用于接口定义、接口上的方法、类定义或类上的方法。然而,仅仅存在 @Transactional
注解并不足以激活事务行为。@Transactional
注解只是元数据,可以被相应的运行时基础设施使用,该基础设施利用这些元数据来配置具有事务行为的适当 bean。在前面的例子中,<tx:annotation-driven/>
元素在运行时启用了实际的事务管理。
Spring 团队建议你在具体类的方法上使用 @Transactional
注解,而不是依赖于接口中的注解方法,即使后者在 5.0 版本中对于基于接口和目标类的代理是有效的。由于 Java 注解不会从接口继承,因此在使用 AspectJ 模式时,织入基础设施仍然无法识别接口声明的注解,从而导致切面不会被应用。这可能导致你的事务注解被静默忽略:你的代码可能看起来“正常工作”,直到你测试回滚场景时才会发现问题。
在代理模式(这是默认模式)下,只有通过代理传入的外部方法调用会被拦截。这意味着自调用(即目标对象内部的一个方法调用目标对象的另一个方法)不会在运行时导致实际的事务,即使被调用的方法标记了 @Transactional
。此外,代理必须完全初始化才能提供预期的行为,因此你不应该在初始化代码中依赖此功能——例如,在 @PostConstruct
方法中。
如果你希望自调用也能被事务包装,可以考虑使用 AspectJ 模式(参见下表中的 mode
属性)。在这种情况下,首先不会有代理。相反,目标类会被织入(即其字节码会被修改),以支持在任何方法上的 @Transactional
运行时行为。
表 1. 注解驱动的事务设置
XML 属性 | 注解属性 | 默认值 | 描述 |
---|---|---|---|
transaction-manager | 无(参见 TransactionManagementConfigurer 文档) | transactionManager | 要使用的事务管理器的名称。仅当事务管理器的名称不是 transactionManager 时才需要,如前面的示例所示。 |
mode | mode | proxy | 默认模式 (proxy ) 使用 Spring 的 AOP 框架处理带注解的 Bean,使其被代理(遵循代理语义,如前所述,仅适用于通过代理传入的方法调用)。另一种模式 (aspectj ) 使用 Spring 的 AspectJ 事务切面织入受影响的类,修改目标类的字节码以适用于任何类型的方法调用。AspectJ 织入需要在类路径中包含 spring-aspects.jar ,并且启用加载时织入(或编译时织入)。(有关如何设置加载时织入的详细信息,请参见 Spring 配置。) |
proxy-target-class | proxyTargetClass | false | 仅适用于 proxy 模式。控制为带有 @Transactional 注解的类创建何种类型的事务代理。如果 proxy-target-class 属性设置为 true ,则创建基于类的代理。如果 proxy-target-class 为 false 或省略该属性,则创建标准的基于 JDK 接口的代理。(有关不同代理类型的详细说明,请参见 代理机制。 |
order | order | Ordered.LOWEST_PRECEDENCE | 定义应用于带有 @Transactional 注解的 Bean 的事务通知的顺序。(有关 AOP 通知排序规则的更多信息,请参见 通知排序。)未指定排序意味着 AOP 子系统决定通知的顺序。 |
处理 @Transactional
注解的默认建议模式是 proxy
,它仅允许通过代理拦截调用。同一类中的本地调用无法以这种方式被拦截。对于更高级的拦截模式,请考虑切换到 aspectj
模式,并结合编译时或加载时织入。
proxy-target-class
属性控制为带有 @Transactional
注解的类创建何种类型的事务代理。如果 proxy-target-class
设置为 true
,则创建基于类的代理。如果 proxy-target-class
为 false
或省略该属性,则创建标准的基于 JDK 接口的代理。(有关不同代理类型的讨论,请参见 代理机制。)
@EnableTransactionManagement
和 <tx:annotation-driven/>
仅在它们定义的同一应用上下文中的 bean 上查找 @Transactional
。这意味着,如果你将注解驱动的配置放在 DispatcherServlet
的 WebApplicationContext
中,它只会检查控制器中的 @Transactional
bean,而不会检查服务中的 bean。更多信息请参阅 MVC。
在评估方法的事务设置时,最具体的设置优先。在以下示例中,DefaultFooService
类在类级别上使用了只读事务的设置进行注解,但同一类中 updateFoo(Foo)
方法上的 @Transactional
注解优先于类级别定义的事务设置。
- Java
- Kotlin
@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(readOnly = true)
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Foo {
// ...
}
// these settings have precedence for this method
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
override fun updateFoo(foo: Foo) {
// ...
}
}
@Transactional
设置
@Transactional
注解是一种元数据,用于指定接口、类或方法必须具有事务语义(例如,“在调用此方法时启动一个全新的只读事务,并暂停任何现有事务”)。默认的 @Transactional
设置如下:
-
传播行为设置为
PROPAGATION_REQUIRED
。 -
隔离级别为
ISOLATION_DEFAULT
。 -
事务为读写事务。
-
事务超时时间默认为底层事务系统的默认超时时间,如果不支持超时,则为无超时。
-
任何
RuntimeException
或Error
都会触发回滚,而任何受检的Exception
不会触发回滚。
你可以更改这些默认设置。下表总结了 @Transactional
注解的各种属性:
表 2. @Transactional 设置
属性 | 类型 | 描述 |
---|---|---|
value | String | 可选的限定符,用于指定要使用的事务管理器。 |
transactionManager | String | value 的别名。 |
label | String 标签数组,用于为事务添加描述性说明。 | 事务管理器可能会评估这些标签,以将特定于实现的行为与实际事务关联起来。 |
propagation | enum : Propagation | 可选的事务传播设置。 |
isolation | enum : Isolation | 可选的事务隔离级别。仅适用于传播值为 REQUIRED 或 REQUIRES_NEW 的情况。 |
timeout | int (以秒为单位) | 可选的事务超时时间。仅适用于传播值为 REQUIRED 或 REQUIRES_NEW 的情况。 |
timeoutString | String (以秒为单位) | 以 String 值指定 timeout 的替代方式,例如作为占位符。 |
readOnly | boolean | 读写事务与只读事务。仅适用于传播值为 REQUIRED 或 REQUIRES_NEW 的情况。 |
rollbackFor | Class 对象数组,必须继承自 Throwable 。 | 可选的异常类型数组,这些异常必须导致回滚。 |
rollbackForClassName | 异常名称模式数组。 | 可选的异常名称模式数组,这些异常必须导致回滚。 |
noRollbackFor | Class 对象数组,必须继承自 Throwable 。 | 可选的异常类型数组,这些异常不得导致回滚。 |
noRollbackForClassName | 异常名称模式数组。 | 可选的异常名称模式数组,这些异常不得导致回滚。 |
有关回滚规则语义、模式以及基于模式回滚规则可能无意匹配的警告的更多详细信息,请参阅回滚规则。
从 6.2 版本开始,你可以全局更改默认的回滚行为——例如,通过 @EnableTransactionManagement(rollbackOn=ALL_EXCEPTIONS)
,使得事务中抛出的所有异常(包括任何受检异常)都会触发回滚。如需进一步自定义,AnnotationTransactionAttributeSource
提供了一个 addDefaultRollbackRule(RollbackRuleAttribute)
方法,用于自定义默认规则。
请注意,事务特定的回滚规则会覆盖默认行为,但对于未指定的异常,仍会保留所选的默认行为。Spring 的 @Transactional
以及 JTA 的 jakarta.transaction.Transactional
注解都是如此。
除非你依赖于 EJB 风格的业务异常及其提交行为,否则建议切换到 ALL_EXCEPTIONS
,以确保即使在(可能是意外的)受检异常情况下也能保持一致的回滚语义。此外,对于基于 Kotlin 的应用程序,由于没有强制检查异常,也建议进行此切换。
目前,你无法显式控制事务的名称,这里的“名称”指的是出现在事务监视器和日志输出中的事务名称。对于声明式事务,事务名称始终是事务通知类的完全限定类名 + .
+ 方法名。例如,如果 BusinessService
类的 handlePayment(..)
方法启动了一个事务,那么该事务的名称将是 com.example.BusinessService.handlePayment
。
使用 @Transactional
实现多个事务管理器
大多数 Spring 应用程序只需要一个事务管理器,但在某些情况下,你可能希望在一个应用程序中使用多个独立的事务管理器。你可以使用 @Transactional
注解的 value
或 transactionManager
属性来可选地指定要使用的 TransactionManager
的标识。这可以是事务管理器 Bean 的名称或限定符值。例如,使用限定符表示法,你可以将以下 Java 代码与应用程序上下文中的以下事务管理器 Bean 声明结合起来:
- Java
- Kotlin
public class TransactionalService {
@Transactional("order")
public void setSomething(String name) { ... }
@Transactional("account")
public void doSomething() { ... }
@Transactional("reactive-account")
public Mono<Void> doSomethingReactive() { ... }
}
class TransactionalService {
@Transactional("order")
fun setSomething(name: String) {
// ...
}
@Transactional("account")
fun doSomething() {
// ...
}
@Transactional("reactive-account")
fun doSomethingReactive(): Mono<Void> {
// ...
}
}
以下清单展示了 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
上的各个方法在不同的事务管理器下运行,这些事务管理器通过 order
、account
和 reactive-account
限定符进行区分。如果没有找到特定限定符的 TransactionManager
bean,则仍然使用默认的 <tx:annotation-driven>
目标 bean 名称 transactionManager
。
如果同一个类上的所有事务方法共享相同的限定符,可以考虑在类型级别声明一个 org.springframework.beans.factory.annotation.Qualifier
注解。如果其值与特定事务管理器的限定符值(或 bean 名称)匹配,则该事务管理器将用于 @Transactional
本身没有特定限定符的事务定义。
这样的类型级别限定符可以在具体类上声明,并应用于来自基类的事务定义。这有效地覆盖了任何未限定的基类方法的默认事务管理器选择。
最后但同样重要的是,这样的类型级别 bean 限定符可以服务于多种目的。例如,值为 "order" 时,它可以用于自动装配目的(识别订单仓库)以及事务管理器选择,只要自动装配的目标 bean 以及关联的事务管理器定义声明相同的限定符值。这样的限定符值只需要在一组类型匹配的 bean 中唯一,而不必充当 ID。
自定义组合注解
如果你发现自己在许多不同的方法上重复使用相同的 @Transactional
属性,Spring 的元注解支持 允许你为特定用例定义自定义的组合注解。例如,考虑以下注解定义:
- Java
- Kotlin
@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 {
}
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional(transactionManager = "order", label = ["causal-consistency"])
annotation class OrderTx
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional(transactionManager = "account", label = ["retryable"])
annotation class AccountTx
前面的注解让我们可以将上一节的示例写成如下形式:
- Java
- Kotlin
public class TransactionalService {
@OrderTx
public void setSomething(String name) {
// ...
}
@AccountTx
public void doSomething() {
// ...
}
}
class TransactionalService {
@OrderTx
fun setSomething(name: String) {
// ...
}
@AccountTx
fun doSomething() {
// ...
}
}
在前面的示例中,我们使用了语法来定义事务管理器限定符和事务标签,但我们也可以包含传播行为、回滚规则、超时时间和其他特性。