Spring @Transactional 你真的会用吗???

前置知识

使用@Transactional 注解会发生什么?

当使用 @Transactional 注解时,Spring 会扫描这些注解并配置 AOP 代理,以便在目标方法执行前后进行事务管理。具体过程如下:

  • 方法拦截:当目标方法被调用时,AOP 代理会拦截这个调用,然后根据事务的配置(如传播行为和隔离级别)来处理事务。
  • 事务管理:在方法执行前,代理会调用事务管理器开始一个新的事务;在方法执行成功后,它会提交事务;如果方法抛出异常,代理会回滚事务。

简单的一句话理解就是,通过目标对象的代理类,调用带有 @Transactional 注解的方法,进行事务的管理。

@Transactional 注解什么时候会失效?

@Transactional 确实好用又方便,但是有坑呀!!!

注解失效几种情况:其实还有很多,所以要小心了。

  1. 如果你在 同一个类内部调用一个带有 @Transactional 注解的方法,事务将不会生效,因为调用是通过 this 引用直接访问的,没有经过代理。
  2. @Transactional 没有指定 rollbackFor,默认只对RuntimeExceptionError进行回滚。比如 IOException,则不会回滚,除非你在注解中指定了 rollbackFor 属性。
  3. 如果在 @Transactional 方法内部捕获了异常(使用 try-catch 语句),仅打日志,没有抛出异常,不会回滚事务,因为异常被捕获后不会传播到 Spring 的事务管理器。
  4. @Transactional 注解不能应用于 privatefinalstatic 方法,因为 Spring 无法通过代理在这些方法上应用 AOP。确保方法是 public 的。
  5. 事务传播行为:如果在方法中调用了另一个带有 @Transactional 注解的方法,并且它的传播行为是 Propagation.NOT_SUPPORTED 或者其他不支持当前事务的类型,那么当前事务将会失效。

好了,我们来详细地讲讲几种事务失效的情况。

1. 同一个类内部调用一个带有 @Transactional 注解的方法

比如:method1 调用了 method2 方法(method2 带有事务注解),此时事务不生效

@Service
public class MyService {

private void method1() {
     method2();
     // ...
}
@Transactional
 public void method2() {
     //...
  }
}

那么怎么解决呢?

既然是 Spring 通过代理类来调用带有 @Transactional 注解的方法 的,那么我直接把 @Transactional 注解的方法 放到另一个类中,然后再调用这个类中的方法不就行了。 嘿嘿!!

@Service
public class MyService {
@Autowired
private MyManager myManager;
    
private void method1() {
     myManager.method2();
     // ...
    }
}

@Service
public class MyManager {

    // 这个方法是事务性的方法
    @Transactional
    public void method2() {
    
    }
}

看到这里,你是不是想说这么麻烦,还要搞多一个类???

没事还有另一个更简单的方法:就是通过 AopContext 获取该类的代理对象

@Service
public class MyService {

private void method1() {
    // 先获取该类的代理对象,然后通过代理对象调用 method2方法。
     ((MyService)AopContext.currentProxy()).method2(); 
     //......
}
@Transactional
 public void method2() {
     //......
  }
}

这样子是不是简单多了。

顺便提一嘴:

你要想通过 AopContext 获取该类的代理对象,记得在启动类上,加上 @EnableAspectJAutoProxy(exposeProxy = true)

@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
public class MainApplication {

    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}

你肯定又好奇,这个注解是干什么的?为什么还加上了 proxyTargetClass = true 属性呢?我来给你讲讲都是干什么的?

  • EnableAspectJAutoProxy 表示开启 AOP功能, 允许你定义切面(Aspect)和切点(Pointcut),使用AOP记得要开启
  • exposeProxy = true 在方法内部获取当前类代理对象,比如上面就是通过 AopContext 获取该类的代理对象
  • proxyTargetClass = true 表示强制使用 CGLIB 代理

我们都知道,在 Spring 中,事务管理可以使用两种代理方式:JDK 动态代理和 CGLIB 代理。具体使用哪种代理方式取决于几个因素,包括目标类是否实现了接口以及 Spring 的配置。

在 Spring Boot 中,默认情况下,会根据目标类的特性自动选择代理方式:

  • 如果目标类实现了接口,使用 JDK 动态代理。
  • 如果目标类没有实现接口,使用 CGLIB 代理。

这里 exposeProxy = true ,表示我强制使用的是 CGLIB 代理。至于 JDK 动态代理 和 CGLIB 代理 在这里我就不过多的介绍了,感兴趣的小伙伴可以自己去了解一下。

2. rollbackFor 没有指定对

没有指定 rollbackFor,默认只对RuntimeExceptionError进行回滚。比如 IOException,则不会回滚。

怎么解决?

简单,在注解上加上 rollbackFor = Exception.class 不就好了,异常类型捕获一个大的,更放心。。

@Service
public class MyService {

private void method1() {
     method2();
     // ...
}
@Transactional(rollbackFor = Exception.class)
 public void method2() {
     //...
  }
}

3. 你捕获异常,你又不抛出来,Spring 怎么知道出异常了,怎么回滚??

比如:@Transactional 方法内部捕获了异常 ,仅打日志,没有抛出异常。不会回滚事务,因为异常被捕获后不会传播到 Spring 的事务管理器。

@Service
@Slf4j
public class UserService {

    @Transactional
    public void userRegister(String username) {
        try {
            // ..... 
            
            // 模拟抛出一个异常
            if (username == null) {
                throw new IllegalArgumentException("用户名不可为空");
            }
        } catch (Exception e) {
        // 捕获了异常,仅打日志,没有抛出异常,不会回滚事务
            log.error("捕获异常: " + e.getMessage());
        }
    }
}

怎么解决?

直接抛出异常不就好了,这里其实我也有点纳闷,既然都出异常被捕获了,还打了日志,为什么不抛业务异常。我猜测又是哪一个粗心的小可爱忘了,一定不是你。嘿嘿!!

@Service
@Slf4j
public class UserService {

    @Transactional
    public void userRegister(String username) {
        try {
           
            // .....
            
            // 模拟抛出一个异常
            if (username == null) {
                throw new IllegalArgumentException("用户名不可为空");
            }
        } catch (Exception e) {
        // 捕获了异常,仅打日志,没有抛出异常,不会回滚事务
            log.error("异常: " + e.getMessage());
            // 记得抛出异常
            throw new BusinessException("业务异常");
        }
    }
}

总结

今天我们讲了 Spring 事务失效的几种情况,也给出了解决的方案,最后希望使用 @Transactional 注解的时候要小心了,不要中招了。。。

 

<think> 我们正在讨论的是Spring框架中的事务注解`@Transactional`在父类和子类中的行为。 用户的问题是:父类配置了`@Transactional`,子类添加`@Transactional`是否会影响父类方法? 根据之前的知识和引用内容,我们可以总结如下: 1. 父类配置`@Transactional`有两种情况: - 配置在类级别:表示该类的所有公共方法都具有事务性。 - 配置在方法级别:表示该方法具有事务性。 2. 子类继承父类时: - 如果父类有类级别的`@Transactional`注解,由于`@Transactional`使用了`@Inherited`元注解,所以子类会继承这个类级别的事务配置。但是,这仅意味着子类自己的方法(非重写父类方法)会具有事务性,而重写父类方法会自动继承事务配置(需要显式在子类重写方法上添加注解)[^1]。 - 如果父类的方法级别有`@Transactional`,子类重写该方法时,父类方法上的事务注解会被继承。因此,如果子类重写了父类的方法,需要在子类方法上显式添加`@Transactional`才能让该方法具有事务性。 3. 子类添加`@Transactional`(无论是类级别还是方法级别)会影响父类方法: - 父类方法的执行只受父类自身的注解配置影响。 - 当通过子类对象调用父类方法时(未重写的情况),该方法的事务行为由父类配置决定。 - 当子类重写了父类方法,并且通过子类对象调用该方法时,执行的是子类的方法,此时事务行为由子类方法上的注解决定(如果子类方法注解)或者子类类级别的注解决定(如果子类方法没有注解但子类有类级别注解)或者父类类级别注解(如果子类没有重写且子类没有类级别注解,但父类有类级别注解)等。但是,父类方法本身会被调用(除非使用`super`关键字,但这样也会走代理,因此事务可能生效,参见引用[4])。 4. 特别注意:同一个类中的方法调用(包括子类调用父类方法)可能导致事务失效(引用[4])。这是因为事务是基于代理的,只有通过代理对象调用的方法才会被拦截,内部调用(包括使用`super`调用父类方法会经过代理,因此事务会生效。 因此,结论是:子类添加`@Transactional`会影响父类方法的事务行为。父类方法的事务行为只由父类自身的注解配置决定。当通过子类对象调用父类方法时,如果该方法没有被重写,则使用父类的事务配置;如果被重写,则使用子类重写方法的事务配置(如果子类重写方法上有注解)或者子类类级别的事务配置(如果子类重写方法上没有注解但子类有类级别注解)等,但父类方法本身的事务配置对子类重写方法没有影响。 下面用代码示例说明: 示例1:父类方法未被重写 ```java @Transactional public class Parent { public void methodA() { // 事务生效,因为父类类级别注解 } } public class Child extends Parent { @Transactional // 这是子类自己的事务配置,会影响父类方法 public void methodB() { // 事务生效,因为子类方法注解 } } ``` 当我们通过Child对象调用methodA()时,由于methodA()是父类方法且未被重写,所以它的事务配置来自父类的类级别注解,与子类上的注解无关。 示例2:父类方法被重写 ```java public class Parent { @Transactional public void methodA() { // 事务生效 } } public class Child extends Parent { @Override public void methodA() { // 事务生效,因为重写后父类方法上的注解会被继承,而子类方法没有添加注解 } } ``` 如果我们在子类重写方法上添加注解: ```java public class Child extends Parent { @Override @Transactional // 子类重写方法上显式添加事务注解 public void methodA() { // 事务生效,由子类注解控制 } } ``` 此时,父类方法的事务配置会影响子类重写方法。当我们通过子类对象调用methodA()时,使用的是子类方法上的事务配置。 另外,如果子类通过super调用父类方法: ```java public class Child extends Parent { @Override @Transactional public void methodA() { super.methodA(); // 这里调用父类方法,但会走代理,因此父类方法上的事务会生效(如果父类方法上有事务注解)? // 注意:这里父类方法上的事务注解在通过super调用时会生效,因为这是内部调用(引用[4]) } } ``` 所以,子类添加的`@Transactional`注解会影响父类方法,父类方法的事务行为独立于子类的注解配置。 总结: - 父类方法的`@Transactional`配置只影响自身(当未被重写且通过代理调用时)。 - 子类添加的`@Transactional`注解只影响子类自己的方法(包括重写父类的方法,此时覆盖了父类方法的行为)。 - 子类通过super调用父类方法会触发事务(因为内部调用绕过代理)。 最后,生成相关问题: </think> ### 父类配置 `@Transactional` 后,子类添加 `@Transactional` 对父类方法的影响分析 #### 一、核心结论 **子类添加 `@Transactional` 会影响父类方法的事务行为** 父类方法的 `@Transactional` 配置独立生效,子类新增的注解仅作用于子类自身方法(包括重写方法)。二者的事务控制相互独立,互影响[^1][^3]。 #### 二、详细场景说明 1. **父类方法未被重写时** ```java @Transactional // ✅ 父类事务独立生效 public class ParentService { public void parentMethod() { // 事务由父类注解控制 } } @Transactional // 子类新增注解 public class ChildService extends ParentService { // 调用父类parentMethod()时: // 父类事务配置依然生效,子类注解影响父类 } ``` 2. **父类方法被子类重写时** ```java public class ParentService { @Transactional // 父类方法注解 public void methodA() { ... } } public class ChildService extends ParentService { @Override @Transactional // 子类重写方法注解 public void methodA() { // ✅ 子类事务独立控制 // ❗ 父类methodA()的事务配置被覆盖 } } ``` - 子类重写方法时,子类 `@Transactional` **覆盖父类配置**(父类原方法再执行) - 若需保留父类事务逻辑,需显式调用 `super.methodA()`(但需注意事务传播问题[^4]) 3. **子类新增方法时** ```java public class ParentService { @Transactional public void parentMethod() { ... } } public class ChildService extends ParentService { @Transactional // 仅控制子类新方法 public void childMethod() { // ✅ 事务独立生效 // 影响父类的parentMethod() } } ``` #### 三、事务控制独立性验证 | 场景 | 父类方法事务是否受影响 | 说明 | |--------------------------|------------------------|--------------------------| | 子类新增类级别注解 | 否 | 父类方法保持原事务配置 | | 子类新增方法级别注解 | 否 | 仅作用于子类新增方法 | | 子类重写父类方法并加注解 | 是(覆盖) | 重写后父类方法再执行 | > **关键机制** > Spring 事务通过代理实现,父类和子类的 `@Transactional` 注解分别生成独立的代理逻辑,互干扰[^2][^4]。 #### 四、最佳实践建议 1. **保留父类事务** 若需在子类中复用父类事务方法,避免重写父类方法: ```java public class ChildService extends ParentService { public void childLogic() { super.parentMethod(); // 直接调用父类事务方法 } } ``` 2. **明确覆盖场景** 重写父类方法时,显式声明事务以明确覆盖意图: ```java @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void parentMethod() { ... } ``` 3. **规避自调用问题** 子类通过 `super` 调用父类方法时,事务可能失效(因代理拦截到内部调用[^4]): ```java // ❌ 错误示例(事务失效) @Transactional public void childMethod() { super.parentMethod(); // 绕过代理,父类事务生效 } // ✅ 解决方案:注入自身代理 @Autowired private ChildService selfProxy; // 通过容器获取代理对象 public void childMethod() { selfProxy.parentMethod(); // 通过代理调用 } ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值