使用 @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 配置中,`
<!-- 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`,则可以省略 ` |
响应式事务方法使用响应式返回类型,与命令式编程安排形成对比,如下面的清单所示
-
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`
`@Transactional` 注解通常用于 `public` 可见性的方法。从 6.0 开始,`protected` 或包可见的方法也可以默认用于基于类的代理的事务性。请注意,基于接口的代理中的事务性方法必须始终是 `public` 并在代理接口中定义。对于这两种代理,只有通过代理传入的外部方法调用才会被拦截。 如果您希望在不同类型的代理之间对方法可见性进行一致处理(这在 5.3 之前是默认设置),请考虑指定 `publicMethodsOnly`
Spring TestContext Framework 默认也支持非私有的 `@Transactional` 测试方法。有关示例,请参阅测试章节中的事务管理。 |
您可以将 `@Transactional` 注解应用于接口定义、接口上的方法、类定义或类上的方法。但是,仅仅存在 `@Transactional` 注解不足以激活事务行为。`@Transactional` 注解只是元数据,可以由相应的运行时基础设施使用,该基础设施使用该元数据为适当的 bean 配置事务行为。在前面的示例中,`
| Spring 团队建议您使用 `@Transactional` 注解来注解具体类的方法,而不是依赖接口中注解的方法,即使后者在 5.0 版本中适用于基于接口和目标类的代理。由于 Java 注解不能从接口继承,因此在使用 AspectJ 模式时,编织基础设施仍然无法识别接口中声明的注解,因此切面不会被应用。因此,您的事务注解可能会被默默地忽略:您的代码可能看起来“正常工作”,直到您测试回滚场景。 |
| 在代理模式(默认)下,只有通过代理传入的外部方法调用才会被拦截。这意味着自我调用(实际上是目标对象内的一个方法调用目标对象的另一个方法)在运行时不会导致实际的事务,即使被调用的方法标记有 `@Transactional`。此外,代理必须完全初始化才能提供预期的行为,因此您不应在初始化代码中依赖此功能,例如在 `@PostConstruct` 方法中。 |
如果您希望自调用也能被事务包装,请考虑使用 AspectJ 模式(请参阅下表中的 `mode` 属性)。在这种情况下,首先没有代理。相反,目标类会被编织(即,其字节码被修改)以支持任何类型方法的 `@Transactional` 运行时行为。
| XML 属性 | 注解属性 | 默认值 | 描述 |
|---|---|---|---|
|
N/A (请参阅 `TransactionManagementConfigurer` javadoc) |
|
要使用的事务管理器的名称。仅当事务管理器的名称不是 `transactionManager` 时才需要,如前面的示例所示。 |
|
|
|
默认模式(`proxy`)使用 Spring 的 AOP 框架处理被注解的 Bean 进行代理(遵循前面讨论的代理语义,仅适用于通过代理传入的方法调用)。另一种模式(`aspectj`)则使用 Spring 的 AspectJ 事务切面编织受影响的类,修改目标类的字节码以应用于任何类型的方法调用。AspectJ 编织需要类路径中包含 `spring-aspects.jar`,并且需要启用加载时编织(或编译时编织)。 (有关如何设置加载时编织的详细信息,请参阅Spring 配置。) |
|
|
|
仅适用于 `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` 和 ` |
在评估方法的事务设置时,最派生的位置优先。在以下示例中,`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` 注解的各种属性
| 财产 | 类型 | 描述 |
|---|---|---|
|
可选的限定符,用于指定要使用的事务管理器。 |
|
|
|
`value` 的别名。 |
|
字符串标签数组,用于为事务添加富有表达力的描述。 |
标签可以由事务管理器评估,以将特定于实现的行为与实际事务关联起来。 |
`enum`: `Propagation` |
可选传播设置。 |
|
|
`enum`: `Isolation` |
可选隔离级别。仅适用于传播值为 `REQUIRED` 或 `REQUIRES_NEW`。 |
|
|
可选事务超时。仅适用于传播值为 `REQUIRED` 或 `REQUIRES_NEW`。 |
|
`String`(以秒为单位) |
用于以 `String` 值指定 `timeout`(以秒为单位)的替代方法,例如作为占位符。 |
|
|
读写与只读事务。仅适用于 `REQUIRED` 或 `REQUIRES_NEW` 的值。 |
|
`Class` 对象数组,必须派生自 `Throwable.` |
必须导致回滚的异常类型的可选数组。 |
|
异常名称模式数组。 |
必须导致回滚的异常名称模式的可选数组。 |
|
`Class` 对象数组,必须派生自 `Throwable.` |
不应导致回滚的异常类型的可选数组。 |
|
异常名称模式数组。 |
不应导致回滚的异常名称模式的可选数组。 |
| 有关回滚规则语义、模式以及对基于模式的回滚规则可能出现的意外匹配的警告的更多详细信息,请参阅回滚规则。 |
|
从 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 的 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,则仍使用默认的 `
|
如果同一类上的所有事务方法共享相同的限定符,请考虑声明一个类型级别的 `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() {
// ...
}
}
在前面的示例中,我们使用了语法来定义事务管理器限定符和事务标签,但我们也可以包含传播行为、回滚规则、超时和其他功能。