Spring Boot单元测试与Mybatis单表增删改查
目录
1. Spring Boot单元测试
1.1 什么是单元测试?
1.2 单元测试有哪些好处?
1.3 Spring Boot 单元测试使用
单元测试的实现步骤
1. 生成单元测试类
2. 添加单元测试代码
简单的断言说明
2. Mybatis 单表增删改查
2.1 单表查询
2.2 参数占位符 ${} 和 #{}
${} 和 #{}的区别
1. 作用不同
2. 安全性: ${} 的SQL注入问题
${} 应用场景
2.3 单表修改操作
2.4 单表删除操作
2.5 单表添加操作
添加返回影响行数
添加返回影响行数和id
2.6 like查询
2.7 标签返回类型使用背景使用
1. Spring Boot单元测试
1.1 什么是单元测试?
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证的过程就叫单元测试。
单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的(代码)功能是否正确。执行单元测试就是为了证明某段代码的执行结果是否符合我们的预期。如果测试结果符合我们的预期,称之为测试通过,否则就是测试未通过 (或者叫测试失败)
1.2 单元测试有哪些好处?
- 可以非常简单、直观、快速的测试某一个功能是否正确。
- 使用单元测试可以帮我们在打包的时候,发现一些问题,因为在打包之前,所有的单元测试必须通过, 否则不能打包成功。
- 使用单元测试,在测试功能的时候,可以不污染连接的数据库,也就是可以不对数据库进行任何改变的情况下,测试功能。
1.3 Spring Boot 单元测试使用
Spring Boot 项目创建时会默认单元测试框架 spring-boot-starter-test,而这个单元测试框架主要是依靠另个著名的测试框架 JUnit 实现的,打开 pom.xml 就可以看到,以下信息是 Spring Boot 项目创建是自动添加的:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
单元测试的实现步骤
1. 生成单元测试类
最终生成的代码:
package com.example.demo.mapper;import org.junit.jupiter.api.Test;class UserMapperTest {@Testvoid getAll() {}
}
这个时候,此方法是不能调用到任何单元测试的方法的,此类只生成了单元测试的框架类,具体的业务代码要自己填充。
2. 添加单元测试代码
- 在测试类上添加Spring Boot 框架测试注解: @SpringBootTest
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest // 表示当前单元测试的类是运行在 Spring Boot 环境中的(一定不能省略)
class UserMapperTest {// ..
}
- 添加单元测试业务逻辑
@Autowiredprivate UserMapper userMapper;@Testvoid getAll() {List<UserEntity> list = userMapper.getAll();System.out.println(list.size());}
简单的断言说明
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
断言: 如果断言失败,则后面的代码都不会执行.
2. Mybatis 单表增删改查
2.1 单表查询
下面我们来实现一下根据用户id查询用户信息的功能.
在UserMapper类中添加接口:
// 根据 id 查询用户对象
UserEntity getUserById(@Param("uid") Integer id); // @Param是给形参起名
<select id="getUserById" resultType="com.example.demo.entity.UserEntity">select * from userinfo where id=${uid}</select>
注: 上面 ${uid} 中的uid对应@Param的uid
使用单元测试的方式去调用它.
@Testvoid getUserById() {UserEntity user = userMapper.getUserById(2);System.out.println(user);}
那么我们的预期结果是能够打印出数据库中"zhangsan"的数据:
执行结果:
可以看到, 预期结果成功执行了.
2.2 参数占位符 ${} 和 #{}
Mybatis获取动态参数有两种实现:
- ${paramName} -> 直接替换
- #{paramName} -> 占位符模式
验证直接替换:
在Spring配置文件中有一个配置, 只需要把这个配置给它配置之后, 那么Mybatis的执行SQL(Mybatis底层是基于JDBC), 最终会生成JDBC的执行SQL和它的执行模式, 那么我们就可以把这个执行的SQL语句打印出来.
需要配置两个配置项, 一个是日志打印的实现, 另一个是设置日志打印的级别 (SQL的打印默认输出的级别的debug级别, 但日志默认级别的info, 默认info要大于debug, 所以并不会显示, 所以要去进行日志级别的设置).
# 打印 Mybatis 执行 SQL
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
logging.level.com.example.demo=debug
配置完成之后再次运行刚才的测试代码, 可以看到SQL的相关信息都被打印了出来, 所以可以知道$是直接替换的模式.
将上文的 $ 换成 # , 会看到的是, SQL语句的id变成了?, 也就是变成了占位符的模式.
而占位符的模式是预执行的, 而预执行是比较安全的, 具体来说预执行可以有效的排除SQL注入的问题.
${} 和 #{}的区别
1. 作用不同
${} 所见即所得, 直接替换, #{} 是预处理的.
在进行使用的时候, 如果传的是int这种简单数据类型的时候, 两者是没有区别的, 但是如果更复杂点的使用varchar, 就会有安全的问题出现.
在UserMapper类中添加接口:
// 根据名称查询用户对象
UserEntity getUserByUserName(@Param("username") String username);
<select id="getUserByUserName" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username=#{username}</select>
测试:
@Testvoid getUserByUserName() {UserEntity user = userMapper.getUserByUserName("zhangsan");System.out.println(user);}
测试结果没有问题, 那么再将#换成$.
<select id="getUserByUserName" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username=${username}</select>
这时程序报错没有找到'zhangsan', 并且我们看到SQL语句变成了.
在数据库客户端中执行图中SQL语句也是会报出和上图一样的错.
那么这里的原因就在于刚才我们的代码中, ${}是直接替换的模式, 当加上单引号后再次运行就正常运行了.
<select id="getUserByUserName" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username='${username}'</select>
但是加单引号只能保证不报错, 但是不能保证安全性问题.
所以当我们遇到是int类型的时候, ${} 和 #{} 在执行上没有什么区别, 当出现字符型的时候${} 就有可能会出现问题.
2. 安全性: ${} 的SQL注入问题
${} 的安全性问题出现在登录, 接下来我们以登录为例看一下什么是SQL注入.
首先SQL注入是 用户用了并不是一个真实的用户名和密码, 但是却查询到了数据. 我们通过代码说明.
// 登录方法
UserEntity login(UserEntity user);
<select id="login" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username='${username}' and password='${password}'</select>
注: 当Interface传的是对象时, xml中获取属性时, 也就是{}里面直接写对象的属性名即可, 无需"对象.属性", 这是Mybatis的约定
为了演示效果, 我们在数据库中删掉id=2的zhangsan.
先来看正常的用户行为.
@Testvoid login() {String username = "admin";String password = "admin";UserEntity inputUser = new UserEntity();inputUser.setUsername(username);inputUser.setPassword(password);UserEntity user = userMapper.login(inputUser);System.out.println(user);}
可以看到, 找到了相关信息.
当输入错误密码时, 即:
String password = "admin2";
可以看到, 结果是null, 以上都是正常的行为.
接下来我们来看一个特殊的, 不正常的行为, 输入如下密码:
String password = "' or 1='1";
此时我们可以发现, 输入了一个不正常的密码, 却把admin查出来了, 这就是SQL注入, 对于程序来说是非常危险的.
那么我们可以看到这里的SQL语句是
select * from userinfo where username='admin' and password='' or 1='1'
所以这便是这里出错的原因, 它把字符串误解析成SQL指令去执行了, 使逻辑运行结果与预期不同, 但却正常执行.
当把 ${} 改为 #{} 后, 再次测试, 可以看到结果是null.
由上可见, 使用 ${} 是会安全性问题的, 而使用 #{} 就不会出现安全性问题, 原因在于 #{} 使用了JDBC的占位符的模式, 那么这种模式是预执行的, 是直接当成字符串来执行的.
${} 应用场景
${} 虽然在查询的时候会有安全性问题, 但是它也有具体的应用场景, 比如以下场景:
在淘宝中有时候需要按照某种属性进行排序, 比如价格低到高或者高到低, 这时SQL传递的就是order by后的规则asc或desc.
使用 ${sort} 可以实现排序查询,而使用 #{sort} 就不能实现排序查询了,因为当使用 #{sort} 查询时如果传递的值为 String 则会加单引号,就会导致 sql 错误。
那么对于我们之前的程序, 我们也可以进行类似的应用.
List<UserEntity> getAllByIdOrder(@Param("ord") String order);
<select id="getAllByIdOrder" resultType="com.example.demo.entity.UserEntity">select * from userinfo order by id ${ord}</select>
@Testvoid getAllByIdOrder() {List<UserEntity> list = userMapper.getAllByIdOrder("desc");System.out.println(list.size());}
这时使用 #{} 就会报错了.
<select id="getAllByIdOrder" resultType="com.example.demo.entity.UserEntity">select * from userinfo order by id #{ord}</select>
既然 ${} 有用, 但是它也极其的危险, 在使用的时候要注意, 要保证它的值必须得被枚举. 所以尽量少用.
2.3 单表修改操作
比如需要修改用户密码.
首先, 在Interface声明方法,
// 修改密码int updatePassword(@Param("id") Integer id,@Param("password") String password,@Param("newPassword") String newPassword);
然后在xml中实现方法, 注意修改操作是使用<update>标签.
<update id="updatePassword">update userinfo set password=#{newPassword}where id=#{id} and password=#{password}</update>
@Testvoid updatePassword() {int result = userMapper.updatePassword(1, "admin", "123456");System.out.println("修改: " + result);}
运行前后查询数据库,
可以看到, password已经成功修改了.
当再次修改newPassword参数的代码时, 即:
int result = userMapper.updatePassword(1, "admin", "666666");
这里说明, 注入参数有问题, 代码没问题.
不过, 这里的测试是把原本数据库污染了, 违背了单元测试的初衷, 那么要想不污染数据库, 需要在测试类前加上@Transactional事务注解.
@Transactional // 事务@Testvoid updatePassword() {int result = userMapper.updatePassword(1, "123456", "666666");System.out.println("修改: " + result);}
当加上注解之后, 测试的代码可以正常执行, 但是就不会污染数据库了.
看到打印了"修改: 1", 就说明成功修改了.
在代码执行的时候不会进行干扰的, 只不过在执行之初, 会开启一个事务, 等全部代码执行完了, 比如这里的"修改: x"已经正常打印了, 然后在它执行完会进行rollback回滚操作, 所以就不会污染数据库了.
验证数据库是否污染:
2.4 单表删除操作
// 删除用户
int delById(@Param("id") Integer id);
<delete id="delById">delete from userinfo where id=#{id}</delete>
@Transactional@Testvoid delById() {int id = 1;int result = userMapper.delById(id);System.out.println("删除结果: " + result);}
2.5 单表添加操作
添加返回影响行数
// 添加用户
int addUser(UserEntity user);
<insert id="addUser">insert into userinfo(username,password) values(#{username},#{password})</insert>
@Testvoid addUser() {UserEntity user = new UserEntity();user.setUsername("lisi");user.setPassword("123456");int result = userMapper.addUser(user);System.out.println("添加: " + result);}
添加返回影响行数和id
int addUserGetId(UserEntity user);
<insert id="addUserGetId" useGeneratedKeys="true" keyProperty="id">insert into userinfo(username,password) values(#{username},#{password})</insert>
@Testvoid addUserGetId() {UserEntity user = new UserEntity();user.setUsername("lili");user.setPassword("123456");int result = userMapper.addUserGetId(user);System.out.println("添加结果: " + result);System.out.println("ID: " + user.getId());}
2.6 like查询
like使用 #{} 会报错
// 根据用户名模糊查询List<UserEntity> getListByName(@Param("username") String username);
<select id="getListByName" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username like '%#{username}%'</select>
@Testvoid getListByName() {String username = "zhang";List<UserEntity> list = userMapper.getListByName(username);list.stream().forEach(System.out::println);}
可以看到, 当我们使用#{}的方式进行like查询的时候, 它是有问题的, 因为它会把#{username}作为占位符, 看到传过来的参数是String后, 相当于又加了一个单引号, 也就是'%'zhang'%', 所以它是报错的.
在数据库中演示一下.
那么可以通过MySQL内置方法concat解决这个问题, concat能实现字符的拼接.
<select id="getListByName" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username like concat('%',#{username},'%')</select>
可以看到, 我们可以查到zhangsan的信息了, 说明此时查询是没有问题的.
2.7 <select>标签返回类型
我们知道, <select>查询标签至少需要设置两个属性:
id属性: 用于标识实现接口中的哪个方法;
结果映射(即返回)属性: 结果映射有两种实现标签: <resultType>和<resultMap>
<resultType>
绝大多数场景可以使用resultType返回, 如下所示:
<select id="getAll" resultType="com.example.demo.entity.UserEntity">select * from userinfo</select>
它的优点是使用方便,直接定义到某个实体类即可.
<resultMap>
<resultMap>使用场景:
- 字段名称和程序中的属性名不同的情况,可使用 resultMap 配置映射
- 一对一和一对多关系可以使用 resultMap 映射并查询数据
使用背景
我们以数据库中的字段为例,
比如当项目实体类中密码的属性为pwd, 而数据库中对应的字段为password, 这个时候Mybatis就匹配不到了, 原本实体类中为password, Mybatis因为做了映射, 而映射规则是根据名称进行匹配, 所以能够拿到数据库中的信息(Mybatis是一个ORM框架). 那么问题在哪里呢?
我们来看前文的getListByName(), 原来使用zhang把信息查询到了,
但是我们此时将实体类中的password改为pwd, 再次测试,
我们可以会发现, 执行没有报错, JDBC可以正常拿到password, 但是在映射的时候给pwd没有映射上.
这里就是因为对比的时候pwd和password是不相同的, Mybatis在属性中没有找到pwd.
所以这时我们<resultType>使用实体类已经和数据库的表对应不上了, 那么就使用<resultMap>.
使用
<resultMap id="BaseMap" type="com.example.demo.entity.UserEntity"><id property="id" column="id"></id><result property="username" column="username"></result><result property="pwd" column="password"></result><result property="createtime" column="createtime"></result><result property="updatetime" column="updatetime"></result></resultMap><select id="getListByName" resultMap="BaseMap">select * from userinfo where username like concat('%',#{username},'%')</select>
可以看到, 此时pwd是有值的, 数据库中为password, 程序中是pwd, 依然能够拿到值.
注: 如果依然要用resultType就需要对SQL语句修改.
<select id="getListByName" resultType="com.example.demo.entity.UserEntity">select id,username,password as pwd from userinfo where username like concat('%',#{username},'%')</select>
可以看到成功的拿到了password, 并且在SQL中是实现了重命名.
相关文章:

Spring Boot单元测试与Mybatis单表增删改查
目录 1. Spring Boot单元测试 1.1 什么是单元测试? 1.2 单元测试有哪些好处? 1.3 Spring Boot 单元测试使用 单元测试的实现步骤 1. 生成单元测试类 2. 添加单元测试代码 简单的断言说明 2. Mybatis 单表增删改查 2.1 单表查询 2.2 参数占位符 ${} 和 #{} ${} 和 …...

机器学习样本数据划分的典型Python方法
机器学习样本数据划分的典型Python方法 DateAuthorVersionNote2023.08.16Dog TaoV1.0完成文档撰写。 文章目录 机器学习样本数据划分的典型Python方法样本数据的分类Training DataValidation DataTest Data numpy.ndarray类型数据直接划分交叉验证基于KFold基于RepeatedKFold基…...

重建与突破,探讨全链游戏的现在与未来
全链游戏(On-Chain Game)是指将游戏内资产通过虚拟货币或 NFT 形式记录上链的游戏类型。除此以外,游戏的状态存储、计算与执行等皆被部署在链上,目的是为用户打造沉浸式、全方位的游戏体验,超越传统游戏玩家被动控制的…...

[C++] 模板template
目录 1、函数模板 1.1 函数模板概念 1.2 函数模板格式 1.3 函数模板的原理 1.4 函数模板的实例化 1.4.1 隐式实例化 1.4.2 显式实例化 1.5 模板参数的匹配原则 2、类模板 2.1 类模板的定义格式 2.2 类模板的实例化 讲模板之前呢,我们先来谈谈泛型编程&am…...

[vite] 项目打包后页面空白,配置了base后也不生效
记录下解决问题的过程和思路 首先打开看打包后的 dist/index.html 文件,和页面上的报错 这里就发现了第一个问题 报错的意思是 index.html中引用的 css文件 和 js文件 找不到 为了解决这个问题,在vite.config.js配置中,增加一项 base:./ …...

springboot整合kafka-笔记
springboot整合kafka-笔记 配置pom.xml 这里我的springboot版本是2.3.8.RELEASE,使用的kafka-mq的版本是2.12 <dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>s…...

Rust软件外包开发语言的特点
Rust 是一种系统级编程语言,强调性能、安全性和并发性的编程语言,适用于广泛的应用领域,特别是那些需要高度可靠性和高性能的场景。下面和大家分享 Rust 语言的一些主要特点以及适用的场合,希望对大家有所帮助。北京木奇移动技术有…...

Spring Boot业务代码中使用@Transactional事务失效踩坑点总结
1.概述 接着之前我们对Spring AOP以及基于AOP实现事务控制的上文,今天我们来看看平时在项目业务开发中使用声明式事务Transactional的失效场景,并分析其失效原因,从而帮助开发人员尽量避免踩坑。 我们知道 Spring 声明式事务功能提供了极其…...

知识体系总结(九)设计原则、设计模式、分布式、高性能、高可用
文章目录 架构设计为什么要进行技术框架的设计 六大设计原则一、单一职责原则二、开闭原则三、依赖倒置原则四、接口分离原则五、迪米特法则(又称最小知道原则)六、里氏替换原则案例诠释 常见设计模式构造型单例模式工厂模式简单工厂工厂方法 生成器模式…...

Springboot 集成Beetl模板
一、在启动类下的pom.xml中导入依赖: <!--beetl模板引擎--><dependency><groupId>com.ibeetl</groupId><artifactId>beetl</artifactId><version>2.9.8</version></dependency> 二、 配置 beetl需要的Beetl…...

RabbitMQ查询队列使用情况和消费者详情实现
spring-boot-starter-amqp spring-boot-starter-amqp是Spring Boot框架中与AMQP(高级消息队列协议)相关的自动配置启动器。它提供了使用AMQP进行消息传递和异步通信的功能。 以下是spring-boot-starter-amqp的主要特性和功能: 自动配置:spring-boot-starter-amqp通过自动…...

Spark第二课RDD的详解
1.前言 RDD JAVA中的IO 1.小知识点穿插 1. 装饰者设计模式 装饰者设计模式:本身功能不变,扩展功能. 举例: 数据流的读取 一层一层的包装,进而将功能进行进一步的扩展 2.sleep和wait的区别 本质区别是字体不一样,sleep斜体,wait正常 斜体是静态方法…...

人工智能学习框架—飞桨Paddle人工智能
1.人工智能框架 机器学习的三要素:模型、学习策略、优化算法。 当我们用机器学习来解决一些模式识别任务时,一般的流程包含以下几个步骤: 1.1.浅层学习和深度学习 浅层学习(Shallow Learning):不涉及特征学习,其特征…...

SElinux 导致 Keepalived 检测脚本无法执行
哈喽大家好,我是咸鱼 今天我们来看一个关于 Keepalived 检测脚本无法执行的问题 一位粉丝后台私信我,说他部署的 keepalived 集群 vrrp_script 模块中的脚本执行失败了,但是手动执行这个脚本却没有任何问题 这个问题也是咸鱼第一次遇到&…...

2022年电赛C题——小车跟随行驶系统——做题记录以及经验分享
前言 自己打算将做过的电赛真题,主要包含控制组的,近几年出现的小车控制题目,自己做过的真题以及在准备电赛期间刷真题出现的问题以及经验分享给大家 这次带来的是22年电赛C题——小车跟随行驶系统,这道题目指定使用的是TI的单片…...

vscode + python
序 参考链接: 【教程】VScode中配置Python运行环境_哔哩哔哩_bilibili Python部分 Python Releases for Windows | Python.org vscode部分 Visual Studio Code - Code Editing. Redefined 一路next,全部勾上: 就可以了: 安装插…...

badgerdb里面的事务
事务的ACID A 原子性(Atomicity) 多步骤操作,只能是两种状态,要么所有的步骤都成功执行,要么所有的步骤都不执行,举例说明就是小明向小红转账30元的场景,拆分成两个步骤,步骤1&#…...

C# this.Invoke(new Action(() => { /* some code */ }))用法说明
在 C# 中,this.Invoke(new Action(() > { /* some code */ })) 是一种用于在 UI 线程上执行代码的方法,通常用于在后台线程中更新 UI 控件的值或执行其他需要在 UI 线程上执行的操作。 在 Windows Forms 或 WPF 等图形界面应用程序中,UI …...

MongoDB:MySQL,Redis,ES,MongoDB的应用场景
简单明了说明MySQL,ES,MongoDB的各自特点,应用场景,以及MongoDB如何使用的第一章节. 一. SQL与NoSQL SQL被称为结构化查询语言.是传统意义上的数据库,数据之间存在很明确的关联关系,例如主外键关联,这种结构可以确保数据的完整性(数据没有缺失并且正确).但是正因为这种严密的结…...

leetcode每日一题_2682.找出转圈游戏输家
2682.找出转圈游戏输家 题目: n 个朋友在玩游戏。这些朋友坐成一个圈,按 顺时针方向 从 1 到 n 编号。从第 i 个朋友的位置开始顺时针移动 1 步会到达第 (i 1) 个朋友的位置(1 < i < n),而从第 n 个朋友的位置开始顺时针移…...

OpenCV之薄板样条插值(ThinPlateSpline)
官方文档:OpenCV: cv::ThinPlateSplineShapeTransformer Class Reference 使用方法: 头文件:#include <opencv2/shape/shape_transformer.hpp> (1)点匹配 一般根据有多少个样本(或者点)…...

034_小驰私房菜_[问题复盘] Qcom平台,某些三方相机拍照旋转90度
全网最具价值的Android Camera开发学习系列资料~ 作者:8年Android Camera开发,从Camera app一直做到Hal和驱动~ 欢迎订阅,相信能扩展你的知识面,提升个人能力~ 【一、问题】 某些三方相机,预览正常,拍照旋转90度 【二、问题排查】 1 ) HAL这边Jpeg编码数据在哪个地方…...

【TI-CCS笔记】工程编译配置 bin文件的编译和生成 各种架构的Post-build配置汇总
【TI-CCS笔记】工程编译配置 bin文件的编译和生成 各种架构的Post-build配置汇总 TI编译器分类 在CCS按照目录下 有个名为${CG_TOOL_ROOT}的目录 其下就是当前工程的编译器 存放目录为: C:\ti\ccs1240\ccs\tools\compiler按类型分为五种: ti-cgt-arm…...

深入探索Java中的File类与IO操作:从路径到文件的一切
文章目录 1. File类的作用与构造方法2. File类常用方法:获取、判断和创建2.1 获取功能方法2.2 判断功能方法2.3 创建和删除功能方法2.4 目录的遍历方法 3. 递归:探索更深的层次代码示例:递归遍历文件夹 结论 🎉欢迎来到Java学习路…...

Python 处理 Excel 表格的 14 个常用操作
目录 1. 安装依赖库 2. 导入库 3. 读取Excel文件 4. 写入Excel文件 5. 创建工作表 6. 访问工作表 7. 读取单元格数据 8. 写入单元格数据 9. 获取行数和列数 10. 过滤数据 11. 排序数据 12. 添加新行 13. 删除行或列 14. 计算汇总统计 总结 无论是数据分析师、财…...

PyQt有哪些主要组件?
这是一个非常强大的跨平台GUI库,可以让你用Python语言创建美观且功能强大的桌面应用程序。让我们先来了解一下它的主要组件。 首先,我们要介绍的是窗口。窗口是PyQt应用程序的基本元素,所有的GUI元素都放置在窗口中。你可以创建主窗口、模态…...

力推C语言必会题目终章(完结篇)
W...Y的主页 😊 代码仓库分享 💕 今天是分享C语言必会题目最终章,全部都是硬货,大家都坐好准备开始喽!!! 编写一个函数,计算字符串中含有的不同字符的个数。字符在 ASCII 码范围内…...

CS5263替代停产IT6561连接DP转HDMI音视频转换器ASL 集睿致远CS5263设计电路原理图
ASL集睿致远CS5263是一款DP1.4到HDMI2.0b转换器芯片,设计用于将DP1.4源连接到HDMI2.0b接收器。 CS5263功能特性: DP接口包括4条主通道、辅助通道和HPD信号。接收器支持每通道5.4Gbps(HBR2)数据速率。DP接收机结合了HDCP1.4和HDCP…...

数据分析 | 随机森林如何确定参数空间的搜索范围
1. 随机森林超参数 极其重要的三个超参数是必须要调整的,一般再加上两到三个其他超参数进行优化即可。 2. 学习曲线确定n_estimators搜索范围 首先导入必要的库,使用sklearn自带的房价预测数据集: import numpy as np import pandas as pd f…...

5G+AI数字化智能工厂建设解决方案PPT
导读:原文《5GAI数字化智能工厂建设解决方案》(获取来源见文尾),本文精选其中精华及架构部分,逻辑清晰、内容完整,为快速形成售前方案提供参考。数字化智能工厂定义 智能基础架构协同框架 - 端、边、云、网…...