所有分类
  • 所有分类
  • 未分类

Spring事务传播机制详解

简介

本文介绍Spring中的事务的传播机制,并且用实例说明它的用法。

本内容也是Java后端面试常见的问题。

概述

Spring在TransactionDefinition接口中规定了7种类型的事务传播行为。Propagation枚举则引用了这些类型,开发过程中我们一般直接用Propagation枚举。常用的三项已经加粗。

事务传播行为类型说明
PROPAGATION_REQUIRED需要事务(默认)。若当前无事务,新建一个事务;若当前有事务,加入此事务中。
PROPAGATION_SUPPORTS支持事务。若当前没有事务,以非事务方式执行;若当前有事务,加入此事务中。
PROPAGATION_MANDATORY强制使用事务。若当前有事务,就使用当前事务;若当前没有事务,抛出异常。
PROPAGATION_REQUIRES_NEW新建事务。无论当前是否有事务,都新建事务运行。
PROPAGATION_NOT_SUPPORTED不支持事务。若当前存在事务,把当前事务挂起,然后运行方法。
PROPAGATION_NEVER不使用事务。若当前方法存在事务,则抛出异常,否则继续使用无事务机制运行。
PROPAGATION_NESTED嵌套。如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

本处所测试的皆是运行addEmpByRequired方法。

Propagation.REQUIRED

员工service

@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    EmployeeMapper employeeMapper;

    @Autowired
    DepartmentService departmentService;

    /**
     * 添加员工的同时添加部门
     * REQUIRED 传播
     * @param name 员工姓名
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addEmpByRequired(String name) {
        Employee employee = new Employee();
        employee.setDeptId(1);
        employee.setName(name);
        employee.setAddress("邯郸");
        employeeMapper.insertSelective(employee);
        departmentService.addDeptByRequired("jishubu");
        int i = 1/0;
    }
}

部门service

@Service
public class DepartmentServiceImpl implements DepartmentService {

    @Autowired
    DepartmentMapper departmentMapper;

    @Override
    public void addDeptByRequired(String name) {
        Department department = new Department();
        department.setName(name);
        departmentMapper.insertSelective(department);
        // int i = 1/0;
    }
}

1,上述代码中,无论int i =1/0 这个异常出现在哪里,添加员工和添加部门都会回滚。因为 REQUIRED 会让添加员工和添加部门变为一个事务。
2,如果在addDeptByRequired上添加@Transactional(propagation = Propagation.REQUIRED),在addEmpByRequired不添加事务,则addDeptByRequired是一个事务,addEmpByRequired并不是一个事务。因为addDeptByRequired开启了一个事务,但是addEmpByRequired并不存在一个事务中。

Propagation.SUPPORTS

员工service

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addEmpBySupports(String name) {
        Employee employee = new Employee();
        employee.setDeptId(2);
        employee.setName(name);
        employee.setAddress("邯郸");
        employeeMapper.insertSelective(employee);
        departmentService.addDeptBySupports("jishubu");
        // int i = 1/0;
    }

部门service

    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public void addDeptBySupports(String name) {
        Department department = new Department();
        department.setName(name);
        departmentMapper.insertSelective(department);
        int i = 1/0;
    }
  • 关于这个属性,在以上代码中,主要是添加到addDeptBySupports上的,也就是被调用方法上。因为添加到addEmpBySupports就不以事务的方式运行了。
  • 然后,如果addEmpBySupports为事务,则addDeptBySupports也为事务。如果addEmpBySupports不是事务,则addDeptBySupports也不是事务。

Propagation.MANDATORY

员工service

    @Override
    // @Transactional(propagation = Propagation.REQUIRED)
    public void addEmpByMandatory(String name) {
        System.out.println("aaaaaa");
        Employee employee = new Employee();
        employee.setDeptId(3);
        employee.setName(name);
        employee.setAddress("邯郸");
        employeeMapper.insertSelective(employee);
        departmentService.addDeptByMandatory("jishubu");
        int i = 1/0;
    }

部门service

    @Override
    @Transactional(propagation = Propagation.MANDATORY)
    public void addDeptByMandatory(String name) {
        Department department = new Department();
        department.setName(name);
        departmentMapper.insertSelective(department);
        int i = 1/0;
    }
  • 这个属性也是添加到addDeptByMandatory(被调用者) 上的。若添加到addEmpByMandatory(调用者)上,则直接抛出异常。
  • 该属性添加到addDeptByMandatory上, 如果addEmpByMandatory有事务,则addDeptByMandatory加入到addEmpByMandatory的事务中,如果addEmpByMandatory没有事务,则直接抛出异常。

Propagation.REQUIRES_NEW

员工service

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addEmpByRequiresNew(String name) {
        Employee employee = new Employee();
        employee.setDeptId(4);
        employee.setName(name);
        employee.setAddress("邯郸");
        employeeMapper.insertSelective(employee);
        departmentService.addDeptByRequiresNew("jishubu");
        int i = 1/0;
    }

部门service

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addDeptByRequiresNew(String name) {
        Department department = new Department();
        department.setName(name);
        departmentMapper.insertSelective(department);
        // int i = 1/0;
    }
  • 这个属性应该是除了REQUIRED用的最多的。这个属性也是针对被调用者的(addDeptByRequiresNew)。
  • 不管调用者(addEmpByRequiresNew)是否存在事务,被调用者(addDeptByRequiresNew)都会新开一个事务,相当于被调用者都存在于自己的事务中和调用者没有关系。
  • 如上述代码,addEmpByRequiresNew会回滚,但addDeptByRequiresNew不会回滚。因为他们是两个事务。

Propagation.NOT_SUPPORTED

员工service

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addEmpByNotSupported(String name) {
        Employee employee = new Employee();
        employee.setDeptId(5);
        employee.setName(name);
        employee.setAddress("邯郸");
        employeeMapper.insertSelective(employee);
        departmentService.addDeptByNotSupported("jishubu");
        int i = 1/0;
    }

部门service

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void addDeptByNotSupported(String name) {
        Department department = new Department();
        department.setName(name);
        departmentMapper.insertSelective(department);
        int i = 1/0;
    }
  • 这个属性如果放在调用者(addEmpByNotSupported)上,则是以非事务方式运行。
  • 如果放在被调用者(addDeptByNotSupported)上,该方法(addDeptByNotSupported)以非事务运行,调用者如果有事务,则运行单独的事务(挂起)。
  • 上述代码,会出现添加员工回滚,添加部门不回滚。

Propagation.NEVER

员工service

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addEmpByNever(String name) {
        Employee employee = new Employee();
        employee.setDeptId(6);
        employee.setName(name);
        employee.setAddress("邯郸");
        employeeMapper.insertSelective(employee);
        departmentService.addDeptByNever("jishubu");
        int i = 1/0;
    }

部门service

    @Override
    @Transactional(propagation = Propagation.NEVER)
    public void addDeptByNever(String name) {
        Department department = new Department();
        department.setName(name);
        departmentMapper.insertSelective(department);
        int i = 1/0;
    }
  • 这个属性如果在调用者上,则直接以非事务运行。如果作用在被调用者上,则看调用者是否有事务,如果调用者有事务,则抛出异常,如果没有事务,则以非事务运行。
  • 上述代码中,则会抛出异常。(并不是除0异常,而是:Existing transaction found for transaction marked with propagation ‘never’)

Propagation.NESTED

说明

PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话)。嵌套子事务是JDBC 的保存点(SavePoint)的一个应用,它是具有可回滚到多个保存点的单个物理事务。被调用者使用PROPAGATION_NESTED时,需JDBC 3.0及以上和JDK1.4及以上,且实现者需要支持保存点事务机制。

提交

如果当前有事务,只有通过外部的事务提交,才能引起内部事务的提交。(内嵌事务不是一个独立的事务,它依赖于外部事务)。

如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

回滚

父事务回滚,子事务一定回滚
子事务回滚到savepoint,父事务可选择性回滚或者不回滚

员工service

    @Override
    @Transactional(propagation = Propagation.REQUIRED)     //1
    public void addEmpByNested(String name) {
        Employee employee = new Employee();
        employee.setDeptId(7);
        employeeMapper.insertSelective(employee);          //2
        try {
            departmentService.addDeptByNested("jishubu");  //3
        } catch (Exception e){
            // 其他业务, 如 departmentService.methodC();    //5
        }
        employee.setDeptId(8);
        employeeMapper.update(employee);                   //6
        // int i = 1/0;
    }

部门service

    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void addDeptByNested(String name) {
        Department department = new Department();
        department.setName(name);
        departmentMapper.insertSelective(department);  //4
        // int i = 1/0;
    }

1. 开启新事务A
2. 插入数据,事务A挂起,不提交
3. 新建一个savepoint 保存 2 插入的数据,不提交。
4. 若addDeptByNested异常:回滚4的操作,不影响2,继续处理后面的5,6逻辑,最后一起提交: 2, 5, 6
    若addDeptByNested无异常:最后一起提交: 2,4,6

假如addDeptByNested使用PROPAGATION_REQUIRES_NEW,情景如下:

若addDeptByNested异常,会提交: 2,5,6,和NESTED一致。
如果addDeptByNested没有出现异常,会先提交4,再提交6,事务将分离开,不能保持一致。假如执行6报错,2和6将回滚,而4却没有被回滚,不能达到预期效果。

并不是只有NESTED用于嵌套,只要理解上述7个传播机制的意思,都可以嵌套用。

0

评论2

请先

  1. Propagation.REQUIRED示例中添加部门的方法名不一致
    love清浅 2024-09-08 0
    • 谢谢指出,已修复
      自学精灵 2024-09-09 0
显示验证码
没有账号?注册  忘记密码?

社交账号快速登录