使用@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

@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框架默认也支持非私有的@Transactional测试方法。有关示例,请参阅测试章节中的事务管理

您可以将@Transactional注解应用于接口定义、接口上的方法、类定义或类上的方法。但是,仅仅存在@Transactional注解不足以激活事务行为。@Transactional注解仅仅是元数据,可以被相应的运行时基础设施使用,该基础设施使用该元数据来配置具有事务行为的相应bean。在前面的示例中,<tx:annotation-driven/>元素在运行时启动实际的事务管理。

Spring团队建议您使用@Transactional注解注释具体类的方法,而不是依赖于接口中已注释的方法,即使后者在5.0及以后版本中对基于接口和目标类的代理有效也是如此。由于Java注解不会从接口继承,因此当使用AspectJ模式时,编织基础设施仍然无法识别接口声明的注解,因此不会应用该方面。结果,您的事务注解可能会被静默忽略:您的代码可能看起来“有效”,直到您测试回滚场景为止。
在代理模式(默认模式)下,只有通过代理进入的外部方法调用才会被拦截。这意味着自调用(实际上,目标对象中的一个方法调用目标对象的另一个方法)在运行时不会导致实际的事务,即使被调用的方法用@Transactional标记也是如此。此外,代理必须完全初始化才能提供预期的行为,因此您不应在初始化代码中依赖此功能,例如在@PostConstruct方法中。

如果您希望自调用也包含在事务中,请考虑使用 AspectJ 模式(参见下表中的mode属性)。在这种情况下,首先不存在代理。而是对目标类进行织入(即修改其字节码),以便在任何类型的 method 上都支持@Transactional运行时行为。

表 1. 注解驱动的交易设置
XML 属性 注解属性 默认值 描述

transaction-manager

N/A(参见TransactionManagementConfigurer javadoc)

transactionManager

要使用的交易管理器的名称。仅当交易管理器的名称不是transactionManager(如上例所示)时才需要。

mode

mode

proxy

默认模式(proxy)处理带注解的 bean,使其通过 Spring 的 AOP 框架进行代理(遵循前面讨论过的代理语义,仅应用于通过代理传入的方法调用)。另一种模式(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/>只查找定义它们的同一应用程序上下文中的 bean 上的@Transactional。这意味着,如果您将注解驱动的配置放在DispatcherServletWebApplicationContext中,它只检查控制器中的@Transactional bean,而不是服务中的@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注解的各种属性。

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

字符串

指定要使用的交易管理器的可选限定符。

transactionManager

字符串

value的别名。

标签

要添加对事务的表达性描述的String标签数组。

标签可以由交易管理器评估,以将实现特定的行为与实际交易关联。

传播

枚举Propagation

可选的传播设置。

隔离

枚举Isolation

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

超时

int(以秒为粒度)

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

timeoutString

String(以秒为粒度)

用于以String值指定timeout(以秒为单位)的替代方法——例如,作为占位符。

readOnly

布尔值

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

rollbackFor

必须派生自ThrowableClass对象数组。

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

rollbackForClassName

异常名称模式数组。

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

noRollbackFor

必须派生自ThrowableClass对象数组。

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

noRollbackForClassName

异常名称模式数组。

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

有关回滚规则语义、模式以及关于基于模式的回滚规则可能出现意外匹配的警告的更多详细信息,请参见回滚规则

从 6.2 版本开始,您可以全局更改默认回滚行为——例如,通过@EnableTransactionManagement(rollbackOn=ALL_EXCEPTIONS),导致事务中引发的所有异常回滚,包括任何已检查的异常。对于进一步的自定义,AnnotationTransactionAttributeSource提供了一个addDefaultRollbackRule(RollbackRuleAttribute)方法用于自定义默认规则。

请注意,特定于事务的回滚规则会覆盖默认行为,但会保留为未指定的异常选择的默认值。Spring 的@Transactional和 JTA 的jakarta.transaction.Transactional注解都是如此。

除非您依赖于具有提交行为的EJB风格的业务异常,否则建议切换到ALL_EXCEPTIONS,以确保即使在(可能意外的)已检查异常的情况下也能保持一致的回滚语义。此外,建议对于 Kotlin 应用程序进行此切换,因为在 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() {
		// ...
	}
}

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