MyBatisPlus——学习笔记

MyBatisPlus

一、导入依赖

 <!-- MyBatisPlus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version></dependency><!-- MySql --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency><!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version></dependency>

我们通过在配置文件中添加日志配置,可以看到sql的信息

 # 配置日志mybatis-plus.configuration.logimpl=org.apache.ibatis.logging.stdout.StdOutImpl

二、基本用法

示例表结构:

  1. 实体类User
     @Data@AllArgsConstructor@NoArgsConstructorpublic class User {@TableId(type = IdType.AUTO)private Long id;private String name;private Integer age;private String email;@TableField(fill = FieldFill.INSERT)private LocalDateTime createTime;@TableField(fill = FieldFill.INSERT_UPDATE)private LocalDateTime updateTime;@Version // 乐观锁注解private Integer version;@TableLogic(value = "0",delval = "1") // 逻辑删除注解 , 0表示未删除,1表示删除, 默认值为0. 这里实际上可以设置,也可以不设置private Integer deleted;}​
  1. 创建UserMapper

    传统的MyBatis的方式来做,我们需要创建Mapper层并且创建对应的Mapper.xml文件,而在适用MyBatisPlus时,我们只需要写一个mapper接口继承plus提供的BaseMapper接口,这个接口包含了大部分我们平时需要用到的CRUD操作。

 @Mapperpublic interface UserMapper extends BaseMapper<User> {  // 这里User是实体类。本文都以User这个实体类为例​// 继承baseMapper,已经有了基本的增删改查方法}​

通过查看BaseMapper的源码,我们可以看出,大部分常用的操作已经被封装

  1. 开启扫描

为了能够让SpringBoot扫描并且识别此组件,我们需要在SpringBoot启动类上开启Mapper接口 扫描功能,添加@MapperScan()注解

 //开启扫描,注意包名不要写错@MapperScan("com.qf.mybatisplusdemo.mapper")@SpringBootApplicationpublic class MyBatisPlusDemoApplication {public static void main(String[] args) {SpringApplication.run(MyBatisPlusDemoApplication.class, args);}}​
  1. 测试
    此时,我们算是已经实现了基本的MyBatisplus的配置,可以进行运用package com.mybatisplusdemo;​import com.baomidou.mybatisplus.extension.plugins.pagination.Page;import com.mybatisplusdemo.Mapper.UserMapper;import com.mybatisplusdemo.entity.User;import com.mybatisplusdemo.service.UserService;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;​import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;​@SpringBootTestclass MybatisPlusDemoApplicationTests {@Autowiredprivate UserMapper userMapper;@Autowiredprivate UserService userService;@Testvoid contextLoads() {// 查询全部用户// selectList方法参数为null,表示查询全部用户List<User> users = userMapper.selectList(null);users.forEach(System.out::println);}​@Testpublic void deleteTest(){// 根据map删除对应的数据Map<String,Object> map = new HashMap<>();map.put("name","Jone");map.put("age",18);int num = userMapper.deleteByMap(map);System.out.println("删除的记录数:"+num);}​@Testpublic void  deleteBatchTest(){// 批量删除List<Long> ids = new ArrayList<>();ids.add(2L);ids.add(3L);int num = userMapper.deleteBatchIds(ids);System.out.println("删除的记录数:"+num);}​@Testpublic void updateTest(){// 根据id去修改指定数据User user = new User();user.setId(5l);user.setName("Eason");int num = userMapper.updateById(user);System.out.println("更新的记录数:"+num);}​@Testpublic void selectTest() {// 根据map中传递的条件进行查询Map<String, Object> map = new HashMap<>();map.put("id",4L);map.put("name","Eason");List<User> users = userMapper.selectByMap(map);users.forEach(System.out::println);}}​

三、主键生成策略

常见的一些主键策略有:UUID, Redis生成ID,雪花算法(snowflake)以及zookeeper。

在MyBatisplus中,主要是通过@TableId注解去设置主键生成策略。@TableId的源码如下:

 @Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})public @interface TableId {// 指定数据库表的主键字段名。如果不设置,MyBatis-Plus 将使用实体类中的字段名作为数据库表的主键字段名。String value() default "";  // 主键的生成策略。IdType type() default IdType.NONE;}
IdType的枚举类型定义:

在实体类中我们需要为主键字段加上这个注解,如果主键字段名为id,则可省略,比如这里我将数据库自增ID作为主键。

 public class User {@TableId(type = IdType.AUTO)private Long id;private String name;private Integer age;private String email;}

四、自定义CRUD

MyBatis-Plus的BaseMapper本身提供了很多通用的CRUD方法,极大的方便了我们的代码编写,当然,当我们遇到复杂的方法时,我们需要自定义一些方法,这个时候我们仍然要跟之前mybatis一样去实现sql编写。因为:MyBatis-Plus是在MyBatis的基础之上只做增强不做修改

我们需要配置mapper.xml的文件位置

 # 指定mapper文件位置mybatis-plus.mapper-locations = classpath*:/mapper/**/*.xml

但是实际上这个配置有一个默认的配置路径也就是classpath:/mapper/**/.xml。一般我们都是默认这个路径,可以不再配置

至于其他的操作,我们仍然和Mybatis一致即可,这里不多记录。

五、IService接口

IService 是 MyBatis-Plus 提供的一个通用 Service 层接口,它封装了常见的 CRUD 操作,包括插入、删除、查询和分页等。通过继承 IService 接口,可以快速实现对数据库的基本操作,同时保持代码的简洁性和可维护性。

IService 接口中的方法命名遵循了一定的规范,如 get 用于查询单行,remove 用于删除,list 用于查询集合,page 用于分页查询,这样可以避免与 Mapper 层的方法混淆。

其实一般情况下,由于项目业务的复杂程度,我们都会使用自定义Service方法,那么这些如果我 们想即使用通用的IService接口提供的方法,又有自定义的方法的话,我们可以参考IService接口 的实现类ServiceImpl。

IService源码:

可以看到,IService接口中提供了许多默认的方法供我们使用,我们的Service接口只需要继承它即可

 // 继承IService接口,已经有了基本的增删改查方法public interface UserService extends IService<User> {​}

同样的,在实现类中我们仍然需要继承ServiceImpl实现类,我们可以先看一下ServiceImpl实现类的源码:

那么当我们自定义实现类时,可以根绝ServiceImpl的定义方法去写:

这里 M extends BaseMapper<T> 指的是继承了BaseMapper的Mapper接口,

T 指的是一个实体类

所以,我们的UserServiceImpl可以这么写:

 //按照ServiceImpl实现类编写自己的业务层实现类@Servicepublic class UserServiceImpl extends ServiceImpl<UserMapper, User>implements UserService {//自定义service方法实现}

测试:

 @SpringBootTestclass MyBatisPlusDemoApplicationTests {@Autowiredprivate UserService service;// 利用IService提供的saveBatch方法去进行批量插入@Testpublic void insertBatchTest() {List<User> users = new ArrayList<>();for (int i = 0; i < 10; i++) {User user = new User();user.setName("smlz_" + i);user.setAge(20 + i);user.setEmail("1666189" + i + "@qq.com");users.add(user);}boolean flag = userService.saveBatch(users);System.out.println("批量插入结果:" + flag);}}
 ​

六、自动填充处理

对于创建时间create_time 和 修改时间update_time,我们可以通过MyBatis-plus进行自动填充处理

自动填充功能通过实现 com.baomidou.mybatisplus.core.handlers.MetaObjectHandler 接口来实现。你需要创建一个类来实现这个接口,并在其中定义插入和更新时的填充逻辑。

  1. 实体类定义

    首先我们需要用@TableField 注解来标记哪些字段需要自动填充,并指定填充的策略.

 @TableField(fill = FieldFill.INSERT)private LocalDateTime createTime;@TableField(fill = FieldFill.INSERT_UPDATE)private LocalDateTime updateTime;

填充策略FieldFill枚举类型主要包括以下几个:

他们的含义分别是: 默认不处理,插入时填充字段、更新时填充字段、插入和更新时都填充字段

  1. 实现 MetaObjectHandler
 @Component@Slf4jpublic class MyMetaObjectHandler implements MetaObjectHandler {@Overridepublic void insertFill(MetaObject metaObject) {log.info("start insert fill ....");this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());}​@Overridepublic void updateFill(MetaObject metaObject) {log.info("start update fill ....");this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());}}
 

七、乐观锁与分页插件

乐观锁是一种并发控制机制,用于确保在更新记录时,该记录未被其他事务修改。MyBatis-Plus 提供了 OptimisticLockerInnerInterceptor 插件,使得在应用中实现乐观锁变得简单。

  1. 基本原理

    乐观锁的实现通常包括以下步骤:

    1. 读取记录时,获取当前的版本号(version)。

    2. 在更新记录时,将这个版本号一同传递。

    3. 执行更新操作时,设置 version = newVersion 的条件为 version = oldVersion

    4. 如果版本号不匹配,则更新失败。

  1. 配置乐观锁插件

首先我们需要在实体类中为version字段添加乐观锁注解@version

 @Version//乐观锁注解private Integer version;

然后,我们需要注册组件,也就是乐观锁拦截器,参考官网代码,我们可以创建一个MyBatisPlusConfig配置类:

我们可以将所有关于MyBatisPlus的配置放到这个配置类中,比如之前的MapperScan

@Configuration  // mybatis-plus配置类, 所有的配置都在这里@MapperScan("com.mybatisplusdemo.Mapper")  // 扫描mapper文件夹public class mybatisPlusConfig {/*** 乐观锁拦截器* @return*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 乐观锁插件interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());// 分页插件interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加return interceptor;}}​

测试:

// 单线程测试@Testpublic void  mybatisPlusOptimisticLockerTest(){// 查询User user = userMapper.selectById(1L);// 修改用户信息user.setName("Eason727");user.setAge(28);// 执行更新操作userMapper.updateById(user);}​// 多线程测试,模仿高并发场景@Testpublic void  mybatisPlusOptimisticLockerTest2(){// 查询User user = userMapper.selectById(1L);// 修改用户信息user.setName("Eason1111");user.setAge(28);// 模拟另外一个线程执行了插队操作, 线程2插队---------------------User user2 = userMapper.selectById(1L);user2.setName("Eason2222");user2.setAge(22);userMapper.updateById(user2);// 执行更新操作userMapper.updateById(user);}

在单线程测试中,并不会受到影响,而在多线程测试中,由于第一个线程还未结束就开始了第二个线程,这样会导致第一个线程并没有得到执行。

MyBatis-Plus 的分页插件 PaginationInnerInterceptor 提供了强大的分页功能,支持多种数据库,使得分页查询变得简单高效。

测试:

 @Testpublic void pageTest(){// 简单分页模型// current:当前页 size:每页显示的记录数Page<User> page = new Page<>(2,5);userMapper.selectPage(page,null);​// 获取记录List<User> users = page.getRecords();users.forEach(System.out::println);​// 获取总页数System.out.println("总页数:"+page.getPages());// 获取总记录数System.out.println("总记录数:"+page.getTotal());// 获取当前页System.out.println("当前页:"+page.getCurrent());// 上一页System.out.println("是否有上一页:"+page.hasPrevious());// 下一页System.out.println("是否有下一页:"+page.hasNext());}

八、逻辑删除

逻辑删除是一种优雅的数据管理策略,它通过在数据库中标记记录为“已删除”而非物理删除,来保留数据的历史痕迹,同时确保查询结果的整洁性。MyBatis-Plus 提供了便捷的逻辑删除支持,使得这一策略的实施变得简单高效。

逻辑删除的工作原理

MyBatis-Plus 的逻辑删除功能会在执行数据库操作时自动处理逻辑删除字段。以下是它的工作方式:

  • 插入:逻辑删除字段的值不受限制。

  • 查找:自动添加条件,过滤掉标记为已删除的记录。

  • 更新:防止更新已删除的记录。

  • 删除:将删除操作转换为更新操作,标记记录为已删除。

逻辑删除字段支持所有数据类型,但推荐使用 IntegerBooleanLocalDateTime。如果使用 datetime 类型,可以配置逻辑未删除值为 null,已删除值可以使用函数如 now() 来获取当前时间。

使用方法:

步骤 1: 配置全局逻辑删除属性

application.yml 中配置 MyBatis-Plus 的全局逻辑删除属性:

 mybatis-plus:global-config:db-config:logic-delete-field: deleted # 全局逻辑删除字段名logic-delete-value: 1 # 逻辑已删除值logic-not-delete-value: 0 # 逻辑未删除值

步骤 2: 在实体类中使用 @TableLogic 注解

  @TableLogicprivate Integer deleted;

我们也可以直接在实体类中设置,不用配置yaml文件

 // 逻辑删除注解 , 0表示未删除,1表示删除, 默认值为0. 这里实际上可以设置,也可以不设置    @TableLogic(value = "0",delval = "1") private Integer deleted;

九、条件构造器

MyBatis-Plus 提供了一套强大的条件构造器(Wrapper),用于构建复杂的数据库查询条件。Wrapper 类允许开发者以链式调用的方式构造查询条件,无需编写繁琐的 SQL 语句,从而提高开发效率并减少 SQL 注入的风险。

AbstractWrapper:这是一个抽象基类,提供了所有 Wrapper 类共有的方法和属性。此外,QueryWrapperUpdateWrapperLambdaQueryWrapperLambdaUpdateWrapper.这个四个类分别实现了关于查询、更新条件的封装以及对应的具有Lambda语法的查询、更新条件封装。

一些基本用法:​

@SpringBootTestpublic class WrapperTest {@Autowiredprivate UserMapper userMapper;​@Testvoid selectTest1(){QueryWrapper<User> wrapper = new QueryWrapper<>();// 查询条件: name不为空,age大于20,email不为空wrapper.isNotNull("name").ge("age",20).isNotNull("email");List<User> users = userMapper.selectList(wrapper);// 根据条件查询用户users.forEach(System.out::println);}​// 查询名字为eason的用户@Testvoid selectTest2(){QueryWrapper<User> wrapper = new QueryWrapper<>();// 查询条件: name为Easonwrapper.eq("name","Eason");User user = userMapper.selectOne(wrapper);  // selectOne方法返回查询得到的一条数据System.out.println(user);}​// 查询年龄在20-30之间的用户@Testvoid selectTest3(){QueryWrapper<User> wrapper = new QueryWrapper<>();// 查询条件: age在20-25之间wrapper.between("age",20,25);userMapper.selectList(wrapper).forEach(System.out::println);}​// 模糊查询, 查询名字中不包含e的用户. like就是包含,notLike就是不包含​@Testvoid selectTest4(){QueryWrapper<User> wrapper = new QueryWrapper<>();// 查询条件: name不包含ewrapper.notLike("name","e");userMapper.selectList(wrapper).forEach(System.out::println);}​// 模糊查询,包含左侧,或者右侧@Testvoid selectTest5(){QueryWrapper<User> wrapper = new QueryWrapper<>();// 查询条件: name的右侧包含ewrapper.likeRight("name","e");userMapper.selectList(wrapper).forEach(System.out::println);}​// 查询用户名中包含e,年龄大于20或者邮箱为null的用户@Testvoid selectTest6(){QueryWrapper<User> wrapper = new QueryWrapper<>();// 查询条件: name包含e,年龄大于20或者邮箱为nullwrapper.like("name","e").and(wq->wq.ge("age",20).or().isNull("email"));userMapper.selectList(wrapper).forEach(System.out::println);}​​// 模糊查询, 查询名字中不包含e的用户. like就是包含,notLike就是不包含// 这里尝试了添加Condition条件//我们在写项目的时候,所有的条件都是由用户进行传递的,那么有的时候就无法避免参数出现空//值null的情况,所以我们应该要做一些判断,其实很多方法都提供了boolean condition这个参// 数,表示该条件是否加入最后生成的sql中,也就是可以通过它来进行判断@Testvoid selectTest7(){// 假设用户传递了参数String name = "e";Integer age = null;​QueryWrapper<User> wrapper = new QueryWrapper<>();// 如果name不为空,就根据name模糊查询wrapper.like(StringUtils.isNotBlank(name),"name",name).orderByAsc(age!=null,"age");  // 如果age不为空,就根据age升序排列userMapper.selectList(wrapper).forEach(System.out::println);}// -------------------------QueryWrapper执行修改和删除操作----------------------------------// 修改用户信息@Testvoid updateTest1(){QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.eq("id",6L);User user = new User();user.setName("ChanEx");// 根据条件更新用户信息userMapper.update(user,wrapper);}​// 删除用户信息@Testvoid deleteTest1(){QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.eq("id",6L);// 根据条件删除用户信息userMapper.delete(wrapper);}//-------------------------------UpdateWrapper--------------------------------------------// 修改年龄大于26,且name为theshy的用户邮箱为19999@163.com@Testpublic void updateTest2() {UpdateWrapper<User> wrapper = new UpdateWrapper<>();wrapper.gt("age",26).eq("name","theshy").set("email","19999@163.com");userMapper.update(null,wrapper);  // 第一个参数为null,表示更新所有符合条件的记录}​// -------------------------------LambdaQueryWrapper&LambdaUpdateWrapper-----------------------//它们两个的主要目的是为了防止我们在编写的时候,字段名称编写错误,我们可以直接通过//Lambda的方式来直接获取指定字段对应的实体类对应的名称 // 模糊查询, 查询名字中不包含e的用户. like就是包含,notLike就是不包含@Testvoid selectTest9(){// 假设用户传递了参数String name = "e";Integer age = null;​LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.like(StringUtils.isNotBlank(name),User::getName,name).orderByAsc(age!=null,User::getAge);  // 如果age不为空,就根据age升序排列userMapper.selectList(wrapper).forEach(System.out::println);}​@Testvoid updateTest3(){LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();wrapper.gt(User::getAge,26).eq(User::getName,"theshy").set(User::getEmail,"10086@gmali.como");userMapper.update(null,wrapper);}// 通过子查询,查询id等于6的用户信息@Testvoid selectTest10(){QueryWrapper<User> wrapper = new QueryWrapper<>();// inSql方法可以传入一个子查询, 可以用于表关联查询wrapper.inSql("id","select id from user where id = 7");userMapper.selectObjs(wrapper).forEach(System.out::println);}}

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

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

相关文章

【AIGC】VoiceControl for ChatGPT指南:轻松开启ChatGPT语音对话模式

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;安装VoiceControl for ChatGPT插件&#x1f4af;如何使用VoiceControl for ChatGPT进行语音输入VoiceControl for ChatGPT快捷键注意点 &#x1f4af;VoiceControl for C…

mfc140u.dll缺失?快速解决方法全解析,解决mfc140u.dll错误

当你的电脑出现找不到mfc140u.dll的问题&#xff0c;不少用户在使用电脑时陷入了困扰。这个错误提示就像一道屏障&#xff0c;阻挡了用户正常使用某些软件。无论是办公软件、游戏还是专业的设计工具&#xff0c;一旦出现这个问题&#xff0c;都会导致软件无法正常运行。如果您也…

webGL入门(六)图形旋转

效果&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</…

ndb9300public-ndb2excel简介

1 引言 ndb9300是一个自己定义的机载导航数据库劳作&#xff08;不敢称为项目&#xff09;代号&#xff0c;其中3表示是第3种数据库。 多年前&#xff0c;对在役民航客机中的某型机载导航数据库的二进制文件进行分析&#xff0c;弄明白它的数据结构后做了几个工具&#xff0c…

Redis-持久化机制

Redis持久化方式 rdb -> 全量 aof -> 增量 也可以两种同时开启&#xff0c;混合持久化(4.0 后) rdb 简介 配置文件 redis 6.0.16 及其以下 redis 6.2 7.0 配置说明 有两种触发方式&#xff1a;手动&#xff0c;自动 修改 save 5 2dir /myredis/dump (储存的文件夹需…

【机器学习】探索GRU:深度学习中门控循环单元的魅力

目录 &#x1f354; GRU介绍 &#x1f354; GRU的内部结构图 2.1 GRU结构分析 2.2 GRU工作原理 2.4 Bi-GRU介绍 2.3 使用Pytorch构建GRU模型 2.5 GRU优缺点 &#x1f354; 小结 学习目标 &#x1f340; 了解GRU内部结构及计算公式. &#x1f340; 掌握Pytorch中GRU工具…

1000题-计算机网络系统概述

术语定义与其他术语的关系SDU&#xff08;服务数据单元&#xff09;相邻层间交换的数据单元&#xff0c;是服务原语的表现形式。在OSI模型中&#xff0c;SDU是某一层待传送和处理的数据单元&#xff0c;即该层接口数据的总和。 - SDU是某一层的数据集&#xff0c;准备传递给下一…

【开源免费】基于SpringBoot+Vue.JS洗衣店订单管理系统(JAVA毕业设计)

本文项目编号 T 068 &#xff0c;文末自助获取源码 \color{red}{T068&#xff0c;文末自助获取源码} T068&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 顾…

vue框架学习 -- 日历控件 FullCalendar 使用总结

最近在项目中要实现日期排班的功能&#xff0c;正好要用到日历视图的控件&#xff0c;经过对比发现&#xff0c;vue 中 使用 FullCalendar 可以实现相关需求&#xff0c;下面对使用过程做一个总结。 一. 引入 FullCalendar 控件 package.json 中添加相关依赖 "dependen…

【CSS in Depth 2 精译_043】6.5 CSS 中的粘性定位技术 + 本章小结

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一章 层叠、优先级与继承&#xff08;已完结&#xff09;第二章 相对单位&#xff08;已完结&#xff09;第三章 文档流与盒模型&#xff08;已完结&#xff09;第四章 Flexbox 布局&#xff08;已…

.net Framework 4.6 WebAPI 使用Hangfire

C# 使用 Hangfire 第一章 .net Framework 4.6 WebAPI 使用Hangfire 文章目录 C# 使用 Hangfire前言一、hangfire是什么?二、hangfire的特点三、.net Framework 中hangfire的使用方法第一步:创建WebAPI控制器第二步:添加nuget包第三步 创建startup类新建项目startup类Startu…

Rust 语言开发 ESP32C3 并在 Wokwi 电子模拟器上运行(esp-hal 非标准库、LCD1602、I2C)

文章目录 esp-rs 简介GithubRust 包仓库Rust 教程Wokwi 电子模拟器开发环境Rust 环境esp-rs 环境创建 ESP32C3 项目项目结构编译项目命令运行模拟器ESP32C3 烧录 esp-rs 简介 esp-rs 是一个专注于为 Espressif 系列芯片&#xff08;如 ESP32、ESP32-S2、ESP32-C3 等&#xff0…

TypeScript 算法手册 - 【冒泡排序】

文章目录 TypeScript 算法手册 - 冒泡排序1. 冒泡排序简介1.1 冒泡排序定义1.2 冒泡排序特点 2. 冒泡排序步骤过程拆解2.1 比较相邻元素2.2 交换元素2.3 重复过程 3. 冒泡排序的优化3.1 提前退出3.2 记录最后交换位置案例代码和动态图 4. 冒泡排序的优点5. 冒泡排序的缺点总结 …

Unity3D播放GIF图片使用Animation来制作动画

系列文章目录 unity工具 文章目录 系列文章目录👉前言👉一、下载GIF动图,用PS制作导出帧动画图片👉二、使用Animation制作动画👉三、脚本控制动画播放👉壁纸分享👉总结👉前言 unity播放gif图片,本身是不支持的,但是可以使用其他方法来实现, 1.有一种使用System…

微信小程序hbuilderx+uniapp+Android 新农村综合风貌旅游展示平台

目录 项目介绍支持以下技术栈&#xff1a;具体实现截图HBuilderXuniappmysql数据库与主流编程语言java类核心代码部分展示登录的业务流程的顺序是&#xff1a;数据库设计性能分析操作可行性技术可行性系统安全性数据完整性软件测试详细视频演示源码获取方式 项目介绍 小程序端…

计算机网络:计算机网络概述 —— 网络拓扑结构

文章目录 网络拓扑总线型拓扑特点缺陷 星型拓扑特点缺陷 环型拓扑特点缺陷 网状型拓扑优点缺陷 树型拓扑特点缺陷 网络拓扑 网络拓扑指的是计算机网络中节点&#xff08;计算机、交换机、路由器等&#xff09;之间物理或逻辑连接的结构。网络拓扑定义了节点之间的布局、连接方…

JAVA基础语法 Day11

一、Set集合 Set特点&#xff1a;无序&#xff08;添加数据的顺序和获取出的数据顺序不一致&#xff09;&#xff0c;不重复&#xff0c;无索引 public class demo1 {public static void main(String[] args) {//1.创建一个集合//HashSet特点&#xff1a;无序&#xff0c;不重…

算法笔记(七)——哈希表

文章目录 两数之和判定是否互为字符重排存在重复元素存在重复元素 II字母异位词分组 哈希表&#xff1a;一种存储数据的容器&#xff1b; 可以快速查找某个元素&#xff0c;时间复杂度O(1)&#xff1b; 当频繁查找某一个数时&#xff0c;我们可以使用哈希表 创建一个容器&#…

SpringBoot使用EasyPoi根据模板导出word or pdf

1、导出效果 1.1 wrod 1.2 pdf 2、依赖 <!--word--><dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-base</artifactId><version>4.3.0</version></dependency><dependency><groupId>cn.…

ESP32 Bluedroid 篇(1)—— ibeacon 广播

前言 前面我们已经了解了 ESP32 的 BLE 整体架构&#xff0c;现在我们开始实际学习一下Bluedroid 从机篇的广播和扫描。本文将会以 ble_ibeacon demo 为例子进行讲解&#xff0c;需要注意的一点是。ibeacon 分为两个部分&#xff0c;一个是作为广播者&#xff0c;一个是作为观…