共243行

事务@Transactional

2026-05-08 18:57:35

@Transactional 注解在以下情况会失效:

  1. ❌ 方法不是 public
  2. ❌ 通过 this 调用本类的其他方法(内部调用)
  3. ❌ 异常被 try-catch 捕获且没有重新抛出
  4. ❌ 类没有被 Spring 管理(没有加 @Service 等注解)
public void add(ZyActivityAddParams params) {

        ZyActivityPO po = new ZyActivityPO(params);

        SpringUtil.getBean(ActivityServiceImpl.class).savePO(po, params.getActivityRuleIdList());
    }
    
@Transactional(rollbackFor = Exception.class)
    public void savePO(ZyActivityPO po, List<Long> activityRuleIdList) {
        this.savePO(po);

        Optional.ofNullable(po.getId())
                .map(zyRelationActivityActivityRuleDAO::findByZyActivityId)
                .ifPresent(zyRelationActivityActivityRuleDAO::deleteInBatch);

        activityRuleIdList.stream()
                .map(x -> new ZyRelationActivityActivityRulePO(po.getId(), x))
                .forEach(zyRelationActivityActivityRuleDAO::save);
    }   
    

📖 代码解释

SpringUtil.getBean(ActivityServiceImpl.class).savePO(po, params.getActivityRuleIdList());

🔍 这行代码的作用:

从 Spring 容器中获取 ActivityServiceImpl 的代理对象,然后调用 savePO 方法。


为什么要这样写?而不是直接用 this.savePO(...)

关键原因:确保 AOP 切面生效!

1. savePO 方法的特殊性(第292-304行):

@Transactional(rollbackFor = Exception.class)
@NeedUpdateRecord(modal = "activity")  // ← 自定义注解,用于记录更新日志
public void savePO(ZyActivityPO po, List<Long> activityRuleIdList) {
    this.savePO(po);  // 保存活动
    
    // 删除旧的活动规则关联
    Optional.ofNullable(po.getId())
            .map(zyRelationActivityActivityRuleDAO::findByZyActivityId)
            .ifPresent(zyRelationActivityActivityRuleDAO::deleteInBatch);
    
    // 创建新的活动规则关联
    activityRuleIdList.stream()
            .map(x -> new ZyRelationActivityActivityRulePO(po.getId(), x))
            .forEach(zyRelationActivityActivityRuleDAO::save);
}

代码解释

这个方法有两个重要的 AOP 切面:

  1. @Transactional - 事务管理
  2. @NeedUpdateRecord - 自定义切面,记录操作日志

⚠️ 如果使用 this.savePO(...) 会怎样?

// ❌ 错误写法
@Override
public void add(ZyActivityAddParams params) {
    ZyActivityPO po = new ZyActivityPO(params);
    
    // 通过 this 调用,Spring AOP 无法拦截!
    this.savePO(po, params.getActivityRuleIdList());
}

// ✅ 正确写法
@Override
public void add(ZyActivityAddParams params) {
    ZyActivityPO po = new ZyActivityPO(params);
    
    // 通过 Spring 容器获取代理对象,AOP 切面可以正常拦截!
    SpringUtil.getBean(ActivityServiceImpl.class).savePO(po, params.getActivityRuleIdList());
}

问题对比:

调用方式 事务生效 @NeedUpdateRecord 生效 说明
this.savePO(...) ❌ 失效 ❌ 失效 绕过 Spring AOP 代理
SpringUtil.getBean(...).savePO(...) ✅ 生效 ✅ 生效 通过代理对象调用

🎯 工作原理:

Spring AOP 代理机制:

外部调用
   ↓
Spring 代理对象 (Proxy)
   ├─ 开启事务 (@Transactional)
   ├─ 记录操作日志 (@NeedUpdateRecord)
   └─ 调用目标方法 savePO()
       └─ 执行数据库操作

使用 this 调用时:

外部调用
   ↓
Spring 代理对象 (Proxy)
   ├─ 开启事务
   ├─ 记录日志
   └─ 调用 add() 方法
       └─ this.savePO() ← 直接调用目标对象的方法,绕过代理!
           └─ ❌ 事务和日志切面都不生效

💡 总结:

这行代码的目的:

  1. 获取 Spring 管理的代理对象 - 而不是当前实例
  2. 确保 AOP 切面正常工作 - 事务和自定义注解都能生效
  3. 避免 this 调用导致的事务失效问题

这是一个最佳实践!

当你需要在同一个类中调用带有 AOP 注解(如 @Transactional@Cacheable、自定义注解等)的方法时,必须通过 Spring 容器获取代理对象,否则 AOP 切面不会生效。

类似的工具还有:

你说得对!这是一个很好的观察。让我解释一下为什么没有报错:

SpringUtil.getBean()AopContext.currentProxy() 的区别

1. SpringUtil.getBean() - 不需要 exposeProxy = true

// 这种方式直接从 Spring 容器获取代理对象
SpringUtil.getBean(ActivityServiceImpl.class).savePO(po, params.getActivityRuleIdList());

工作原理:

2. AopContext.currentProxy() - 需要 exposeProxy = true

// 这种方式从 ThreadLocal 获取当前代理对象
ActivityServiceImpl proxy = (ActivityServiceImpl) AopContext.currentProxy();
proxy.savePO(po, params.getActivityRuleIdList());

工作原理:


📊 两种方式的对比

特性 SpringUtil.getBean() AopContext.currentProxy()
是否需要 exposeProxy ❌ 不需要 ✅ 必须
获取方式 从 IOC 容器 从 ThreadLocal
性能 略慢(需要从容器查找) 略快(直接从线程上下文获取)
线程安全 ✅ 安全 ✅ 安全
推荐程度 ⭐⭐⭐⭐ 推荐 ⭐⭐ 不推荐

🎯 最佳实践建议

推荐使用 SpringUtil.getBean() 的方式

@Override
public void add(ZyActivityAddParams params) {
    ZyActivityPO po = new ZyActivityPO(params);
    
    // ✅ 推荐:直接从容器获取代理对象
    SpringUtil.getBean(ActivityServiceImpl.class).savePO(po, params.getActivityRuleIdList());
}

优点:

不推荐使用 AopContext.currentProxy()

// ❌ 不推荐
@Override
public void add(ZyActivityAddParams params) {
    ZyActivityPO po = new ZyActivityPO(params);
    
    ActivityServiceImpl proxy = (ActivityServiceImpl) AopContext.currentProxy();
    proxy.savePO(po, params.getActivityRuleIdList());
}

缺点: