使用 @Transactional

除了基于XML的声明式事务配置之外,您还可以使用基于注解的方法。直接在Java源代码中声明事务语义将使声明更接近受影响的代码。由于通常部署了希望以事务方式使用的代码,因此几乎没有不当耦合的危险。

标准的jakarta.transaction.Transactional注解也被支持作为Spring自己注解的可替代项。请参阅JTA文档以获取更多详细信息。

使用@Transactional注解所提供的易用性最好通过下面的示例来说明,该示例将在接下来的文本中解释。考虑以下类定义:

  • Java

  • Kotlin

// 我们想要使其具有事务性的服务类
@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) {
		// ...
	}
}
// 我们想要使其具有事务性的服务类
@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/>标签提供了类似的便利:

<!-- 来自文件 '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">

	<!-- 这是我们想要使其具有事务性的服务对象 -->
	<bean id="fooService" class="x.y.service.DefaultFooService"/>

	<!-- 启用基于注解的事务行为配置 -->
	<!-- 仍然需要一个TransactionManager -->
	<tx:annotation-driven transaction-manager="txManager"/> (1)

	<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- (此依赖关系在其他地方定义) -->
		<property name="dataSource" ref="dataSource"/>
	</bean>

	<!-- 这里还有其他<bean/>定义 -->

</beans>
1 使bean实例具有事务性的行。
如果您要将要注入的TransactionManager的bean名称为transactionManager,则可以在<tx:annotation-driven/>标签中省略transaction-manager属性。如果您要注入的TransactionManager bean具有其他名称,则必须使用transaction-manager属性,就像前面的示例中那样。

响应式事务方法使用响应式返回类型,与命令式编程安排形成对比,如下面的清单所示:

  • Java

  • Kotlin

// 我们想要使其具有事务性的响应式服务类
@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) {
		// ...
	}
}
// 我们想要使其具有事务性的响应式服务类
@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

/**
 * 使用publicMethodsOnly标志设置为true注册自定义的AnnotationTransactionAttributeSource,以始终忽略非公共方法。
 * @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运行时行为对任何类型的方法。

表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。这意味着,如果您将注解驱动的配置放在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 注解的各种属性:

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

value

String

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

transactionManager

String

别名为 value

label

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

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

propagation

enum: Propagation

可选传播设置。

isolation

enum: Isolation

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

timeout

int(以秒为粒度)

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

timeoutString

String(以秒为粒度)

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

readOnly

boolean

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

rollbackFor

必须派生自 ThrowableClass 对象数组。

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

rollbackForClassName

异常名称模式的数组。

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

noRollbackFor

必须派生自 ThrowableClass 对象数组。

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

noRollbackForClassName

异常名称模式的数组。

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

有关回滚规则语义、模式以及有关基于模式的回滚规则可能意外匹配的警告,请参见 回滚规则

目前,您无法显式控制事务的名称,这里的“名称”是指事务名称,出现在事务监视器和日志输出中。对于声明式事务,事务名称始终是事务建议类的完全限定类名 + . + 方法名。例如,如果 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

自定义组合注解

如果您发现在许多不同方法上重复使用@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() {
		// ...
	}
}

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