您现在的位置是:首页 >技术教程 >Spring事务和事务传播机制网站首页技术教程

Spring事务和事务传播机制

学习自省 2024-10-16 12:01:04
简介Spring事务和事务传播机制

日升时奋斗,日落时自省 

目录

1、事务

2、Spring针对事务的实现

2.1、Spring编程式事务(了解)

2.2、Spring声明事务(自动启动)

2.2.1、异常回滚

2.2.2、@Transactional作用范围

2.2.3、@Transactional相关参数

2.3、SpringBoot事务失效场景

2.3.1、try-catch事务失效

2.3.2、try-catch事务失效处理

2.3.3、其他事务失效场景

2.3.4、@Transactional工作原理

3、事务隔离级别

3.1、事务特性(ACID)

3.2、Spring中设置事务隔离级别

3.2.1、mysql事务基础

3.2.2、Spring事务隔离级别

4、Spring事务传播机制

4.1、事务传播机制概念

4.2、事务传播机制作用

4.3、事务传播机制种类

4.3.1、支持当前事务

4.3.2、不支持当前事务

4.3.3、嵌套事务

4.4、Spring事务传播机制场景

4.4.1、支持当前事务(REQUIRED)

4.4.2、支持当前事务(REQUIRED_NEW)

4.4.3、NESTED嵌套事务

4.4.4、(REQUIRED)加入和(NESTED)嵌套的区别

4.4.5、总结


1、事务

首先了解一下事务基本是个什么(其实在mysql中也学习过)

事务是指在计算机系统中一系列的操作,这些操作要么全部执行成功,要么全部失败回滚。事务可以看作是一个原子性操作单元,即要么全部执行成功,要么全部失败

2、Spring针对事务的实现

Spring中的事务操作分为两类:

<1>编程式事务(手动写代码操作事务,特定的语言和API来管理数据库操作的事务)

<2>声明式事务(利用注解自动开启和提交事务)

2.1、Spring编程式事务(了解)

Spring手动操作事务和mysql操作事务类似,它也是有3个操作步骤:

<1>开启事务(获取事务)

<2>提交事务

<3>回滚事务

SpringBoot内置了两个对象:

DataSourceTransactionManager对象就是进行获取事务(开启事务,提交事务,回滚事务)相当于一个管理者,管理事务的操作

TransactionDefinition对象是事务的属性,在获取事务的时候需要将TransactionDefinition这个事务传递进DataSourceTransactionManager中此时获取事务就结束了

@RestController
@RequestMapping("/user")
public class UserController {
     //事务操作对象引入
    @Autowired
    private StudentService studentService;

    //编程式事务
    @Autowired
    private DataSourceTransactionManager transactionManager;  //事务管理者

    @Autowired
    private TransactionDefinition transactionDefinition; //事务
    //这里我们就写一个删除的事务
    @RequestMapping("/del")
    public int del(Integer id){
        if(id==null||id<0){
            return 0;
        }
        //定义  用来接收开启事务的
        TransactionStatus  transactionStatus =null;
        int result=0;
        try{
            //开启事务
            transactionStatus=transactionManager.getTransaction(transactionDefinition);
            //业务操作,删除用户
            result=studentService.del(id);
            //业务操作 完成之后, 就可以进行提交事务 和 回滚事务
            transactionManager.commit(transactionStatus);
            //为什么这里加了一个 try catch 为的就是如果事务出错就进行回滚保持事务的一致性
        }catch (Exception e){
            //如果操作事务不是空的 那就进行回滚
            if(transactionStatus!=null){
                transactionManager.rollback(transactionStatus);
            }
        }
        return result;
    }
}

那接下来就看一下运行后的操作:

2.2、Spring声明事务(自动启动)

声明事务的操作很简单,只需要添加上@Transactional注解就可以实现了,无需手动开启事务和提交事务,进入方法时自动开启事务方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务(下面做一个演示还是以Student类进行删除操作)

注:@Transactional注解可以理解为把手动操作事务的代码进行封装,直接进行事务的业务逻辑操作就行 

这里访问路由为 127.0.0.1:8080/user2/del

@RestController   //返回字符串类型 同时 添加类注解
@RequestMapping("/user2")  //设置访问路由
public class UserController2 {
    @Autowired
    private StudentService studentService;

    @Transactional //执行方法前开启事务,方法正常执行结束之后提交事务,如果执行途中发现异常就进行回滚
    @RequestMapping("/del")
    public int del(Integer id){
        if(id==null||id<=0){
            return 0;
        }
        //进行业务逻辑 删除操作
        int result= studentService.del(id);
        System.out.println("删除:"+result);
        //在操作中这里设置一个 异常 by zero异常 让事务进行回滚
        int num=10/0;
        return  result;
    }
}

访问路由效果(和手动效果是一样的,并且更方便,也比较常用):

2.2.1、异常回滚

那会不会回滚呢,会的,这里也演示一下回滚操作,这里就以by zero异常来演示回滚操作

@RestController   //返回字符串类型 同时 添加类注解
@RequestMapping("/user2")  //设置访问路由
public class UserController2 {
    @Autowired
    private StudentService studentService;

    @Transactional //执行方法前开启事务,方法正常执行结束之后提交事务,如果执行途中发现异常就进行回滚
    @RequestMapping("/del")
    public int del(Integer id){
        if(id==null||id<=0){
            return 0;
        }
        //进行业务逻辑 删除操作
        int result= studentService.del(id);
        System.out.println("删除:"+result);
        //在操作中这里设置一个 异常 by zero异常 让事务进行回滚
        int num=10/0;
        return  result;
    }
}

2.2.2、@Transactional作用范围

@Transactional可以用来修饰方法或者类

修饰方法时:需要注意只能应用到public方法上,否则不生效;如果该方法抛出异常,则事务会自动回滚

修饰类时:该注解对该类中所有的public方法都生效,表示该类的所有公共方法都需要被事务管理器所管理。如果该类中任何一个方法抛出异常,则整个类的事务都会回滚

2.2.3、@Transactional相关参数

 这里可以先做一个简单的了解,下面还会针对propagation(传播行为)isolation(隔离级别)这两个内容进行解析,其他内容看后面的作用就可以使用

2.3、SpringBoot事务失效场景

2.3.1、try-catch事务失效

@Transactional在异常被捕获的情况下,不会进行事务自动回滚

就在刚刚写的by zero异常中添加 try-catch来检测

    @Transactional //执行方法前开启事务,方法正常执行结束之后提交事务,如果执行途中发现异常就进行回滚
    @RequestMapping("/del")
    public int del(Integer id){
        if(id==null||id<=0){
            return 0;
        }
        //进行业务逻辑 删除操作
        int result= studentService.del(id);
        System.out.println("删除:"+result);
        //在操作中这里设置一个 异常 by zero异常 让事务进行回滚
        try{
            int num=10/0;
        }catch (Exception e){

        }
        return  result;
    }

访问路由效果:

2.3.2、try-catch事务失效处理

<1>方法一 :

抛出一个异常就它同样会进行回滚,效果和没有加try-catch是一样的,页面显示报错处理

<2>方法二:

手动回滚事务,在方法中使用

这里我们就演示手动回滚事务,因为抛出异常很简单(这里选择一种方法就可以)

        try{
            int num=10/0;
        }catch (Exception e){
            //方法一: 抛出一个异常, e记录错误信息提示
            //e.getMessage();  
            /*
            * 方法二: 手动回滚
            * AOP支持 当前线程回滚 
            * try catch 中事务失效  我们此处演示手动回滚
            * */
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }

注:使用方法一 就把方法代码注释掉就行

 注:因为这里数据库没有变化,也就没有再拿出来进行比对

2.3.3、其他事务失效场景

<1>非public修饰方法加了@Transactional注解,事务是失效状态

<2>timeout是@Transactional注解的一个参数,可以设置事务超时时间,这是设置了事务的超时时间,但不是方法的限制时间,所以如果方法内部设置一个等待时间超过了事务的超时时间,此时事务也是失效状态

<3>@Transactional方法修饰内部类时也不会生效

 <5>数据库不支持事务,那就没有用了(数据库mysql的InnoDB引擎出自于mysql5.6版本,并且支持事务操作)

show engines;

2.3.4、@Transactional工作原理

@Transactional是基于AOP实现的,AOP又是使用动态代理实现,默认情况下会采用JDK动态代理,如果目标对象没有实现接口,会使用CGLIB动态代理

@Transactional在执行业务之前,通过动态代理开启事务,业务操作之后再提交事务

@Transactional基本实现思路:

3、事务隔离级别

3.1、事务特性(ACID)

事务基本特性:原子性、一致性、隔离性、持久性

<1>原子性(Atomicity):事务是一个不可分割的工作单位,其中包含的所有操作要么全部成功完成,要么全部失败回滚,事务必须是不可分割的“原子”操作。

<2>一致性(Consistency):事务完成前后,系统的状态应该是一致的。在执行事务时,如果有任何错误发生,所有事务所做的修改将会被回滚到事务开始之前的状态。

<3>隔离性(Isolation):同一个时间段内,多个事务执行之间是互不干扰的,每个事务都认为自己是系统唯一的操作者。一个事务所做的修改在提交之前,对其他事务都是不可见的;级别划分包括读未提交(Readuncommitted)、读提交(read committed)、可重复读(repeatable read)和串⾏化
(Serializable)

<4>持久性(Durability):一旦事务提交成功,对数据的修改就是永久性的,即使系统崩溃,这些修改也不会丢失。实现持久性非常重要的一点是,在事务提交之前,需要将事务记录的日志写入主要存储器中。

注:当前这些都是事务的特性,Spring Boot同样就有着样的事务特性,这里如果使用声明式事务,这里的隔离性需要去设置,其他都是事务本身的特性,也没有层级划分

隔离性的好处:设置事务的隔离级别是用来保障多个并发事务执行可控,防止其他事务影响当前事务执行的一种策略

3.2、Spring中设置事务隔离级别

Spring中的事务隔离级别可以通过@Transactional中参数isolation属性进行设置

3.2.1、mysql事务基础

在了解参数设置之前可以先熟悉mysql中事务的隔离性,有助于我们对@Transactional设置参数

3.2.2、Spring事务隔离级别

Spring中事务隔离级别包括以下5种(其实和mysql事务的区别不是很大,就是参数点级别格式):

<1>Isolation.DEFAULT:以连接的数据库的事务隔离级别为主
<2>Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读
<3>Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读
<4>Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级
别)
<5>Isolation.SERIALIZABLE:串⾏化,可以解决所有并发问题,但性能太低

注:Isolation.DEFAULT只有这个级别可能比较陌生,这是默认级别;默认的隔离级别是由底层事务管理器来决定的,底层事务管理器默认隔离级别是数据库的默认隔离级别(在spring没有设置的情况下,就是默认级别)

不同数据库有不同的默认级别,所以spring设置的默认级别根据数据库而定

这里暂时还做演示因为看不出效果,演示效果需要从事务传播机制开始

4、Spring事务传播机制

4.1、事务传播机制概念

事务传播机制定义了当一个事务方法被另一个事务方法调用时,当前事务如何和被调用方法的事务关联的规则

注:官话有点绕,简单解释就是事务与事务之间的关系,是怎么联系起来的

4.2、事务传播机制作用

事务的传播机制伴随着事务的隔离级别,事务隔离级别是保证多个并发事务执⾏的可控性的(稳定性的),⽽事务传播机制是保证⼀个事务在多个调⽤⽅法间的可控性的(稳定性的)

以下是多个事务同时调用数据库的问题,同时触发会影响数据库(数据产生问题)

 事务传播机制解决的是一个事务在多个节点(方法)中传递的问题

4.3、事务传播机制种类

Spring事务传播机制包含以下7种:

4.3.1、支持当前事务

<1>Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该是何物;如果当前没有事务,则创建一个新的事务;

<2>Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行;

<3>Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常

4.3.2、不支持当前事务

<1>Propagation.REQUIRES_NEW:表示创建一个新的事务,如果当前存在事务,则把当前事务挂起,实际意思就是不管当前是否有事务存在,我都要新创建一个事务,且开启的事务相互独立,互不干扰

<2>Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起

<3>Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常

4.3.3、嵌套事务

<1>Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行,如果当前没有事务,则该取值等价于Propagation.REQUIRED(相当于默认使用事务)

4.4、Spring事务传播机制场景

4.4.1、支持当前事务(REQUIRED)

如果演示REQUIRED的效果,这里会先插入一条数据,同时在后续加入的事务中设置一个异常,观察事务是否会回滚

这里controller层,写一个操作student表的类,此时没有事务就会创建一个新的事务进行运行

@RestController
@RequestMapping("/user3")
public class UserController3 {
    @Autowired
    private StudentService studentService;
    @RequestMapping("/add")
    @Transactional(propagation = Propagation.REQUIRED)  //事务处理当前一定会有一个事务
    public int add(String name,String email){
        if(null==name||null==email
                ||name.equals("")||email.equals("")){
            return 0;
        }
        Student student=new Student();
        student.setName(name);
        student.setQq_mail(email);
        //添加一个学生
        int result=studentService.add(student);
        return result;
    }
}

但是我们还想在加入插入一个学生的日志,所以这里有在接口处写一个日志添加,这里把添加的日志写到了Service层了(应该还是写到controller层的)

学生添加:添加一个学生信息同时也添加一个日志,学生添加事务设置为Propagation.REQUIRED,当前方法是被调用的,调用者UserController3已经创建一个事务了,这里直接加入到事务中即可(后面访问路由后展示效果,也会进行解释)

@Service
public class StudentService {

    @Autowired
    private StudentMapper studentMapper; 

    @Autowired
    private LogService logService; //日志的对象注入

    @Transactional(propagation = Propagation.REQUIRED)
    public int add(Student student){
        //添加 到数据库
        int addStudent= studentMapper.add(student);
        System.out.println("添加学生:"+addStudent);
        //添加日志
        Log log=new Log();
        log.setMessage("添加学生信息");
        //添加日志
        logService.add(log);
        return addStudent;
    }

}

日志添加:同时设置事务为Propagation.REQUIRED,日志添加方法被学生添加方法所调用,所以事务也是已经存在了的,不需要创建事务直接添加到已有事务中

@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public int add(Log log){
        //添加日志到数据库
        int result=logMapper.add(log);
        System.out.println("添加日志结果:"+result);

        //这里设置一个 报错 为了 展示事务的加入是如何加入的
        int num=10/0;
        return result;
    }
}

这里画一个图给友友们解析以下:

4.4.2、支持当前事务(REQUIRED_NEW)

支持当前事务,这里原来的代码不用改变什么,仅仅只需要改变添加学生和添加日志事务设置为支持当前事务(REQUIRED_NEW)

添加学生:(将事务传播机制设置为Propagation.REQUIRES_NEW)

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int add(Student student){
        //添加 到数据库
        int addStudent= studentMapper.add(student);
        System.out.println("添加学生:"+addStudent);
        //添加日志
        Log log=new Log();
        log.setMessage("添加学生信息");
        //添加日志
        logService.add(log);
        return addStudent;
    }

添加日志:(将事务传播机制设置为Propagation.REQUIRES_NEW)

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int add(Log log){
        //添加日志到数据库
        int result=logMapper.add(log);
        System.out.println("添加日志结果:"+result);

        //这里设置一个 报错 为了 展示事务的加入是如何加入的
        int num=10/0;
        return result;
    }

预期结果是不是:学生添加应该是存到数据库的,日志添加应该回滚,访问路由后:

结果是什么数据没有添加数据,不管是学生还是日志:所示事务传播机制已经设置每个事务都是一个新建事务,大家互不影响,但是事务同样会检测到服务器错误,所以都会进行回滚,这里就需要我们自己去抛异常自己解决(手动回滚就不会影响到其他的单独的事务)

因为这里是给日志写了一个异常,所以这里就将日志添加中写一个回滚

日志添加(修改):

@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int add(Log log){
        //添加日志到数据库
        int result=logMapper.add(log);
        System.out.println("添加日志结果:"+result);

        //手动添加回滚操作
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        
        return result;
    }
}

现在访问路由后看结果:

 注:结果就是学生添加事务成功,日志添加事务失败

4.4.3、NESTED嵌套事务

这里我们仅仅修改日志的事务传播机制,将机制设置为NESTED(嵌套)

其他的设置为REQUIRED即可

这里的嵌套:就是把多个事务连在一起,是一个整体,但是却有可以进行部分处理,下面进行演示

    @Transactional(propagation = Propagation.NESTED)
    public int add(Log log){
        //添加日志到数据库
        int result=logMapper.add(log);
        System.out.println("添加日志结果:"+result);

        //手动添加回滚操作
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

        return result;
    }

 访问路由结果:

4.4.4、(REQUIRED)加入和(NESTED)嵌套的区别

 (REQUIRED)加入:就是之多个事务加入到第一个外部事务中,让多个事务成为一个整体,如果其中一个事务出现问题,整体就会进行回滚

(NESTED)嵌套:虽然也是将多个事务放在一起成为了一个整体,但是可以进行部分事务的回滚,嵌套到外部事务,内部事务出现异常回滚后,不会影响外部事务的进行

注:嵌套事务之所以能够实现部分事务的回滚,是因为事务中有一个保存点,嵌套事务进入之后相当于新建了一个保存点,而回滚时值回滚到当前保存点,因此之前的事务是不受影响的。

4.4.5、总结

REQUIRED 给用户添加的同时 后面加入的事务也不能有问题,一旦有问题,所有操作全部 回滚:

内部事务 和 外部事务 是一体的

REQUIRES_NEW 给用户添加的同时 后面新的事务有问题,只会回滚新事务 同时也会报错:

内部事务 和 外部事务 相互影响

NESTED(嵌套) 给用户添加的同时 相比加入事务不会报错:

内部事务 和 外部事务 互不影响

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。