• Spring -- 事务


    Spring中事务的操作分为两类:
    (1)编程式事务 – 手动写代码操作事务
    (2)声明式事务 – 利用注解开启事务和提交事务

    1. 编程式事务

    准备Controller

    
    @RestController
    @RequestMapping("/user")
    public class UserInfoController {
        @Autowired
        private UserInfoService userInfoService;
        @RequestMapping("addUser")
        public String addUser(UserInfo userInfo) {
            userInfoService.insert(userInfo);
            return "SUCCESS";
        }
    }
    

    Spring Boot内置了两个对象
    DataSourceTransactionManager事务管理器,用来开始事务,提交或回滚事务
    TransactionDefinition是事务的属性,在开启事务的时候需要将TransactionDefinition传递进去从而获得一个事务TransactionStatus

    先来看没有事务的执行结果:
    访问后:
    image.png

    如果设置了事务:

    @RestController
    @RequestMapping("/user")
    @Slf4j
    public class UserInfoController {
        //事务管理器
        @Autowired
        private DataSourceTransactionManager transactionManager;
    
        //定义事务属性
        @Autowired
        private TransactionDefinition transactionDefinition;
    
    
        @Autowired
        private UserInfoService userInfoService;
    
        @RequestMapping("/addUser")
        public String addUser(UserInfo userInfo) {
            log.info("addUser:{}",userInfo);
            //开启事务
            TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
    
            userInfoService.insert(userInfo);
    
            //事务提交
            transactionManager.commit(transactionStatus);
            return "SUCCESS";
        }
    }
    

    执行结果:
    image.png
    观察数据库,发现两条数据都有插入:
    image.png

    如果我们设置了回滚

    @RequestMapping("/addUser")
    public String addUser(UserInfo userInfo) {
    log.info("addUser:{}",userInfo);
    //开启事务
    TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
    
    userInfoService.insert(userInfo);
    
    //事务提交
    transactionManager.rollback(transactionStatus);
    return "SUCCESS";
    }
    

    image.png
    对比发现,如果是成功提交,输出的日志是
    image.png
    如果回滚了,输出的日志是没有这一条信息的,并且数据库也不会更新

    但是如果此时我们再次提交事务:
    image.png
    查看数据库的数据:
    image.png
    中间空着的id为17的数据就是我们刚刚回滚的操作

    2. Spring声明式事务@Transactional

    声明式事务的实现需要两步:

    2.1 添加依赖

    <dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-txartifactId>
    dependency> 
    

    2.2 添加@Transactional注解

    在需要添加事务的地方加上@Transactional注解即可,无需手动开启事务和提交事务
    如果中途发生了没有处理的异常会回滚事务

    正常情况:

    @RestController
    @RequestMapping("/user1")
    @Slf4j
    public class UserInfoController1 {
        @Autowired
        private UserInfoService userInfoService;
    
        @Transactional
        @RequestMapping("/addUser1")
        public String addUser(UserInfo userInfo) {
            log.info("addUser:{}",userInfo);
            userInfoService.insert(userInfo);
            return "SUCCESS";
        }
    }
    

    image.png

    异常情况:

    @Transactional
    @RequestMapping("/addUser1")
    public String addUser(UserInfo userInfo) {
    log.info("addUser:{}",userInfo);
    
    userInfoService.insert(userInfo);
    int a = 10/0;//出现未处理的异常
    return "SUCCESS";
    }
    

    image.png
    此时会自动回滚,没有提交
    但是如果异常在方法内被处理了,就会提交事务

    @Transactional
    @RequestMapping("/addUser1")
    public String addUser(UserInfo userInfo) {
    log.info("addUser:{}",userInfo);
    
    userInfoService.insert(userInfo);
    try {
        int a = 10/0;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return "SUCCESS";
    }
    

    image.png
    我们可以在catch里面手动进行回滚事务:

    @Transactional
    @RequestMapping("/addUser1")
    public String addUser(UserInfo userInfo) {
    log.info("addUser:{}",userInfo);
    
    userInfoService.insert(userInfo);
    try {
        int a = 10/0;
    } catch (Exception e) {
        e.printStackTrace();
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    return "SUCCESS";
    }
    

    再次访问:不会提交
    image.png

    注意:@**Transactional**** **注解可以用来修饰方法或类:

    • 修饰方法时,只有修饰public方法时才生效(修饰其他方法时不会报错,但是不会生效)
    • 修饰类时,类中所有的public方法都会生效
    @Transactional
    @RequestMapping("/addUser1")
    public String addUser(UserInfo userInfo) {
    log.info("addUser:{}",userInfo);
    
    userInfoService.insert(userInfo);
    try {
        int a = 10/0;
    } catch (Exception e) {
        e.printStackTrace();
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    return "SUCCESS";
    }
    

    2.3 @Transactional详解

    @Transactional有三个重要的属性:

    2.3.1 rollbackFor

    @Transactional默认只是在遇到运行时异常以及Error时才会回滚,非运行时异常不会回滚

    @Transactional
    @RequestMapping("/addUser1")
    public String addUser(UserInfo userInfo) throws IOException {
        log.info("addUser:{}",userInfo);
    
        userInfoService.insert(userInfo);
        if (true) {
            throw new IOException();
        }
        return "SUCCESS";
    }
    

    访问:
    image.png
    事务是会提交的
    如果我们需要对所有的异常都回滚,需要配置@Transactional注解当中的rollbackFor属性指定出现何种异常时事务进行回滚

    @Transactional(rollbackFor = Exception.class)
    @RequestMapping("/addUser1")
    public String addUser(UserInfo userInfo) throws IOException {
        log.info("addUser:{}",userInfo);
    
        userInfoService.insert(userInfo);
        if (true) {
            throw new IOException();
        }
        return "SUCCESS";
    }
    

    再次访问:事务不会提交
    image.png

    2.3.2 事务隔离级别

    MySQL事务隔离级别

    SQL定义了四种隔离级别:

    1. 读未提交:该隔离级别的事务可以看到其他事务中未提交的数据

    其他事务未提交的数据可能会发生回滚,但是该隔离级别的事务可以读到,我们吧该级别读到的数据称为脏数据,这个问题称为脏读

    1. 读已提交:该隔离级别的事务能读取到已经提交事务的数据

    该隔离级别不会有脏读的问题,但是由于在事务的执行过程中可以读取到其他事务提交的结果,所以在不同时间相同的sql查询可能会读到不同的结果,这种现象叫做不可重复读

    1. 可重复读:事务不会读到其他事务对已有数据的修改,即使其他事务已经提交,也可以确保

    在同一事务多次查询的结果是一致的,但是其他事务新插入的数据,是可以感知到的,这也就引发了幻读问题.可重复读是MySQL默认的事务隔离级别

    1. 串行化:序列化,事务最高的隔离级别,他会强制事务排序,使之不会产生冲突,从而解决

    脏读,不可重复读和幻读问题,但是执行效率低,使用场景不多

    Spring事务隔离级别

    Spring中事务隔离级别有5种

    1. Isolatio.DEFAULT以连接的数据库的事务隔离级别为主
    2. Isolation.READ_UNCOMMITTED:读未提交,对应SQL标准中的读未提交
    3. Isolation.READ_COMMITTED: 读已提交,对应SQL标准中的读已提交
    4. Isolation.REPEATABLE_READ:可重复读.对应SQL标准中的可重复读
    5. Isolation.SERIALIZABLE:串行化,对应SQL标准中的串行化

    image.png
    Spring中事务隔离级别可以通过@Transactional中的Isolation属性进行设置

    @Transactional(rollbackFor = Exception.class,isolation = Isolation.READ_UNCOMMITTED)
    @RequestMapping("/addUser1")
    public String addUser(UserInfo userInfo) throws IOException {
        log.info("addUser:{}",userInfo);
    
        userInfoService.insert(userInfo);
    
        return "SUCCESS";
    }
    

    2.3.3 事务传播机制

    事务传播机制就是,多个事务之间存在调用关系时,事务是如何在这些方法间进行传播的
    比如A方法调用B方法,A执行时,会开启一个事务,B本身也存在一个事务,此时B的事务是加入A的事务,还是创建一个新的事务
    这就涉及到了事务传播机制
    @Transactional注解支持事务传播机制的设置,通过propagation属性来指定传播行为

    1. Propagation.REQUIRED:默认的事务传播级别.表示如果当前存在事务,则加入该事务.如果当前没有事务,则创建一个事务
    2. Propagation.SUPPORTS:如果当前存在事务,则加入事务;如果当前没有存在事务,则以非事务的方式继续进行
    3. Propagation.MANDATORY:强制性,如果当前存在事务,则加入事务,如果当前没有事务,则抛出异常
    4. Propagation.REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起,也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法都会新开自己的事务,且开启的事务相互独立,互不干扰
    5. Propagation.NOT_SUPPORTED以非事务的方式运行,如果当前存在事务,则把当前的事务挂起
    6. Propagation.NEVER以非事务的方式运行,如果当前存在事务,则抛出异常
    7. Propagation.NESTED如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果没有事务,则该取值等价于Propagation.REQUIRED

    我们通过代码来演示一下区别:
    (1)REQUIRED
    image.png
    此时由于是REQUIRED,那么三个方法针对的是同一个事务,只要其中有一个出现异常,都会回滚
    image.png
    image.png
    (2)REQUIRES_NEW
    image.png
    在这种条件下,事务之间是独立的,因此互不影响
    image.png
    此时user的事务提交了,但是log的事务回滚了

    (3)NEVER
    image.png
    如果存在事务,那就抛出异常
    image.png

    (4) NESTED
    我们先来看看REQUIRED如果方法都执行成功的情况下:
    image.png
    如果是NESTED,方法都执行成功的情况下:
    image.png
    image.png
    可以发现是等价的,同样是只是开启了一次事务

    如果发生了异常
    对于REQUIRED方法:

    如果是NESTED
    image.png
    事务都是全部回滚
    看似两种好像都是一样的,实际上存在差别
    REQUIRED是加入,而NESTED是嵌套
    我们通过以下代码就能很好的看出两种的区别:
    image.png
    对于NESTEDE:
    image.png
    image.png
    但是对于REQUIRED:
    image.png
    数据库两个表都没有新增数据
    可以注意到,在REQUIRED里面,由于是同一个事务,因此不能部分回滚

  • 相关阅读:
    智慧班级管理系统 java
    Nginx部署vue项目,刷新后找不到界面
    Pytorch知识点学习
    Opencv项目实战:11 使用Opencv高亮显示文本检测
    STM32的SPI口的DMA读写[原创www.cnblogs.com/helesheng]
    Centos搭建socks5代理服务器
    Ubuntu基础操作
    【Vue3】自定义 Vue3 插件(全局实现页面加载动画)
    Java 自定义Excel数据排序
    centos上部署Ollama平台,实现语言大模型本地部署
  • 原文地址:https://blog.csdn.net/m0_60963435/article/details/141060404