Skip to content

为不同 Bean 配置不同的事务语义 (Configuring Different Transactional Semantics for Different Beans)

考虑这样一种场景:你有许多个服务层对象,并且你想对它们中的每一个应用完全不同的事务配置。你可以通过定义具有不同 pointcutadvice-ref 属性值的不同 <aop:advisor/> 元素来实现这一点。

作为对比,首先假设你所有的服务层类都定义在一个根 x.y.service 包中。要使所有在该包(或子包)中定义的、且名称以 Service 结尾的类的 Bean 实例都具有默认的事务配置,你可以编写如下代码:

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">

	<aop:config>

		<aop:pointcut id="serviceOperation"
				expression="execution(* x.y.service..*Service.*(..))"/>

		<aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>

	</aop:config>

	<!-- 这两个 bean 将是事务性的... -->
	<bean id="fooService" class="x.y.service.DefaultFooService"/>
	<bean id="barService" class="x.y.service.extras.SimpleBarService"/>

	<!-- ... 而这两个 bean 不会是事务性的 -->
	<bean id="anotherService" class="org.xyz.SomeService"/> <!-- (不在正确的包中) -->
	<bean id="barManager" class="x.y.service.SimpleBarManager"/> <!-- (不以 'Service' 结尾) -->

	<tx:advice id="txAdvice">
		<tx:attributes>
			<tx:method name="get*" read-only="true"/>
			<tx:method name="*"/>
		</tx:attributes>
	</tx:advice>

	<!-- 省略了其他事务基础设施 bean,例如 TransactionManager... -->

</beans>

以下示例展示了如何配置两个具有完全不同事务设置的不同 Bean:

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">

	<aop:config>

		<aop:pointcut id="defaultServiceOperation"
				expression="execution(* x.y.service.*Service.*(..))"/>

		<aop:pointcut id="noTxServiceOperation"
				expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>

		<aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>

		<aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>

	</aop:config>

	<!-- 这个 bean 将是事务性的(参见 'defaultServiceOperation' 切入点) -->
	<bean id="fooService" class="x.y.service.DefaultFooService"/>

	<!-- 这个 bean 也将是事务性的,但具有完全不同的事务设置 -->
	<bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>

	<tx:advice id="defaultTxAdvice">
		<tx:attributes>
			<tx:method name="get*" read-only="true"/>
			<tx:method name="*"/>
		</tx:attributes>
	</tx:advice>

	<tx:advice id="noTxAdvice">
		<tx:attributes>
			<tx:method name="*" propagation="NEVER"/>
		</tx:attributes>
	</tx:advice>

	<!-- 省略了其他事务基础设施 bean,例如 TransactionManager... -->

</beans>

补充教学

1. XML 配置的“批量威力”

@Transactional 注解盛行的今天,XML 配置依然有其独特的不可替代性:批量管理。 如果你有 100 个 Service 类,为了遵循公司规范(所有 get 方法只读,其他方法读写),使用注解你需要修改 100 个文件。而使用 XML 配置,你只需要写 一段 <aop:config> 就能覆盖所有符合命名规则(如 *Service)的类。这在维护大型遗留系统或强规范系统时非常高效。

2. AOP 切入点表达式 (Pointcut Expression) 复习

这里的关键在于 execution 表达式: execution(* x.y.service..*Service.*(..))

  • * (第一个):匹配任何返回值类型。
  • x.y.service..:匹配 x.y.service 包及其所有子包
  • *Service:匹配类名以 Service 结尾的类。
  • .* (在类名后):匹配该类中的任何方法。
  • (..):匹配任何参数列表。

3. 多事务管理器场景

虽然本节主要讲的是不同的事务属性(如只读 vs 读写),但这种配置模式同样适用于多个事务管理器(如一个 MySQL,一个 Oracle)。 你可以定义两个 <tx:advice>

  • <tx:advice id="mysqlAdvice" transaction-manager="mysqlTxManager">
  • <tx:advice id="oracleAdvice" transaction-manager="oracleTxManager">

然后通过两个 <aop:advisor> 分别绑定到不同的包或类上,从而实现优雅的多数据源事务管理,而无需在每个类上写 @Transactional("mysqlTxManager")

4. propagation="NEVER" 的应用场景

示例中出现的 noTxAdvice 使用了 propagation="NEVER"。这不仅仅意味着“没有事务”。

  • 不使用事务propagation="NOT_SUPPORTED"(如果当前有事务,挂起它,以非事务方式运行)。
  • 禁止事务propagation="NEVER"(如果当前有事务,抛出异常)。 这在执行 DDL(建表、删表)操作时非常有用,因为某些数据库在事务中执行 DDL 会导致隐式提交或死锁,或者你明确希望该操作是原子性的且独立于任何外部事务上下文。

Based on Spring Framework.