You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring抽象类与子类事务代理疑问:@Transactional是否生效?

问题分析与解决方案

先直接给结论:在你当前的代码结构和Spring默认代理模式下,事务不会按预期生效,原因、解决办法和验证方法我一步步拆解:

为什么事务不生效?

这刚好触发了Spring事务代理的两个核心规则,和抽象类/子类的继承关系无关,本质是代理的工作机制:

  1. 非public方法不触发代理事务:Spring默认代理(JDK动态代理/CGLIB)只会处理标注了@Transactional的public方法,protected方法即使加了注解,代理逻辑也会直接跳过,不会织入事务切面。
  2. 同类内部调用不触发代理:基类的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:暴露代理对象,通过代理调用自己

这个方案改动最小,但需要调整方法权限和开启代理暴露:

  1. 在配置类上添加@EnableTransactionManagement(exposeProxy = true);
  2. 把基类和子类的doSomething改成public方法(代理只处理public);
  3. 修改基类的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:51:43