Spring事务

开启事务

在发生异常时又可能会导致数据库的数据不完整。下面我们来模拟一个案例。

删除与解散部门

在解散部门的时候,我们不仅要删除该部门,而且还要删除隶属于该部门的所有员工。不过,这两个操作并不是原子性的。

如果正常运行的话,就会是下面这样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Service层
public void deleteDeptById (Integer id) {
    // 删除部门
    deptMapper.deleteDeptById(id); 
    
    // anything
    
    // 删除员工
    empMapper.deleteEmpByDeptId(id);
}

如果这两条语句之间不发生异常,那么就可以正确的把deptemp删除,从而可以确保数据的完整性;如果中间发生异常,那么就会导致发生异常之前的代码可以被成功执行,而异常之后的代码未被执行。

为了解决这个问题,我们就需要使用事务这个注解来解决

@Transactional

这个注解可以帮助我们开启事务。

@Transactional注解书写位置:

  • 方法
    • 当前方法交给spring进行事务管理
    • 当前类中所有的方法都交由spring进行事务管理
  • 接口
    • 接口下所有的实现类当中所有的方法都交给spring 进行事务管理

一般的,我们可以只在Service的实现类里面去加上这个注解。当方法发生异常时,就会引发事务回滚。我们来模拟一下发生异常的情况。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;

    @Autowired
    private EmpMapper empMapper;


    //根据部门id,删除部门信息及部门下的所有员工
    @Override
    public void delete(Integer id){
        //根据部门id删除部门信息
        deptMapper.deleteById(id);
        
        //模拟:异常发生
        int i = 1/0;

        //删除部门下的所有员工信息
        empMapper.deleteByDeptId(id);   
    }
}

当异常发生时,删除员工信息的操作不会成功。

加上注解后,可以发现,数据库成功回滚

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;

    @Autowired
    private EmpMapper empMapper;

    
    @Override
    @Transactional  //当前方法添加了事务管理
    public void delete(Integer id){
        //根据部门id删除部门信息
        deptMapper.deleteById(id);
        
        //模拟:异常发生
        int i = 1/0;

        //删除部门下的所有员工信息
        empMapper.deleteByDeptId(id);   
    }
}

rollbackFor

如果这里我们手动抛出一个异常,即throws new Exception,那么事务回滚还是会发生失败,这是因为抛出异常相当于该方法直接return,那么return后的语句是不会执行的,所以这个时候就需要我们在@Transactional中指定异常处理的类别。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;

    @Autowired
    private EmpMapper empMapper;

    
    @Override
    @Transactional(rollbackFor=Exception.class) // 不管出现什么异常,都要回滚事务
    public void delete(Integer id){
        //根据部门id删除部门信息
        deptMapper.deleteById(id);
        
        //模拟:异常发生
        int num = id/0;

        //删除部门下的所有员工信息
        empMapper.deleteByDeptId(id);   
    }
}

一般的,如果不加rollbackFor,那么只支持RunTimeException

propagation

事务也是具有自己的传播性的。

假设这里有一个方法a,在方法a里面我们有=又去调用方法b,假设ab都开启了事务,那么有没有可能会发生事务失败的情况呢,我们来模拟一下一种情形。

删除解散部门

在删除部门时,不管删除成功与否,我们都希望把删除的操作写进日志里面。看起来像是下面这样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Slf4j
@Service
//@Transactional //当前业务实现类中的所有的方法,都添加了spring事务管理机制
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;
    
    @Autowired
    private EmpMapper empMapper;

    @Autowired
    private DeptLogService deptLogService;


    //根据部门id,删除部门信息及部门下的所有员工
    @Override
    @Log
    @Transactional(rollbackFor = Exception.class) 
    public void delete(Integer id) throws Exception {
        try {
            //根据部门id删除部门信息
            deptMapper.deleteById(id);
            //模拟:异常
            if(true){
                throw new Exception("出现异常了~~~");
            }
            //删除部门下的所有员工信息
            empMapper.deleteByDeptId(id);
        }finally {
            //不论是否有异常,最终都要执行的代码:记录日志
            DeptLog deptLog = new DeptLog();
            deptLog.setCreateTime(LocalDateTime.now());
            deptLog.setDescription("执行了解散部门的操作,此时解散的是"+id+"号部门");
            //调用其他业务类中的方法
            deptLogService.insert(deptLog);
        }
    }
    
    //省略其他代码...
}

首先,这两个操作都是开启事务的,这就意味着一旦发生了异常操作,那么事务是必须要进行回退的。但是插入日志的操作是在删除部门这个逻辑里面的,经过实验我们得出了一个结论

  • 在默认情况下,如果a方法发生异常并回滚,那么a调用的支持事务的方法也要跟着a进行回滚a中的方法与a存在于同一个事物环境

    在这个方法里面,如果发生了异常,那么执行插入日志操作会发生回滚。

  • 使用@Transactional(propagation = Propagation.REQUIRES_NEW)参数来进行指定

    通过这行注释,就说明被注释的方法要求一个新的事物环境,与它被调用的那个方法不在同一个事物环境内,这样当调用者发生事务回滚时,被调用者不会发生事务回滚。

到此事务传播行为已演示完成,事务的传播行为我们只需要掌握两个:REQUIRED、REQUIRES_NEW。

  • REQUIRED :大部分情况下都是用该传播行为即可。

  • REQUIRES_NEW :当我们不希望事务之间相互影响时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功。

总结

  • 使用@Transactional开启注解
  • rollback进行回滚,默认情况下只在发生运行时异常时才进行回滚,可以加入Exception.class参数来支持所有异常回滚。
  • 事务传播。可以使用REQUIRES_NEW参数来指定该方法说一个独立的事务
Licensed under CC BY-NC-SA 4.0
花有重开日,人无再少年
使用 Hugo 构建
主题 StackJimmy 设计