开启事务
在发生异常时又可能会导致数据库的数据不完整。下面我们来模拟一个案例。
删除与解散部门
在解散部门的时候,我们不仅要删除该部门,而且还要删除隶属于该部门的所有员工。不过,这两个操作并不是原子性的。
如果正常运行的话,就会是下面这样
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
 | // Service层
public void deleteDeptById (Integer id) {
    // 删除部门
    deptMapper.deleteDeptById(id); 
    
    // anything
    
    // 删除员工
    empMapper.deleteEmpByDeptId(id);
}
 | 
 
如果这两条语句之间不发生异常,那么就可以正确的把dept和emp删除,从而可以确保数据的完整性;如果中间发生异常,那么就会导致发生异常之前的代码可以被成功执行,而异常之后的代码未被执行。
为了解决这个问题,我们就需要使用事务这个注解来解决
@Transactional
这个注解可以帮助我们开启事务。
@Transactional注解书写位置:
- 方法
- 类
- 接口
- 接口下所有的实现类当中所有的方法都交给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,假设a和b都开启了事务,那么有没有可能会发生事务失败的情况呢,我们来模拟一下一种情形。
删除解散部门
在删除部门时,不管删除成功与否,我们都希望把删除的操作写进日志里面。看起来像是下面这样
|  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。
总结
- 使用@Transactional开启注解
- rollback进行回滚,默认情况下只在发生运行时异常时才进行回滚,可以加入Exception.class参数来支持所有异常回滚。
- 事务传播。可以使用REQUIRES_NEW参数来指定该方法说一个独立的事务