Using @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) {
		// ...
	}
}

在类级别如上使用时,注解表示声明类(以及其子类)的所有方法的默认值。或者,可以单独对每个方法进行注解。请参阅 method visibility 以了解 Spring 认为是事务性方法的更多详细信息。请注意,类级注解不适用于类层次结构上方的祖先类;在这种情况下,需要在本地重新声明继承的方法,以便参与子类级别的注解。 当像上面那样的 POJO 类在 Spring 上下文中被定义为 Bean 时,您可以通过 @Configuration 类中的 @EnableTransactionManagement 注解使 Bean 实例成为事务性的。请参阅https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/EnableTransactionManagement.html[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 有特殊考虑。有关更多详细信息,请参阅“使用事务操作符”下的 Cancel Signals 一节。

Method visibility and @Transactional in proxy mode

@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 测试方法。有关示例,请参阅测试章节中的 Transaction Management

您可以将 @Transactional 注解应用于接口定义、接口上的方法、类定义或类上的方法。但是,只有 @Transactional 注解本身还不足以激活事务行为。@Transactional 注解只是一种元数据,相应的运行时基础结构可以使用这些元数据,并使用该元数据配置具有事务行为的适当 bean。在前面的示例中,<tx:annotation-driven/> 元素会在运行时开启实际的事务管理。

Spring 团队建议使用 @Transactional 注释对具体类的メソッド进行注释,而不是依赖于接口中的注释方法,即使后者对基于接口和目标类的代理在 5.0 中有效也是如此。由于 Java 注释不会从接口继承,因此在使用 AspectJ 模式时,界面声明的注释仍然不会被织入基础结构识别,因此不会应用该方面。结果,你的事务注释可能会被忽略:在测试回滚方案之前,你的代码可能会“正常”工作。

在代理模式(它是默认模式)中,仅拦截通过代理传入的外部方法调用。这意味着自调用(实际上,目标对象中的方法调用目标对象中的另一个方法)不会导致实际事务在运行时,即使调用的方法用 @Transactional 标记。此外,必须完全初始化代理以提供预期的行为,因此你不应在此功能中依赖初始化代码 — 例如在 @PostConstruct 方法中。

如果您希望自行调用也用事务包装,请考虑使用 AspectJ 模式(请参阅下表中的 mode 属性)。在这种情况下,首先没有代理。相反,目标类被编织(即,它的字节码被修改),以支持在任何方法上进行 @Transactional 运行时行为。

Table 1. Annotation driven transaction settings
XML Attribute Annotation Attribute Default Description

transaction-manager

N/A (see TransactionManagementConfigurer javadoc)

transactionManager

要使用的事务管理器的名称。仅当事务管理器的名称不是 transactionManager,如前面示例中所述时才需要。

mode

mode

proxy

默认模式 (proxy) 会处理用注释标记的 Bean,Spring 的 AOP 框架会代理这些 Bean(遵循前面讨论的代理语义,仅适用于通过代理传入的方法调用)。替代模式 (aspectj) 则使用 Spring 的 AspectJ 事务方面来编排受影响的类,修改目标类的字节码以应用于任何类型的方法调用。AspectJ 织入需要 spring-aspects.jar 存在于类路径中,并且启用了加载时织入(或编译时织入)。(有关如何设置加载时织入的详细信息,请参阅 Spring configuration。)

proxy-target-class

proxyTargetClass

false

仅适用于 proxy 模式。控制为用 @Transactional 注释标记的类创建哪种类型的事务代理。如果将 proxy-target-class 属性设置为 true,则可以创建基于类的代理。如果 proxy-target-classfalse,或者省略了该属性,则可以创建基于 JDK 标准接口的代理。(请参阅 Proxying Mechanisms 以详细了解不同代理类型的检查。)

order

order

Ordered.LOWEST_PRECEDENCE

定义应用于用 @Transactional 注释标记的 Bean 的事务建议的顺序。(有关与 AOP 建议排序相关的规则的更多信息,请参阅 Advice Ordering。)未指定顺序意味着 AOP 子系统确定建议的顺序。

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

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

@EnableTransactionManagement<tx:annotation-driven/> 仅在与其定义在同一应用程序上下文中的 Bean 上查找 @Transactional。这意味着,如果您为 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 Settings

@Transactional 注解是一种元数据,它指定接口、类或方法必须具有事务语义(例如,“调用此方法时,启动一个全新的只读事务,暂停任何现有事务”)。@Transactional 的默认设置如下:

  • 传播设置是 PROPAGATION_REQUIRED.

  • 隔离级别是 ISOLATION_DEFAULT.

  • The transaction is read-write.

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

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

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

Table 2. @Transactional Settings
Property Type Description

value

String

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

transactionManager

String

Alias for value.

label

String 标签的数组来向事务添加富有表现力的描述。

标签可通过事务管理器评估以用特定于实现的行为与实际事务关联。

propagation

enum: Propagation

Optional propagation setting.

isolation

enum: Isolation

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

timeout

int(以秒为单位)

(以秒为单位)可选的事务超时时间。仅适用于传播值 REQUIREDREQUIRES_NEW

timeoutString

String(以秒为单位的粒度)

作为 String 值(例如,作为占位符)在秒中指定 timeout 的替代方案。

readOnly

boolean

读写事务与只读事务。仅适用于 REQUIREDREQUIRES_NEW 的值。

rollbackFor

必须从 Throwable. 得出的 Class 对象的数组

可能导致回滚的异常类型的可选数组。

rollbackForClassName

异常名称模式的数组。

可能导致回滚的异常名称模式的可选数组。

noRollbackFor

必须从 Throwable. 得出的 Class 对象的数组

不会导致回滚的异常类型的可选数组。

noRollbackForClassName

异常名称模式的数组。

不会导致回滚的异常名称模式的可选数组。

请参阅 Rollback rules 以进一步了解回滚规则语义、模式以及有关基于模式的回滚规则可能无意间匹配的警告。

从 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

Multiple Transaction Managers with @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 限定符进行区分。默认 <tx:annotation-driven> 目标 bean 名称 transactionManager 仍用于未找到特定限定符的 TransactionManager bean 的情况。

如果同一类上的所有事务方法共享相同的限定符,请考虑声明一个类型级别的 org.springframework.beans.factory.annotation.Qualifier 注解。如果它的值与特定事务管理器的限定符值(或 bean 名称)匹配,则该事务管理器将用于没有在 @Transactional 本身上指定特定限定符的事务定义。 此类类型级别限定符可以在具体类上声明,也可以应用于基类中的事务定义。这实际上覆盖了任何不合格基类方法的默认事务管理器选择。 最后但并非最不重要的是,这样的类型级bean限定符可以起到多种用途,例如:当值为“order”时,可以用它进行自动注入(识别order存储库)以及事务管理器选择,只要用于自动注入的目标bean以及关联的事务管理器定义都声明了相同的限定符值即可。这样的限定符值只需在匹配类型的bean集合中才是唯一的,而不必用作ID。

Custom Composed Annotations

如果你发现自己在许多不同的方法上重复使用相同的属性, Spring’s meta-annotation support 让你可以定义自定义组合注解以满足你的特定用例。例如,考虑以下注解定义:

  • 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() {
		// ...
	}
}

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