Transactional事务失效场景汇总

news/2024/5/19 18:42:17/

文章目录

  • 1、前言
  • 2、失效场景
    • 2.1、Service没有被Spring管理
    • 2.2、事务方法被final、static关键字修饰
    • 2.3、同一个类中,方法内部调用
    • 2.4、方法的访问权限不是public
    • 2.5、数据库的存储引擎不支持事务
    • 2.6、@Transactional 注解配置错误
    • 2.7、使用了错误的事务传播机制
    • 2.8、rollbackFor属性配置错误
    • 2.9、异常被捕获并处理了,没有抛出
    • 2.10、手动抛了别的异常
    • 2.11、多线程调用场景
  • 3、总结

1、前言

作为后端程序员,在日常开发中,经常会遇到事务处理的场景,在Spring中,为了更好的支撑我们进行数据库操作,它提供了两种事务管理的方式:

  • 编程式事务
  • 声明式事务

那众所周知,我们平时用的最多的就是声明式事务,也就是使用**@Transactional**注解的方式了

但是在日常开发中,如果对注解@Transactional使用不当的话,可能会导致事务失效,所以今天我们一起来总结梳理一下常见的一些失效场景,我这里梳理了下面这些场景:

image-20230419182746992

2、失效场景

2.1、Service没有被Spring管理

看如下代码:

package org.wujiangbo.service.impl.user;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.wujiangbo.domain.log.SysOperLog;
import org.wujiangbo.domain.user.User;
import org.wujiangbo.mapper.log.SysOperLogMapper;
import org.wujiangbo.mapper.user.UserMapper;
import org.wujiangbo.query.user.UserQuery;
import org.wujiangbo.result.JSONResult;
import org.wujiangbo.service.user.UserService;
import org.wujiangbo.utils.StringUtils;import javax.annotation.Resource;
import java.util.List;/*** <p>* 用户表 服务实现类* </p>** @author bobo(weixin:javabobo0513)*/
//@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{@Resourceprivate UserMapper userMapper;@Resourceprivate SysOperLogMapper logMapper;@Override@Transactionalpublic JSONResult addUser(User user, SysOperLog log) {//新增用户信息userMapper.insert(user);//新增日志记录logMapper.insert(log);int i = 1/0;//制造异常:发生算数异常return JSONResult.success("操作成功");}}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

上面例子中, @Service注解注释之后,spring事务(@Transactional)没有生效,因为Spring事务是由AOP机制实现的,也就是说从Spring IOC容器获取bean时,Spring会为目标类创建代理来支持事务。但是@Service被注释后,你的service类都不是spring管理的,那怎么创建代理类来支持事务呢,所以此种场景事务注解会失效,大家在开发过程中要仔细了,不要忘记,将@Transactional所在的类,交给Spring管理

2.2、事务方法被final、static关键字修饰

看如下代码:

package org.wujiangbo.service.impl.user;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.wujiangbo.domain.log.SysOperLog;
import org.wujiangbo.domain.user.User;
import org.wujiangbo.mapper.log.SysOperLogMapper;
import org.wujiangbo.mapper.user.UserMapper;
import org.wujiangbo.query.user.UserQuery;
import org.wujiangbo.result.JSONResult;
import org.wujiangbo.service.user.UserService;
import org.wujiangbo.utils.StringUtils;import javax.annotation.Resource;
import java.util.List;/*** <p>* 用户表 服务实现类* </p>** @author bobo(weixin:javabobo0513)*/
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{@Resourceprivate UserMapper userMapper;@Resourceprivate SysOperLogMapper logMapper;@Override@Transactionalpublic final JSONResult addUser(User user, SysOperLog log) {//新增用户信息userMapper.insert(user);//新增日志记录logMapper.insert(log);int i = 1/0;return JSONResult.success("操作成功");}}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

如果一个方法被声明为final或者static,则该方法不能被子类重写,也就是说无法在该方法上进行动态代理,这会导致Spring无法生成事务代理对象来管理事务

2.3、同一个类中,方法内部调用

看下面代码:

package org.wujiangbo.service.impl.user;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.wujiangbo.domain.log.SysOperLog;
import org.wujiangbo.domain.user.User;
import org.wujiangbo.mapper.log.SysOperLogMapper;
import org.wujiangbo.mapper.user.UserMapper;
import org.wujiangbo.query.user.UserQuery;
import org.wujiangbo.result.JSONResult;
import org.wujiangbo.service.user.UserService;
import org.wujiangbo.utils.StringUtils;import javax.annotation.Resource;
import java.util.List;/*** <p>* 用户表 服务实现类* </p>** @author bobo(weixin:javabobo0513)*/
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{@Resourceprivate UserMapper userMapper;@Resourceprivate SysOperLogMapper logMapper;@Overridepublic JSONResult addUser(User user, SysOperLog log) {doSomething(user, log);return JSONResult.success("操作成功");}@Transactionalpublic void doSomething(User user, SysOperLog log){//新增用户信息userMapper.insert(user);//新增日志记录logMapper.insert(log);int i = 1/0;}}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

事务是通过Spring AOP代理来实现的,而在同一个类中,一个方法调用另一个方法时,调用方法直接调用目标方法的代码,而不是通过代理类进行调用。即以上代码,调用目标doSomething方法不是通过代理类进行的,因此事务不生效

2.4、方法的访问权限不是public

看下面代码:

package org.wujiangbo.service.impl.user;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.wujiangbo.domain.log.SysOperLog;
import org.wujiangbo.domain.user.User;
import org.wujiangbo.mapper.log.SysOperLogMapper;
import org.wujiangbo.mapper.user.UserMapper;
import org.wujiangbo.query.user.UserQuery;
import org.wujiangbo.result.JSONResult;
import org.wujiangbo.service.user.UserService;
import org.wujiangbo.utils.StringUtils;import javax.annotation.Resource;
import java.util.List;/*** <p>* 用户表 服务实现类* </p>** @author bobo(weixin:javabobo0513)*/
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{@Resourceprivate UserMapper userMapper;@Resourceprivate SysOperLogMapper logMapper;@Override@Transactionalprivate JSONResult addUser(User user, SysOperLog log) {//新增用户信息userMapper.insert(user);//新增日志记录logMapper.insert(log);int i = 1/0;return JSONResult.success("操作成功");}}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

spring事务方法addUser的访问权限不是public,所以事务就不生效了,因为Spring事务是由AOP机制实现的,AOP机制的本质就是动态代理,而代理的事务方法不是public的话,computeTransactionAttribute()就会返回null,也就是这时事务属性不存在了

大家可以看下AbstractFallbackTransactionAttributeSource的源码:

image-20230419173640624

2.5、数据库的存储引擎不支持事务

Spring事务的底层,还是依赖于数据库本身的事务支持。在MySQL中,MyISAM存储引擎是不支持事务的,InnoDB引擎才支持事务。因此开发阶段设计表的时候,必须要确认你的选择的存储引擎是支持事务的

比如下面的SQL创建用户表时,就采用的是InnoDB存储引擎:

DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',`name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '姓名',`age` int(11) NULL DEFAULT NULL COMMENT '年龄',`phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;

2.6、@Transactional 注解配置错误

看如下代码:

@Transactional(readOnly = true)
public JSONResult addUser(User user, SysOperLog log) {//新增用户信息userMapper.insert(user);//新增日志记录logMapper.insert(log);int i = 1/0;return JSONResult.success("操作成功");
}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

虽然使用了@Transactional注解,但是注解中的readOnly=true属性指示这是一个只读事务,因此在保存数据时会抛出如下异常:

image-20230419174314860

我们使用@Transactional注解时,一般不需要跟后面的readOnly属性

2.7、使用了错误的事务传播机制

看如下代码:

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public JSONResult addUser(User user, SysOperLog log) {//新增用户信息userMapper.insert(user);//新增日志记录logMapper.insert(log);int i = 1/0;return JSONResult.success("操作成功");
}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

这里事务失效的原因是:Propagation.NOT_SUPPORTED表示传播特性不支持事务

我们一起来回顾下Spring提供了七种事务传播机制。它们分别是:

  • REQUIRED(默认):如果当前存在一个事务,则加入该事务;否则,创建一个新事务。该传播级别表示方法必须在事务中执行。
  • SUPPORTS:如果当前存在一个事务,则加入该事务;否则,以非事务的方式继续执行。
  • MANDATORY:如果当前存在一个事务,则加入该事务;否则,抛出异常。
  • REQUIRES_NEW:创建一个新的事务,并且如果存在一个事务,则将该事务挂起。
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在一个事务,则将该事务挂起。
  • NEVER:以非事务方式执行操作,如果当前存在一个事务,则抛出异常。
  • NESTED:如果当前存在一个事务,则在嵌套事务内执行。如果没有事务,则按REQUIRED传播级别执行。嵌套事务是外部事务的一部分,可以在外部事务提交或回滚时部分提交或回滚。

2.8、rollbackFor属性配置错误

看如下代码:

@Transactional(rollbackFor = Error.class)
public JSONResult addUser(User user, SysOperLog log) throws Exception {//新增用户信息userMapper.insert(user);//新增日志记录logMapper.insert(log);if(1 == 1){//模拟抛出异常throw new Exception();}return JSONResult.success("操作成功");
}

分析:

rollbackFor属性指定的异常必须是Throwable或者其子类。默认情况下,RuntimeExceptionError两种异常都是会自动回滚的。但是因为以上的代码例子,指定了rollbackFor = Error.class,但是抛出的异常又是Exception,而Exception和Error没有任何什么继承关系,因此事务就不生效

大家可以看一下Transactional注解源码:

image-20230419175336972

2.9、异常被捕获并处理了,没有抛出

看如下代码:

@Transactional
public JSONResult addUser(User user, SysOperLog log){try {//新增用户信息userMapper.insert(user);//新增日志记录logMapper.insert(log);int i = 1/0;}catch (Exception e){e.printStackTrace();}return JSONResult.success("操作成功");
}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

事务中的异常已经被业务代码捕获并处理,而没有被正确地传播回事务管理器,事务将无法回滚

我们可以从spring源码(TransactionAspectSupport这个类)中找到答案:

public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {//省略其他代码,只留了下面核心代码@Nullableprotected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable {if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);Object retVal;try {//Spring AOP中MethodInterceptor接口的一个方法,它允许拦截器在执行被代理方法之前和之后执行额外的逻辑。retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {//用于在发生异常时完成事务(如果Spring catch不到对应的异常的话,就不会进入回滚事务的逻辑)completeTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}//用于在方法正常返回后提交事务。commitTransactionAfterReturning(txInfo);return retVal;}
}

invokeWithinTransaction方法中,当Spring catch到Throwable异常的时候,就会调用completeTransactionAfterThrowing()方法进行事务回滚的逻辑。但是在我们测试代码中,直接把异常catch住了,并没有重新throw出来,因此 Spring自然就catch不到异常啦,因此事务回滚的逻辑就不会执行,事务就失效了

解决方案

spring事务方法中,当我们使用了try-catch,如果catch住异常,记录完异常日志,一定要重新把异常抛出来,正例如下:

@Transactional
public JSONResult addUser(User user, SysOperLog log){try {//新增用户信息userMapper.insert(user);//新增日志记录logMapper.insert(log);int i = 1/0;}catch (Exception e){e.printStackTrace();throw e;}return JSONResult.success("操作成功");
}

在catch中添加:throw e;

2.10、手动抛了别的异常

看下面代码:

@Transactional
public JSONResult addUser(User user, SysOperLog log) throws Exception {//新增用户信息userMapper.insert(user);//新增日志记录logMapper.insert(log);if(1 == 1){//模拟抛出异常throw new Exception();}return JSONResult.success("操作成功");
}

分析:

Spring默认只处理RuntimeException和Error或其子类,对于普通的Exception是不会回滚的,但是上面的代码例子中,手动抛了Exception异常,所以是不会回滚,除非用rollbackFor属性指定,如下:

@Transactional(rollbackFor = Exception.class)

2.11、多线程调用场景

看下面代码:

@Transactional
public JSONResult addUser(User user, SysOperLog log){try {//新增用户信息userMapper.insert(user);//多线程调用new Thread(() -> {//新增日志记录logMapper.insert(log);}).start();//模拟异常int i = 1/0;}catch (Exception e){e.printStackTrace();throw e;}return JSONResult.success("操作成功");
}

代码执行结果:

虽然发生了算数异常,但是日志数据还是会存到数据库之中,只有用户数据会回滚

分析:

这是因为Spring事务是基于线程绑定的,每个线程都有自己的事务上下文,而多线程环境下可能会存在多个线程共享同一个事务上下文的情况,导致事务不生效

我们可以进入到TransactionAspectSupport类的prepareTransactionInfo方法中看一下,有一个解释如下:

image-20230419181636899

简单翻译:

image-20230419181711752

从这里我们得知,事务信息是跟线程绑定的。

因此在多线程环境下,事务的信息都是独立的,将会导致Spring在接管事务上出现差异

3、总结

经过这样的总结梳理,相信你应该已经对@Transactional 注解使用的一些坑有所了解了,以后在开发过程中就要格外注意了


http://www.ppmy.cn/news/47051.html

相关文章

【校招VIP】浙大、上海交大、北航等顶级985都参加的大厂校招计划 前两年就业率97%

我们除了互联网的业务外&#xff0c;还有一个比较厉害的小业务&#xff0c;叫稳拿计划。 国内有很多985的学生都报考了大厂的就业班稳拿计划&#xff0c;且过去几届的就业率都达到了97%以上。 只有极少数人&#xff0c;没有找到符合要求的工作。 如果学员没有达到我们规定的…

vue3+ts+pinia+vite一次性全搞懂

vue3tspiniavite项目 一&#xff1a;新建一个vue3ts的项目二&#xff1a;安装一些依赖三&#xff1a;pinia介绍、安装、使用介绍pinia页面使用pinia修改pinia中的值 四&#xff1a;typescript的使用类型初识枚举 一&#xff1a;新建一个vue3ts的项目 前提是所处vue环境为vue3&…

统信UOS + Windows双系统安装教程

全文导读&#xff1a;本文主要介绍了AMD架构下&#xff08;Intel/amd/兆芯/海光&#xff09;的机器同时安装Windows系统UOS系统的方法。 准备环境 1、下载好UOS系统镜像&#xff08;AMD64&#xff09;&#xff0c;下载地址&#xff1a;https://www.chinauos.com/resource/down…

代码随想录算法训练营第52天|300.最长递增子序列,674. 最长连续递增序列,718. 最长重复子数组

代码随想录算法训练营第52天|300.最长递增子序列&#xff0c;674. 最长连续递增序列&#xff0c;718. 最长重复子数组 300.最长递增子序列674. 最长连续递增序列718. 最长重复子数组 300.最长递增子序列 题目链接&#xff1a;300.最长递增子序列&#xff0c;难度&#xff1a;中…

C/C++外观模式解析:简化复杂子系统的高效方法

C外观模式揭秘&#xff1a;简化复杂子系统的高效方法 引言设计模式的重要性外观模式简介与应用场景外观模式在现代软件设计中的地位与价值 外观模式基本概念外观模式的定义与核心思想提供简单接口隐藏复杂子系统设计原则与外观模式的关系外观模式实现外观模式的UML图 外观模式的…

【通过Cpython3.9源码看看python的内存回收机制】

一&#xff1a;建立对象引用计数 1. 相关代码 void _Py_NewReference(PyObject *op) {if (_Py_tracemalloc_config.tracing) {_PyTraceMalloc_NewReference(op);} #ifdef Py_REF_DEBUG_Py_RefTotal; #endifPy_SET_REFCNT(op, 1); #ifdef Py_TRACE_REFS_Py_AddToAllObjects(op…

webRtc播放rtsp视频流(vue2、vue3+vite+ts)

一、下载webRtc 开发环境用的win10版本的。 github上直接下载&#xff0c;速度感人。 Releases mpromonet/webrtc-streamer GitHub 提供资源下载&#xff0c;0积分 https://download.csdn.net/download/weiqiang915/87700892 二、启动&#xff0c;测试 webrtc-streame…

辉煌优配|刚刚!“中字头”再度爆发

今天早盘&#xff0c;A股全体持续震动收拾&#xff0c;上证50指数跌破2700点整数关口&#xff0c;沪深300亦失守4100点。 盘面上&#xff0c;国防军工、种业、中字头、电气设备等板块涨幅居前&#xff0c;前期抢手的人工智能、半导体、信创、软件服务等板块全线回调。北上资金净…

高效又稳定的ChatGPT大模型训练技巧总结,让训练事半功倍!

文&#xff5c;python 前言 近期&#xff0c;ChatGPT成为了全网热议的话题。ChatGPT是一种基于大规模语言模型技术&#xff08;LLM&#xff0c; large language model&#xff09;实现的人机对话工具。现在主流的大规模语言模型都采用Transformer网络&#xff0c;通过极大规模的…

腾讯新增长,AI扛大旗?

经历了疫情期间的低谷与波折&#xff0c;腾讯正在恢复它的活力。 3月22日&#xff0c;腾讯发布了2022年第四季度及全年财报。财报显示&#xff0c;2022全年营收为5546亿元人民币&#xff0c;归母净利润(Non-IFRS)为1156亿元人民币&#xff1b;2022年腾讯第四季度的营收为1450亿…

Python爬虫实战——获取电影影评

Python爬虫实战——获取电影影评 前言第三方库的安装示例代码效果演示结尾 前言 使用Python爬取指定电影的影评&#xff0c; 注意&#xff1a;本文仅用于学习交流&#xff0c;禁止用于盈利或侵权行为。 操作系统&#xff1a;windows10 家庭版 开发环境&#xff1a;Pycharm Co…

nginx 简介 第四章

一、Nginx简介 1、Nginx简介 Nginx&#xff08;特点&#xff1a;占用内存少&#xff0c;并发能力强&#xff09; Nginx是一个高性能的 HTTP 和反向代理服务器。 Nginx是一款轻量级的 Web 服务器/反向代理服务器及电子邮件 单台物理服务器可支持30 000&#xff5e;50 000个并发…

当,Kotlin Flow与Channel相逢

Flow之所以用起来香&#xff0c;Flow便捷的操作符功不可没&#xff0c;而想要熟练使用更复杂的操作符&#xff0c;那么需要厘清Flow和Channel的关系。 本篇文章构成&#xff1a; 1. Flow与Channel 对比 1.1 Flow核心原理与使用场景 原理 先看最简单的Demo&#xff1a; fun…

WMS智能仓储

子产品介绍篇--智能仓储 智能仓储 我们通常也称 WMS 系统。是一个实时的计算机软件系统&#xff0c;它能够按照运作的业务规则和运算法则&#xff0c;对信息、资源、行为、存货和分销运作进行更完美地管理&#xff0c;提高效率。 一. 仓储管理系统&#xff08;wms&#xff09;…

柔性数组【结构体和动态内存的结合】

全文目录 前言柔性数组的定义语法柔性数组的特点柔性数组的使用柔性数组的优势 前言 很多人可能没有听过柔性数组这个概念&#xff0c;但是在C99中柔性数组是确实存在的。我个人感觉有点像动态内存和结构体的结合。 柔性数组的定义语法 结构中的最后一个元素允许是未知大小的…

IO多路复用 学习笔记 (阻塞 IO,非阻塞IO,select 模型,poll 模型,epoll 模型)

参考了一下网络资源做的笔记 什么是IO多路复用 就是用一个线程或者一个进程监控文件描述符是否能执行 IO操作 传统网络IO - 阻塞 IO &#xff08;BIO&#xff09; 阻塞IO就是当我们执行一次IO操作中&#xff0c;整个程序是阻塞的&#xff0c;意味在途中我们必须等待返回才…

你了解C语言中的数组指针和函数指针吗?

如题&#xff0c;本篇文章重点讲解C语言中的数组指针和函数指针。这2种指针其实都不是很常用&#xff0c;个人感觉使用起来代码的可读性不是很高&#xff0c;但是还是需要了解一下。 数组指针 数组指针&#xff0c;即指向数组的指针&#xff0c;是用来存放数组的地址的。那如…

车载网络 - Autosar网络管理 - 跳转状态

四、Autosar网络管理跳转状态 网络模式对应报文状态 Autosar网络管理报文各个状态对应的网络管理报文和应用报文的发送和接收状态。 网络模式 网络管理报文 应用报文 收发类型 发送报文 接收报文 发送报文 接收报文 总线睡眠模式(BSM) No Yes No NA 准备总线睡眠模…

第14届蓝桥杯 | 冶炼金属

作者&#xff1a;指针不指南吗 专栏&#xff1a;第14届蓝桥杯真题 &#x1f43e;慢慢来&#xff0c;慢慢来&#x1f43e; 文章目录 题目代码摸索第一次 AC 5/10第二次 AC 100% 反思 题目 链接&#xff1a; 4956. 冶炼金属 - AcWing题库 小蓝有一个神奇的炉子用于将普通金属 O …

老宋 带你五分钟搞懂vue

Vue 1.1 什么是框架 任何编程语言在最初的时候都是没有框架的&#xff0c;后来随着在实际开发过程中不断总结『经验』&#xff0c;积累『最佳实践』&#xff0c;慢慢的人们发现很多『特定场景』下的『特定问题』总是可以『套用固定解决方案』。于是有人把成熟的『固定解决方案…