Spring事务(声明式事务)(Spring的事务,Spring隔离级别,事务传播机制)

news2024/12/29 10:12:12

目录

一、什么是事务,为什么要用事务

二、Spring声明式事务

🍅 1、@Transactional的使用

🎈 事务回滚

🎈注意:异常被捕获,不会发生事务回滚

🍅 2、@Transactional 作⽤范围

🍅 3、@Transactional 参数说明 

🍅 4、@Transactional的工作原理

 三、事务的特性以及隔离级别(重要)

🍅 事务的特性(ACID)

🍅 Spring隔离级别

四、Spring事务的传播机制

🍅为什么需要事务传播机制

🍅 事务传播机制的种类

        🎈Propagation.REQUIRED:

        🎈 Propagation.SUPPORTS

         🎈 Propagation.NEVER

        🎈Propagation.NESTED


一、什么是事务,为什么要用事务

定义:事务就是将一组操作封装成一个执行单元(封装到一起),要么全部成功,要么全部失败

为什么要用事务?举例如下:

        转账分为两步:

                第一步:A账户:-100¥

                第二部:B账户:+100¥

        如果没有事务,那么,第一步执行成功,第二部失败,就会导致100¥不见了,不知道转哪里去了。但是当有了事务,着两步操作要么一起成功要么一起失败,这样就可以解决这个问题

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

  1. 编程式事务(手动写代码操作事务,不常用,复杂)
  2. 声明式事务(利用注解自动开启和提交事务,常用,简便)

下面主要介绍声明式事务

二、Spring声明式事务

🍅 1、@Transactional的使用

🎈 事务回滚

 声明式事务的使用很简单,只需要在方法上添加@Transactional注解就可以实现了

不需要手动开启事务和提交事务,进入方法时自动开启事务,方法执行完就会自动提交事务,如果中途发生了没有处理的异常就会自动回滚事务。

代码示例:

@Data
public class UserInfo {
    private int id;
    private String username;
    private String password;
    private String photo;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private int state;
}
@Mapper
public interface UserMapper {
    @Insert("insert into userinfo(username,password) values (#{username},#{password})")
    int add(UserInfo userInfo);
}
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public int add(UserInfo userInfo){
        return userMapper.add(userInfo);
    }
}
@RequestMapping("/user")
@RestController
public class UserController {

    @Autowired
    private UserService userService;
    @RequestMapping("/add")
    @Transactional
    public int add(){
//        1.非空判断
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("huhu");
        userInfo.setPassword("123");
//        2.调用service执行添加
        int result = userService.add(userInfo);
        System.out.println("result:" + result);
        int num = 10/0;
//        3.将结果给前端
        return result;
    }
}

执行以上代码:结果如下,发生算数异常

 但是sql执行没有异常:

这时候我们查看以下数据库有没有该数据,有没有进行数据回滚?

图中并没有我添加的数据,由此可见,@Transactional注解进行了数据回滚。

🎈注意:异常被捕获,不会发生事务回滚

问题:当自己把程序捕获以后,代表着事务不会发现程序发生了异常,在这种情况下,事务不会发生回滚。

  @RequestMapping("/add")
    @Transactional
    public int add(){
//        1.非空判断
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("huhu");
        userInfo.setPassword("123");
//        2.调用service执行添加
        int result = userService.add(userInfo);
        System.out.println("result:" + result);
        try{
            int num = 10/0;
        }catch (Exception e){

        }
//        3.将结果给前端
        return result;
    }

解决方案1: 将异常继续抛出去(代理对象就能感知到异常,也能够回滚到事务)

@RequestMapping("/add")
    @Transactional
    public int add(){
//        1.非空判断
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("万叶");
        userInfo.setPassword("123");
//        2.调用service执行添加
        int result = userService.add(userInfo);
        System.out.println("result:" + result);
        try{
            int num = 10/0;
        }catch (Exception e){
            throw e;
        }
//        3.将结果给前端
        return result;
    }

新增的信息不在里面:万叶的信息

 解决方案2:使用代码手动回滚事务

@RequestMapping("/add")
    @Transactional
    public int add(){
//        1.非空判断
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("万叶");
        userInfo.setPassword("123");
//        2.调用service执行添加
        int result = userService.add(userInfo);
        System.out.println("result:" + result);
        try{
            int num = 10/0;
        }catch (Exception e){
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
//        3.将结果给前端
        return result;
    }


🍅 2、@Transactional 作⽤范围

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

  • 修饰方法时,只能应用到public方法上面,否则不生效
  • 修饰类时,表明该注解对该类的所有public方法都生效

🍅 3、@Transactional 参数说明 

一般情况下,以下参数都是默认的


🍅 4、@Transactional的工作原理

@Transactional是基于AOP实现的,AOP又是基于动态代理实现的。

如果目标对象实现了接口,默认情况下就会采用JDK的动态代理,如果目标对象没有实现接口,会使用CGLIB的动态代理。

@Transactional在开始执行业务之前,通过代理先开始事务,在执行成功之后再提交事务。如果中途遇见异常,则回滚事务。

@Transactional实现思路预览:

@Transactional具体执行细节如下图所示:

 


 三、事务的特性以及隔离级别(重要)

🎄 脏读:一个事务读取到了另一个事务修改的数据以后,后一个事务又进行了回滚操作,从而导致第一个事务读取到的数据是错误的。

🎄 不可重复读:一个事务两次查询得到的结果不同,因为在两次查询中间,有另一个事务把数据修改了

🎄 幻读:一个事务两次查询中得到的结果集不同,因为在两次查询中另一个事务又新增了一部分数据。

🍅 事务的特性(ACID)

🎄 原子性(不可分割性)(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束中间某个环节。事务在执行过程中发生错误,就会回滚到事务开始前的状态,相当这个事务从来没有执行过

🎄 一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,包含资料的精确度、串联性以及后续数据库可以自发性的完成预定的工作。

🎄 持久性(Isolation):事务处理结束以后,对数据的修改就是永久的,即便系统故障也不会丢失

🎄 隔离性(Durability):数据库允许多个并发事务同时对齐数据进行读写和修改的能力,隔离性可以放置多个事务并发执行时由于交叉执行导致数据的不一致。事务的隔离分为不同级别,包括读未提交,读提交,可重复读,串行化。

🍅 Spring隔离级别

Spring事务隔离级别有五种:

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

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

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

🎄 Isolation.REPEATABLE_READ:可重复读,解决了不可重复度,但是存在幻读。

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

默认情况下,是以SQL的事务隔离级别为主的(Isolation.DEFAULT)。

但是当Sping设置了事务隔离级别以后,就会以Spring的事务隔离级别为主。以下就是以SERIALIZABLE为主的。

 @RequestMapping("/add")
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public int add(){
}

事务的隔离级别保证了多个并发事务执行的可控性。


四、Spring事务的传播机制

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

🍅为什么需要事务传播机制

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

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


🍅 事务传播机制的种类

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

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

🎄 Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常

🎄 Propagation.REQUIRES_NEW:表示创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立互不干扰。

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

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

🎄 Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于Propagation.REQUIRED

         


        🎈Propagation.REQUIRED:

默认的事务传播级别,表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务

 示例:

UserService:

@Transactional
    public int add(UserInfo userInfo){
        int result = userMapper.add(userInfo);
        System.out.println("add result -> "+result);
        insert(userInfo);
        return result;
    }
    
    @Transactional
    public int insert(UserInfo userInfo){
        int result = userMapper.add(userInfo);
        System.out.println("insert resullt -> "+ result);
        int num = 10 / 0;
        return result;
    }

Controller:

@RequestMapping("/add")
    @Transactional(propagation = Propagation.REQUIRED)
    public int add(){
//        1.非空判断
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("万叶");
        userInfo.setPassword("123");
//        2.调用service执行添加
        int result = userService.add(userInfo);
        return result;
    }

此时结果是,报错是算数异常,进行了事务回滚,数据库中没有添加任何数据


        🎈 Propagation.SUPPORTS

如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行

示例:

UserService

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

    @Transactional(propagation = Propagation.SUPPORTS)
    public int insert(UserInfo userInfo){
        int result = userMapper.add(userInfo);
        System.out.println("insert resullt -> "+ result);
        int num = 10 / 0;
        return result;
    }

 UserController:

@Transactional(propagation = Propagation.SUPPORTS)
    public int add(){
//        1.非空判断
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("万叶");
        userInfo.setPassword("123");
//        2.调用service执行添加
        int result = userService.add(userInfo);
        return result;
    }

当前调用链不存在事务,结果是数据库中添加了两条数据,并且报错是算数异常,但是并没有进行数据回滚


         🎈 Propagation.NEVER

以非事务方式运行,如果当前存在事务,则抛出异常

 UserController:

 @RequestMapping("/add")
//调用链存在事务
    @Transactional(propagation = Propagation.REQUIRED)
    public int add(){
//        1.非空判断
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("万叶");
        userInfo.setPassword("123");
//        2.调用service执行添加
        int result = userService.add(userInfo);
        return result;
    }

 USerService:

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

    @Transactional(propagation = Propagation.NEVER)
    public int insert(UserInfo userInfo){
        int result = userMapper.add(userInfo);
        System.out.println("insert resullt -> "+ result);
        int num = 10 / 0;
        return result;
    }

 说明程序在发现有事务以后,就没有运行了,不存在事务回滚,而是发现事务以后,程序就没有运行,而是直接抛出异常。


        🎈Propagation.NESTED

如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于Propagation.REQUIRED

 示例:

UserService:

注意:为什么要try-catch

如果不try-catch就会报错,报错就会使得整个程序进行回滚,这里为了使得满足NESTED的情况,就进行了try-catch,就能使得回滚到事务保存点。

@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 resullt -> "+ result);

        try{
            int num = 10 / 0;
        }catch (Exception e){
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return result;
    }

UserController:

@RequestMapping("/add")
    @Transactional(propagation = Propagation.REQUIRED)
    public int add(){
//        1.非空判断
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("影");
        userInfo.setPassword("123");
//        2.调用service执行添加
        int result = userService.add(userInfo);
        userService.insert(userInfo);
        return result;
    }

结果如下:

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

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

相关文章

硬盘的分类

目前常见的硬盘种类主要有以下2种: 机械硬盘(HDD) 机械硬盘(HDD)是一种利用旋转磁盘和读写头来存储和访问数据的存储设备。它由磁盘、读写头、电机和控制电路等组成,磁盘通常是一种铝合金或玻璃材质的圆盘&…

《HeadFirst设计模式(第二版)》第二章代码——观察者模式

代码文件目录结构: DisplayElement package Chapter2_ObserverPattern.interfaces;/*** Author 竹心* Date 2023/8/2**/public interface DisplayElement {public void display(); }Observer package Chapter2_ObserverPattern.interfaces;/*** Author 竹心* Date…

【win11+vs 2017+OpenCV4.5.5+Qt5.12配置】详细安装过程+小问题解决

0.版本选择 由于Qt5无法与最新的vs2022兼容,扩展工具中一直显示不可用,所以将vs降级成vs2017。 在安装Qt的过程中,会选择安装Qt套件,其中就的MCVS 2017,说明vs2017是与qt兼容的。 当然也可以用qt creator这一原生IDE。…

PROFINet转Modbus协议转换网关Profinet数据通讯模块

产品概述 你是否曾经遇到过不同网络协议之间的沟通问题?捷米特JM-RTU-PN为你解决这个难题! 捷米特JM-RTU-PN是一款数据通讯模块,能够实现PROFINet网络与Modbus网络之间的数据传输。它可以将RS485网络连接到PROFINet网络,并支持不…

《剑指offer》刷题(1)链表篇

class ListNode: def __init__(self, x): self.val x self.next None class Solution: def printListFromTailToHead(self , listNode: ListNode) -> List[int]: #用栈记录遍历的结果,然后返回出栈结果 if listNode is None: return [] stack [] p listNode…

8.2Thread类的常见属性

1. 2.前台线程和后台线程 前台线程:影响进程结束(如果前台线程没有执行完,进程不结束). 后台线程(守护线程):不影响线程结束. 创建线程默认是前台线程. 修改成后台线程:thread.setDaetrue);

33.利用abs 解决绝对值问题(matlab程序 )

1.简述 abs函数的功能是绝对值和复数的模 语法 Y abs(X) 说明 Y abs(X) 返回数组 X 中每个元素的绝对值。如果 X 是复数,则 abs(X) 返回复数的模。 示例 标量的绝对值 y abs(-5) y 5 向量的绝对值 创建实值的数值向量。 x [1.3 -3.56 8.23 -5 -0.01…

ARP断网攻击及防御

ARP断网攻击及防御 攻击防御 攻击 PC1的IP地址 10.9.136.222 PC2的IP地址 10.9.136.55在局域网里通信 需要有IP地址和MAC地址 两台电脑PC1和PC2要想相互通信,PC1在连接PC2的时候,PC1会先查看自己的ARP缓存表(命令:arp -a &#xf…

利用鸿鹄可观测性监控Istio Ingress网关

一、需求描述 在上一篇《利用Vector和鸿鹄搭建微服务应用的可观测性平台》中,阐述了微服务的基本概念、优点及如何利用鸿鹄来处理分布式应用的日志。本文将进一步讨论微服务架构面临的问题、服务网格及鸿鹄处理Istio Gateway的独特优势。 1.1 微服务架构面临的挑战 …

学习委员之作业管理系统—前端部分

拯救学习委员之作业管理系统—前端部分 项目背景 学习委员收集作业的过程,繁琐且曲折,作者充分理解并体谅为大家服务的苦逼学习委员,以此为出发点和灵感,设计并开发了此套作业管理系统,希望能帮助各位提高效率&#…

语义检索系统【三】:基于Milvus 搭建召回系统抽取向量进行检索,加速索引

搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术细节以及项目实战(含码源) 专栏详细介绍:搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术…

[openCV]基于拟合中线的智能车巡线方案V4

import cv2 as cv import os import numpy as np# 遍历文件夹函数 def getFileList(dir, Filelist, extNone):"""获取文件夹及其子文件夹中文件列表输入 dir:文件夹根目录输入 ext: 扩展名返回: 文件路径列表"""newDir d…

Linux(四)--包软件管理器与Linux上软件的下载示例

一.包软件管理器【yum和apt】 1.先来学习使用yum命令。yum:RPM包软件管理器,用于自动化安装配置Linux软件,并可以自动解决依赖问题。通过yum命令我们可以轻松实现软件的下载,查找,卸载与更新等管理软件的操作。 最常用…

线程池-手写线程池Linux C简单版本(生产者-消费者模型)

目录 简介手写线程池线程池结构体分析task_ttask_queue_tthread_pool_t 线程池函数分析thread_pool_createthread_pool_postthread_workerthread_pool_destroywait_all_donethread_pool_free 主函数调用 运行结果 简介 本线程池采用C语言实现 线程池的场景: 当某些…

Delphi 开发的QR二维码生成工具,开箱即用

目录 一、基本功能: 二、使用说明: 三、操作演示gif 四、下载链接 在日常的开发中,经常需要将一个链接生成为二维码图片,特别是在进行支付开发的时候,因为我们支付后台获取了支付链接,需要变成二维码扫…

设计模式行为型——解释器模式

目录 什么是解释器模式 解释器模式的实现 解释器模式角色 解释器模式类图 解释器模式举例 解释器模式代码实现 解释器模式的特点 优点 缺点 使用场景 注意事项 实际应用 什么是解释器模式 解释器模式(Interpreter Pattern)属于行为型模式&…

SOLIDWORKS中的弹簧设计指南

SOLIDWORKS是一款广泛使用的三维计算机辅助设计软件,可以用于设计各种机械零件和组件,包括弹簧。在SOLIDWORKS中设计弹簧需要注意一些关键点,本文将为您介绍SOLIDWORKS中的弹簧设计指南。 1. 弹簧类型 按受力性质,弹簧类型包括压…

小程序云开发快速入门(1/4)

前言 从上次完成了码仔备忘录本地版本后,码仔就养成了每天记录备忘录的好习惯,每周早上会记录下自己要做的任务,然后晚上在复盘一下今天的计划是否完成。 有一天,码仔看到它最喜欢的码妞在一旁愁眉苦脸。 码仔:“怎么…

5个设计师必备的绘画工具,不看错亿

在设计工作中,绘画工具是设计师经常会用到的设计工具,今天本文将与大家分享5个好用的绘画工具,一起来看看吧! 1、即时灵感 即时灵感是一款非常受欢迎的绘画工具,它为设计师提供了自由的绘画方式,也提供了…

【雕爷学编程】Arduino动手做(181)---Maixduino AI开发板

37款传感器与执行器的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的&am…