SSM框架学习(七、MyBatis-Plus高级用法:最优化持久层开发)

目录

一、MyBatis-Plus快速入门

1.简介

 2.快速入门

二、MyBatis-Plus核心功能

1.基于Mapper接口CRUD

(1)Insert 方法

(2)Delete方法

 (3)Update 方法

(4)Select方法

 2.基于Service接口CRUD

(1)save 方法 

 (2)saveOrUpdate方法

(3)remove 方法

(4)update 方法 

(5)get 和 list 方法 

3.分页查询实现 

4.条件构造器使用 

(1)条件构造器继承

(2)条件构造器继承结构

(3)基于QueryWrapper 组装条件

Ⅰ.组装查询条件 

Ⅱ. 组装排序条件

 Ⅲ. 组装删除条件

Ⅳ. and 和 or 关键字使用(修改): 

Ⅴ. 指定列映射查询 

 Ⅵ. condition判断组织条件

(4)基于 UpdateWrapper 组装条件

(5)基于LambdaQueryWrapper 组装条件 

(6)基于 LambdaUpdateWrapper 组装条件

5.核心注解使用

(1)理解和介绍

(2)@TableName 注解 

(3)@TableId 注解

(4)@TableField 注解

三、MyBatis-Plus 高级扩展 

1.逻辑删除实现

 2.乐观锁实现

(1)悲观锁和乐观锁场景和介绍

(2)使用mybatis-plus数据使用乐观锁(基于版本号技术)

3.防全表更新和删除实现

 四、MyBatis-Plus代码生成器(MyBatisX插件)

1.Mybatisx 插件逆向工程

2.MyBatisX 快速代码生成 


一、MyBatis-Plus快速入门

1.简介

MyBatis-Plus  (opens new window)(简称 MP)是一个 MyBatis  (opens new window) 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

特性:

① 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑

② 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作

③ 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求

④ 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错

⑤ 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题

⑥ 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作

⑦ 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )

⑧ 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用

⑨ 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询

⑩ 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库

⑪ 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询

⑫ 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

⑬ 支持数据库:

MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb,informix,TDengine,redshift

达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库,优炫数据库

mybatis-plus总结:

自动生成单表的CRUD功能

提供丰富的条件拼接方式

全自动ORM类型持久层框架

 2.快速入门

① 准备数据库脚本

CREATE TABLE user
(id BIGINT(20) NOT NULL COMMENT '主键ID',name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',age INT(11) NULL DEFAULT NULL COMMENT '年龄',email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',PRIMARY KEY (id)
);INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

② 准备boot工程,导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.0.5</version></parent><groupId>com.mihoyo</groupId><artifactId>mybatis-plus-quick-01</artifactId><version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!-- 测试环境 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><!-- mybatis-plus  --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version></dependency><!-- 数据库相关配置启动器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><!-- druid启动器的依赖  --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-3-starter</artifactId><version>1.2.21</version></dependency><!-- 驱动类--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.28</version></dependency></dependencies><!--    SpringBoot应用打包插件--><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

③ 配置文件和启动类

application.yaml:

# 连接池配置
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedruid:url: jdbc:mysql:///mybatis-exampleusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Drivermybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制态输入日志(sql语句)type-aliases-package: com.mihoyo.pojo #起别名

注意:

mybatis-plus 已经将驼峰式映射的默认值设置为 true, 无需手动设置

启动类:

@MapperScan("com.mihoyo.mapper")
@SpringBootApplication
public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class,args);}}

④ 功能编码

编写实体类 User.java(此处使用了 Lombok 简化代码)

@Data
public class User {private Long id;private String name;private Integer age;private String email;
}

编写 Mapper 包下的 UserMapper 接口

public interface UserMapper extends BaseMapper<User> {}

注意:继承mybatis-plus提供的基础Mapper接口,自带 crud 方法!

⑤ 测试和使用

添加测试类,进行功能测试:

@SpringBootTest //springboot下测试环境注解
public class MybatisPlusTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testSelect() {//查询全部数据List<User> userList = userMapper.selectList(null);//条件为空userList.forEach(System.out::println);}
}

总结:

通过以上几个简单的步骤,我们就实现了 User 表的 CRUD 功能,甚至连 XML 文件都不用编写

从以上步骤中,我们可以看到集成 MyBatis-Plus 非常的简单,只需要引入 starter 工程,并配置 mapper 扫描路径即可。

二、MyBatis-Plus核心功能

1.基于Mapper接口CRUD

通用 CRUD 封装 BaseMapper (opens new window) 接口, Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器。

内部包含常见的单表操作:

(1)Insert 方法

// 插入一条记录
// T 就是要插入的实体对象
// 默认主键生成策略为雪花算法(后面讲解)
int insert(T entity);

参数说明: 

类型参数名描述
Tentity实体对象

注意:

T,即实体类名 必须要和 数据库表名 相同,Mybatis-Plus 会根据 实体类名 自动映射到 数据库对应的表。

测试:

@SpringBootTest //springboot下测试环境注解
public class MybatisPlusTest {@Autowiredprivate UserMapper userMapper;@Testpublic void test_insert(){User user = new User();user.setName("张三");user.setAge(18);user.setEmail("xxxx@qq.com");//baseMapper提供的数据库插入方法int row = userMapper.insert(user);System.out.println("row = " + row);}
}

运行结果:

(2)Delete方法

// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);// 根据 ID 删除
int deleteById(Serializable id);// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

参数说明: 

类型参数名描述
Wrapper<T>wrapper实体对象封装操作类(可以为 null)
Collection<? extends Serializable>idList主键 ID 列表(不能为 null 以及 empty)
Serializableid主键 ID
Map<String, Object>columnMap表字段 map 对象

测试:

    @Testpublic void test_delete(){//根据id删除int rows = userMapper.deleteById(1846463349906759682L);System.out.println("rows = " + rows);//根据条件(age=20)删除Map param = new HashMap();param.put("age",20);//如果有其他条件继续putint i = userMapper.deleteByMap(param);System.out.println("i = " + i);}

运行结果:

 (3)Update 方法

// 根据 whereWrapper 条件,更新记录
int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper);// 根据 ID 修改  主键属性必须值
int updateById(@Param(Constants.ENTITY) T entity);

参数说明: 

类型参数名描述
Tentity实体对象 (set 条件值,可为 null)
Wrapper<T>updateWrapper实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)

测试:

    @Testpublic void test_update() {//user  id=1的age改为30User user = new User();user.setId(1L);user.setAge(30);// update user set age=30 where id=1(当有属性值为空时,该属性不修改)int i = userMapper.updateById(user);System.out.println("i = " + i);//将所有人的年龄改为22User user1 = new User();user1.setAge(22);// update user set age=22int rows = userMapper.update(user1, null);}

注意:

根据 id 修改,user 的 id 属性必须给值

② 修改时,如果 user 有部分属性为空,该属性就不修改

③ 由于 ② 的因素,属性的类型必须为包装类型。 如果是基本数据类型,会存在默认值。

比如:

age 如果定义成 int 类型,即使没有通过 set 方法赋值,也会有默认值 0,修改时会被修改为 0。
如果定义成 Integer 类型,没有赋值就是 null,就不会发生修改。

 运行结果:

(4)Select方法

// 根据 ID 查询
T selectById(Serializable id);// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

参数说明: 

类型参数名描述
Serializableid主键 ID
Wrapper<T>queryWrapper实体对象封装操作类(可以为 null)
Collection<? extends Serializable>idList主键 ID 列表(不能为 null 以及 empty)
Map<String, Object>columnMap表字段 map 对象
IPage<T>page分页查询条件(可以为 RowBounds.DEFAULT)

测试:

    @Testpublic void test_select(){//根据id查询User user = userMapper.selectById(1);System.out.println("user = " + user);//根据id集合查询List<Long> ids=new ArrayList<>();ids.add(1L);ids.add(2L);List<User> list = userMapper.selectBatchIds(ids);System.out.println("list = " + list);}

运行结果:

 2.基于Service接口CRUD

通用 Service CRUD 封装 IService (opens new window) 接口,进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆。

对比Mapper接口CRUD区别:

① service添加了批量方法

② service层的方法自动添加事务

使用 Iservice 接口方式:

接口继承 IService 接口

public interface UserService extends IService<User> {
}

实现类继承 ServiceImpl 实现类

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService{}

注意:

IService 接口中有一半方法默认实现,还有一半没有实现的抽象方法。

所以想要使用 IService 中的 crud 方法,还得重写另一半没有实现的方法。

ServiceImpl类 实现了 IService 接口,已经重写另一半方法,我们可以直接继承。

ServiceImpl 底层还是通过 mapper 对象调用方法,所以泛型中需要对应的 mapper 接口

(1)save 方法 

// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);

参数说明: 

类型参数名描述
Tentity实体对象
Collection<T>entityList实体对象集合
intbatchSize插入批次数量

返回值: boolean,表示插入操作是否成功。 

测试:

    @Testpublic void test_save() {User user1 = new User();user1.setAge(18);user1.setName("张三");user1.setEmail("xxx@qq.com");User user2 = new User();user2.setAge(20);user2.setName("李四");user2.setEmail("yyy@qq.com");List<User> list = new ArrayList<>();list.add(user1);list.add(user2);boolean b = userService.saveBatch(list);System.out.println("b = " + b);}

运行结果:

 (2)saveOrUpdate方法

// TableId 注解存在更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);

参数说明:

类型参数名描述
Tentity实体对象
Wrapper<T>updateWrapper实体对象封装操作类 UpdateWrapper
Collection<T>entityList实体对象集合
intbatchSize插入批次数量

返回值: boolean,表示插入或更新操作是否成功。 

测试:

    @Testpublic void test_saveOrUpdate() {/* 如果id有值,不为null --> 修改如果id没值,为null  --> 保存(插入)*/User user1 = new User();user1.setAge(18);user1.setName("Tom");user1.setEmail("xxx@qq.com");boolean b1 = userService.saveOrUpdate(user1);//插入System.out.println("b1 = " + b1);User user2 = new User();user2.setId(1L);user2.setAge(20);user2.setName("Jerry");user2.setEmail("yyy@qq.com");boolean b2 = userService.saveOrUpdate(user2);//修改System.out.println("b2 = " + b2);}

运行结果:

(3)remove 方法

// 根据 queryWrapper 设置的条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);

参数说明:

类型参数名描述
Wrapper<T>queryWrapper实体包装类 QueryWrapper
Serializableid主键 ID
Map<String, Object>columnMap表字段 map 对象
Collection<? extends Serializable>idList主键 ID 列表

返回值: boolean,表示删除操作是否成功。

测试:

    @Testpublic void test_remove() {boolean b = userService.removeById(1846557126650564609L);System.out.println("b = " + b);}

 运行结果:

(4)update 方法 

// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);

参数说明:

类型参数名描述
Wrapper<T>updateWrapper实体对象封装操作类 UpdateWrapper
Tentity实体对象
Collection<T>entityList实体对象集合
intbatchSize更新批次数量

返回值: boolean,表示更新操作是否成功。 

测试:

    @Testpublic void test_update() {User user = new User();user.setId(1L);user.setAge(18);user.setName("Tom");user.setEmail("xxx@qq.com");boolean b = userService.updateById(user);System.out.println("b = " + b);}

运行结果:

(5)get 和 list 方法 

查询:
// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);集合:
// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

参数说明:

类型参数名描述
Serializableid主键 ID
Wrapper<T>queryWrapper实体对象封装操作类 QueryWrapper
booleanthrowEx有多个 result 是否抛出异常
Tentity实体对象
Function<? super Object, V>mapper转换函数
Collection<? extends Serializable>idList主键 ID 列表
Map<String, Object>columnMap表字段 map 对象

返回值: 查询结果,可能是实体对象、Map 对象或其他类型。

测试:

    @Testpublic void test_getOrList(){User user = userService.getById(1L);// get返回的是单个对象System.out.println("user = " + user);List<User> list = userService.list(null);//查询全部,list返回的是一个集合System.out.println("list = " + list);}

运行结果:

3.分页查询实现 

① 导入分页插件

@MapperScan("com.mihoyo.mapper")
@SpringBootApplication
public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class,args);}//mybatis-plus插件集合加入到ioc容器@Beanpublic MybatisPlusInterceptor plusInterceptor(){//mybatis-plus的插件集合(所有插件都加入到这个集合:分页插件,乐观锁插件...)MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();//加入分页插件(参数:数据库类型)mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return mybatisPlusInterceptor;}}

② 使用分页查询

@SpringBootTest
public class MybatisPlusTest {@Autowiredprivate UserMapper userMapper;@Testpublic void test_page(){//接口:IPage --> 实现类:Page(页码,页容量)Page<User> page = new Page<>(1,3);userMapper.selectPage(page, null);//结果也会被封装进page中long current = page.getCurrent();//页码System.out.println("current = " + current);long size = page.getSize();//页容量System.out.println("size = " + size);List<User> records = page.getRecords();//当前页的数据System.out.println("records = " + records);long total = page.getTotal();//总条数System.out.println("total = " + total);}
}

**************** Question:如果想要自定义一个能够分页的查询方法,如何实现呢?****************

步骤:

① mapper 接口定义方法

public interface UserMapper extends BaseMapper<User> {//定义一个方法:根据age进行查询,且分页IPage<User> queryByAge(IPage<User> page, @Param("age") Integer age);
}

② mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.mihoyo.mapper.UserMapper"><!-- 查询返回的是user集合,所以resultType是集合的泛型,也就是page的泛型user --><select id="queryByAge" resultType="user">select * from user where age > #{age}</select>
</mapper>

③ 测试

    @Testpublic void testMyPage(){Page<User> page = new Page<>(1,3);userMapper.queryByAge(page,1);long current = page.getCurrent();//页码System.out.println("current = " + current);long size = page.getSize();//页容量System.out.println("size = " + size);List<User> records = page.getRecords();//当前页的数据System.out.println("records = " + records);long total = page.getTotal();//总条数System.out.println("total = " + total);}

运行结果: 

4.条件构造器使用 

(1)条件构造器继承

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "John"); // 添加等于条件
queryWrapper.ne("age", 30); // 添加不等于条件
queryWrapper.like("email", "@gmail.com"); // 添加模糊匹配条件
等同于: 
delete from user where name = "John" and age != 30and email like "%@gmail.com%"
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);

使用MyBatis-Plus 的条件构造器,你可以构建灵活、高效的查询条件,而不需要手动编写复杂的 SQL 语句。

它提供了许多方法来支持各种条件操作符,并且可以通过链式调用来组合多个条件。这样可以简化查询的编写过程,并提高开发效率。 

(2)条件构造器继承结构

条件构造器类结构:

Wrapper : 条件构造抽象类,最顶端父类

  • AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
    • QueryWrapper : 查询/删除条件封装
    • UpdateWrapper : 修改条件封装
    • AbstractLambdaWrapper : 使用 Lambda 语法
      • LambdaQueryWrapper :用于 Lambda 语法使用的查询 Wrapper
      • LambdaUpdateWrapper : Lambda 更新封装 Wrapper

(3)基于QueryWrapper 组装条件

Ⅰ.组装查询条件 

需求:查询用户名包含 a,年龄在 20 到 30 之间,并且邮箱不为空的用户信息 

@SpringBootTest
public class MybatisPlusQueryWrapper {@Autowiredprivate UserMapper userMapper;@Testpublic void test_01(){QueryWrapper<User> queryWrapper = new QueryWrapper<>();//条件(动态调用wrapper的方法完成拼接)/*queryWrapper.like("name","a");queryWrapper.between("age",20,30);queryWrapper.isNotNull("email");*///链式调用queryWrapper.like("name","a").between("age",20,30).isNotNull("email");//select * from user where name like "%a%" and age >= 20 age <= 30 email is not nullList<User> users = userMapper.selectList(queryWrapper);}
}

运行结果:

Ⅱ. 组装排序条件

需求:按年龄降序查询用户,如果年龄相同则按 id 升序排列

    @Testpublic void test_02(){QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.orderByDesc("age").orderByAsc("id");// order by age desc, id asc;List<User> users = userMapper.selectList(queryWrapper);}

运行结果:

 Ⅲ. 组装删除条件

需求:删除 email 为空的用户

    @Testpublic void test_03() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.isNull("email");userMapper.delete(queryWrapper);}

运行结果:

Ⅳ. and 和 or 关键字使用(修改): 

需求:将年龄大于 20 并且用户名中包含有 a邮箱为 null 的用户信息修改

    @Testpublic void test_04(){QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.gt("age",20).like("name","a") //条件直接调用方法,默认 and 拼接.or().isNull("email"); //想要使用 or 拼接,前面必须紧跟着一个 or()User user = new User();user.setAge(88);user.setEmail("hehehe@qq.com");userMapper.update(user,queryWrapper);}

运行结果:

Ⅴ. 指定列映射查询 

需求:查询用户信息的 name 和 age 字段

    @Testpublic void test_05() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.gt("id", 1L);//默认是查询全部列queryWrapper.select("name", "age");//指定要查询的列名userMapper.selectList(queryWrapper);}

运行结果:

 Ⅵ. condition判断组织条件

需求:前端传入了两个参数(name 和 age),如果 name 不为空,作为 = 条件查询。age > 18,作为 = 条件查询

方案一:手动判断

    @Testpublic void test_06() {//模拟前端输入String name = "xxx";Integer age = 20;QueryWrapper<User> queryWrapper = new QueryWrapper<>();//动态条件判断if (!StringUtils.isBlank(name)) {queryWrapper.eq("name", name);}if (age != null || age > 18) {queryWrapper.eq("age", age);}userMapper.selectList(queryWrapper);}

方案二:拼接 condition 判断

每个条件方法都会有一个 boolean 类型的 condition,允许我们放入一个比较表达式。

如果表达式为 true,整个条件生效;否则不生效。

    @Testpublic void test_06() {//模拟前端输入String name = "xxx";Integer age = 20;QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq(!StringUtils.isBlank(name),"name",name);queryWrapper.eq(age != null || age > 18,"age", age);userMapper.selectList(queryWrapper);}

运行结果:

(4)基于 UpdateWrapper 组装条件

需求:将年龄大于 20 并且用户名中包含有 a邮箱为 null 的用户信息修改

使用 queryWrapper:

    @Testpublic void test_04(){QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.gt("age",20).like("name","a") //条件直接调用方法,默认 and 拼接.or().isNull("email"); //想要使用 or 拼接,前面必须紧跟着一个 or()User user = new User();user.setAge(88);user.setEmail("hehehe@qq.com");userMapper.update(user,queryWrapper);}

缺点:

必须要准备修改的实体类数据(User 对象)

不能将某个属性值修改为 null。(queryWrapper 发现该属性值是 null,就不会对其修改)


使用 updateWrapper: 

    @Testpublic void test_update(){UpdateWrapper<User> queryWrapper = new UpdateWrapper<>();queryWrapper.gt("age",20).like("name","a").or().isNull("email").set("name",null)//直接修改,可以是任意值.set("age",99);userMapper.update(null,queryWrapper);}

优点:

可以通过 set 方法直接携带修改数据

可以修改成任意数据

运行结果:

(5)基于LambdaQueryWrapper 组装条件 

需求:查询用户名包含 a,年龄在 20 到 30 之间,并且邮箱不为空的用户信息 

    @Testpublic void test_01() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.like("name", "a").between("age", 20, 30).isNotNull("email");//select * from user where name like "%a%" and age >= 20 age <= 30 email is not nullList<User> users = userMapper.selectList(queryWrapper);}

缺点:

在条件方法(like,between...)中,我们每次都要书写属性名的字符串,这很容易出现书写错误!

使用方法引用,可以有效避免这个问题。 

    @Testpublic void test_01() {//使用LambdaQueryWrapperLambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.like(User::getName, "a").between(User::getAge, 20, 30).isNotNull(User::getEmail);//select * from user where name like "%a%" and age >= 20 age <= 30 email is not nullList<User> users = userMapper.selectList(queryWrapper);}

注意:

虽然被引用处是一个函数式接口,可以使用 Lambda 表达式。 

但由于 MybatisPLus 的 LambdaQueryWrapper 在内部对 SFunction 接口 进行了封装,只有使用 方法引用 能确保发挥 LambdaQueryWrapper 的完整功能,并避免翻译错误。

所以,不能直接使用 Lambda 表达式:like(user -> user.getName(),"a")

(6)基于 LambdaUpdateWrapper 组装条件

需求:将年龄大于 20 并且用户名中包含有 a邮箱为 null 的用户信息修改

使用 updateWrapper: 

    @Testpublic void test_update(){UpdateWrapper<User> queryWrapper = new UpdateWrapper<>();queryWrapper.gt("age",20).like("name","a").or().isNull("email").set("name",null)//直接修改,可以是任意值.set("age",99);userMapper.update(null,queryWrapper);}

使用 LambdaUpdateWrapper:

    @Testpublic void test_update(){//使用LambdaUpdateWrapperLambdaUpdateWrapper<User> queryWrapper = new LambdaUpdateWrapper<>();queryWrapper.gt(User::getAge,20).like(User::getName,"a").or().isNull(User::getEmail).set(User::getAge,null).set(User::getEmail,99);userMapper.update(null,queryWrapper);}

5.核心注解使用

(1)理解和介绍

MyBatis-Plus是一个基于 MyBatis 框架的增强工具,提供了一系列简化和增强的功能,用于加快开发人员在使用 MyBatis 进行数据库访问时的效率。

MyBatis-Plus 提供了一种基于注解的方式来定义和映射数据库操作,其中的注解起到了重要作用。

理解:

public interface UserMapper extends BaseMapper<User> {}

此接口对应的方法为什么会自动触发 user 表的crud呢?

默认情况下, 根据指定的 <实体类> 的名称对应数据库表名,属性名对应数据库的列名。

但不是所有数据库的信息和实体类都完全映射!

例如: 表名 t_user → 实体类 User 这时候就不对应了。

自定义映射关系就可以使用 mybatis-plus 提供的注解即可!

(2)@TableName 注解 

描述:表名注解,标识实体类对应的表

使用位置:实体类

@TableName("t_user") //对应数据库表名
public class User {private Long id;private String name;private Integer age;private String email;
}

注意:

① 特殊情况:如果表名和实体类名相同(忽略大小写)可以省略该注解

如果每个数据库表都以 t_ 开头,就要对每个实体类加上 @TableName 注解,过于麻烦。

可以在 application.yml 中设置 全局设置前缀:

mybatis-plus: # mybatis-plus的配置global-config:db-config:table-prefix: t_ # 表名前缀字符串

(3)@TableId 注解

描述:主键注解

使用位置:实体类主键字段

@TableName("t_user")
public class User {@TableId(value="主键列名",type=主键策略)private Long id;private String name;private Integer age;private String email;
}

属性: 

属性类型必须指定默认值描述
valueString""主键字段名
typeEnumIdType.NONE指定主键类型

IdType 属性可选值: 

描述
AUTO数据库 ID 自增 (mysql 配置主键自增长 auto_increment)
ASSIGN_ID(默认)

分配 ID(主键类型为 Number(Long )或 String)(since 3.3.0),

使用接口 IdentifierGenerator 的 nextId 方法

(默认实现类为 DefaultIdentifierGenerator 雪花算法)

细节:

① 雪花算法(默认)

  • 数据库主键类型设置:bigint / varchar(64)
  • 实体类主键类型设置:long / string
  • 随机生成一个主键值,与之前的不会重复

② auto:mysql 在创建数据库表时,必须要给主键设置自增:auto_increment

③ 如果有多个表都想设置主键为自增,一个个设置会很麻烦,可以在 application.yml 中全局设置 :

mybatis-plus: # mybatis-plus的配置global-config:db-config:id-type: auto # 全局设置主键策略

使用场景:

主键的列名 与 实体类的属性名不一致 --> 使用 value 指定主键名

(u_id --> uid 是一致的,因为 MyBatis-Plus 会自动开启驼峰命名风格映射)

指定插入新数据时,主键值如何生成(自增 还是 随机 --> 使用 IdType 指定主键策略)。


雪花算法(Snowflake Algorithm):是一种用于生成唯一ID的算法。它由Twitter公司提出,用于解决分布式系统中生成全局唯一ID的需求。

在传统的自增ID生成方式中,使用单点数据库生成 ID 会成为系统的瓶颈,而雪花算法通过在分布式系统中生成唯一ID,避免了单点故障和性能瓶颈的问题。

雪花算法生成的ID是一个64位的整数,由以下几个部分组成:

① 时间戳:41位,精确到毫秒级,可以使用69年。

② 节点ID:10位,用于标识分布式系统中的不同节点。

③ 序列号:12位,表示在同一毫秒内生成的不同ID的序号。

通过将这三个部分组合在一起,雪花算法可以在分布式系统中生成全局唯一的ID,并保证ID的生成顺序性。

雪花算法的工作方式如下:

① 当前时间戳从某一固定的起始时间开始计算,可以用于计算 ID 的时间部分。

② 节点 ID 是分布式系统中每个节点的唯一标识,可以通过配置或自动分配的方式获得。

③ 序列号用于记录在同一毫秒内生成的不同ID的序号,从0开始自增,最多支持4096个ID生成。

需要注意的是,雪花算法依赖于系统的时钟,需要确保系统时钟的准确性和单调性,否则可能会导致生成的ID不唯一或不符合预期的顺序。

总结:雪花算法是一种简单但有效的生成唯一ID的算法,广泛应用于分布式系统中,如微服务架构、分布式数据库、分布式锁等场景,以满足全局唯一标识的需求。

(4)@TableField 注解

描述:字段注解(非主键)

@TableName("t_user")
public class User {@TableIdprivate Long id;@TableField("nickname")private String name;private Integer age;private String email;
}
属性类型必须指定默认值描述
valueString""数据库字段名
existboolean

true

是否为数据库字段

使用场景:

①  非主键的列名 与 实体类的属性名不一致 --> 使用 value 指定字段名

(u_name --> uname 是一致的,因为 MyBatis-Plus 会自动开启驼峰命名风格映射)

该实体类中定义了其他成员变量,但并不是作为数据库表中的字段 --> 使用 exist = false

三、MyBatis-Plus 高级扩展 

1.逻辑删除实现

概念:

逻辑删除,可以方便地实现对数据库记录的逻辑删除而不是物理删除。逻辑删除是指通过更改记录的状态或添加标记字段来模拟删除操作,从而保留了删除前的数据,便于后续的数据分析和恢复。

  • 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
  • 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为 “被删除状态”,之后在数据库中仍旧能看到此条数据记录

逻辑删除实现: 

① 数据库和实体类添加逻辑删除字段(可以是一个布尔类型、整数类型或枚举类型)

# int 类型 规定: 1 逻辑删除 0 未逻辑删除
ALTER TABLE USER ADD deleted INT DEFAULT 0 ;

② 实体类添加逻辑删除属性,属性添加 @TableLogic 注解

@Data
public class User {// @TableIdprivate Integer id;private String name;private Integer age;private String email;@TableLogic//逻辑删除字段 int mybatis-plus下,默认 逻辑删除值为1 未逻辑删除 0 private Integer deleted;
}

细节:

①  实体类中的逻辑删除属性,要与数据库中的逻辑删除字段相对应。

② 设置逻辑删除后:

  • 删除数据,自动变成修改此条数据的的逻辑删除字段 deleted,将其变成 1
  • 查询数据,默认只查询 deleted = 0,即未被逻辑删除的数据

如果有多个表都要设置逻辑删除,可以进行全局设置:

mybatis-plus:global-config:db-config:logic-delete-field: deleted # 全局逻辑删除的实体属性名logic-delete-value: 1 # 逻辑已删除值(默认为 1)logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

 ③ 演示逻辑删除操作

//逻辑删除
@Test
public void testQuick5(){//逻辑删除userMapper.deleteById(3);
}

运行结果: 

 2.乐观锁实现

(1)悲观锁和乐观锁场景和介绍

并发问题场景演示:

解决思路:乐观锁和悲观锁是在并发编程中用于处理并发访问和资源竞争的两种不同的锁机制。

  • 悲观锁: 悲观锁的基本思想是,在整个数据访问过程中,将共享资源锁定,以确保其他线程或进程不能同时访问和修改该资源。悲观锁的核心思想是"先保护,再修改"。在悲观锁的应用中,线程在访问共享资源之前会获取到锁,并在整个操作过程中保持锁的状态,阻塞其他线程的访问。只有当前线程完成操作后,才会释放锁,让其他线程继续操作资源。这种锁机制可以确保资源独占性和数据的一致性,但是在高并发环境下,悲观锁的效率相对较低。
  • 乐观锁: 乐观锁的基本思想是,认为并发冲突的概率较低,因此不需要提前加锁,而是在数据更新阶段进行冲突检测和处理。乐观锁的核心思想是"先修改,后校验"。在乐观锁的应用中,线程在读取共享资源时不会加锁,而是记录特定的版本信息。当线程准备更新资源时,会先检查该资源的版本信息是否与之前读取的版本信息一致,如果一致则执行更新操作,否则说明有其他线程修改了该资源,需要进行相应的冲突处理。乐观锁通过避免加锁操作,提高了系统的并发性能和吞吐量,但是在并发冲突较为频繁的情况下,乐观锁会导致较多的冲突处理和重试操作。

注意:悲观锁和乐观锁是两种解决并发数据问题的思路,不是具体技术。

具体技术和方案: 

① 乐观锁实现方案和技术:

  • 版本号/时间戳:为数据添加一个版本号或时间戳字段,每次更新数据时,比较当前版本号或时间戳与期望值是否一致,若一致则更新成功,否则表示数据已被修改,需要进行冲突处理。
  • CAS(Compare-and-Swap):使用原子操作比较当前值与旧值是否一致,若一致则进行更新操作,否则重新尝试。
  • 无锁数据结构:采用无锁数据结构,如无锁队列、无锁哈希表等,通过使用原子操作实现并发安全。

② 悲观锁实现方案和技术:

  • 锁机制:使用传统的锁机制,如互斥锁(Mutex Lock)或读写锁(Read-Write Lock)来保证对共享资源的独占访问。
  • 数据库锁:在数据库层面使用行级锁或表级锁来控制并发访问。
  • 信号量(Semaphore):使用信号量来限制对资源的并发访问。

版本号乐观锁技术的实现流程:

  1. 每条数据添加一个版本号字段 version
  2. 取出记录时,获取当前 version
  3. 更新时,检查获取版本号是不是数据库当前最新版本号
  4. 如果是 [ 证明没有人修改数据 ],执行更新,set 数据更新,version = version+ 1
  5. 如果 version 不对 [ 证明有人已经修改了 ],我们现在的其他记录就是失效数据,就更新失败

(2)使用mybatis-plus数据使用乐观锁(基于版本号技术)

① 添加版本号更新插件

@MapperScan("com.mihoyo.mapper")
@SpringBootApplication
public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class,args);}//mybatis-plus插件集合加入到ioc容器@Beanpublic MybatisPlusInterceptor plusInterceptor(){//mybatis-plus的插件集合(所有插件都加入到这个集合:分页插件,乐观锁插件...)MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();//乐观锁(版本号插件)mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return mybatisPlusInterceptor;}}

② 数据库添加版本号 version 字段

ALTER TABLE USER ADD VERSION INT DEFAULT 1 ;  # int 类型 乐观锁字段

③ 实体类添加版本号属性,属性添加 @Version 注解 

@Data
public class User {// @TableIdprivate Integer id;private String name;private Integer age;private String email;@TableLogic//逻辑删除字段 int mybatis-plus下,默认 逻辑删除值为1 未逻辑删除 0 private Integer deleted;@Versionprivate Integer version;//版本号字段
}

细节:

  • 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
  • 仅支持 updateById(id) 与 update(entity, wrapper) 方法

④ 正常更新使用即可

//演示乐观锁生效场景
@Test
public void testQuick7(){//步骤1: 先查询,取出数据,从而获取当前版本号User user  = userMapper.selectById(5);User user1  = userMapper.selectById(5);user.setAge(20);user1.setAge(30);//修改成功,version + 1 = 2userMapper.updateById(user);//版本号校验不对,乐观锁生效,失败!userMapper.updateById(user1);
}

运行结果:

3.防全表更新和删除实现

针对 update 和 delete 语句

作用:阻止恶意的全表更新删除

步骤:

① 添加防止全表更新和删除拦截器

@MapperScan("com.mihoyo.mapper")
@SpringBootApplication
public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class,args);}//mybatis-plus插件集合加入到ioc容器@Beanpublic MybatisPlusInterceptor plusInterceptor(){//mybatis-plus的插件集合(所有插件都加入到这个集合:分页插件,乐观锁插件...)MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();//防止全表删除和更新的拦截器mybatisPlusInterceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());return mybatisPlusInterceptor;}}

② 测试全部更新或者删除

    @Testpublic void test_delete(){//全表删除 delete from user;userMapper.delete(null);}

运行结果:

 四、MyBatis-Plus代码生成器(MyBatisX插件)

1.Mybatisx 插件逆向工程

MyBatis-Plus为我们提供了强大的 mapper 和 service 模板,能够大大的提高开发效率

但是在真正开发过程中,MyBatis-Plus 并不能为我们解决所有问题,例如一些复杂的SQL,多表联查,我们就需要自己去编写代码和SQL语句,我们该如何快速的解决这个问题呢,这个时候可以使用 MyBatisX 插件。

MyBatisX一款基于 IDEA 的快速开发插件,为效率而生。

2.MyBatisX 快速代码生成 

使用 mybatisX 插件,自动生成 sql 语句实现(快捷键:Alt + enter)

具体介绍,可查看官方文档:Mybatis X 插件 | MyBatis-Plus (baomidou.com)icon-default.png?t=O83Ahttps://baomidou.com/guides/mybatis-x/

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

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

相关文章

使用LangGraph构建多Agent系统架构!

0 前言 Agent是一个使用大语言模型决定应用程序控制流的系统。随着这些系统的开发&#xff0c;它们随时间推移变得复杂&#xff0c;使管理和扩展更困难。如你可能会遇到&#xff1a; Agent拥有太多的工具可供使用&#xff0c;对接下来应该调用哪个工具做出糟糕决策上下文过于…

重塑企业数字化未来:物联网与微服务架构的战略性深度融合

从物联网到微服务架构的战略价值解读 随着全球数字化转型的不断加速&#xff0c;企业需要重新审视其技术基础架构&#xff0c;以适应日益复杂的业务需求和市场变化。物联网&#xff08;IoT&#xff09;作为核心技术&#xff0c;已广泛应用于制造、农业、交通、医疗等各个行业&…

Qt 支持打包成安卓

1. 打开维护Qt&#xff0c;双击MaintenanceTool.exe 2.登陆进去,默认是添加或移除组件&#xff0c;点击下一步&#xff0c; 勾选Android, 点击下一步 3.更新安装中 4.进度100%&#xff0c;完成安装&#xff0c;重启。 5.打开 Qt Creator&#xff0c;编辑-》Preferences... 6.进…

比亚迪车机安装第三方应用教程

比亚迪车机安装第三方应用教程 比亚迪车机U盘安装APP&#xff0c; 无论是dlink3.0还是4.0都是安卓系统&#xff0c;因此理论上安卓应用是都可以安装的&#xff0c;主要就是横屏和竖屏的区别。在比亚迪上安装软件我主要推荐两种方法。 第一种&#xff0c;直接从电脑端下载安装布…

(01)fastapi的基础学习——开启学习之路

前言 性能极高&#xff0c;可与 NodeJS, Go 媲美。(得益于Starlette和Pydantic)。 Starlette 是一个轻量级 ASGI 框架/工具包。它非常适合用来构建高性能的 asyncio 服务&#xff0c;并支持 HTTP 和 WebSockets。 官方网址&#xff1a;Starlette Pydantic 是一个使用Python…

摇人摇人, JD内推岗位(社招+校招)

摇人摇人, 有找工作的家人们看过来啊~ 虚位以待, 快到碗里来 算法开发工程师岗 京东云 北京|T7, 5-10年 岗位职责&#xff1a; 参与基于RAG知识库平台和ChatBI产品打造和商业化落地&#xff0c;进行相关技术&#xff1a;包括OCR、文档拆分、意图理解、多轮对话、NL2SQL、Embed…

idea删除git历史提交记录

前言&#xff1a;此文章是我在实际工作中有效解决问题的方法&#xff0c;做记录的同时也供大家参考&#xff01; 一、 首先&#xff0c;通过idea的终端或系统的cmd控制台&#xff0c;进入到你的项目文件根目录&#xff0c;idea终端默认就是项目根目录。 二、确保你当前处于要删…

Bluetooth Channel Sounding中关于CS Step及Phase Based Ranging相应Mode介绍

目录 BLE CS中Step定义 BLE CS中交互的数据包/波形格式 BLE CS中Step的不同Mode BLE CS中Step的执行过程 Mode0介绍 Mode0 步骤的作用 Mode0步骤的执行过程 Mode0步骤的执行时间 Mode0步骤的时间精度要求 Mode2介绍 Mode2步骤的作用和执行过程 Mode2步骤的执行时间 B…

Vue3 集成Monaco Editor编辑器

Vue3 集成Monaco Editor编辑器 1. 安装依赖2. 使用3. 效果 Monaco Editor &#xff08;官方链接 https://microsoft.github.io/monaco-editor/&#xff09;是一个由微软开发的功能强大的在线代码编辑器&#xff0c;被广泛应用于各种 Web 开发场景中。以下是对 Monaco Editor 的…

Linux 阻塞和非阻塞 IO 实验

阻塞和非阻塞 IO 是 Linux 驱动开发里面很常见的两种设备访问模式&#xff0c;在编写驱动的时候一定要考虑到阻塞和非阻塞。本章我们就来学习一下阻塞和非阻塞 IO&#xff0c;以及如何在驱动程序中处理阻塞与非阻塞&#xff0c;如何在驱动程序使用等待队列和 poll 机制。 阻塞和…

【机器学习】聚类算法|KMeans实现流程|SSE误差平法和|SC轮廓系数法|顾客数据聚类分析案例

文章目录 聚类算法聚类算法简介聚类算法分类 聚类算法API案例 使用KMeans模型数据探索聚类 KMeans实现流程***模型评估方法误差平方和 SSE(The sum of squares due to error)“肘”方法 (Elbow method) - K值确定 SC轮廓系数法&#xff08;Silhouette Coefficient&#xff09;聚…

微服务--OpenFeign【重点】

如果哪天 我们硬编码写的接口变了&#xff0c;只要写过该接口的 都要改&#xff0c;太麻烦了&#xff0c; 所以 就用 OpenFeign 来解决这个麻烦 了解&#xff1a; SimpleClientHttpRequestFactory和 HttpComponentsClientHttpRequestFactory 都是Spring框架中用于创建ClientH…

sentinel原理源码分析系列(六)-统计指标

调用链和统计节点构建完成&#xff0c;进入统计指标插槽&#xff0c;统计指标在最后执行的&#xff0c;等后面的插槽执行完&#xff0c;资源调用完成了&#xff0c;根据资源调用情况累计。指标统计是最重要的插槽&#xff0c;所有的功能都依靠指标数据&#xff0c;指标的正确与…

尤雨溪都点赞的表单校验解决方案,到底多么强

尤雨溪都点赞的表单校验解决方案&#xff0c;到底多么强 如果你是 Vue 开发者&#xff0c;那么 Vorms 绝对是你不能错过的表单验证利器。本文将带你快速了解 Vorms 的基本功能、特点和如何简单使用它提升你的开发体验。 软件简介 Vorms 是一个基于 Vue 3 组合式 API 的表单验证…

STL——string类

前言 从本篇博客开始&#xff0c;就正式来介绍STL的正式内容&#xff0c;STL在C中的学习中非常重要&#xff0c;具有举足轻重的地位&#xff0c;这块儿内容会给我们提供很多现成的接口&#xff0c;可以大大简化我们的代码&#xff0c;我们之前用C语言写的代码将会被极大地简化…

【数据分享】全国能源-电力平衡表(2000-2020年)

数据介绍 一级标题指标名称单位能源电力可供量亿千瓦小时能源电力生产量亿千瓦小时能源水电生产电力量亿千瓦小时能源火电生产电力量亿千瓦小时能源核电生产电力量亿千瓦小时能源风电生产电力量亿千瓦小时能源电力进口量亿千瓦小时能源电力出口量亿千瓦小时能源电力能源消费总量…

007、链表的回文结构

0、题目描述 链表回文结构 1、法1 一个复杂的问题可以拆解成几个简单的问题&#xff0c;找中间节点和逆置链表&#xff08;翻转链表&#xff09;之前都做过。 class PalindromeList { public://1、找中间节点ListNode* FindMid(ListNode* A){if (A nullptr || A->next …

设计模式03-装饰模式(Java)

4.4 装饰模式 1.模式定义 不改变现有对象结构的情况下&#xff0c;动态地给该对象增加一些职责&#xff08;即增加其额外功能&#xff09;的模式。 2.模式结构 抽象构件角色 &#xff1a;定义一个抽象接口以规范准备接收附加责任的对象。客户端可以方便调用装饰类和被装饰类…

基于STM32的电动汽车遥控器设计

引言 本项目设计了一个基于STM32的电动汽车遥控器&#xff0c;能够通过无线通信&#xff08;如蓝牙或射频模块&#xff09;控制电动汽车的前进、后退、左右转向等动作。该遥控器采用按键或摇杆操作&#xff0c;并通过无线模块将控制指令发送给汽车控制端&#xff0c;实现远程操…

吴恩达深度学习笔记(8)

计算机视觉 包括&#xff1a;图像分类也叫做图像识别、目标检测等 一个小的图像可能1M&#xff0c;但是他的像素是一个超级大向量&#xff0c;如果直接深度学习那么运算量会很大&#xff0c;因此需要运用卷积运算。 卷积运算是卷积神经网络的基础单元之一。下面用边缘检测理…