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

目录

一.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;
    }
}

运行结果:用户添加成功

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ASCII码表介绍

一、ASCII码是什么 ASCII&#xff08;American Standard Code for Information Interchange&#xff0c;美国信息交换标准代码&#xff09;是基于拉丁字母的一套电脑编码系统。它可分为基于7位二进制数的标准版本和基于8位二进制数的扩展版本&#xff0c;标准版本主要用于显示现…

[AI Stability] 开源AI新利器:Stable Diffusion 3 Medium震撼发布!文本到图像再升级!

Stable Diffusion 3 Medium(SD3) 开源了&#xff0c;我们来看下。 关键要点 Stable Diffusion 3 Medium 是 Stability AI 迄今为止最先进的文本到图像开源模型。该模型的体积小巧&#xff0c;非常适合在消费级 PC 和笔记本电脑上运行&#xff0c;也适合在企业级 GPU 上运行。…

精彩回顾!安全智能体的前沿技术研究与实践

&#xff08;关注“安全极客”&#xff0c;回复“智能体”下载第一期系列专题PPT&#xff01;&#xff09; 近日&#xff0c;安全极客和Wisemodel社区联合发起并主办了“AISecurity”系列第1期&#xff1a;大模型与网络空间安全前沿探索线下活动。在这次活动中&#xff0c;云起…

读取CSV文件生成RDD去掉标题行

文章目录 1. 创建CSV文件2. 上传CSV文件3. 读取CSV文件生成RDD4. 去掉标题行生成新RDD5. 查看新生成的RDD 1. 创建CSV文件 执行命令&#xff1a;vim scores.csv 在WPS里查看CSV文件 2. 上传CSV文件 执行命令&#xff1a;hdfs dfs -put scores.csv /park 3. 读取CSV文件生…

「漏洞复现」I Doc View 在线文档预览 qJvqhFt.json 任意文件读取漏洞(XVE-2024-2115)

0x01 免责声明 请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;作者不为此承担任何责任。工具来自网络&#xff0c;安全性自测&#xff0c;如有侵权请联系删…

【 EI会议 | 西南大学主办 | 往届均已实现检索】第三届神经形态计算国际会议(ICNC 2024)

第三届神经形态计算国际会议&#xff08;ICNC 2024) 2024 3rd International Conference on Neuromorphic Computing (ICNC 2024) 一、重要信息 大会官网&#xff1a;www.ic-nc.org&#xff08;点击投稿/参会/了解会议详情&#xff09; 会议时间&#xff1a;2024年12月13-15…

在线的、完全免费的、提供回放的技术传播方面的大会:Adobe DITA World 2024

▲ 搜索“大龙谈智能内容”关注公众号▲ 最近美国苹果公司召开了WWDC24&#xff0c;国内不少人熬夜观看。 对于我来说&#xff0c;我更关注在美国召开的另外一个会&#xff0c;它就是Adobe DITA world。 一年一度的Adobe DITA world号称是全球最大的DITA营销和技术传播专业人…

【docker 不希望每次sudo docker cmd】

一、背景 ubuntu系统下安装好了docker 不希望每次sudo docker cmd&#xff0c;这样每次多输入很多字&#xff0c;比较麻烦 二、原理 在 Ubuntu 等 Linux 发行版上&#xff0c;使用 Docker 命令时常常需要使用 sudo 命令&#xff0c;这是因为 Docker 的服务是以 root 权限运行…

基于STM32和人工智能的智能交通管理系统

目录 引言环境准备智能交通管理系统基础代码实现&#xff1a;实现智能交通管理系统 4.1 数据采集模块4.2 数据处理与分析4.3 控制系统4.4 用户界面与数据可视化应用场景&#xff1a;智能交通管理与优化问题解决方案与优化收尾与总结 1. 引言 随着城市化进程的加快&#xff0…

Three.js做了一个网页版的我的世界

前言 笔者在前一阵子接触到 Three.js 后, 发现了它能为前端 3D 可视化 / 动画 / 游戏方向带来的无限可能, 正好最近在与朋友重温我的世界, 便有了用 Three.js 来仿制 MineCraft 的想法, 正好也可以通过一个有趣的项目来学习一下前端 3D 领域 介绍 游戏介绍 相信大家对我的世…

Web基础和HTTP协议

1、Web基础 &#xff08;1&#xff09;域名概述 域名空间结构 域名注册 2、网页 &#xff08;1&#xff09;网页概述 网页 纯文本格式文件 编写语言为HTML 在用户的浏览器中被“翻译”成网页形式显示出来 网站 由一个一个页面构成的&#xff0c;是多个网页的结合体 主页…

ChatGPT-4o引领医学革命:临床科研创新与效率的新纪元

2024年5月12日&#xff0c;更强版本的ChatGPT-4o上线&#xff0c;文本、语音、图像等多模态交互方式使其在各行各业的应用呈现了更多的可能性。因此&#xff0c;帮助广大临床医学相关的医院管理人员、医生、学生、科研人员更加熟练地掌握ChatGPT-4o在临床医学日常生活、工作与学…

Tabby:一款革新的Mac/Win现代化终端模拟器

在信息技术日新月异的今天&#xff0c;终端操作已成为众多开发者、系统管理员和技术爱好者的日常必备工具。然而&#xff0c;传统的终端模拟器往往功能单一、界面陈旧&#xff0c;无法满足用户对于高效、便捷操作体验的追求。Tabby应运而生&#xff0c;作为一款现代化、功能强大…

WebGL渲染引擎优化方向 -- 内存管理的优化

作者&#xff1a;caven chen 对此系列感兴趣还可以看前文&#xff1a; WebGL渲染引擎优化方向 -- 加载性能优化 WebGL渲染引擎优化方向——渲染帧率的优化 前言 WebGL 是一种强大的图形渲染技术&#xff0c;可以在浏览器中快速渲染复杂的 3D 场景。但是&#xff0c;由于 W…

先导小型五轴联动数控加工中心

先导小型五轴联动加工中心可以作为学校或培训机构的教学工具&#xff0c;帮助学生了解数控加工的基本原理和操作方法。它特别适用于机械、自动化、工业设计等相关专业的学生进行实践操作和课程项目。 小型五轴联动加工中心是一种能够同时控制五个自由度进行联动的加工设备。这五…

VBA实现关闭Excel自动计算,关闭屏幕刷新

Excel代码提速神器 涉及到提取表格大量数据操作&#xff0c;复制粘贴多个单元格时&#xff0c;尽量避免一个个单元格提取&#xff0c;或者一行行一列列提取数值&#xff0c;设计大量IO流操作非常浪费时间。尽量找出数据之间的规律&#xff0c;批量选中复制粘贴&#xff0c;找到…

字符集相关变量理解

建表 创建一个新表&#xff0c;想让他的字符集是 gbk&#xff0c;怎么弄? 尝试1&#xff1a; 失败&#xff01;原因&#xff1a; set names gbk; 等价于&#xff1a;set character_set_client gbk; set character_set_connection gbk; set character_set_results gbk;尝…

LSS 和 BEVDepth算法解读

前言 当前BEV的研究大都基于深度学习的方法&#xff0c;从组织BEV特征信息的方式来看&#xff0c;主流方法分属两类&#xff1a;自底向上方法和自顶向下方法。 自底向上方法比较早的代表工作是LSS&#xff0c;后来BEVDet、BEVDepth等也是基于LSS的框架来进行优化。自底向上方…

redis 一些笔记1

redis 一、redis事务二、管道2.1 事务与管道的区别 三、主从复制3.13.2 权限细节3.3 基本操作命令3.4 常用3.4.1 一主几从3.4.2 薪火相传3.4.3 反客为主 3.5 步骤3.6 缺点 一、redis事务 放在一个队列里&#xff0c;依次执行&#xff0c;并不保证一致性。与mysql事务不同。 命…

滚雪球学Java(82):快速上手Java线程通信:零基础学习指南

咦咦咦&#xff0c;各位小可爱&#xff0c;我是你们的好伙伴 bug菌&#xff0c;今天又来给大家手把手教学Java SE系列知识点啦&#xff0c;赶紧出来哇&#xff0c;别躲起来啊&#xff0c;听我讲干货记得点点赞&#xff0c;赞多了我就更有动力讲得更欢哦&#xff01;所以呀&…