在基于XML的Spring事务配置方案中,<tx:advice>扮演的是事务通知的角色。它不是一个普通的业务Bean,而是Spring tx命名空间提供的声明式事务配置入口,底层对应着TransactionInterceptor以及TransactionAspectSupport这套基础设施。理解了tx:advice的本质,就理解了Spring声明式事务的关键一环,也能顺着配置文件把事务管理器、通知、切面这几部分完整串联起来。

tx:advice在Spring事务体系中的角色
Spring声明式事务的核心理念是:事务控制不写在业务代码里,而是通过AOP把事务逻辑织入到目标方法周围。在这个机制里,真正执行事务开启、提交、回滚动作的组件是TransactionInterceptor,它实现了MethodInterceptor接口,会在目标方法被调用时读取事务属性,然后委托给事务管理器完成操作。
tx:advice的作用就是声明这样一个事务拦截器。在Spring 2.0之前的XML配置中,开发者需要手工定义TransactionProxyFactoryBean来完成类似工作,这种方式配置烦琐且难以复用。而<tx:advice>让开发者可以在XML中直接描述事务属性,例如哪些方法需要传播行为、哪些方法需要只读事务、遇到什么异常需要回滚,Spring会在启动时解析这些声明并生成对应的事务通知Bean。
特别需要注意的是,<tx:advice>本身只负责定义事务增强的逻辑,并不决定要拦截哪些方法。它和AOP切面是分开配置的,通常需要配合<aop:config>里的<aop:advisor>把这个通知绑定到具体的切入点表达式上。这种职责分离是Spring XML事务配置中很核心的设计思想,理解这一点就不会把事务通知和切面混为一谈。
配置事务管理器与命名空间前提
使用tx:advice之前,必须有一个事务管理器。事务管理器是Spring事务抽象的顶层入口,负责真正的资源管理,例如打开数据库连接、提交或者回滚事务。如果项目使用JDBC或MyBatis,通常配置DataSourceTransactionManager;如果使用JPA,则配置JpaTransactionManager;如果涉及分布式多数据源,还可以考虑JtaTransactionManager。
在XML配置文件中,事务管理器和tx命名空间都需要提前声明。下面是一个最基础的配置文件骨架,注意xmlns:tx和xmlns:aop这两个命名空间的引入位置,缺少任何一个都会导致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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/example"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
这里的数据源使用了DBCP连接池,你也可以换成Druid、HikariCP等产品,只要把BasicDataSource替换成对应的实现类即可。事务管理器通过<property name="dataSource">与数据源关联,这一步决定了事务资源从哪里获得连接,后续所有事务操作都围绕这个数据源展开。
有一点容易被忽略:如果容器中定义了多个事务管理器,那么在<tx:advice>中必须通过transaction-manager属性显式指定使用哪一个。如果只有一个事务管理器,Spring会自动从容器中按类型查找,此时该属性可以省略,但为了配置可读性,显式指定往往是更好的习惯。
使用tx:advice构建事务增强
有了事务管理器,下一步就是定义事务通知。一个完整的<tx:advice>包含一个唯一标识ID、一个事务管理器引用以及一组事务属性。事务属性是核心,它针对方法名模式设置传播行为、隔离级别、只读标志以及触发回滚的异常类型。
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="query*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
上面的配置中,name属性支持通配符匹配,例如insert*匹配所有以insert开头的方法。带有写操作的方法使用REQUIRED传播行为,表示如果当前没有事务就新建一个,如果已有事务就加入当前事务;而只读方法使用SUPPORTS,表示有事务就参与,没有事务也可以正常执行,这样既能避免不必要的锁竞争,也能提高查询效率。
事务属性里最隐蔽的坑是rollback-for的默认行为。Spring声明式事务默认只在遇到RuntimeException和Error时回滚,如果业务方法抛出受检异常,事务不会自动回滚。上面的配置通过rollback-for="Exception"强制指定所有异常都触发回滚,这个设计取舍取决于业务语义。如果某个受检异常代表一种业务结果而不是程序错误,那么默认不回滚反而是合理的。理解默认行为,再决定是否要覆盖它,比盲目配置安全得多。
通过AOP切面让事务通知生效
<tx:advice>定义完成之后,还需要把它织入到目标对象上。这一步由<aop:config>完成。在配置切面时,推荐使用<aop:advisor>而不是<aop:aspect>,因为advisor天然支持将通知和切入点组合,而且tx:advice本身就是一种Advice,advisor正好是用来桥接这两者的。
<aop:config>
<aop:pointcut id="servicePointcut"
expression="execution(* com.example.service.*.*(..))"/>
<aop:advisor advice-ref="transactionAdvice"
pointcut-ref="servicePointcut"/>
</aop:config>
切入点表达式execution(* com.example.service.*.*(..))的含义是匹配com.example.service包下所有类的所有方法。这样一层事务包裹在Service层之上,Controller层调用Service时自动进入事务边界。细心的读者会发现,tx:advice中的方法名匹配规则和这里的切入点表达式是两层不同的过滤:切入点决定哪些Bean的哪些方法会被代理,事务属性中的name决定在已经代理的方法中具体套用哪一套事务配置。两者配合使用,才能实现精确控制。
当Spring容器加载完成上述配置后,会为匹配到的Service Bean生成AOP代理。外部调用者持有的是代理对象,调用方法时先经过TransactionInterceptor,再由拦截器决定开启事务、提交或回滚。如果是在同一个类内部通过this方法调用另一个被代理的方法,代理对象不会参与,事务自然也就不会生效,这是声明式事务最常见却最容易被忽视的问题。
常见配置误区和排查建议
第一类问题是事务不回滚。rollback-for没有覆盖受检异常通常是最直接的原因;另一个原因是切入点没有覆盖到实际调用的方法,或者事务通知的name没有匹配到该方法。排查时可以先确认方法签名是否匹配切入点表达式,再看异常类型是否符合回滚条件。如果配置看起来都对,可以开启Spring AOP的调试日志,观察代理是否真的被创建。
第二类问题是只读事务不生效。read-only="true"并不等于数据库连接被设置为只读,MySQL等数据库即使收到只读事务标记,也不一定会拒绝写操作。Spring的只读提示主要用于优化,例如Hibernate会跳过脏检查,底层JDBC驱动也可能把连接设置为只读。如果业务代码在只读事务里尝试写入,数据库层面并不会强制拦截,所以不要把只读配置当作数据保护机制。
第三类问题是配置冗余与维护成本。XML事务配置在方法数量庞大时会产生大量重复规则,此时可以考虑把公共规则抽出来,或者按模块拆分<tx:advice>,再通过多个advisor挂载到不同包路径上。对于新项目,Spring的@Transactional注解方案更加简洁,但大量历史遗留系统仍然依赖XML配置。理解tx:advice的底层机制,无论是维护老项目还是迁移到注解风格,都能做到思路清晰、减少踩坑。
Spring事务管理Spring XML配置tx:advice修改时间:2026-08-26 15:18:30