Spring事务失效 -方法内部调用

首先感谢网友的文章
Spring事务失效的2种情况

JDK动态代理给Spring事务埋下的坑

前提知识: 两个前提

1 注解使用 spring容器的事务管理注解
@org.springframework.transaction.annotation.Transactional

2.方法开启了事务注解之后,需要异常的触发,
如果在方法内部捕获了异常,即使你开启了事务,也是没法事务回滚的

错误的使用:下面这种捕获了异常 , saveEmployee 方法里面的即使有事务,也是不会回滚的

@Transactional() //开启事务,捕获了异常,没法触发,所以有注解也没作用
public void save(){
    // 捕获了异常,没法触发,所以有注解也没作用
    try {   
     // 捕获了异常,没法触发,所以有注解也没作用     
        this.saveEmployee();  
    }catch (Exception e){
        e.printStackTrace();
    }
}

@Transactional()
public void saveEmployee(){
        Employee employee = new Employee();
        employee.setName("zhangsan");
        employee.setAge("26";
        employeeDao.save(employee);
        throw new RuntimeException();
}

写作背景:

我们平时在java开发中经常使用的是 Spring 的事务回滚,而且我们大多数情况下都是使用注解为主的,最近工作中,方法内部调用,事务没有起效,走了一些弯路,今天来总结一下.

方法内部调用是指: 同一个类,两个方法, 外层方法( 暂且叫 A 方法 )调用内层方法(B 方法)

使用默认的事务处理方式
  因为在java的设计中,它认为不继承RuntimeException的异常是”checkException”或普通异常,如IOException,这些异常在java语法中是要求强制处理的。
  对于这些普通异常,spring默认它们都已经处理,所以默认不回滚。
  可以添加rollbackfor=Exception.class来表示所有的Exception都回滚。

内部调用
  不带事务的方法调用该类中带事务的方法,不会回滚。因为spring的回滚是用过代理模式生成的,如果是一个不带事务的方法调用该类的带事务的方法,直接通过this.xxx()调用,而不生成代理事务,所以事务不起作用。常见解决方法,拆类。

内部调用没有发生我们期待中的事务回滚:**
  spring中在一个拥有事务的方法A中调用另一个会挂起事务并创建新事务的方法B,
  如果使用this调用这个方法B, 此时方法B抛出了一个异常,
  此时的方法B的事务会失效的。并不会回滚。**
  
代码如下:

@Service
public class EmployeeService {
 
    @Autowired
    private EmployeeDao employeeDao;
 
    public void save(){
        try {        
            //此处this调用不会开启事务,数据会被保存
            this.saveEmployee();  
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    
    @Transactional()
    public void saveEmployee(){
        Employee employee = new Employee();
        employee.setName("zhangsan");
        employee.setAge("26";
        employeeDao.save(employee);
        throw new RuntimeException();
    }
}

问题原因: JDK的动态代理
JDK的动态代理。只有被动态代理直接调用时才会产生事务。
在SpringIoC容器中返回的调用的对象是代理对象而不是真实的对象。
而这里的this是EmployeeService真实对象而不是代理对象。

解决办法:

方法1:
在方法A上开启事务,方法B不用事务或默认事务,
并在方法A的catch中throw new RuntimeException();
(在没指定rollbackFor时,默认回滚的异常为RuntimeException),这样使用的就是方法A的事务。(一定要throw new RuntimeException();否则异常被捕捉处理,同样不会回滚。)
如下:

@Transactional() //开启事务
public void save(){
    try {        
        this.saveEmployee();  
    }catch (Exception e){
        e.printStackTrace();
        throw new RuntimeException();
    }
}

方法2:
方法2、方法A上可以不开启事务,方法B上开启事务,并在方法A中将this调用改成动态代理调用(AopContext.currentProxy()),如下:

public void save(){
    try {        
        EmployeeService proxy =(EmployeeService) AopContext.currentProxy();
        proxy.saveEmployee();
    }catch (Exception e){
        e.printStackTrace();
    }
}

另外还需要注意: 使用动态代理时候记得,在启动类配置开启代理

需要在启动类配置开启代理配置上:

@EnableTransactionManagement
@EnableAspectJAutoProxy(exposeProxy = true)

@EnableTransactionManagement
@SpringBootApplication
@EnableFeignClients(basePackages={"com.***"})
@EnableEurekaClient
@MapperScan("com.***.production.order.dao.mapper")
@ComponentScan(basePackages={"com.***"})
// 暴露代理
@EnableAspectJAutoProxy(exposeProxy = true)
public class ProductionOrderJobApplication {

    protected final static Logger logger = LoggerFactory.getLogger( ProductionOrderJobApplication.class);

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication( ProductionOrderJobApplication.class);
        app.setBannerMode(Banner.Mode.OFF);
        app.run(args);
        logger.info("ProductionOrderJobApplication is success!");
    }

    @Bean
    public RequestContextListener requestContextListener(){
        return new RequestContextListener();
    }

}

补充一点: 拆类 的方法可以试试

最终解决方案: 拆类的方法

你可能感兴趣的:(Spring事务)