Spring抽象类与子类事务代理疑问:@Transactional是否生效?
问题分析与解决方案
先直接给结论:在你当前的代码结构和Spring默认代理模式下,事务不会按预期生效,原因、解决办法和验证方法我一步步拆解:
为什么事务不生效?
这刚好触发了Spring事务代理的两个核心规则,和抽象类/子类的继承关系无关,本质是代理的工作机制:
- 非public方法不触发代理事务:Spring默认代理(JDK动态代理/CGLIB)只会处理标注了
@Transactional的public方法,protected方法即使加了注解,代理逻辑也会直接跳过,不会织入事务切面。 - 同类内部调用不触发代理:基类的
mainProcess是public方法,当代理对象调用它时,方法内部调用doSomething(foo)是直接调用当前对象的目标方法(而非代理方法)——因为这是同一个对象内部的方法调用,没有经过代理的拦截逻辑,就算doSomething改成public,事务也不会生效。
怎么让事务生效?
给你几个可行的方案,按复杂度从低到高排序:
方案1:调整代码结构,拆分事务逻辑
把doSomething的事务逻辑抽成独立的Spring Bean,绕开内部调用的限制:
@Service public class FooTransactionHandler { @Transactional public void processFoo(Foo foo) { // 原来doSomething的业务逻辑 } }
然后在子类中注入这个Bean,修改doSomething:
public class ServiceImplOne extends AbstractBaseService { @Autowired private FooTransactionHandler transactionHandler; @Override protected List<Foo> getFoos() { ... } @Override protected void doSomething(Foo foo) { transactionHandler.processFoo(foo); // 调用独立Bean的public事务方法 } }
这样调用的是外部Bean的方法,会经过代理拦截,事务就能正常生效。
方案2:暴露代理对象,通过代理调用自己
这个方案改动最小,但需要调整方法权限和开启代理暴露:
- 在配置类上添加
@EnableTransactionManagement(exposeProxy = true); - 把基类和子类的
doSomething改成public方法(代理只处理public); - 修改基类的
mainProcess,通过代理对象调用doSomething:
public abstract class AbstractBaseService { public void mainProcess() { for (Foo foo : getFoos()) { try { // 拿到当前代理对象,调用事务方法 ((AbstractBaseService) AopContext.currentProxy()).doSomething(foo); } catch (Exception e) { // 记录日志,事务会自动回滚 log.error("处理Foo对象失败", e); } } } abstract List<Foo> getFoos(); // 改为public抽象方法 public abstract void doSomething(Foo foo); }
子类的doSomething标注@Transactional:
@Service public class ServiceImplOne extends AbstractBaseService { @Override public List<Foo> getFoos() { ... } @Transactional @Override public void doSomething(Foo foo) { ... } }
方案3:改用AspectJ织入
如果不想调整代码结构,可以用AspectJ的编译时/加载时织入,直接修改字节码实现事务拦截——不管方法权限、不管是不是内部调用,都会织入事务逻辑。需要:
- 引入AspectJ相关依赖;
- 配置类添加
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ); - 加载时织入还需要设置JVM参数
-javaagent:path/to/aspectjweaver.jar。
简单的验证方法
想要快速确认事务是否生效,试试这几个办法:
- 在
doSomething里故意抛出RuntimeException(Spring默认只回滚RuntimeException/Error),执行mainProcess后查看数据库,看当前Foo的修改是否被回滚; - 开启Spring事务日志(设置
org.springframework.transaction包为DEBUG级别),查看是否有事务开启、回滚的日志输出; - 在
doSomething里打印this.getClass().getName(),如果输出的是代理类名(比如带$EnhancerBySpringCGLIB$或$Proxy$后缀),说明走了代理,事务才可能生效。
内容的提问来源于stack exchange,提问作者AJPerez
相关产品推荐
相关产品推荐

