Spring Boot 事务和事务传播机制
1. 为什么需要事务?
事务定义
将一组操作封装成一个执行单元 (封装到一起),这一组的执行具备原子性, 那么就要么全部成功,要么全部失败.
为什么要用事务?
比如转账分为两个操作:
第一步操作:A 账户-100 元。
第二步操作:B账户 +100 元。
如果没有事务,第一步执行成功了,第二步执行失败了,那么 A 账户平白无故的 100 元就“人间蒸发”了。而如果使用事务就可以解决这个问题,让这一组操作要么一起成功,要么一起失败。
2. Spring中事务的实现
Spring中的事务操作分为两类:
- 编程式事务(手动写代码操作事务)
- 声明式事务(利用注解自动开启和提交事务)
在开始讲解它们之前,咱们先来回顾事务在MSQL 中是如何使用的?
2.1 MySQL中的事务使用
事务在 MySQL 有 3个重要的操作: 开启事务、提交事务、回滚事务,它们对应的操作命令如下:
-- 开启事务
start transaction;
-- 业务执行-- 提交事务
commit;-- 回滚事务
rollback;
2.2 Spring 编程式事务(手动)
Spring 手动操作事务和上面MySQL 操作事务类似,它也是有3个重要操作步骤:
- 开启事务(获取事务)
- 提交事务
- 回滚事务
SpringBoot 内置了两个对象,DataSourceTransactionManager
用来获取事务(开启事务)、提交或回滚事务的,而 TransactionDefinition
是事务的属性,在获取事务的时候需要将TransactionDefinition
传递进去从而获得一个事务 TransactionStatus
,实现代码如下:
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;// 编程式事务// JDBC 事务管理器@Autowiredprivate DataSourceTransactionManager transactionManager;// 定义事务属性 (使用TransactionDefinition记录事务属性)@Autowiredprivate TransactionDefinition transactionDefinition;@RequestMapping("/del")public int del(Integer id) {if (id == null || id <= 0) return 0;// 1. 开启事务TransactionStatus transactionStatus = null;int result = 0;try {transactionStatus = transactionManager.getTransaction(transactionDefinition);// 业务操作: 删除用户result = userService.del(id);System.out.println("删除: " + result);// 2. 提交事务 / 回滚事务
// transactionManager.commit(transactionStatus); // 提交事务} catch (Exception e) {if (transactionStatus != null) {transactionManager.rollback(transactionStatus); // 回滚事务}}return result;}
}
业务操作相关代码如下:
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public int del(Integer id){return userMapper.del(id);}
}
@Mapper
public interface UserMapper {int del(@Param("id") Integer id);
}
<delete id="del">delete from userinfo where id=#{id}</delete>
运行结果:
可以看到, 删除的业务操作已经成功了, 但是因为回滚操作, 所以在执行前后查询数据库的时候是可以看到对应的数据依然存在.
从上述代码可以看出,以上代码虽然可以实现事务,但操作也很繁琐,有没有更简单的实现方法呢?请看下面声明式事务。
2.3 Spring 声明式事务 (自动)
声明式事务的实现很简单,只需要在需要的方法上添加 @Transactional 注解就可以实现了,无需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务,具体实现代码如下:
@RestController
@RequestMapping("/user2")
public class UserController2 {@Autowiredprivate UserService userService;@Transactional // 在方法开始之前开启事务, 方法正常执行结束之后提交事务, 如果执行途中发生异常, 则回滚事务.@RequestMapping("/del")public int del(Integer id) {if (id == null || id <= 0) return 0;return userService.del(id);}
}
执行前后查询数据库, 可以看到由于正常执行所以没有触发回滚.
2.3.1 @Transactional作用范围
@Transactional 可以用来修饰方法或类
- 修饰方法时:需要注意只能应用到 public 方法上,否则不生效。推荐此种用法
- 修饰类时:表明该注解对该类中所有的 public 方法都生效。
2.3.2 @Transactional 参数说明
参数 | 作用 |
---|---|
value | 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器 |
transactionManager | 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器 |
propagation | 事务的传播行为,默认值为 Propagation.REQUIRED |
isolation | 事务的隔离级别.默认值为 Isolation.DEFAULT |
timeout | 事务的超时时间,默认值为-1. 如果超过该时间限制但事务还没有完成,则自动回滚事务. |
readOnly | 指定事务是否为只读事务.默认值为 false; 为了忽略那些不需要事务的方法,比如读取数据可以设置read-only为 true. |
rollbackFor | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型 |
rollbackForClassName | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型 |
noRollbackFor | 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型 |
noRollbackForClassName | 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型 |
2.3.3 注意事项
@Transactional 在异常被捕获的情况下,不会进行事务自动回滚,验证以下代码是否会发生事务回滚:
@RestController
@RequestMapping("/user2")
public class UserController2 {@Autowiredprivate UserService userService;@Transactional @RequestMapping("/del")public int del(Integer id) {if (id == null || id <= 0) return 0;int result = userService.del(id);System.out.println("删除: " + result);try {int num = 10 / 0;} catch (Exception e) {System.out.println(e.getMessage());}return result;}
}
执行前后查询数据库, 可以看到在异常被捕获的情况下,并不会进行事务自动回滚.
事务不会自动回滚解决方案
解决方案1
对于捕获的异常,事务是会自动回滚的,因此解决方案1就是可以将异常重新抛出,具体实现如下:
@RestController
@RequestMapping("/user2")
public class UserController2 {@Autowiredprivate UserService userService;@Transactional @RequestMapping("/del")public int del(Integer id) {if (id == null || id <= 0) return 0;int result = userService.del(id);System.out.println("删除: " + result);try {int num = 10 / 0;} catch (Exception e) {System.out.println(e.getMessage());throw e;}return result;}
}
执行前后查询数据库, 可以看到这里是有异常, 触发了回滚.
解决方案2
手动回滚事务,在方法中使用 TransactionAspectSupport.currentTransactionStatus() 可以得到当前的事务,然后设置回滚方法 setRollbackOnly 就可以实现回滚了,具体实现代码如下:
public class UserController2 {// .. 省略代码, 同上try {int num = 10 / 0;} catch (Exception e) {System.out.println(e.getMessage());// 手动回滚事务TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return result;}
}
执行前后访问数据库可以看到, 这里成功实现了回滚操作.
2.3.4 @Transactional 工作原理
@Transactional 是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用JDK 的动态代理,如果目标对象没有实现了接口,会使用CGLIB 动态代理。
@Transactional 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果中途遇到的异常,则回滚事务。
@Transactional 实现思路预览:
@Transactional 具体执行细节如下图所示:
3. 事务隔离级别
3.1 事务特性回顾
事务有4 大特性 (ACID),原子性、持久性、一致性和隔离性,具体概念如下:
- 原子性: 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚 (Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性: 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
- 持久性: 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
- 隔离性: 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交 (Read uncommitted)、读提交 (read committed) 、可重复读 (repeatable read) 和串行化(Serializable)。
上面 4个属性,可以简称为ACID
原子性 (Atomicity,或称不可分割性)
一致性 (Consistency)
隔离性 (lsolation,又称独立性)
持久性 (Durability)
而这 4 种特性中,只有隔离性 (隔离级别) 是可以设置的。
为什么要设置事务的隔离级别?
设置事务的隔离级别是用来保障多个并发事务执行更可控,更符合操作者预期的。
什么是可控呢?
比如近几年比较严重的新冠病毒,我们会把直接接触到确诊病例的人员隔离到酒店,而把间接接触者 (和直接接触着但未确诊的人) 隔离在自己的家中,也就是针对不同的人群,采取不同的隔离级别,这种隔离方式就和事务的隔离级别类似,都是采取某种行动让某个事件变的“更可控”。而事务的隔离级别就是为了防止,其他的事务影响当前事务执行的一种策略。
3.2 Spring 中设置事务隔离级别
Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进行设置,具体操作如下图所示:
3.2.1 MySQL事务隔离级别有 4 种
- READ UNCOMMITTED: 读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,而未提交的数据可能会发生回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读。
- READ COMMITTED: 读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据,因此它不会有脏读问题。但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL查询中,可能会得到不同的结果,这种现象叫做不可重复读。
- REPEATABLE READ: 可重复读,是MySQL的默认事务隔离级别,它能确保同一事务多次查询的结果一致。但也会有新的问题,比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因)。明明在事务中查询不到这条信息,但自己就是插入不进去,这就叫幻读(Phantom Read)
- SERIALIZABLE: 序列化,事务最高隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读、不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多
1. 脏读: 一个事务读取到了另一个事务修改的数据之后,后一个事务又进行了回滚操作,从而导致第一个事务读取的数据是错误的。
2. 不可重复读: 一个事务两次查询得到的结果不同,因为在两次查询中间,有另一个事务把数据修改了。
3. 幻读: 一个事务两次查询中得到的结果集不同,因为在两次查询中另一个事务有新增了一部分数据。
在数据库中通过以下SQL 查询全局事务隔离级别和当前连接的事务隔离级别:
select @@global.tx_isolation,@@tx_isolation;
以上SQL的执行结果如下:
3.2.2 Spring 中事务隔离级别有 5 种
Spring 中事务隔离级别包含以下 5 种:
- Isolation.DEFAULT: 以连接的数据库的事务隔离级别为主
- lsolation.READ_UNCOMMITTED: 读未提交,可以读取到未提交的事务,存在脏读
- Isolation.READ_COMMITTED: 读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
- Isolation.REPEATABLE_READ: 可重复读,解决了不可重复读,但存在幻读(MVSQL默认级
- lsolation.SERIALIZABLE: 串行化,可以解决所有并发问题,但性能太低。
从上述介绍可以看出,相比于 MySQL 的事务隔离级别,Spring 的事务隔离级别只是多了一个lsolation.DEFAULT (以数据库的全局事务隔离级别为主)。
Spring 中事务隔离级别只需要设置 @Transactional 里的 isolation 属性即可,具体实现代码如下:
@RequestMapping("/save")
@Transactional(isolation = Isolation.SERIALIZABLE)
public Object save(User user) {// 业务实现
}
4. Spring 事务传播机制
4.1 事务传播机制是什么?
Spring 事务传播机制定义了多个包含了事务的方法,相互调用时,事务是如何在这些方法间进行传递的。
4.2 为什么需要事务传播机制?
事务隔离级别是保证多个并发事务执行的可控性的(稳定性的),而事务传播机制是保证一个事务在多个调用方法间的可控性的 (稳定性的)。
例子: 像新冠病毒一样,它有不同的隔离方式(酒店隔离还是居家隔离),是为了保证疫情可控,然而在每个人的隔离过程中,会有很多个执行的环节,比如酒店隔离,需要负责人员运送、物品运送消杀原生活区域、定时核算检查和定时送餐等很多环节,而事务传播机制就是保证一个事务在传递过程中是可靠性的,回到本身案例中就是保证每个人在隔离的过程中可控的。
事务隔离级别解决的是多个事务同时调用一个数据库的问题,如下图所示:
而事务传播机制解决的是一个事务在多个节点(方法) 中传递的问题,如下图所示:
4.3 事务传播机制有哪些?
Spring 事务传播机制包含以下 7种:
- Propagation.REQUIRED: 默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- Propagation.SUPPORTS: 如果当前存在事务,则加入该事务; 如果当前没有事务,则以非事务的方式继续运行。
- Propagation.MANDATORY: (mandatory: 强制性) 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- Propagation.REQUIRES_NEW: 表示创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
- Propagation.NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- Propagation.NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常
- Propagation.NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。
以上 7 种传播行为,可以根据是否支持当前事务分为以下 3类:
以情侣关系为例来理解以上分类:
4.4 Spring 事务传播机制使用和各种场景演示
4.4.1 支持当前事务(REQUIRED)
我们有多个方法进行数据的传递.
不考虑拦截器, 考虑标准的分层, 比如要执行用户User的添加add操作, 下图为该执行流程图
首先要执行add, 就会先在UserController加@Transactional, 下面为了演示事务的传播机制, 就需要让用户调用UserService, 并加上@Transactional.
在UserService中, 我们需要操作两张表, 先调用UserMapper中的add添加方法, 这个方法是添加用户的, 再调用日志添加类.
从上图可见, @Transactional是通过UserController传递到UserService, 再从UserService传递到LogService.
注意, 正常来说在*Service要调用*Mapper类, 但是这里如果使用LogMapper是Interface, 就无法演示清楚预期效果, 所以这里调用LogService.
在mycnblog中创建日志表.
CREATE TABLE `log` (`id` int(11) NOT NULL AUTO_INCREMENT,`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,`message` text COLLATE utf8mb4_unicode_ci NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
在项目中创建实体类:
@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;
}
@Data
public class Log {private int id;private LocalDateTime timestamp;private String message;
}
演示事务传播机制代码实现:
User
@RestController
@RequestMapping("/user3")
public class UserController3 {@Autowiredprivate UserService userService;@RequestMapping("/add")@Transactional(propagation = Propagation.REQUIRED)public int add(String username, String password){if (null == username || null == password ||username.equals("") || password.equals("")) return 0;UserInfo user = new UserInfo();user.setUsername(username);user.setPassword(password);int result = userService.add(user);// 用户添加操作return 0;}
}
UserService 实现代码如下:
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate LogService logService;@Transactional(propagation = Propagation.REQUIRED)public int add(UserInfo userInfo) {// 给用户表添加用户信息int addUserResult = userMapper.add(userInfo);System.out.println("添加用户结果: " + addUserResult);// 添加日志信息Log log = new Log();log.setMessage("添加用户信息"); logService.add(log);return addUserResult;}
}
在UserMapper中写添加add方法.
@Mapper
public interface UserMapper {int add(UserInfo userInfo);
}
在xml中写具体的实现:
<insert id="add">insert into userinfo(username, password) values(#{username},#{password})</insert>
Log
LogMapper.interface
@Mapper
public interface LogMapper {int add(Log log);
}
LogMapper.xml
<insert id="add">insert into log(`message`) values(#{message})</insert>
LogService:
@Service
public class LogService {@Autowiredprivate LogMapper logMapper;@Transactional(propagation = Propagation.REQUIRED)public int add(Log log) {int result = logMapper.add(log);System.out.println("添加日志结果: " + result);// 回滚操作TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();int num = 10 / 0;return result;}
}
接下来我们观察一下在UserService中已经执行成功的user添加操作有没有回滚事务, 如果user中没有正常添加数据, 那么就说明REQUIRED是支持当前的事务, 并且是以加入的方式去进行的.
可以看到, 添加用户和添加日志都已经成功了, 最终有一个算数异常报出. 我们再来看一下数据库.
可以看到, log回滚了, 符合预期, 然而userinfo中并没有将前面url所传数据wangwu添加过来, 那么这就说明, REQUIRED这种事务传播机制是支持当前事务的, 并且是加入事务的方式, 让自己变成事务的一部分, 如果其中有任何一个地方出现问题, 不论前面做了多少业务, 那么整条调用链上所有的方法都会进行回滚.
4.4.2 不支持当前事务(REQUIRESNEW)
将整条事务调用链上的所有方法修改为 REQUIRES_NEW不支持当前事务,重新创建事务,观察执行结果:
@Transactional(propagation = Propagation.REQUIRES_NEW)
预期执行结果: 在一个调用链上的事务,各自执行相互不干扰。
对上面的示例来说, 是用户添加 (事务) 成功、但是日志添加 (事务) 失败。
可以看到, 我们的执行结果并没有符合我们的预计结果.
原因是由于没有处理 by zero的异常, 导致整个调用链上的其他对象都感知到了, 所以进行了回滚.
此时修改LogService代码:
@Service
public class LogService {@Autowiredprivate LogMapper logMapper;@Transactional(propagation = Propagation.REQUIRES_NEW)public int add(Log log) {int result = logMapper.add(log);System.out.println("添加日志结果: " + result);// 回滚操作TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return result;}
}
这个时候便不会报错了.
可以看到, 用户和日志都添加成功了, 但是日志中进行了手动回滚.
验证一下:
说明符合了预期.
4.4.3 嵌套事务 (NESTED) 和加入事务 (REQUIRED )的区别
- 整个事务如果全部执行成功,二者的结果是一样的。
- 如果事务执行到一半失败了,那么加入事务整个事务会全部回滚;而嵌套事务会局部回滚,不会影响上一个方法中执行的结果
至此, 整个JavaEE专栏的知识介绍就到这里.
相关文章:
Spring Boot 事务和事务传播机制
1. 为什么需要事务? 事务定义 将一组操作封装成一个执行单元 (封装到一起),这一组的执行具备原子性, 那么就要么全部成功,要么全部失败. 为什么要用事务? 比如转账分为两个操作: 第一步操作:A 账户-100 元。 第二步操作:B账户 100 元。 如果没有事务&a…...
计算机组成原理(巨巨巨基础篇)
有关《计算机组成原理》课本中有关 内存计算换算(字,位,字节) 个人理解 前面知识点搭建框架,最后两道例题是直观理解体会 主存储器的基本概念 位:存储信息的最小单位,称为存储位或存储元。 背…...
C语言:选择+编程(每日一练Day7)
目录 选择题: 题一: 题二: 题三: 题四: 题五: 编程题: 题一:图片整理 思路一: 思路二: 题二:寻找数组的中心下标 思路一࿱…...
leetcode做题笔记93. 复原 IP 地址
有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 . 分隔。 例如:"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.2…...
HTTPS 中间人攻击
HTTPS 中间人攻击 中间人攻击过程 通讯过程 客户端——中间人——服务器 过程如下 服务器向客户端发送公钥攻击者截获公钥,保留在自己手上然后攻击者自己生成一个【伪造的】公钥,发给客户端客户端收到【伪造的】公钥后,利用【伪造的】公…...
MATLAB打开excel读取写入操作例程
本文使用素材含代码测试用例等 MATLAB读写excel文件历程含,内含有测试代码资源-CSDN文库 打开文件 使用uigetfile函数过滤非xlsx文件,找到需要读取的文件,首先判断文件是否存在,如果文件不存在,程序直接返回&#x…...
[C语言]分支与循环
导言: 在人生中我们总会有选择,**如下一顿吃啥?**又或者每天都是在重复,吃饭!!!!,当然在C语言中也有选择和重复那就是分支语句与循环语句 文章目录 分支循环循环中的关键…...
绘制区块链之链:解码去中心化、安全性和透明性的奇迹
区块链技术以其去中心化、安全性和透明性等特点在全球范围内引起了广泛的关注和兴趣。区块链是一种分布式账本技术,通过将数据以不可篡改的方式链接在一起,创建了一个安全可靠的数据库。这种革命性的技术正在许多领域中发挥作用,包括加密货币…...
4G工业路由器的功能与选型!详解工作原理、关键参数、典型品牌
随着工业互联网的发展,4G工业路由器得到越来越广泛的应用。但是如何根据实际需求选择合适的4G工业路由器,是许多用户关心的问题。为此,本文将深入剖析4G工业路由器的工作原理、重要参数及选型要点,并推荐优质的品牌及产品,以提供选型参考。 一、4G工业路由器的工作原理 4G工业…...
c与c++中struct的主要区别和c++中的struct与class的主要区别
1、c和c中struct的主要区别 c中的struct不可以含有成员函数,而c中的struct可以。 C语言 c中struct 是一种用于组合多个不同数据类型的数据成员的方式。struct 声明中的成员默认是公共的,并且不支持成员函数、访问控制和继承等概念。C中的struct通常被用…...
mysql中char_length()和length()
MySQL中计算字符串长度有两个函数分别为char_length和length。 char_length char_length函数可以计算unicode字符,包括中文等字符集的长度 char_length(‘string’)/char_length(column_name) 1、返回值为字符串string或者对应字段长度,长度的单位为字…...
Numpy学习笔记
科学计算库(Numpy) 通常数据都能转换成矩阵,行就是每一条样本数据,列就是每个字段的特征,Numpy在矩阵运算上非常高效,可以快速处理数据并进行数据计算。 Numpy基本操作 先导入 import numpy as nparray…...
LAMP配置与应用
目录 一、LAMP架构的组成 1、WEB资源类型 2、LAMP架构的组成 二、编译安装LAMP 编译安装apache 1、环境准备 2、导入apache相关压缩安装包,然后安装编译环境 3、解压软件包,并移动apr包与apr-util包到安装目录中,并切换到http解压出…...
Dockerfile搭建LNMP运行Wordpress平台
Dockerfile搭建LNMP运行Wordpress平台 一、项目1.1 项目环境1.2 服务器环境1.3 任务需求 二、Linux 系统基础镜像三、Nginx1、建立工作目录2、编写 Dockerfile 脚本3、准备 nginx.conf 配置文件4、生成镜像5、创建自定义网络6、启动镜像容器7、验证 nginx 四、Mysql1、建立工作…...
数据库第十五课-------------非关系型数据库----------Redis
作者前言 🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂 🎂 作者介绍: 🎂🎂 🎂 🎉🎉🎉…...
BM2 链表内指定区间反转,为什么链表要new一个结点?
链表内指定区间反转_牛客题霸_牛客网 (nowcoder.com) 思路就是,把需要反转的结点放入栈中,然后在弹出来。 /*** struct ListNode {* int val;* struct ListNode *next;* ListNode(int x) : val(x), next(nullptr) {}* };*/#include<stack> class…...
SQL阶段性优化
😜作 者:是江迪呀✒️本文关键词:MySQL、SQL优化、阶段性优化☀️每日 一言:我们要把懦弱扼杀在摇篮中。 一、前言 我们在做系统的过程中,难免会遇到页面查询速度慢,性能差的问题,…...
2023-08-22 Unity Shader 开发入门2 —— Shader 开发介绍
文章目录 一、必备概念1 计算机图形程序接口2 图形接口程序与其他概念的联系 二、Shader 开发1 Shader2 Shader 开发3 需掌握的内容 一、必备概念 1 计算机图形程序接口 计算机图形程序接口(Graphics API)是一套可编程的开放标准,不论 2…...
UE5 运行时捕捉外部窗口并嵌入到主窗口
UE5 运行时捕捉外部窗口并嵌入到主窗口的一种方法 创建一个Slate类用于生成一个窗口 .h// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h" #include "Widgets/SCompoundWidget.h"/*…...
uniapp 使用permission获取录音权限
使用前,需要先配置权限 android.permission.RECORD_AUDIO...
基于paddleocr的文档识别
1、版面分析 使用轻量模型PP-PicoDet检测模型实现版面各种类别的检测。 数据集: 英文:publaynet数据集的训练集合中包含35万张图像,验证集合中包含1.1万张图像。总共包含5个类别。 中文:CDLA据集的训练集合中包含5000张图像&a…...
魏副业而战:闲鱼卖货赚钱策略
我是魏哥,与其躺平,不如魏副业而战! 闲鱼卖货有人赚钱,有人不赚钱。 什么原因呢?闲鱼卖货的策略不对。 这不,社群成员小K找我反馈40单赚了150。 利润太低,不在正常范围之内。 魏哥建议继续…...
语法篇--XML数据传输格式
一、XML概述 1.1简介 XML,全称为Extensible Markup Language,即可扩展标记语言,是一种用于存储和传输数据的文本格式。它是由W3C(万维网联盟)推荐的标准,广泛应用于各种系统中,如Web服务、数据…...
【Redis】缓存雪崩、缓存击穿、缓存穿透
在使用 Redis 缓存时,常常会遇到三个主要的问题,分别是缓存雪崩、缓存击穿和缓存穿透。这些问题都可能导致缓存系统的性能下降或数据不一致性的问题。 一、缓存雪崩(Cache Avalanche) 缓存雪崩是指在某个时间点,缓存…...
通过示例学习 JavaScript 运算符 - 逻辑、比较、三元和更多 JS 运算符
JavaScript 有许多运算符,可用于对值和变量(也称为操作数)执行操作 根据这些 JS 运算符执行的操作类型,我们可以将它们分为七组: 目录 算术运算符赋值运算符比较运算符逻辑运算符三元运算符typeof操作员按位运算符 算术运算符 1. 加法运算符 2.减法运算符 3. 乘法运…...
基于微信小程序+Springboot校园二手商城系统设计和实现
博主介绍:✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、目前专注于大学生项目实战开发,讲解,毕业答疑辅导✌ 🍅文末获取源码联系🍅 👇dz…...
excel 动态表头与合并列
零、希望Springboot-java导出excel文件,包括动态表头与下边合并的列 使用 org.apache.poi 与自己封装工具类实现相关功能。代码如下 一、代码 1、依赖 implementation(group: org.apache.poi,name: poi-ooxml,version: 4.1.0)implementation(group: org.apache.po…...
jenkins自动部署微服务到docker
1、代码上传到git; 2、jenkins拉取git的代码,maven打包,使用插件生成镜像,自动上传docker; 两个插件,一个打包插件,一个创建镜像上传docker仓库.(将dockerfile内容搬到插件配置&…...
【蔚来汽车】蔚来20220713第三题-旅游规划 <模拟、滑动窗口>
【蔚来汽车】蔚来20220713第三题-旅游规划 牛牛对 n 个城市旅游情况进行了规划,已知每个城市有两种属性 x 和 y ,其中 x 表示去第 i 号城市的花费,y 表示在第 i 号城市游玩后会得到的开心值。 现在牛牛希望从中挑选出一些城市去游玩&…...
[解决方案]Antd TreeSelect/Select placeholder失效
🔎嘿,这里是慰慰👩🏻🎓,会发各种类型的文章,智能专业,从事前端🐾 🎉如果有帮助的话,就点个赞叭,让我开心一下!…...
广州哪家网站建设公司好/线上营销课程
电子天平的精度也就是精确读到小数几位数。比如:赛多利斯天平专根据准确度和精度分为精密天平 (> 0.001 g),分析天平 (0.0001 g),半微量天平 (0.00001 g),微量天平 (0.000001 g),超微量天平 (0.0000001 g)…...
遵义网站制作报价/查找关键词的工具叫什么
在spark中,reduceByKey、groupByKey和combineByKey这三种算子用的较多,结合使用过程中的体会简单总结: 我的代码实践:https://github.com/wwcom614/Spark •reduceByKey 用于对每个key对应的多个value进行merge操作,最…...
wordpress会员下载功能/换友情链接的网站
WebSocket是一种允许通过保持服务器端和用户端始终连接来进行双向通信的技术,所以WebSocket既可以发送数据也可以接收数据,本篇文章我们就来看看如何使用WebSocket发送和接收数据。我们先来看一下如何发送文本数据? 使用免费提供的http://ec…...
手机如何安装wordpress/百度百科搜索入口
直接上代码 package persistent.prestige.redis.lock; import persistent.prestige.redis.RedisUtils;import redis.clients.jedis.Jedis; /** * 基于 redis 实现 公平的、自旋式、 分布式锁 * 实现思路: * 使用队列保持请求锁的顺序,请求线程在上一个线…...
企业运营流程/快速排名优化系统
你叕要贴邮票了。现有1元、2元、5元的邮票各无数张。这次的规则是邮资固定,所贴邮票张数固定。你的任务是计算出共有多少种贴法。 输入格式: 第一行为两个用空格分隔的整数m,n(0<m,n<1000),依次代表邮资(单位为元)和规定所…...
vs做网站创建项目时选哪个/百度关键词排名神器
http://www.security-projects.com/?Patriot_NG:Download...