轻松学习 Spring 事务

这篇具有很好参考价值的文章主要介绍了轻松学习 Spring 事务。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一. Spring事务简介

在之前的博客已经介绍了在 Spring 环境中整合 mybatis 完成数据库的增删查改操作,在正常情况下,操作数据库是没有问题的,但是当一个业务需要并发式的操作数据库,并且需要涉及到修改,插入,删除操作就可能会有问题,如转账业务,其实是有两个步骤,第一步从 A 账户扣钱,第二步在 B 账户中加钱。

如果第一步顺利执行的,在执行完成第二步前,程序发生了异常, 此时第二个操作就不能够正常执行了,这就会是很严重的事故了,致使 A 的钱少了,但 B 的钱没有增多,无论是用户还是我们都是不能容忍这样的 bug 的。

为了解决这个问题,Spring 引入了事务管理的机制,事务的作用是保证执行一组数据库操作的时候,要么全部失败,要不全部成功,即同成功或同失败。

那也就是在程序发生异常的时候,回滚所有已经成功数据库操作,这样就算这一次转账失败了,也不会给客户和商家带来损失。

Spring 中事务的作用是保证在数据层或业务层执行的一系列数据库操作同成功或同失败。

二. Spring事务使用

Spring 中的事务分为两类:

  1. 编程式事务(手动操作)。
  2. 声明式事务(自动提交事务)。

1. 编程式事务

SpringBoot 内置了两个对象,DataSourceTransactionManager用来获取事务(开启事务、提交事务、回滚事务);ransactionDefinition是事务的属性,在获取事务时,需要将TransactionDefinition传递进去获取一个TransactionStatus

Controller,Service,Mapper 各层演示代码如下:

package com.example.demo.controller;

import com.example.demo.model.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    // JDBC 事务管理器
    @Autowired
    private DataSourceTransactionManager transactionManager;
    // 定义事务属性
    @Autowired
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/add")
    public int del(Integer id) {
        UserInfo userinfo = new UserInfo();
        userinfo.setUsername("zhaoliu");
        userinfo.setPassword("123");
        if(userinfo != null) {
            //开启事务
            TransactionStatus transactionStatus =
                    transactionManager.getTransaction(transactionDefinition);
            //删除用户业务操作
            int result = userService.add(userinfo);
            System.out.println("受影响行数: " + result);
            // 提交事务/回滚事务
           //  transactionManager.commit(transactionStatus); //提交事务
            transactionManager.rollback(transactionStatus); //回滚事务
        }
        return 0;
    }
}

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional
数据库 userinfo 表中现有数据如下:
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

启动程序,在浏览器地址栏中进行 url 的访问,结果如下:
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional
控制台显示的结果是插入成功的,此时再看数据库数据是否有变化。
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional
我们可以发现,数据库中数据是没有变化的,说明在插入数据后,事务成功的进行了回滚操作,但是这样的方式是比较繁琐的,下面介绍更简单的声明式事务。

2. 声明式事务

声明式事务我们只需要在方法上加上@Transactional注解就可以实现,此时无需我们手动进行开启事务和提交事务,进入方法就会自动开启事务,执行完毕自动提交,发生异常后会自动回滚事务。

package com.example.demo.controller;

import com.example.demo.model.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/user")
@RestController
public class UserController2 {
    @Autowired
    private UserService userService;

    @Transactional
    @RequestMapping("/add")
    public int add() {
        UserInfo userinfo = new UserInfo();
        userinfo.setUsername("zhaoliu");
        userinfo.setPassword("123");
        // 非空判断
        if (userinfo == null) {
            return 0;
        }
        // 调用 service 执行添加
        int result = userService.add(userinfo);
        System.out.println("受影响行数: " + result);
        // 将结果返回给前端
        return result;
    }
}

目前数据库数据:
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

访问结果:
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

此时数据库中就成功插入了一条数据,这种情况是没有异常的情况下,代码执行成功后自动进行了commit
🍂我们来在业务中加上一段异常代码:

@RestController
@RequestMapping("/user2")
public class UserController2 {

    @Autowired
    private UserService userService;

    @Transactional
    @RequestMapping("/del")
    public int del(Integer id) {
        if(id == null || id <= 0) {
            return 0;
        }
        int result = userService.del(id);
        int n = 1 / 0; //异常业务
        return result;
    }
}

重新访问进行插入操作,结果如下:
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional
可以看到的控制台中信息显示成功的进行了插入操作,但同时报了一个算术异常,再来看数据库;
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional
还是原来的那几条记录,此时的情况就是发生异常自动进行了回滚操作了。

三. @Transactional的使用

@Transactional 的作用范围:它既可以用来修饰方法也可以用来修饰类。

  1. 修饰方法(更推荐使用):只能应用到public方法上,否则不生效。
  2. 修饰类:表明该注解对类中所有的public方法都生效。

1. 参数作用

我们可以通过设置 @Transactional 的一些参数来决定事务的一些具体的功能。

参数 作用
value 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。
transactionManager 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。
propagation 事务的传播行为,默认值为 Propagation.REQUIRED
isolation 事务的隔离级别,默认值为 lsolation.DEFAULT
timeout 事务的超时时间,默认值为 -1,如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only为true。
rollbackFor 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
rollbackForClassName 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
noRollbackFor 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。
noRollbackForClassName 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

2. 事务失效的场景

要注意:@Transactional 在异常被捕获的情况下,不会进行事务自动回滚。
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional
我们将上面的代码手动创建的一个异常进行try catch处理。
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

再来进行访问,结果如下:
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

此时程序抛出了异常,但数据库数据插入后并没有进行回滚操作,出现这种情况的原因是事务AOP通知只有自己捕捉到了目标抛出的异常,才能进行后续的回滚操作,如果目标自己处理掉了异常,事务是无法知悉的,也就无法处理了。

🎯解决方案:

1️⃣方式一:在 catch 块中将异常继续抛出,此时代理对象就能感知到异常,也就能自动的回滚事务了。
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

再次访问,结果如下:

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional
此时数据库中数据是没有变化的,即在成功插入后进行了回滚操作。

2️⃣方式二: 手动回滚事务,在方法中使用TransactionAspectSupport.currentTransactionStatus()可以得到当前的事务,然后设置回滚方法setRollbackOnly就可以实现回滚了。

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

访问结果:

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

此时数据插入后也是成功进行回滚,而程序也不会报错。

3. @Transactional工作原理

@Transactional 是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。
@Transactional 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果中途遇到异常,会进行回滚业务。
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional
@Transactional 具体执行主要是以下逻辑:

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

  1. bean实例化:Spring 容器创建了一个目标对象(bean),这个目标对象通常是一个普通的 Java 类,其中包含了一些需要被事务管理的方法。
  2. 看方法适配是否有切面:Spring 框架会检查目标对象是否有被 @Transactional 注解标记的方法,如果有这样的方法,那么这些方法需要被事务管理。
  3. 如果目标对象中存在被 @Transactional 注解标记的方法,那么 Spring 将会创建一个代理对象。这个代理对象包含了与事务管理相关的逻辑;代理对象会实现与目标对象相同的接口(如果有),或者继承目标对象的类(如果没有接口),从而能够替代目标对象的工作,这意味着当其他部分的代码请求目标对象时,实际上会得到代理对象,而不是直接的目标对象。
  4. 如果目标对象中不存在被 @Transactional 注解标记的方法,代理对象会被返回给 Spring 的容器,从而成为容器中的实际 bean。
  5. 当调用代理对象的方法时,代理对象会在执行目标方法之前执行切面逻辑 before(通常是事务管理的逻辑),然后调用目标方法。
  6. 如果切面逻辑中的 method 调用成功执行(没有抛出异常),代理对象会继续执行方法,然后在方法执行后执行切面逻辑 after(通常是事务提交)。
  7. 如果在目标方法的执行过程中抛出了异常,代理对象会执行切面逻辑中的回滚操作,以确保事务的一致性。

四. Spring 事务的隔离级别

我们可以使用以下 sql 查询 MySQL 中全局事务隔离级别和当前连接的事务隔离级别:

select @@global.tx_isolation,@@tx_isolation;

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

Spring 的事务隔离级别是有以下 5 种的,
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

相比于 MySQL 中多了一种DEFAULT

  1. DEFAULT: 以连接的数据库的 库的全局事务隔离级别为主 。
  2. READ_UNCOMMITTED:读未提交,允许读取还未提交的数据,会出现脏读、不可重复读和幻读等问题。
  3. READ_COMMITTED:读已提交,只能读取已经提交的数据,避免了脏读的问题,但是可能出现不可重复读和幻读的问题。
  4. REPEATABLE_READ:可重复读,保证同一事务中多次读取同一记录结果是一致的,避免了脏读和不可重复读的问题,但是仍然可能出现幻读的问题。
  5. SERIALIZABLE:串行化,保证事务串行执行,避免了脏读、不可重复读和幻读等问题,但是影响系统性能。

默认情况下,Spring 会使用底层数据库的默认隔离级别,通常是 READ_COMMITTED 级别。可以通过事务管理器的 setDefaultTransactionIsolation() 方法或在@Transactional注解中使用isolation属性来设置隔离级别:

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

五. Spring事务传播机制

Spring 事务传播机制定义了多个包含事务的方法相互调用时的行为。
事务隔离级别是保证多个并发事务执行是可控的,而事务传播机制是保证一个事务在多个调用方法间是可控的。
事务隔离级别是为了解决多个事务同时调用一个数据库的问题:

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional
事务传播机制是解决一个事务在多个方法中传递的问题:

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

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

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

通过设置@Transactional注解中的propagation属性就可以完成Spring事务传播机制的修改。

🍂事务传播机制可以分为以下三类:
轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

上面的前 6 种事务机制是比较容易理解的,下面主要来演示一下NESTED这种嵌套事务的效果,为了便于观察,我们清一下数据库中的数据;

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

首先我们在UserService再声明一个insert方法,方法中再调用一次UserMapper中的add接口执行插入逻辑;在UserController2add中再加入一个调用insert的逻辑。

把调用逻辑写好后构造嵌套事务,将UserController2中的add的事务机制声明为REQUIREDUserService中的addinsert的事务机制都声明为NESTED,整体代码如下:

@RequestMapping("/user2")
@RestController
public class UserController2 {
    @Autowired
    private UserService userService;

    @Transactional(propagation = Propagation.REQUIRED)
    @RequestMapping("/add")
    public int add() {
        UserInfo userinfo = new UserInfo();
        userinfo.setUsername("zhaoliu");
        userinfo.setPassword("123");
        // 非空判断
        if (userinfo == null) {
            return 0;
        }
        int result = userService.add(userinfo);

        result +=userService.insert(userinfo);
        System.out.println("受影响行数: " + result);
        return result;
    }
}

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.NESTED)
    public int add(UserInfo userinfo) {
        int result = userMapper.add(userinfo);
        System.out.println("add result -> " + result);
        return result;
    }

    @Transactional(propagation = Propagation.NESTED)
    public int insert(UserInfo userInfo) {
        int result = userMapper.add(userInfo);
        System.out.println("insert result ->" + result);
        try {
            int num = 10 / 0;
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return result;
    }
}

访问结果:

此时执行到Controller中的add会开启一个事务,其中调用的addinsert开始的是嵌套事务,嵌套事务如果发生了回滚,只会影响它自己的局部逻辑,而不影响全局。

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

控制台显示的是成功插入了两条记录的,

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

查看数据库只是成功插入了一条记录,这是因为insert逻辑中发生了回滚,但它不会影响其他部分的逻辑。

轻松学习 Spring 事务,JavaEE,spring,事务,java,后端,sql,数据库,Transactional

要注意在嵌套事务中可能出现异常的部分要手动处理进行回滚,不能让异常抛出,否则会被全局代理感知到造成全局事务的整体回滚。

嵌套事务之所以能够实现部分事务的回滚,是因为事务中有⼀个保存点(savepoint)的概念,嵌套事务进入之后相当于新建了⼀个保存点,而滚回时只回滚到当前保存点,因此之前的事务是不受影响的,这一点可以在 MySQL 的官方文档汇总找到相应的资料:网页链接 。

🍂嵌套事务(NESTED)和加入事务(REQUIRED )的区别文章来源地址https://www.toymoban.com/news/detail-724624.html

  • 整个事务如果全部执行成功,二者的结果是⼀样的。
  • 如果事务执行到⼀半失败了,那么加入事务整个事务会全部回滚;而嵌套事务会局部回滚,不会影响上⼀个方法中执行的结果。

到了这里,关于轻松学习 Spring 事务的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Java学习】 Spring的基础理解 IOC、AOP以及事务

        官网: https://spring.io/projects/spring-framework#overview     官方下载工具: https://repo.spring.io/release/org/springframework/spring/     github下载: https://github.com/spring-projects/spring-framework     maven依赖: 1.spring全家桶的结构构图:              最下边的是测试单元   其中spring封装

    2024年02月09日
    浏览(43)
  • “从零开始学习Spring Boot:快速搭建Java后端开发环境“

    标题:从零开始学习Spring Boot:快速搭建Java后端开发环境 摘要:本文将介绍如何从零开始学习Spring Boot,并详细讲解如何快速搭建Java后端开发环境。通过本文的指导,您将能够快速搭建一个基于Spring Boot的Java后端开发环境并开始编写代码。 正文: 一、准备工作 在开始之前,

    2024年02月15日
    浏览(58)
  • 【JavaEE】JavaEE进阶:框架的学习 - Spring的初步认识

    JavaEE进阶首章 在之前的学习中,我们已经掌握了Servlet的框架去完成一个项目,而目前企业正在使用的技术则是我们接下来重点学习的! 本文章为 JavaEE进阶的第一篇文章,JavaEE进阶我们将学习: Spring全家桶 Spring / Spring Boot/ Spring MVC ,Spring Cloud为社招必须,校招不须 MyBatis

    2024年02月17日
    浏览(41)
  • Spring声明式事务(Spring学习笔记十三)

            不推荐使用编程式事务  在Spring-dao.xml中配置声明式事务  结合aop实现事务的织入 分两步         第一步:          第二步:

    2024年04月10日
    浏览(39)
  • SSM框架的学习与应用(Spring + Spring MVC + MyBatis)-Java EE企业级应用开发学习记录(第三天)动态SQL

    昨天我们深入学习了 Mybatis的核心对象SqlSessionFactoryBuilder , 掌握MyBatis核心配置文件以及元素的使用 ,也掌握MyBatis映射文件及其元素的使用。那么今天我们需要掌握的是更加复杂的查询操作。 学会编写MyBatis中动态SQL 学会MyBatis的条件查询操作 学会MyBatis的更新操作 学会MyBati

    2024年02月11日
    浏览(54)
  • 学习笔记-Spring事务

    学习的文章 小姐姐非要问我:spring编程式事务是啥? (qq.com) 一文搞懂什么是事务 - 知乎 (zhihu.com) 阿里3面:Spring声明式事务连环炮,让我措手不及。。 (qq.com) 带你读懂Spring 事务——事务的传播机制 - 知乎 (zhihu.com) spring 事务失效的 12 种场景_事务什么时候失效_hanjq_code的博客

    2024年02月06日
    浏览(39)
  • Spring使用@Transactional 管理事务,Java事务详解。

    B站视频:https://www.bilibili.com/video/BV1eV411u7cg 技术文档:https://d9bp4nr5ye.feishu.cn/wiki/HX50wdHFyiFoLrkfEAAcTBdinvh 简单来说事务就是一组对数据库的操作 要么都成功,要么都失败。 事务要保证可靠性,必须具备四个特性:ACID。 A:原子性:事务是一个原子操作单元,要么完全执行,要么

    2024年02月11日
    浏览(31)
  • SSM框架学习-Spring事务

    数据库事务和Spring中的事务是两个不同的概念,但Spring中的事务是建立在数据库事务之上的。 数据库事务是指一组数据库操作,要么全部成功执行,要么全部回滚(撤销)。这么做是为了保证数据库的一致性和完整性。在数据库操作过程中,如果其中任意一步操作失败,整个

    2024年02月05日
    浏览(54)
  • Java后端07(Spring)

    ​涉及的设计模式:单例模式,简单工厂模式,代理模式,观察者模式,反射,注解。。。。。 ​在传统模式下,对象的创建和赋值,都是由开发者自己手动完成,事实情况下,开发者只关心如何获取赋值好的对象,但是并不希望自己手动进行创建对象和赋值的事情(sprin

    2024年02月13日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包