文章目录
- 事务管理
- 一、 Spring事务管理
- 1.1 事务回顾
- 1.2 案例: 解散部门(未开启事务)
- 1.3 事务管理注解@Transactional
- 1.4 事务管理日志开关
- 1.5 rollbackFor 异常回滚属性
- 1.6 propagation 事务传播行为
- 1.7 解散部门并记录操作日志
- 1.7.1 创建数据库表
- 1.7.2 代码实现
事务管理
一、 Spring事务管理
1.1 事务回顾
事务: 是一组操作的集合,是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败
事务的操作
- 开启事务(一组操作开始前,开启事务):start transaction / begin
- 提交事务:(这组操作全部成功后,提交事务),commit
- 回滚事务:(中间任何一个操作出现异常,回滚事务),rollback
1.2 案例: 解散部门(未开启事务)
需求:解散部门(删除部门),同时删除该部门下的员工
我们之前业务逻辑仅仅删除了部门,并没有删除该部门下的员工,此时造成数据的不完整、不一致。
@Delete("delete from dept where id= #{id}")
void deleteById(Integer id);
下面进行完善
SQL
DeptMapper
@Delete("delete from dept where id= #{id}")
void deleteById(Integer id);
EmpMapper
/**
* 根据部门ID删除该部门下的员工数据
* @param id 部门id
*/
@Delete("delete from emp where dept_id =#{id}")
void deleteByDeptId(Integer id);
业务代码
@Override
public void deleteById(Integer id) {
//根据id删除部门数据
deptMapper.deleteById(id);
//根据部门id删除员工数据
empMapper.deleteByDeptId(id);
}
假如在业务代码执行过程中出现异常了,会发生什么情况?
可能会发生部门删除了但是部门里面员工没有被删除。此时造成数据的不一致。
为了保证数据一致性,我们要保证删除部门和删除员工同时成功或者同时失败,也就是说这两步操作都在同一个事务当中
1.3 事务管理注解@Transactional
位置: 业务(Service)层方法上、类上、接口上
作用: 将当前方法交给Spring进行事务管理,方法执行前开启事务;成功执行完毕,提交事务;出现异常,回滚事务
我们一般添加在业务层执行多次数据访问操作的方法上
@Transactional
@Override
public void deleteById(Integer id) {
//根据id删除部门数据
deptMapper.deleteById(id);
//根据部门id删除员工数据
empMapper.deleteByDeptId(id);
}
1.4 事务管理日志开关
logging:
level:
org.springframework.jdbc.support JdbcTransactionManager: debug
1.5 rollbackFor 异常回滚属性
- 默认情况下,只有出现RuntimeException才会回滚异常。
比如说我们手动throw了一个Exception,并不会出现回滚的情况,而是直接将事务提交了
@Transactional
@Override
public void deleteById(Integer id) throws Exception {
//根据id删除部门数据
deptMapper.deleteById(id);
if (true) {
throw new Exception("出错了");
}
//根据部门id删除员工数据
empMapper.deleteByDeptId(id);
}
-
rollbackFor 属性用于控制出现哪一种异常类型的时候,进行回滚事务
这样配置后,所有的异常都会进行事务的回滚
@Transactional(rollbackFor = Exception.class)
@Override
public void deleteById(Integer id) throws Exception {
//根据id删除部门数据
deptMapper.deleteById(id);
if (true) {
throw new Exception("出错了");
}
//根据部门id删除员工数据
empMapper.deleteByDeptId(id);
}
1.6 propagation 事务传播行为
事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。
属性值 | 含义 |
---|---|
REQUIRED | [默认值] 需要事务,有则加入(b事务加入到a事务),无则创建新事务 |
**QEQUIRES_NEW ** | 需要创建新事务,无论有无,总是创建新事务(b创立一个新事务),如果创建新事务,当前事务进行挂起,等新事务完成后再进行当前事务 |
SUPPORTS | 支持事务,有则加入,无则在无事务状态 |
NOT_SUPPORTED | 不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务(a事务先挂起先执行b事务,b事务完成后再执行a事务) |
MANDATORY | 必须有事务,否则抛异常 |
NEVER | 必须没事务,否则抛异常 |
… | … |
1.7 解散部门并记录操作日志
需求:解散部门时,无论成功还是失败,都要记录操作日志
步骤:
① 解散部门: 删除部门、删除部门下的员工
② 记录日志到数据库表中
1.7.1 创建数据库表
create table dept_log(
id int auto_increment comment '主键ID' primary key,
create_time datetime null comment '操作时间',
description varchar(300) null comment '操作描述'
)comment '部门操作日志表';
1.7.2 代码实现
日志信息实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeptLog {
private Integer id;
private LocalDateTime createTime;
private String description;
}
日志插入SQL
@Mapper
public interface DeptLogMapper {
@Insert("insert into dept_log(create_time,description) values(#{createTime},#{description})")
void insert(DeptLog log);
}
日志插入业务代码
@Service
public class DeptLogServiceImpl implements DeptLogService {
@Autowired
private DeptLogMapper deptLogMapper;
@Transactional //事务传播行为:有事务就加入、没有事务就新建事务
@Override
public void insert(DeptLog deptLog) {
deptLogMapper.insert(deptLog);
}
}
删除部门业务代码
@Transactional(rollbackFor = Exception.class)
@Override
public void deleteById(Integer id) throws Exception {
//根据id删除部门数据
deptMapper.deleteById(id);
//根据部门id删除员工数据
empMapper.deleteByDeptId(id);
// TODO 记录操作日志
DeptLog deptLog = new DeptLog();
deptLog.setCreateTime(LocalDateTime.now());
deptLog.setDescription("执行了解散部门的操作,此时解散的是"+id+"号部门");
//调用其他业务类中的方法
deptLogService.insert(deptLog);
}
此时方法调用有两个 @Transactional注解
- 一个在deleteById方法,删除部门与对应用户
- 一个在insert方法,并且这个方法在deleteById方法中被调用
此时涉及事务传播行为
进行测试,发现数据库中并不存在日志信息,是什么原因?
两个方法都有 @Transactional注解,采用的是默认事务传播行为,需要事务,有则加入(b事务加入到a事务),无则创建新事务。
很显然insert事务会加入到deleteById事务,但是在deleteById业务执行时发生异常,进行回滚,那同属于一个事务的insert也会回滚,导致数据库中没有记录。
所以现在我们需要修改一下事务传播行为
propagation = Propagation.REQUIRES_NEW,表示无论deleteById中是否有事务,insert方法中都会新开启一个新的事物
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void insert(DeptLog deptLog) {
deptLogMapper.insert(deptLog);
}
当在deleteById事务中开启了insert事务,此时deleteById事务会被挂起,进行insert事务,当insert事务进行完成后继续运行deleteById事务
propagation = Propagation.REQUIRES_NEW,表示无论deleteById中是否有事务,insert方法中都会新开启一个新的事物
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void insert(DeptLog deptLog) {
deptLogMapper.insert(deptLog);
}
当在deleteById事务中开启了insert事务,此时deleteById事务会被挂起,进行insert事务,当insert事务进行完成后继续运行deleteById事务