开启事务
在发生异常时又可能会导致数据库的数据不完整。下面我们来模拟一个案例。
删除与解散部门
在解散部门的时候,我们不仅要删除该部门,而且还要删除隶属于该部门的所有员工。不过,这两个操作并不是原子性的。
如果正常运行的话,就会是下面这样
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参数来指定该方法说一个独立的事务