案例
接下来我们就通过一个案例来演示下事务传播行为propagation属性的使用。
需求:解散部门时需要记录操作日志
由于解散部门是一个非常重要而且非常危险的操作,所以在业务当中要求每一次执行解散部门的操作都需要留下痕迹,就是要记录操作日志。而且还要求无论是执行成功了还是执行失败了,都需要留下痕迹。
步骤:
- 执行解散部门的业务:先删除部门,再删除部门下的员工(前面已实现)
- 记录解散部门的日志,到日志表(未实现)
准备工作:
- 创建数据库表 dept_log 日志表:
create table dept_log( id int auto_increment comment '主键ID' primary key, create_time datetime null comment '操作时间', description varchar(300) null comment '操作描述' )comment '部门操作日志表';
- 引入资料中提供的实体类:DeptLog
@Data @NoArgsConstructor @AllArgsConstructor public class DeptLog { private Integer id; private LocalDateTime createTime; private String description; }
- 引入资料中提供的Mapper接口:DeptLogMapper
@Mapper public interface DeptLogMapper { @Insert("insert into dept_log(create_time,description) values(#{createTime},#{description})") void insert(DeptLog log); }
- 引入资料中提供的业务接口:DeptLogService
public interface DeptLogService { void insert(DeptLog deptLog); }
- 引入资料中提供的业务实现类:DeptLogServiceImpl
@Service public class DeptLogServiceImpl implements DeptLogService { @Autowired private DeptLogMapper deptLogMapper; @Transactional //事务传播行为:有事务就加入、没有事务就新建事务 @Override public void insert(DeptLog deptLog) { deptLogMapper.insert(deptLog); } }
代码实现:
业务实现类:DeptServiceImpl
@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); } } //省略其他代码... }
测试:
重新启动SpringBoot服务,测试删除3号部门后会发生什么?
- 执行了删除3号部门操作
- 执行了插入部门日志操作
- 程序发生Exception异常
- 执行事务回滚(删除、插入操作因为在一个事务范围内,两个操作都会被回滚)
-
然后在dept_log表中没有记录日志数据
原因分析:
接下来我们就需要来分析一下具体是什么原因导致的日志没有成功的记录。
- 在执行delete操作时开启了一个事务
- 当执行insert操作时,insert设置的事务传播行是默认值REQUIRED,表示有事务就加入,没有则新建事务
- 此时:delete和insert操作使用了同一个事务,同一个事务中的多个操作,要么同时成功,要么同时失败,所以当异常发生时进行事务回滚,就会回滚delete和insert操作
-
解决方案:
在DeptLogServiceImpl类中insert方法上,添加@Transactional(propagation = Propagation.REQUIRES_NEW)
Propagation.REQUIRES_NEW :不论是否有事务,都创建新事务 ,运行在一个独立的事务中。
@Service public class DeptLogServiceImpl implements DeptLogService { @Autowired private DeptLogMapper deptLogMapper; @Transactional(propagation = Propagation.REQUIRES_NEW)//事务传播行为:不论是否有事务,都新建事务 @Override public void insert(DeptLog deptLog) { deptLogMapper.insert(deptLog); } }
重启SpringBoot服务,再次测试删除3号部门:
那此时,DeptServiceImpl中的delete方法运行时,会开启一个事务。 当调用 deptLogService.insert(deptLog) 时,也会创建一个新的事务,那此时,当insert方法运行完毕之后,事务就已经提交了。 即使外部的事务出现异常,内部已经提交的事务,也不会回滚了,因为是两个独立的事务。
到此事务传播行为已演示完成,事务的传播行为我们只需要掌握两个:REQUIRED、REQUIRES_NEW。
- REQUIRED :大部分情况下都是用该传播行为即可。
- REQUIRES_NEW :当我们不希望事务之间相互影响时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功。