使用 @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>
1 | 使 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
,关于 Reactive Streams 取消信号有一些特殊的考虑。有关更多详细信息,请参阅“使用 TransactionalOperator”下的取消信号部分。
代理模式下的方法可见性和 `@Transactional`
如果您希望在不同类型的代理之间对方法可见性进行一致处理(这在 5.3 版本之前是默认设置),请考虑指定
Spring TestContext 框架也默认支持非私有的 `@Transactional` 测试方法。有关示例,请参阅测试章节中的事务管理。 |
您可以将 `@Transactional` 注解应用于接口定义、接口上的方法、类定义或类上的方法。然而,仅存在 `@Transactional` 注解不足以激活事务行为。`@Transactional` 注解只是一种元数据,可以由相应的运行时基础设施消费,该基础设施利用这些元数据来配置具有事务行为的适当 Bean。在前面的示例中,`<tx:annotation-driven/>` 元素在运行时开启了实际的事务管理。
Spring 团队建议您使用 `@Transactional` 注解来注解具体类的方法,而不是依赖于接口中注解的方法,尽管后者在 5.0 版本后对基于接口和基于目标类的代理都有效。由于 Java 注解不会从接口继承,因此在使用 AspectJ 模式时,编织基础设施仍然不识别接口中声明的注解,所以切面不会被应用。因此,您的事务注解可能会被静默忽略:您的代码可能看起来“正常工作”,直到您测试回滚场景。 |
在代理模式下(这是默认模式),只有通过代理进入的外部方法调用才会被拦截。这意味着自调用(实际上是目标对象中的一个方法调用目标对象的另一个方法)在运行时不会导致实际的事务,即使被调用的方法标记了 `@Transactional`。此外,代理必须完全初始化才能提供预期的行为,因此您不应在初始化代码中依赖此特性,例如在 `@PostConstruct` 方法中。 |
如果您希望自调用也被事务包装,请考虑使用 AspectJ 模式(参见下表中的 `mode` 属性)。在这种情况下,首先没有代理。相反,目标类会被编织(即其字节码被修改),以支持任何类型的方法上的 `@Transactional` 运行时行为。
XML 属性 | 注解属性 | 默认值 | 描述 |
---|---|---|---|
|
不适用 (参见 |
|
要使用的事务管理器的名称。仅当事务管理器的名称不是 |
|
|
|
默认模式(`proxy`)使用 Spring 的 AOP 框架处理要通过代理进行代理的带注解 Bean(遵循代理语义,如前所述,仅应用于通过代理进入的方法调用)。替代模式(`aspectj`)则使用 Spring 的 AspectJ 事务切面编织受影响的类,修改目标类的字节码以应用于任何类型的方法调用。AspectJ 编织需要在类路径中包含 |
|
|
|
仅适用于 `proxy` 模式。控制为带有 `@Transactional` 注解的类创建哪种类型的事务代理。如果将 `proxy-target-class` 属性设置为 `true`,则创建基于类的代理。如果 `proxy-target-class` 为 `false` 或省略该属性,则创建标准的 JDK 基于接口的代理。(有关不同代理类型的详细讨论,请参见代理机制。) |
|
|
|
定义应用于带有 `@Transactional` 注解的 Bean 的事务通知的顺序。(有关 AOP 通知排序规则的更多信息,请参见通知排序。)未指定顺序意味着 AOP 子系统确定通知的顺序。 |
处理 `@Transactional` 注解的默认通知模式是 `proxy`,它只允许拦截通过代理进行的调用。同一类内部的本地调用无法以这种方式拦截。对于更高级的拦截模式,考虑切换到 `aspectj` 模式并结合编译时或加载时编织。 |
proxy-target-class 属性控制为带有 `@Transactional` 注解的类创建哪种类型的事务代理。如果 `proxy-target-class` 设置为 `true`,则创建基于类的代理。如果 `proxy-target-class` 为 `false` 或省略该属性,则创建标准的 JDK 基于接口的代理。(有关不同代理类型的讨论,请参见代理机制。) |
@EnableTransactionManagement 和 <tx:annotation-driven/> 只在其定义所在的同一个应用上下文中查找带有 `@Transactional` 的 Bean。这意味着,如果您将注解驱动的配置放在 DispatcherServlet 的 WebApplicationContext 中,它只会检查控制器中的 `@Transactional` Bean,而不会检查服务中的。有关更多信息,请参见MVC。 |
在评估方法的事务设置时,最具体的(most derived)位置优先。在以下示例中,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` 注解的各种属性
属性 | 类型 | 描述 |
---|---|---|
|
可选的限定符,用于指定要使用的事务管理器。 |
|
|
|
|
|
字符串标签数组,用于为事务添加表达性描述。 |
标签可以由事务管理器评估,以将特定于实现的行为与实际事务关联起来。 |
|
可选的传播设置。 |
|
|
|
可选的隔离级别。仅适用于传播值为 |
|
|
可选的事务超时。仅适用于传播值为 |
|
|
另一种指定 |
|
|
读写与只读事务。仅适用于传播值为 |
|
|
可选的异常类型数组,必须导致回滚。 |
|
异常名称模式数组。 |
可选的异常名称模式数组,必须导致回滚。 |
|
|
可选的异常类型数组,不能导致回滚。 |
|
异常名称模式数组。 |
可选的异常名称模式数组,不能导致回滚。 |
有关回滚规则语义、模式以及基于模式的回滚规则可能无意匹配的警告,请参见回滚规则以获取更多详细信息。 |
自 6.2 版本起,您可以全局更改默认的回滚行为——例如,通过 `@EnableTransactionManagement(rollbackOn=ALL_EXCEPTIONS)`,这将导致事务中抛出的所有异常(包括任何检查型异常)都触发回滚。如需进一步定制,`AnnotationTransactionAttributeSource` 提供了 `addDefaultRollbackRule(RollbackRuleAttribute)` 方法用于自定义默认规则。 请注意,事务特定的回滚规则会覆盖默认行为,但对于未指定的异常会保留所选的默认行为。Spring 的 除非您依赖具有提交行为的 EJB 风格的业务异常,否则建议切换到 |
目前,您无法显式控制事务的名称,其中“名称”是指在事务监视器和日志输出中显示的事务名称。对于声明式事务,事务名称始终是事务性增强类的完全限定类名 + .
+ 方法名。例如,如果 BusinessService
类的 handlePayment(..)
方法启动了一个事务,则事务的名称将是 com.example.BusinessService.handlePayment
。
使用 @Transactional
的多个事务管理器
大多数 Spring 应用程序只需要一个事务管理器,但有时您可能希望在单个应用程序中有多个独立的事务管理器。您可以使用 @Transactional
注解的 value
或 transactionManager
属性来可选地指定要使用的 TransactionManager
的身份。这可以是 bean 名称或事务管理器 bean 的 qualifier(限定符)值。例如,使用限定符表示法,您可以将以下 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
。
如果同一个类上的所有事务性方法共享同一个限定符,则考虑声明一个类级别的 这种类级别的限定符可以声明在具体类上,也适用于基类中的事务定义。这有效地覆盖了任何未限定的基类方法的默认事务管理器选择。 最后同样重要的是,这种类级别的 bean 限定符可以服务于多种目的,例如,值为 "order" 时,它既可以用于自动装配(识别订单 repository),也可以用于事务管理器选择,只要用于自动装配的目标 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() {
// ...
}
}
在上面的示例中,我们使用了语法来定义事务管理器限定符和事务标签,但我们也可以包含传播行为、回滚规则、超时和其他特性。