一文学会Spring 实现事务,事务的隔离级别以及事务的传播机制

news2024/11/27 0:36:44

目录

一.Spring (Spring Boot) 实现事务

1.通过代码的方式手动实现事务 (手动档的车)

2.通过注解的方式实现声明式事务 (自动挡的车)

二.事务的4大特性(ACID)

三.事务的隔离级别

①Mysql的事务隔离级别:

②Spring的事务隔离级别:

四.事务的传播机制

①事务传播机制的概念

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

1. Propagation.REQUIRED

2. Propagation.SUPPORTS

3. Propagation.MANDATORY (mandatory:强制性)

4. Propagation.REQUIRES_NEW

5. Propagation.NOT_SUPPORTED

6. Propagation.NEVER

7. Propagation.NESTED

③以情侣关系为例理解这七种传播机制


一.Spring (Spring Boot) 实现事务

1.通过代码的方式手动实现事务 (手动档的车)

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private DataSourceTransactionManager transactionManager; //事务数据管理器
    @Autowired
    private TransactionDefinition transactionDefinition; //设置事务属性的对象

    @RequestMapping("/add")
    public int add(UserInfo userInfo) {
        // todo:非空效验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
        || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }
        // 1.开启事务
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition); //transactionStatus就是事务

        // 手动设置创建时间和修改时间的默认值
        userInfo.setCreatetime(LocalDateTime.now().toString());
        userInfo.setUpdatetime(LocalDateTime.now().toString());

        int result = userService.add(userInfo);
        System.out.println("添加: " + result);

        // 2.回滚事务
//        transactionManager.rollback(transactionStatus);

        transactionManager.commit(transactionStatus);
        return result;
    }
}
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public Integer add(UserInfo userInfo) {
        return userMapper.add(userInfo);
    }
}
@Mapper
public interface UserMapper {
    int add(UserInfo userInfo);
}

 UserMapper.xml中的添加操作

<mapper namespace="com.example.demo.mapper.UserMapper">
    <insert id="add">
        insert into userinfo(username,password,createtime,updatetime)
        values(#{username},#{password},#{createtime},#{updatetime})
    </insert>
</mapper>

运行前数据库中的数据:

运行程序提交事务:

 

此时的代码中是没有执行回滚事务的操作的,所以数据库当中能够看到新增的数据

如果开启回滚事务,关闭提交事务,则idea中能够看见操作的信息,但是数据库中找不到新增的数据

2.通过注解的方式实现声明式事务 (自动挡的车)

通过使用@Transactional注解来实现

@Transactional的特点:

Ⅰ.可以添加在类上或方法上

        该注解只在public修饰的方法上生效,在类上添加该注解则表明当前类的所有公共方法都会自动提交或回滚事务

Ⅱ.在方法执行前开启事务,在方法执行完(没有任何异常) 自动提交事务,但是如果方法在执行期间出现异常,那么将自动回滚事务

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private DataSourceTransactionManager transactionManager; //事务数据管理器
    @Autowired
    private TransactionDefinition transactionDefinition; //设置事务属性的对象

    @Transactional // 声明式事务(自动提交)
    @RequestMapping("/insert")
    public Integer insert(UserInfo userInfo) {
        // todo:非空效验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
        || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }

        int result = userService.add(userInfo);
        System.out.println("添加 insert: " + result);
        int num = 10 / 0; //算数异常,查看是否会回滚事务
        return result;
    }
}

 通过控制台可以看到前面的添加数据的操作执行成功了,后面的代码报异常了

数据库中的数据:

 可以发现数据库中没有新数据的添加,说明事务回滚了

将@Transactional注释掉再次运行程序,数据库就能够在发生异常的时候添加新数据了

 也可以手动进行回滚事务

 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

有一种情况程序发生异常也不会进行回滚,就是程序进行try-catch的时候

上述情况的解决方案:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private DataSourceTransactionManager transactionManager; //事务数据管理器
    @Autowired
    private TransactionDefinition transactionDefinition; //设置事务属性的对象

    @Transactional // 声明式事务(自动提交)
    @RequestMapping("/insert")
    public Integer insert(UserInfo userInfo) {
        // todo:非空效验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
        || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }

        int result = userService.add(userInfo);
        System.out.println("添加 insert: " + result);
        try {
            int num = 10 / 0;
        } catch (Exception e) {
            System.out.println(e.getMessage());
            // 1.将异常继续抛出
            throw e;
            // 2.使用代码手动回滚事务
//            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return result;
    }
}

 1.将异常抛出

 2.手动回滚事务

两者都不会在数据库中添加新数据

二.事务的4大特性(ACID)

  • 原子性(Atomicity) : 一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间的某个环节。事务在执⾏过程中发⽣错误,会被回滚(Rollback)到事务开始前的状态,就像这个 事务从来没有执⾏过⼀样。
  • 一致性(Consistency) : 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写⼊的资料必须完 全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以⾃发性地完成预定的⼯作。
  • 持久性(Durability) : 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
  • 隔离性(Isolation) : 数据库允许多个并发事务同时对其数据进⾏读写和修改的能⼒,隔离性可以防⽌多个事务 并发执⾏时由于交叉执⾏⽽导致数据的不⼀致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串⾏化 (Serializable)。

三.事务的隔离级别

①Mysql的事务隔离级别:

1. READ UNCOMMITTED:读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,⽽未提交的数据可能会发⽣回 滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读

2. READ COMMITTED:读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据, 因此它不会有脏读问题。但由于在事务的执⾏中可以读取到其他事务提交的结果,所以在不同时间 的相同 SQL 查询中,可能会得到不同的结果,这种现象叫做不可重复读

3. REPEATABLE READ:可重复读,是 MySQL 的默认事务隔离级别,它能确保同⼀事务多次查询 的结果⼀致。但也会有新的问题,⽐如此级别的事务正在执⾏时,另⼀个事务成功的插⼊了某条数据,但因为它每次查询的结果都是⼀样的,所以会导致查询不到这条数据,⾃⼰重复插⼊时⼜失败 (因为唯⼀约束的原因)。明明在事务中查询不到这条信息,但⾃⼰就是插⼊不进去,这就叫幻读 (Phantom Read)。

4. SERIALIZABLE:序列化,事务最⾼隔离级别,它会强制事务排序,使之不会发⽣冲突,从⽽解决 了脏读、不可重复读和幻读问题,但因为执⾏效率低,所以真正使⽤的场景并不多。

②Spring的事务隔离级别:

1. Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。

2. Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。

3. Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。

4. Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。

5. Isolation.SERIALIZABLE:串⾏化,可以解决所有并发问题,但性能太低。

四.事务的传播机制

①事务传播机制的概念

Spring 事务传播机制定义了多个包含事务的方法,相互调用时,事务是如何在这些方法之间进行传递的。

事务传播机制时保证一个事务在多个调用方法之间的可控性(稳定性)

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

1. Propagation.REQUIRED

默认的事务传播机制它表示如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务。

示例Ⅰ:方法testA和testB的事务级别为PROPAGATION_REQUIRED,在testA方法里面调用testB方法,testA方法有事务,则在执行testB方法时就加入该事务(当前存在事务,则加入这个事务),此时在执行testB方法的时候如果抛出异常了,由于testA和testB方法是处于同一个事务,所以两者都会发生回滚,数据库里面的数据仍然保持原本状态

示例Ⅱ:方法testA没有声明事务,testB声明了事务且事务级别为Propagation_REQUIRED,在testA方法里面调用testB方法,此时执行testB方法时会自动创建一个事务(如果当前没有事务,则自己创建一个事务),此时在执行testB方法的时候如果抛出异常了,testB方法里面的操作会进行回滚,而testA方法里面的操作不受影响

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private DataSourceTransactionManager transactionManager; //事务数据管理器
    @Autowired
    private TransactionDefinition transactionDefinition; //设置事务属性的对象

    @Autowired
    private LogService logService;

    @Transactional // 声明式事务(自动提交)
    @RequestMapping("/insert")
    public Integer insert(UserInfo userInfo) {
        // todo:非空效验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
        || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }
        // 添加用户
        int result = userService.add(userInfo);
        if(result > 0) {
            // 添加日志
            logService.add();
        }
        return result;
    }
}
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public Integer add(UserInfo userInfo) {
        int result = userMapper.add(userInfo);
        System.out.println("用户添加: " + result);
        return result;
    }
}
@Service
public class LogService {

    @Transactional(propagation = Propagation.REQUIRED)
    public int add() {
        int num = 10 / 0;// 算术异常
        return 1;
    }
}

 运行结果:

 

在添加用户完成后进行添加日志,在添加日志里面抛出一个算术异常,令程序回滚,验证添加日志的这个方法已经加入了当前事务中,会整体发生回滚,前面的添加用户操作不生效 

2. Propagation.SUPPORTS

如果当前存在事务,则加⼊该事务(与Propagation_REQUIRED相同);如果当前没有事务,则以⾮事务的⽅式继续运⾏。

示例Ⅰ:方法testA声明了事务,方法testB的事务级别为Propagation_SUPPORTS,在testA方法里面调用testB方法,则在执行testB方法时就加入到testA的事务

示例Ⅱ:方法testA没有声明事务,方法testB的事务级别为Propagation_SUPPORTS,在testA方法里面调用testB方法,则在执行testB方法时就以非事务的方式运行。此时如果testB抛出异常了,也不会发生回滚,所以抛出异常前面的操作能够执行成功

3. Propagation.MANDATORY (mandatory:强制性)

如果当前存在事务,则加⼊该事务;如果当 前没有事务,则抛出异常。

示例:方法testA没有声明事务,方法testB的事务级别为Propagation_MANDATORY,testA方法里面调用testB方法,在执行testB方法的时候就会直接抛出事务要求的异常,testB方法里面的操作就没有被执行。

如果testA方法有声明事务且事务级别为Propagation_REQUIRED,则testB方法就会使用testA方法开启的事务,如果在执行testB方法的时候遇见了异常就能够正常的回滚了

4. Propagation.REQUIRES_NEW

表示创建⼀个新的事务,如果当前存在事务,则把当前事务挂 起。也就是说不管外部⽅法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部⽅法会新开 启⾃⼰的事务,且开启的事务相互独⽴,互不⼲扰。

示例:方法testA的事务级别为Propagation_REQUIRED,方法testB的事务级别为Propagation_REQUIRES_NEW,testA方法中调用testB方法,在执行testB方法的时候会创建一个新的事务,如果testA方法在调用testB方法之后发生了异常,因为两者不是在同一个事务下,所以不会影响到testB方法中的操作,testB方法中刚刚执行的操作不会进行回滚

5. Propagation.NOT_SUPPORTED

以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。

示例:方法testA的事务级别为Propagation_REQUIRED,方法testB的事务级别为Propagation_NOT_SUPPORTED,testA方法中调用testB方法,因为testB方法是以非事务方式运行的,如果在执行testB方法的时候抛出了异常,testB抛异常前的操作不会回滚,抛异常后的操作不会执行,而在testA方法中执行testB前的操作会由于程序发生了异常而回滚

6. Propagation.NEVER

以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。

示例:方法testA的事务级别为Propagation_REQUIRED,方法testB的事务级别为Propagation_NEVER,testA方法中调用testB方法,因为testB方法的事务级别为Propagation_NEVER,所以已进入testB方法就会抛出事务异常,testA方法检测到了异常就会进行回滚

7. Propagation.NESTED

如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如 果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。

示例:方法testA的事务级别为Propagation_REQUIRED,方法testB的事务级别为Propagation_NESTED,testA方法中调用testB方法

Ⅰ.如果执行完testB方法之后,在testA方法中抛出了异常,则testA方法和testB方法中的操作都会进行回滚

Ⅱ.如果异常发生在testB方法里面,因为testA方法里面捕获了testB的异常,所以只有testB方法中的操作会进行回滚,而testA方法中的操作不会受到影响,如果将testB的传播类型改为Propagation_REQUIRED的话,那就testA方法和testB方法都会发生回滚

此处的testA方法就像是老板,testB方法就像是员工,老板公司倒闭了,员工自然就没工作了。而员工犯错了,老板做出应对措施,把员工给开除掉,此级别的事务差不多就是这个意思

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private DataSourceTransactionManager transactionManager; //事务数据管理器
    @Autowired
    private TransactionDefinition transactionDefinition; //设置事务属性的对象

    @Autowired
    private LogService logService;

    @Transactional // 声明式事务(自动提交)
    @RequestMapping("/insert")
    public Integer insert(UserInfo userInfo) {
        // todo:非空效验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
        || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }
        // 添加用户
        int result = userService.add(userInfo);
        if(result > 0) {
            // 日志
            try {
                logService.add();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }
}
@Service
public class LogService {
    @Transactional(propagation = Propagation.NESTED)
    public int add() {
        int num = 10 / 0;
        return 1;
    }
}
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.NESTED)
    public Integer add(UserInfo userInfo) {
        int result = userMapper.add(userInfo);
        System.out.println("用户添加: " + result);
        return result;
    }
}

运行结果:用户添加成功

③以情侣关系为例理解这七种传播机制

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1805412.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Git配置 安装及使用

团队开发的神 找工作必备 环境变量 配置好环境后 打开终端环境 winr cmd 我习惯在桌面打开&#xff0c;然后进入相应的文件夹 &#xff08;文件夹结构&#xff09; &#xff08;个人感觉能用cmd不用git&#xff0c;cmd更好用一些&#xff09; 进入对应的文件夹 填写自己对…

Valgo,类型安全,表达能⼒强的go验证器

valgo 是一个为 Go 语言设计的类型安全、表达性强且可扩展的验证库。该库的特点包括&#xff1a; github.com/cohesivestack/valgo 类型安全&#xff1a;利用 Go 语言的泛型特性&#xff08;从 Go 1.18 版本开始支持&#xff09;&#xff0c;确保验证逻辑的类型安全。表达性&a…

Docker高级篇之Docker微服务实战

文章目录 1. 构建一个简单的微服务项目2. 编写Dockerfile发布微服务部署到docker容器 1. 构建一个简单的微服务项目 创建一个SpringBoot项目 创建一个Controller RestController public class OrderController {Value("${server.port")private String port;Reques…

深入分析 Android BroadcastReceiver (二)

文章目录 深入分析 Android BroadcastReceiver (二)1. 深入理解 BroadcastReceiver 的高级使用和优化2. 有序广播&#xff08;Ordered Broadcasts&#xff09;2.1 实现有序广播 3. 粘性广播&#xff08;Sticky Broadcasts&#xff09;3.1 使用粘性广播 4. 本地广播&#xff08;…

阿里云 MQTT 服务器搭建与测试(上传和下发数据finish)

一、 MQTT 概念 MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于 TCP/IP协议上,由 IBM 在 1999 年发布。MQTT 最大优点在于,可以以极少的代码和有限的带宽,…

CNCF项目全景图介绍

本文首发在个人博客上&#xff0c;欢迎来踩&#xff01; 云原生计算基金会&#xff08;CNCF&#xff09;介绍 CNCF(Cloud Native Computing Foundation)官网链接&#xff1a;https://www.cncf.io/ 官方的介绍如下&#xff1a; 云原生技术有利于各组织在公有云、私有云和混合…

Mysql(一):深入理解Mysql索引底层数据结构与算法

众所众知&#xff0c;MySql的查询效率以及查询方式&#xff0c;基本上和索引息息相关&#xff0c;所以&#xff0c;我们一定要对MySql的索引有一个具体到数据底层上的认知。 这一次也是借着整理的机会&#xff0c;和大家一起重新复习一下MySql的索引底层。 本节也主要有一下的…

【马琴绿绮】马维衡古琴之马氏汉风 明代杉木制;周身髹朱红色漆

【马琴绿绮式】马维衡古琴之马氏汉风 明代杉木制&#xff1b;琴体周身髹朱红色漆&#xff0c;鹿角霜灰胎&#xff1b;形体壮硕、风格高古&#xff1b;音色松透、浑厚&#xff0c;音质纯净&#xff0c;按弹舒适&#xff0c;手感丝滑。

【vue实战项目】通用管理系统:图表功能

目录 前言 1.概述 2.数据概览页 2.1.柱状图 2.2.折线图 2.3.地图 前言 本文是博主前端Vue实战系列中的一篇文章&#xff0c;本系列将会带大家一起从0开始一步步完整的做完一个小项目&#xff0c;让你找到Vue实战的技巧和感觉。 专栏地址&#xff1a; https://blog.csd…

Vue3【十三】watch监视

Vue3【十三】watch监视 Vue3 中的watch祝你能监视以下四种数据 ref 定义的数据reactive定义的数据函数返回一个值一个包含上述内容的数组 案例截图 目录结构 案例代码 Person.vue <template><div class"person"><!-- <h1>Watch情况1&#xff…

遗址博物馆ar互动展示软件提供丰富的趣味化体验

在自然博物馆的每一个角落&#xff0c;都隐藏着大自然的奥秘与魅力。为了让每一位参观者都能深入体验、探索这些奥秘&#xff0c;我们引入了前沿的AR技术&#xff0c;为您带来一场前所未有的沉浸式自然之旅。 步入博物馆&#xff0c;您手中的AR相机将成为您的更佳向导。自然博物…

如何合并pdf文件?告别软件,教你用python轻松解决

首先安装相关库文件&#xff1a; pip install PyPDF2, tkinter 接着&#xff0c;定义合并pdf函数&#xff0c;使用了PdfMerger构建对象&#xff0c;输入pdf文件路径列表&#xff0c;以及输出文件夹路径 from PyPDF2 import PdfMergerdef merge_pdf(file_paths, output_dir…

【leetcode--30.串联所有单词的子串】

有没有一样喜欢看示例的&#xff0c;&#xff0c;看题目就觉得很难懂。大致就是words要进行排列组合&#xff0c;返回s中所有包含这个排列组合的首标。 顺完逻辑蛮好懂的&#xff0c;应该不算困难题&#xff0c;只是不知道用什么模块实现。 class Solution:def findSubstring…

python数据分析-连云港石化基地2023年用电量分析

接下来对连云港石化基地2023年用电量进行分析&#xff0c;首先导入数据分析基本的包&#xff1a; import pandas as pd import matplotlib.pyplot as plt# Load the data from the provided Excel files file_path1 data1.xlsx file_path2 data2.xlsxdata1 pd.read_excel(f…

网络学了点socket,写个聊天室,还得改进

目录 第一版: common 服务端: 客户端 第一版问题总结: 第二版 服务端: 客户端: 改进: Windows客户端 一些小问题 还可以进行的改进 这篇文章我就先不讲网络基础的东西了,我讲讲在我进行制作我这个拉跨聊天室中遇到的问题,并写了三版代码. 第一版: common #pragm…

使用 Keras 的 Stable Diffusion 实现高性能文生图

前言 在本文中&#xff0c;我们将使用基于 KerasCV 实现的 [Stable Diffusion] 模型进行图像生成&#xff0c;这是由 stable.ai 开发的文本生成图像的多模态模型。 Stable Diffusion 是一种功能强大的开源的文本到图像生成模型。虽然市场上存在多种开源实现可以让用户根据文本…

Linux磁盘分区使用情况查询

一、磁盘分区使用情况查询 1. 查询磁盘整体使用情况使用 df -h进行查询 如图我们可以了解到磁盘的一些大致的使用情况&#xff0c;注意当已用部分有超过80%使用的分区就意味着你需要进行磁盘的清理了。 2.查询指定的磁盘使用情况 使用指令 du -h 当不指定目录时&#xff0c;默…

Vivado时序报告之Datasheet详解

目录 一、前言 二、Datasheet配置选项说明 2.1 Options 2.2 Groups 2.3 Timer Settings 2.4 Common Options 三、Datasheet报告 3.1 General Information 3.2 Input Ports Setup/Hold 3.3 Output Ports Clock-to-out 3.4 Setup between Clocks 3.5 Combinational…

物联网实战--平台篇之(十四)物模型(用户端)

目录 一、底层数据解析 二、物模型后端 三、物模型前端 四、数据下行 本项目的交流QQ群:701889554 物联网实战--入门篇https://blog.csdn.net/ypp240124016/category_12609773.html 物联网实战--驱动篇https://blog.csdn.net/ypp240124016/category_12631333.html 物联网…

基于Python的AI动物识别技术研究

基于Python的AI动物识别技术研究 开发语言:Python 数据库&#xff1a;MySQL所用到的知识&#xff1a;Django框架工具&#xff1a;pycharm、Navicat、Maven 系统功能实现 系统的登录模块设计 本次设计的AI动物识别系统为了保证用户的数据安全&#xff0c;设计了登录的模块&…