@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
相关产品推荐
相关产品推荐

