使用 @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): Mono<Void> {
		// ...
	}

	override fun updateFoo(foo: Foo): Mono<Void> {
		// ...
	}
}

如上所示,在类级别使用时,该注解表示声明类(及其子类)的所有方法的默认设置。或者,每个方法可以单独注解。有关 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"/> [id="CO1-1"]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

@Transactional 注解通常用于 public 可见性的方法。从 6.0 开始,protected 或包可见的方法也可以默认用于基于类的代理的事务。请注意,基于接口的代理中的事务方法必须始终是 public 并定义在被代理的接口中。对于两种代理,只有通过代理进行的外部方法调用才会被拦截。 如果你希望在不同类型的代理之间对方法可见性进行一致处理(这是直到 5.3 的默认设置),请考虑指定 publicMethodsOnly

/**
 * Register a custom AnnotationTransactionAttributeSource with the
 * publicMethodsOnly flag set to true to consistently ignore non-public methods.
 * @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 运行时行为。

Table 1. 注解驱动的事务设置
XML 属性 注解属性 默认值 描述

transaction-manager

N/A (请参阅 TransactionManagementConfigurer javadoc)

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-classfalse 或省略该属性,则创建标准 JDK 基于接口的代理。(有关不同代理类型的详细分析,请参阅 代理机制)。

order

order

Ordered.LOWEST_PRECEDENCE

定义应用于 @Transactional 注解的 bean 的事务通知的顺序。(有关 AOP 通知排序规则的更多信息,请参阅 通知排序)。未指定排序意味着 AOP 子系统确定通知的顺序。

处理 @Transactional 注解的默认通知模式是 proxy,它只允许通过代理拦截调用。同一类中的本地调用无法以这种方式拦截。对于更高级的拦截模式,请考虑切换到 aspectj 模式并结合编译时或加载时织入。

proxy-target-class 属性控制为使用 @Transactional 注解的类创建的事务代理类型。如果 proxy-target-class 设置为 true,则创建基于类的代理。如果 proxy-target-classfalse 或省略该属性,则创建标准 JDK 基于接口的代理。(有关不同代理类型的讨论,请参阅 代理机制。)

@EnableTransactionManagement<tx:annotation-driven/> 只在其定义的相同应用程序上下文中查找 @Transactional bean。这意味着,如果你将注解驱动的配置放在 DispatcherServletWebApplicationContext 中,它只会在你的控制器中检查 @Transactional 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.

  • 事务是读写的。

  • 事务超时默认为底层事务系统的默认超时,如果不支持超时则为无。

  • 任何 RuntimeExceptionError 都会触发回滚,任何检查型 Exception 则不会。

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

Table 2. @Transactional 设置
属性 类型 描述

value

String

可选限定符,指定要使用的事务管理器。

transactionManager

String

value 的别名。

label

String 标签数组,用于为事务添加表达性描述。

标签可以由事务管理器评估,以将特定于实现的行为与实际事务关联起来。

propagation

enum: Propagation

可选的传播设置。

isolation

enum: Isolation

可选的隔离级别。仅适用于 REQUIREDREQUIRES_NEW 的传播值。

timeout

int (以秒为单位的粒度)

可选的事务超时。仅适用于 REQUIREDREQUIRES_NEW 的传播值。

timeoutString

String (以秒为单位的粒度)

替代方式,以 String 值指定 timeout(以秒为单位),例如作为占位符。

readOnly

boolean

读写与只读事务。仅适用于 REQUIREDREQUIRES_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 注解的 valuetransactionManager 属性来可选地指定要使用的 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 上的各个方法在不同的事务管理器下运行,通过 orderaccountreactive-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() {
		// ...
	}
}

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