Web 事务管理

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中的事务管理

为此我们引入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

执行上述代码可以看到:
运行时错误,同时整个事务进行了回滚。(同时数据库中的两个表都没有进行删除)
在这里插入图片描述

事务管理——rollbackFor

默认情况下@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,告诉事务,遇见所有异常我都要进行回滚。

事务管理——propagation 事务传播行为

事务传播行为:指的是一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。
主要有这些事务属性
Web 事务管理_第1张图片
一般而言,我们知道两个就行REQUIRED、REQUIRES_NEW。
Web 事务管理_第2张图片

例如我这里的需求是解散部门,同时无论成功失败,都需要将记录写在操作日志上
那我们的步骤就总共为两部: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);

你可能感兴趣的:(JAVA后端,前端,数据库,java)