目录
事务管理
事务回顾
概念:事物是一组操作的集合,它是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败
操作:
- 开启事务(一组操作开始前,开启事务):start transaction / begin ;
- 提交事务(这组操作全部成功后,提交事务):commit ;
- 回滚事务(中间任何一个操作出现异常,回滚事务):rollback ;
事务的必要性:当一组操作(如 "删除部门 + 删除该部门下的员工")需要保证数据一致性时,必须通过事务控制。若没有事务,某一步操作异常可能导致部分操作生效、部分失效,造成数据不一致(例如:删除部门后出现异常,员工数据未被删除,导致数据关联错误)。
Spring 事务管理基础
在员工部门中,关于部门的代码为:
-
controller:
javaimport com.example.demo.pojo.Dept; import com.example.demo.responed.Result; import com.example.demo.service.DeptService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @Slf4j @RequestMapping("/depts") @RestController public class DeptController { @Autowired private DeptService deptService; // 查询部门数据 @GetMapping public Result list(){ log.info("查询全部部门数据"); //调用service查询部门数据 List<Dept> deptList = deptService.list(); return Result.success(deptList); } // 删除部门 @DeleteMapping("/{id}") public Result delete(@PathVariable Integer id){ log.info("根据id删除部门:{}",id); //调用service删除部门 deptService.deleteById(id); return Result.success(); } //新增部门 @PostMapping public Result add(@RequestBody Dept dept){ log.info("新增部门: {}" , dept); //调用service新增部门 deptService.add(dept); return Result.success(); } }
-
service 接口:
javaimport com.example.demo.pojo.Dept; import java.util.List; public interface DeptService { void deleteById(Integer id); // 查询全部部门数据 List<Dept> list(); // 删除部门 void delete(Integer id); // 新增部门 void add(Dept dept); }
-
service 实现类
javaimport com.example.demo.mapper.DeptMapper; import com.example.demo.mapper.EmpMapper; import com.example.demo.pojo.Dept; import com.example.demo.service.DeptService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; @Service public class DeptServiceImpl implements DeptService { @Autowired private DeptMapper deptMapper; @Override public void deleteById(Integer id) { deptMapper.deleteById(id); } @Override public List<Dept> list() { return deptMapper.list(); } @Override public void delete(Integer id) { deptMapper.deleteById(id); } @Override public void add(Dept dept) { dept.setCreateTime(LocalDateTime.now()); dept.setUpdateTime(LocalDateTime.now()); deptMapper.insert(dept); } }
-
mapper 接口:
javaimport com.example.demo.pojo.Dept; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; @Mapper public interface DeptMapper { // 查询全部部门 @Select("select * from dept") List<Dept> list(); // 根据ID删除部门 @Delete("delete from dept where id = #{id}") void deleteById(Integer id); // 新增部门 @Insert("insert into dept(name, create_time, update_time) values(#{name},#{createTime},#{updateTime})") void insert(Dept dept); }
目前,部门的删除操作只是删除了部门,然而在实际操作中,部门解散后相应的部门员工的部门编号也应该进行更改,为了方便,后面演示为部门删除后对应的部门员工也删除。
所以需要将部门的 service 实现类改为以下:
java
import com.example.demo.mapper.DeptMapper;
import com.example.demo.mapper.EmpMapper;
import com.example.demo.pojo.Dept;
import com.example.demo.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Autowired
private EmpMapper empMapper;
@Override
public void deleteById(Integer id) {
// 1. 删除部门
deptMapper.deleteById(id);
// 2. 删除部门下的员工
empMapper.deleteByDeptId(id);
}
// ...
}
在 EmpMapper 中添加接口方法:
java
// 根据部门id删除员工
@Delete("delete from emp where dept_id=#{deptId}")
void deleteByDeptId(Integer deptId);
目前代码存在一个问题,如果说在实现类调用的两个方法之间添加一个异常:
java
@Override
public void deleteById(Integer id) {
// 1. 删除部门
deptMapper.deleteById(id);
int i = 1 / 0;// 添加异常
// 2. 删除部门下的员工
empMapper.deleteByDeptId(id);
}
即是程序抛出异常,部门依然删除了,部门被删除但相应的部门员工未被删除,导致数据不一致,这是因为异常导致后续删除员工的代码未执行,需将两步操作置于同一事务中才能保证事务的一致性。
Spring 中提供了 @Transactional 注解来解决这个问题:
- 注解:@Transactional
- 位置:service 层的方法上、类上、接口上
- 作用:将当前方法交给 Spring 进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常回滚事务
- 通常加在业务层执行多次数据访问操作的增删改方法上
java
import com.example.demo.mapper.DeptMapper;
import com.example.demo.mapper.EmpMapper;
import com.example.demo.pojo.Dept;
import com.example.demo.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Autowired
private EmpMapper empMapper;
@Transactional
@Override
public void deleteById(Integer id) {
// 1. 删除部门
deptMapper.deleteById(id);
int i = 1/0;
// 2. 删除部门下的员工
empMapper.deleteByDeptId(id);
}
// ...
}
这样就能保证事务的一致性,一起提交或者一起回滚。
可以配置 Spring 事务管理的日志开关,方便在控制台中查看日志:
application.properties:
yaml
logging.level.org.springframework.jdbc.support.JdbcTransactionManager=DEBUG
application.yml:
yaml
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: DEBUG
Spring 事务管理进阶
@Transactional 注解的两个常见属性:
- rollbackFor(异常回滚属性)
- propagation(事务传播行为)
rollbackFor
将实现类的方法改为:
java
@Transactional
@Override
public void deleteById(Integer id) throw Exception{
// 1. 删除部门
deptMapper.deleteById(id);
// 添加异常
if(true) {
throw new Exception("程序出错了!!!");
}
// 2. 删除部门下的员工
empMapper.deleteByDeptId(id);
}
如果代码中添加的是这种异常,即使添加了 @Transactional,也还是会出现程序抛出异常,部门依然删除了,部门被删除但相应的部门员工未被删除,导致数据不一致的情况。
这是因为 @Transactional 默认情况下,只有出现 RuntimeException(运行时异常)才会回滚,之前模拟的 1÷0 产生的算术运算异常属于运行时异常,所以能正常回滚,而直接抛出的 Exception 异常不是运行时异常,不会回滚。
rollbackFor 属性用于控制出现何种异常类型时回滚事务,配置为 rollbackFor = Exception.class 时,代表出现所有异常都会进行事务回滚。
java
@Transactional(rollbackFor = Exception.class)
@Override
public void deleteById(Integer id) throw Exception{
// 1. 删除部门
deptMapper.deleteById(id);
// 添加异常
if(true) {
throw new Exception("程序出错了!!!");
}
// 2. 删除部门下的员工
empMapper.deleteByDeptId(id);
}
如此一来,不论是何种异常,都会进行事务回滚,保证了事务的一致性
propagation
事务传播行为指当一个事务方法被另一个事务方法调用时,该事务方法如何进行事务控制。例如 a 方法(带事务)调用 b 方法(带事务)时,b 是加入 a 的事务还是新建事务,由传播行为决定。
事务传播行为:
属性值 | 含义 |
---|---|
REQUIRED | (默认值)需要事务,有则加入,无则创建新事务 |
REQUIRES_NEW | 需要新事务,无论有无,总是创建新事务 |
SUPPORTS | 支持事务,有则加入,无则在无事务状态中运行 |
NOT_SUPPORTED | 不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务 |
MANDATORY | 必须有事务,否则抛出异常 |
NEVER | 必须无事务,否则抛出异常 |
往数据库中新增一个部门操作日志表:
sql
create table dept_log(
id int unsigned primary key auto_increment comment '主键ID',
create_time datetime not null comment '创建时间',
description varchar(300) not null comment '操作描述'
)'部门操作日志表';
实体类为:
java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeptLog {
private Integer id;
private LocalDateTime createTime;
private String description;
}
mapper 接口:
java
import org.apache.ibatis.annotations.Insert;
public interface DeptLogMapper {
@Insert("insert into dept_log(description, create_time) values(#{description}, #{createTime})")
void insert(DeptLog deptLog);
}
service 接口以及实现类:
java
// service接口
import com.example.demo.pojo.DeptLog;
public interface DeptLogService {
void insert(DeptLog deptLog);
}
// service实现类
import com.example.demo.mapper.DeptLogMapper;
import com.example.demo.pojo.DeptLog;
import com.example.demo.service.DeptLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DeptLogServiceImpl implements DeptLogService {
@Autowired
private DeptLogMapper deptLogMapper;
@Transactional
@Override
public void insert(DeptLog deptLog) {
deptLogMapper.insert(deptLog);
}
}
现在要求,不论删除操作是否成功,都要记录操作日志,将 DeptServiceImpl 改为:
java
import com.example.demo.mapper.DeptMapper;
import com.example.demo.mapper.EmpMapper;
import com.example.demo.pojo.Dept;
import com.example.demo.pojo.DeptLog;
import com.example.demo.service.DeptLogService;
import com.example.demo.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Autowired
private EmpMapper empMapper;
@Autowired
private DeptLogService deptLogService;
@Transactional
@Override
public void deleteById(Integer id) {
try {
// 1. 删除部门
deptMapper.deleteById(id);
int i = 1/0;
// 2. 删除部门下的员工
empMapper.deleteByDeptId(id);
} finally {
// 3. 删除部门日志
DeptLog deptLog = new DeptLog();
deptLog.setCreateTime(LocalDateTime.now());
deptLog.setDescription("删除了部门id为" + id + "的部门");
deptLogService.insert(deptLog);
}
}
// ...
}
deleteById 方法(带事务)调用 insert 方法(带事务)时,insert 会加入 delete 的事务。若 delete 执行中出现异常,整个事务回滚,导致日志记录也回滚,无法满足需求。
java
import com.example.demo.mapper.DeptLogMapper;
import com.example.demo.pojo.DeptLog;
import com.example.demo.service.DeptLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class DeptLogServiceImpl implements DeptLogService {
@Autowired
private DeptLogMapper deptLogMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void insert(DeptLog deptLog) {
deptLogMapper.insert(deptLog);
}
}
将 insert 方法的传播行为设为 REQUIRES_NEW 后,调用 insert 时会挂起当前事务并新建事务。insert 执行完毕后提交,不受 delete 事务回滚影响,即使 delete 异常,日志也能成功记录。