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

@Transactional在Service公共方法生效,保护方法调用触发懒加载异常

问题根源:Spring事务的代理机制

这个问题其实是Spring声明式事务的经典坑——同一个类内部的方法调用不会触发代理逻辑,咱们一步步拆解:

Spring的@Transactional注解是基于动态代理实现的:当外部调用被注解标记的方法时,Spring会通过代理对象拦截调用,自动开启、管理事务;但如果是类内部方法互相调用,调用会直接执行目标方法,完全绕开代理,所以方法B上的@Transactional根本不会被Spring的事务处理器识别,自然不会开启事务。

这就导致两种场景的差异:

  • 把注解加在方法A(public)上时,外部调用A是通过代理,事务正常开启,Hibernate Session会一直保持到A执行结束,所以方法B里的懒加载集合childs能正常初始化;
  • 把注解加在方法B(protected)上时,内部调用B不走代理,没有活跃的Session,懒加载集合就抛出LazyInitializationException。
解决办法:让方法B的调用走代理

要让方法B上的@Transactional生效,核心是让调用B的操作经过Spring的代理对象,这里有几种常用方案:

方案1:自注入当前Service的代理对象

在你的Service类中,注入自身的代理实例(Spring会自动注入代理对象而非原始对象),然后通过这个代理对象调用方法B:

@Service
public class MyService {
    // 自注入代理对象
    @Autowired
    private MyService self;

    public void doProcessS() {
        // 通过代理对象调用方法B,触发事务逻辑
        self.doSubProcessS();
        // 这里可以直接使用doSubProcessS提交到数据库的数据做校验
        ...
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW) // 推荐用REQUIRES_NEW确保事务独立提交
    protected void doSubProcessS() {
        // 操作数据库,包括懒加载MyEntity.childs的逻辑
        ...
    }
}

注意:这里给方法B的事务设置propagation = Propagation.REQUIRES_NEW,是为了让方法B的事务独立开启并提交,确保方法A能立即看到B提交的数据,完美匹配你“基于B提交的数据做校验”的需求。

方案2:使用AopContext获取当前代理

首先在启动类上开启暴露代理对象:

@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true) // 开启暴露代理
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

然后在方法A中通过AopContext获取代理对象并调用方法B:

public void doProcessS() {
    // 获取当前代理对象并调用方法B
    ((MyService) AopContext.currentProxy()).doSubProcessS();
    // 后续校验逻辑
    ...
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
protected void doSubProcessS() {
    ...
}

方案3:拆分方法到独立Service

如果业务允许,可以把doSubProcessS()方法抽离到一个新的Service类中,然后在原Service中注入这个新Service并调用,这样天然会走代理逻辑,事务注解正常生效:

// 新的子Service
@Service
public class SubProcessService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void doSubProcessS() {
        ...
    }
}

// 原Service
@Service
public class MyService {
    @Autowired
    private SubProcessService subProcessService;

    public void doProcessS() {
        subProcessService.doSubProcessS();
        // 校验逻辑
        ...
    }
}
补充说明
  • 为什么protected方法也能生效?:Spring的CGLIB代理会继承目标类,protected方法在子类中是可访问的,所以只要调用走代理,protected方法上的@Transactional一样能被正常处理。
  • 事务传播行为的选择:如果用默认的Propagation.REQUIRED,当方法A没有事务时,方法B会新建事务;但如果方法A后续还有其他数据库操作,可能会和方法B的事务合并。所以用REQUIRES_NEW更符合你“提交后立即校验”的需求,确保B的事务独立提交。

内容的提问来源于stack exchange,提问作者Jayesh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:31:22