Web在执行的过程中需要保证一致性,从而需要引入事务来对SQL事件进行事务的管理。具体而言可以参考这篇博客MySQL事务(transaction)。
具体而言,我们获得一个这样的需求,删除一个部门,在删除部门的过程中需要删除部门下的所有员工
@Override
public void delete(Integer id)
{
// 由于这里要执行两端删除代码,为了保证一致性
// 外面使用事务的方法进行封装与回滚
deptMapper.delete(id);
empMapper.deleteEmpByDeptId(id);
}
正常而言这段代码是可以正常执行的,但是我们在中间引入一个错误,即在两个请求过程中加入一个运行时错误,此时deptMapper.delete(id);
执行成功,empMapper.deleteEmpByDeptId(id);
执行失败,这时部门删除了,但是该部门下的所有员工并没有删除(这就导致了数据库的不一致性)。
@Override
@Transactional
public void delete(Integer id)
{
// 由于这里要执行两端删除代码,为了保证一致性
// 外面使用事务的方法进行封装与回滚
deptMapper.delete(id);
int i =1/0;
empMapper.deleteEmpByDeptId(id);
}
为此我们引入SpringBoot中的事务管理,它是通过@Transactional进行注解标注的,有个这个注解整个函数包裹的部分变成一个事务。
在这里插入图片描述
@Override
@Transactional
public void delete(Integer id)
{
// 由于这里要执行两端删除代码,为了保证一致性
// 外面使用事务的方法进行封装与回滚
deptMapper.delete(id);
int i =1/0;
empMapper.deleteEmpByDeptId(id);
}
为了观察到日志,我们在application.yml引入事务管理日志。
logging:
level:
org.springframework.jdbc.support.jdbtTransactionManager: debug
执行上述代码可以看到:
运行时错误,同时整个事务进行了回滚。(同时数据库中的两个表都没有进行删除)
默认情况下@Transactional只会在系统出现运行时错误(RuntimeException)才会回滚(1/0操作就是典型的运行时错误)。
像下面,如果我们自己定义一个异常,事务是不会出现回滚的。
@Override
@Transactional
public void delete(Integer id) throws Exception
{
// 由于这里要执行两端删除代码,为了保证一致性
// 外面使用事务的方法进行封装与回滚
deptMapper.delete(id);
if(true)
throw new Exception("一个错误");
empMapper.deleteEmpByDeptId(id);
}
为此我们就需要引入rollbackFor,来定义回滚时的错误。
@Transactional(rollbackFor = Exception.class)
在注解上添加rollbackFor = Exception.class,告诉事务,遇见所有异常我都要进行回滚。
事务传播行为:指的是一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。
主要有这些事务属性
一般而言,我们知道两个就行REQUIRED、REQUIRES_NEW。
例如我这里的需求是解散部门,同时无论成功失败,都需要将记录写在操作日志上
那我们的步骤就总共为两部:1.解散部门和员工、2.记录日志到数据库
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(Integer id) throws Exception
{
// 由于这里要执行两端删除代码,为了保证一致性
// 外面使用事务的方法进行封装与回滚
try {
deptMapper.delete(id);
int i = 1/0;
empMapper.deleteEmpByDeptId(id);
}
finally {
LocalDate createTime = LocalDate.now();
String description = "执行了解散部门的操作,解散的部门为"+id;
deptMapper.insertDeptLog(createTime,description);
}
}
日志记录我们这里也是一个@Transactional注解的方法
@Transactional
public void insertDeptLog(LocalDate createTime,String description);
将这个记录的方法放在了finally {}中,无论成功失败我们都要执行,但是操作这个代码发现,日志上并没有记录。
原因是这两个@Transactional默认都是REQUIRED类型,首先执行void delete(Integer id) throws Exception代码,其自动为自己创建一个Transactional;当执行到finally {}中的void insertDeptLog(LocalDate createTime,String description)时候,insertDeptLog发现目前自己已经在一个事务中运行了,则不会创建新的事物,这导致两端代码同时回滚。
为了解决这个问题,则需要对insertDeptLog中注解声明参数REQUIRES_NEW,告诉系统,无论外面有没有事物,我都会自己创建一个事物。
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertDeptLog(LocalDate createTime,String description);