MyBatisPlus详解(四)通用枚举、JSON类型处理器、配置加密、自动分页插件与工具类

文章目录

  • 前言
  • 3 扩展功能
    • 3.3 通用枚举
      • 3.3.1 使用枚举类
      • 3.3.2 功能测试
    • 3.4 JSON类型处理器
      • 3.4.1 使用JSON类型处理器
      • 3.4.2 功能测试
    • 3.5 配置加密
      • 3.5.1 生成密钥
      • 3.5.2 修改配置
      • 3.5.3 功能测试
  • 4 插件功能
    • 4.1 自动分页插件
      • 4.1.1 配置分页插件
      • 4.1.2 分页API
    • 4.2 通用分页实体
      • 4.2.1 创建实体
      • 4.2.2 开发接口
      • 4.2.3 封装PageQuery工具方法
      • 4.2.4 封装PageDTO工具方法

前言

MyBatisPlus详解系列文章:

MyBatisPlus详解(一)项目搭建、@TableName、@TableId、@TableField注解与常见配置
MyBatisPlus详解(二)条件构造器Wrapper、自定义SQL、Service接口
MyBatisPlus详解(三)lambdaQuery、lambdaUpdate、批量新增、代码生成、Db静态工具、逻辑删除

3 扩展功能

3.3 通用枚举

在User实体类中有一个状态字段status

对于这样的码表字段,一般会定义一个枚举类,做业务判断的时候直接基于枚举类进行比较。但是该字段在数据库采用的是int类型,对应的实体类中是Integer类型,因此业务操作时必须手动把枚举类与Integer进行转换,相对麻烦。

为此,MybatisPlus提供了一个处理枚举类的类型转换器,可以对枚举类型与数据库类型进行自动转换。

3.3.1 使用枚举类

  • 1)定义一个用户状态的枚举类UserStatus
// com.star.learning.enums.UserStatus@Getter
public enum  UserStatus {NORMAL(1, "正常"),FREEZE(2, "冻结");private final int value;private final String desc;UserStatus(int value, String desc) {this.value = value;this.desc = desc;}
}
  • 2)将User类中的status字段改为UserStatus类型
// com.star.learning.pojo.User/*** 使用状态(1正常 2冻结)*/
//private Integer status;
private UserStatus status;
  • 3)使用@EnumValue注解标记枚举属性:

要让MybatisPlus处理枚举类与数据库类型自动转换,就必须告诉MybatisPlus,枚举中的哪个字段的值作为数据库值。MybatisPlus提供了@EnumValue注解来标记枚举属性:

// com.star.learning.enums.UserStatus@EnumValue
private final int value;// ...
  • 4)配置枚举处理器

在application.yaml文件中添加配置:

# src\main\resources\application.yamlmybatis-plus:configuration:# 默认枚举类处理器(从3.5.2开始无需配置)default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

3.3.2 功能测试

执行前面几节编写好的/user/{id}接口,根据id查询用户信息,可以发现查询出的User类的status字段是枚举类型:

此时返回前端的信息是:

可见,status字段JSON序列化后的值为NORMAL,这显然是不符合要求的。

为此,MybatisPlus支持通过@JsonValue注解,用于标记JSON序列化时展示的字段:

// com.star.learning.enums.UserStatus@EnumValue
private final int value;
@JsonValue
private final String desc;

再次执行以上接口,返回前端的信息是:

3.4 JSON类型处理器

在数据库的t_user表中,有一个类型为JSON的字段info(从MySQL5.7开始支持JSON类型),保存的数据是JSON字符串:

在其对应的实体类User中,该字段是String类型的:

这样设计时,查询到的info字段就是一个JSON字符串,而要读取其中的属性,还需要将JSON字符串转换为对象。而写入数据库时,需要将对象转换为JSON字符串,较为繁琐。

为此,MybatisPlus提供了很多特殊类型字段的类型处理器,解决特殊字段类型与数据库类型转换的问题,例如处理JSON问题就可以使用JacksonTypeHandler处理器。

3.4.1 使用JSON类型处理器

  • 1)定义一个实体类UserInfo,对应info字段保存的信息:
// com.star.learning.pojo.UserInfo@Data
public class UserInfo {private Integer age;private String intro;private String gender;
}
  • 2)将User类的info字段修改为UserInfo类型,并使用@TableField注解声明该字段的类型处理器为JacksonTypeHandler,同时在实体类的@TableName注解上添加autoResultMap = true属性:
// com.star.learning.pojo.User@Data
@TableName(value = "t_user", autoResultMap = true)
public class User {// ...@TableField(typeHandler = JacksonTypeHandler.class)private UserInfo info;
}

3.4.2 功能测试

调用/user/{id}接口,根据id查询用户信息,可以发现查询出的User类的info字段是UserInfo类型,且正确封装:

3.5 配置加密

目前在application.yaml配置文件中,很多参数都是明文的,例如数据库的用户名和密码。如果开发人员发生流动,则很容易导致敏感信息的泄露。为此,MybatisPlus支持配置文件的加密和解密功能。

3.5.1 生成密钥

利用AES工具生成一个随机秘钥,然后使用该密钥对数据库用户名、密码加密:

@Test
public void testAES() {// 生成 16 位随机 AES 密钥String randomKey = AES.generateRandomKey();System.out.println("randomKey = " + randomKey);// 利用密钥对用户名加密String username = AES.encrypt("root", randomKey);System.out.println("username = " + username);// 利用密钥对用户名加密String password = AES.encrypt("123456", randomKey);System.out.println("password = " + password);
}

执行以上单元测试,控制台打印信息如下:

randomKey = cb3d37297a857ac9
username = nvC21NLjkZ8SToc+FxM7Jw==
password = p898OXS2GwKDdJI9P+GlGA==

3.5.2 修改配置

修改application.yaml文件,把jdbc的用户名、密码修改为刚刚加密生成的密文:

spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mybatis_plus_db?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=truedriver-class-name: com.mysql.cj.jdbc.Driver# 密文要以 mpw:开头username: mpw:nvC21NLjkZ8SToc+FxM7Jw==password: mpw:p898OXS2GwKDdJI9P+GlGA==

3.5.3 功能测试

启动项目后,调用/user/{id}接口,发现报错:

java.sql.SQLNonTransientConnectionException: Could not create connection to database server. Attempted reconnect 3 times. Giving up.

报错是因为在启动项目的时,需要把刚才生成的秘钥添加到启动参数中(以IDEA配置为例):

重新启动项目,再次调用/user/{id}接口,发现可以正常访问数据库了。

4 插件功能

MybatisPlus提供了很多插件,进一步拓展其功能。目前已有的插件包括:

  • PaginationInnerInterceptor:自动分页
  • TenantLineInnerInterceptor:多租户
  • DynamicTableNameInnerInterceptor:动态表名
  • OptimisticLockerInnerInterceptor:乐观锁
  • IllegalSQLInnerInterceptor:SQL性能规范
  • BlockAttackInnerInterceptor:防止全表更新与删除

使用最多的是自动分页插件。

4.1 自动分页插件

4.1.1 配置分页插件

在未配置分页插件的情况下,MybatisPlus是不支持分页功能的,IService接口和BaseMapper类中的分页方法都无法正常使用。

要配置分页插件,需要新建一个配置类:

// com.star.learning.config.MyBatisConfig@Configuration
public class MyBatisConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {// 初始化核心插件MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加分页插件interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}}

4.1.2 分页API

分页API的基本使用如下:

@Test
public void testPageQuery() {// 1.分页查询,new Page()的两个参数分别是:页码、每页大小Page<User> page = new Page<>(2, 2);page = userService.page(page);// 2.总条数System.out.println("total = " + page.getTotal());// 3.总页数System.out.println("pages = " + page.getPages());// 4.数据List<User> records = page.getRecords();records.forEach(System.out::println);
}

执行以上单元测试,控制台打印信息如下:

// 查询总条数
==>  Preparing: SELECT COUNT(*) AS total FROM t_user
==> Parameters: 
<==      Total: 1
// 使用LIMIT进行分页查询
==>  Preparing: SELECT id,username,password,phone,info,status,balance,create_time,update_time FROM t_user LIMIT ?,?
==> Parameters: 2(Long), 2(Long)
<==      Total: 2
// 查询结果
total = 4
pages = 2
User(id=3, username=Hope, password=123, phone=13900112222, info=UserInfo(age=25, intro=上进青年, gender=male), status=NORMAL, balance=100000, createTime=2024-04-23T16:08:49, updateTime=2024-04-23T16:08:49, addressList=null)
User(id=4, username=Thomas, password=123, phone=17701265258, info=UserInfo(age=29, intro=伏地魔, gender=male), status=NORMAL, balance=800, createTime=2024-04-23T16:08:49, updateTime=2024-04-23T16:08:49, addressList=null)

分页的核心是Page类,除了可以设置当前页和每页大小,还支持排序参数,例如将以上单元测试修改如下:

// 1.分页查询,new Page()的两个参数分别是:页码、每页大小
Page<User> page = new Page<>(2, 2);
// 支持排序参数,第二个参数的true表示升序,false表示降序
page.addOrder(new OrderItem("balance", true));
// 支持多个排序参数
page.addOrder(new OrderItem("id", true));// ...

再次执行以上单元测试,控制台打印信息如下:

==>  Preparing: SELECT id, username, password, phone, info, status, balance, create_time, update_time FROM t_user ORDER BY balance ASC, id ASC LIMIT ?,?
==> Parameters: 2(Long), 2(Long)
<==      Total: 2

可见,ORDER BY balance ASC, id ASC已经被添加到了SQL语句中。

4.2 通用分页实体

下面要实现分页查询用户信息的接口:

参数说明
请求方式GET
请求路径/user/page
请求参数{"pageNo": 1, "pageSize": 5, "sortBy": "balance", "isAsc": false, "name": "o", "status": 1}
返回值{"total": 100, "pages": 10, "list": [{"id": 1, "username": "user_1", "info": ...}]}
特殊说明如果排序字段为空,则默认按照更新时间排序

以上接口文档主要涉及3个实体类:

  • UserQuery:分页查询条件的实体,包含分页、排序参数、查询条件
  • PageDTO:分页结果实体,包含总条数、总页数、当前页数据
  • User:用户信息实体

4.2.1 创建实体

  • 1)创建一个PageQuery实体类,定义分页、排序参数:
// com.star.learning.pojo.PageQuery@Data
public class PageQuery {/** 页码 */private Long pageNo;/** 每页大小 */private Long pageSize;/** 排序字段 */private String sortBy;/** 是否升序 */private Boolean isAsc;}
  • 2)修改UserQuery实体类,继承PageQuery类,这样UserQuery就即包含查询条件,又包含分页信息:
// com.star.learning.pojo.UserQuery@Data
public class UserQuery extends PageQuery {...}
  • 3)创建分页结果实体PageDTO类:
// com.star.learning.pojo.PageDTO@Data
@AllArgsConstructor
public class PageDTO<T> {/** 总条数 */private Long total;/** 总页数 */private Long pages;/** 数据集合 */private List<T> list;
}

4.2.2 开发接口

  • 1)在UserController中定义分页查询用户的接口:
// com.star.learning.controller.UserController@GetMapping("/page")
public PageDTO<User> queryUsersPage(UserQuery query){return userService.queryUsersPage(query);
}
  • 2)在IUserService接口中创建queryUsersPage方法,在UserServiceImpl中实现该方法:
// com.star.learning.service.IUserServicePageDTO<User> queryUsersPage(UserQuery userQuery);
// com.star.learning.service.impl.UserServiceImpl@Override
public PageDTO<User> queryUsersPage(UserQuery userQuery) {// 1.构建条件// 1.1.分页条件Page<User> page = new Page<>(userQuery.getPageNo(), userQuery.getPageSize());// 1.2.排序条件if(userQuery.getSortBy() != null) {page.addOrder(new OrderItem(userQuery.getSortBy(), userQuery.getIsAsc()));} else {// 默认按照更新时间降序排列page.addOrder(new OrderItem("update_time", false));}// 2.查询数据lambdaQuery().like(userQuery.getUsername() != null, User::getUsername, userQuery.getUsername()).eq(userQuery.getStatus() != null, User::getStatus, userQuery.getStatus()).ge(userQuery.getMinBalance() != null, User::getBalance, userQuery.getMinBalance()).le(userQuery.getMaxBalance() != null, User::getBalance, userQuery.getMaxBalance()).page(page);// 3.数据处理List<User> userList = page.getRecords();if (userList == null || userList.isEmpty()) {// 无数据,返回空结果return new PageDTO<>(page.getTotal(), page.getPages(), Collections.emptyList());}// 有数据,返回return new PageDTO<>(page.getTotal(), page.getPages(), userList);
}
  • 3)启动项目,调用接口进行测试

控制台打印信息如下:

==>  Preparing: SELECT COUNT(*) AS total FROM t_user WHERE (username LIKE ?)
==> Parameters: %o%(String)
<==      Total: 1
==>  Preparing: SELECT id, username, password, phone, info, status, balance, create_time, update_time FROM t_user WHERE (username LIKE ?) ORDER BY balance DESC LIMIT ?
==> Parameters: %o%(String), 2(Long)
<==      Total: 2

可见,SQL语句实现了分页、排序和条件查询。

4.2.3 封装PageQuery工具方法

在上述代码中,从PageQuery类到MyBatisPlus的Page类之间的转换逻辑是这样的:

// com.star.learning.service.impl.UserServiceImpl#queryUsersPage()// 1.构建条件
// 1.1.分页条件
Page<User> page = new Page<>(userQuery.getPageNo(), userQuery.getPageSize());
// 1.2.排序条件
if(userQuery.getSortBy() != null) {page.addOrder(new OrderItem(userQuery.getSortBy(), userQuery.getIsAsc()));
} else {// 默认按照更新时间降序排列page.addOrder(new OrderItem("update_time", false));
}

这段代码还是比较麻烦的,并且每一个需要分页查询的Service方法都需要重写这一段转换逻辑。

为此,我们应该在PageQuery类中定义一个工具方法,完成PageQuery类到Page类之间的转换:

// com.star.learning.pojo.PageQuery@Data
public class PageQuery {/** 页码 */private Long pageNo;/** 每页大小 */private Long pageSize;/** 排序字段 */private String sortBy;/** 是否升序 */private Boolean isAsc;public <T> Page<T> toMpPage(OrderItem... orders){// 1.分页条件Page<T> p = Page.of(pageNo, pageSize);// 2.排序条件// 2.1.先看前端有没有传排序字段if (sortBy != null) {p.addOrder(new OrderItem(sortBy, isAsc));return p;}// 2.2.再看有没有手动指定排序字段if(orders != null){p.addOrder(orders);}return p;}/*** 指定排序字段和升降序* @param defaultSortBy 排序字段* @param isAsc 是否升序*/public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc){return this.toMpPage(new OrderItem(defaultSortBy, isAsc));}/*** 默认create_time降序*/public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {return toMpPage("create_time", false);}/*** 默认update_time降序*/public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {return toMpPage("update_time", false);}}

接下来改造UserServiceImpl类的queryUsersPage()方法:

// com.star.learning.service.impl.UserServiceImpl#queryUsersPage()// 1.构建条件:使用工具方法获得Page对象
Page<User> page = userQuery.toMpPageDefaultSortByUpdateTimeDesc();// 2.查询数据......

4.2.4 封装PageDTO工具方法

在上述代码中,查询出结果后,从Page类到PageDTO类之间的转换逻辑是这样的:

// com.star.learning.service.impl.UserServiceImpl#queryUsersPage()// ...
// 3.数据处理
List<User> userList = page.getRecords();
if (userList == null || userList.isEmpty()) {// 无数据,返回空结果return new PageDTO<>(page.getTotal(), page.getPages(), Collections.emptyList());
}
// 有数据,返回
return new PageDTO<>(page.getTotal(), page.getPages(), userList);

对于不同的Service方法,这一段代码逻辑应该是差不多的,因此可以把它封装成工具方法,在Service方法中直接调用:

// com.star.learning.pojo.PageDTO@Data
@AllArgsConstructor
public class PageDTO<T> {/** 总条数 */private Long total;/** 总页数 */private Long pages;/** 数据集合 */private List<T> list;/*** 返回空分页结果*/public static <P> PageDTO<P> empty(Page<P> p){return new PageDTO<>(p.getTotal(), p.getPages(), Collections.emptyList());}/*** 将MybatisPlus分页结果转为PO分页结果* @return VO的分页对象*/public static <P> PageDTO<P> of(Page<P> p) {// 1.非空校验List<P> records = p.getRecords();if (records == null || records.isEmpty()) {// 无数据,返回空结果return empty(p);}// 2.数据返回return new PageDTO<>(p.getTotal(), p.getPages(), records);}
}

接下来改造UserServiceImpl类的queryUsersPage()方法,最终得到该方法的最简化版本:

// com.star.learning.service.impl.UserServiceImpl@Overridepublic PageDTO<User> queryUsersPage(UserQuery userQuery) {// 1.构建条件:使用工具方法获得Page对象Page<User> page = userQuery.toMpPageDefaultSortByUpdateTimeDesc();// 2.查询数据lambdaQuery().like(userQuery.getUsername() != null, User::getUsername, userQuery.getUsername()).eq(userQuery.getStatus() != null, User::getStatus, userQuery.getStatus()).ge(userQuery.getMinBalance() != null, User::getBalance, userQuery.getMinBalance()).le(userQuery.getMaxBalance() != null, User::getBalance, userQuery.getMaxBalance()).page(page);// 3.数据处理,使用工具方法返回数据return PageDTO.of(page);
}

本节完,更多内容请查阅分类专栏:MyBatisPlus详解

本文涉及代码下载地址:https://gitee.com/weidag/mybatis_plus_learning.git

感兴趣的读者还可以查阅我的另外几个专栏:

  • SpringBoot源码解读与原理分析(已完结)
  • MyBatis3源码深度解析(已完结)
  • Redis从入门到精通(已完结)
  • 再探Java为面试赋能(持续更新中…)

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

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

相关文章

递归实现斐波那契数列的空间复杂度的讲解

题目&#xff1a;计算斐波那契数列Fib的空间复杂度 过程图解&#xff1a; 理解要点&#xff1a; 递归的运算顺序和方式不是同时进行图中的所有Fib函数&#xff0c;而是有顺序的&#xff01; 第一步&#xff1a;单独的一个Fib&#xff08;N&#xff09;进行到底Fib&#xff08…

XV6源码阅读——进程地址空间

文章目录 前言页表实际情况 前言 一个本硕双非的小菜鸡&#xff0c;备战24年秋招。打算尝试6.S081&#xff0c;将它的Lab逐一实现&#xff0c;并记录期间心酸历程。 代码下载 官方网站&#xff1a;6.S081官方网站 页表 每个进程都有一个单独的页表&#xff0c;当xv6在进程之…

Linux套接字编程详解

Linux套接字编程 预备知识IP地址和MAC地址套接字结构网络字节序 UDP套接字编程服务端代码客服端代码 TCP 套接字守护进程 计算器模块1 日志头文件序列化和反序列化 预备知识 IP地址和MAC地址 MAC地址用来在局域网中标识唯一主机 Ip地址用于在广域网中标识唯一主机 &#xff0…

2767. 将字符串分割为最少的美丽子字符串

2767. 将字符串分割为最少的美丽子字符串 Java: class Solution {int cnt;int ans;int[] arr;Set<Integer> set;private void dfs(String s, String s1, int pos, int len) {if (pos len) {ans Math.min(ans, cnt);return;}for(int i pos; i < len; i) {String s2…

软件游戏缺失d3dcompiler_43.dll怎么修复?分享多种靠谱的解决方法

在我们日常频繁地操作和使用电脑的过程中&#xff0c;时常会遇到一些突发的技术问题。其中一种常见的情况是&#xff0c;在尝试启动或运行某个应用程序时&#xff0c;系统会弹出一个错误提示窗口&#xff0c;明确指出当前电脑环境中缺少了一个至关重要的动态链接库文件——d3dc…

Linux配置腾讯云yum源(保姆级教学)

1. 备份原有的 yum 源配置文件 例如&#xff1a; mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2. 下载腾讯云的 yum 源配置文件 例如&#xff1a; wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.cloud.tencent.com/repo/…

将数字状态码在后台转换为中文状态

这是我们的实体类 可以看出我们的状态status是2如果返回到前端我们根本不知道2代表的是什么&#xff0c;所以我们需要再这里将数字转换成能看懂的中文状态&#xff0c;首先我们创建一个枚举类 先将我们状态码所对应的中文状态枚举出来&#xff0c;然后创建一个静态方法&#…

基于OpenCV+QT的人脸识别打卡项目

1.基本概念 基于OpenCV的人脸识别是一个多步骤的过程&#xff0c;通常涉及以下步骤&#xff1a; 人脸检测&#xff1a;使用Haar级联或深度学习模型来检测图像中的面部区域。OpenCV提供了预训练的Haar级联分类器&#xff0c;可以用于快速检测。 特征提取&#xff1a;一旦检测到…

eCharts 折线图 一段是实线,一段是虚线的实现效果

在lineStyle里写了不生效的话&#xff0c;可以尝试数据拼接 option {xAxis: {type: category,data: [Mon, Tue, Wed, Thu, Fri, Sat, Sun]},yAxis: {type: value},series: [{data: [150, 230, 224,218 ,,,],type: line},{data: [,,, 218, 135, 147, 260],type: line,lineStyl…

【御控工业物联网】JAVA JSON结构转换、JSON结构重构、JSON结构互换(5):对象To对象——转换映射方式

御控官网&#xff1a;https://www.yu-con.com/ 文章目录 御控官网&#xff1a;[https://www.yu-con.com/](https://www.yu-con.com/)一、JSON结构转换是什么&#xff1f;二、术语解释三、案例之《JSON对象 To JSON对象》四、代码实现五、在线转换工具六、技术资料 一、JSON结构…

JavaScript实现代码雨

一、功能描述 使用canvas实现一个代码雨的功能&#xff0c;炫一个~~~ 二、上码 html <canvas id"canvas"></canvas> js let canvas document.querySelector(canvas);let ctx canvas.getContext(2d);// screen.availWidth:可视区域的宽度canvas.width…

深度学习框架pytorch:tensor.data和tensor.detach()的区别

本文重点 本文我们区别一下tensor.data和tensor.detach(),我们所讲解的都是pytorch的1.0版本的情况 官方解释 返回一个新的张量,它与当前图形分离。结果永远不需要梯度。返回的张量与原始张量共享相同的存储空间。将看到对其中任何一个的就地修改,并且可能在正确性检查中…

测试用例设计方法-探索性测试

生活犹如骑单车&#xff0c;唯有前进才能保持平衡。大家好&#xff0c;今天给大家分享一下关于探索性测试的方法&#xff0c;在探索性测试中更加考验测试人员的经验&#xff0c;所以我们在平时的测试工作中一定要多记录、多总结、多复盘&#xff0c;对于经常出现的bug深究其根本…

【论文解析】笔触渲染生成 前沿工作梳理

最近的一些工作梳理 2023年 Stroke-based Neural Painting and Stylization with Dynamically Predicted Painting Region 2022年Im2Oil: Stroke-Based Oil Painting Rendering with Linearly Controllable Fineness Via Adaptive Sampling 文章目录 1 Stroke-based Neural P…

Python实现“黑猫投诉平台,舆论监控系统”

黑猫投诉平台&#xff0c;舆论监控系统 BuzzMonitor https://github.com/nangongchengfeng/BuzzMonitor.git 简介 "黑猫投诉"舆论监控系统是一款专为快速识别和响应网络投诉而设计的应用&#xff0c;旨在帮助企业或机构第一时间掌握公众意见和反馈。通过实时监控网…

Echarts异步数据与动画加载

目录 简介 头部代码 这段代码是使用 Echarts 绘制图表的关键部分。首先&#xff0c;初始化了一个 Echarts 实例。然后&#xff0c;通过 Ajax 请求获取数据&#xff0c;并基于此设置图表选项。其中包括颜色、背景色、标题、提示框、图例以及饼图的具体配置。 具体解释如下&a…

excel相同行不同列查询

EXCEL中e列和f列是每一行对应的&#xff0c;我想在d列中找和e列一样的元素&#xff0c;然后获取同一行中f列的值 IFERROR(VLOOKUP(D1, E:F, 2, FALSE), "")

MySQL索引为什么选择B+树,而不是二叉树、红黑树、B树?

12.1.为什么没有选择二叉树? 二叉树是一种二分查找树,有很好的查找性能,相当于二分查找。 二叉树的非叶子节值大于左边子节点、小于右边子节点。 原因: 但是当N比较大的时候,树的深度比较高。数据查询的时间主要依赖于磁盘IO的次数,二叉树深度越大,查找的次数越多,性能…

python爬虫 - 爬取 json 格式数据(股票行情信息:雪球网,自选股)

文章目录 1. 第一步&#xff1a;安装requests库2. 第二步&#xff1a;获取爬虫所需的header和cookie3. 第三步&#xff1a;获取网页4. 第四步&#xff1a;解析网页5. 第五步&#xff1a;解析 json 结构数据体6. 代码实例以及结果展示 python爬虫五部曲&#xff1a; 第一步&…

157平新中式复式装修,双客厅设计+开放式客餐厅。福州中宅装饰,福州装修

设计亮点 这个户型改造案例的亮点在于户型空间结构的优化。与传统复式户型不同&#xff0c;一层被重新规划为家庭入户门厅及会客厅茶室&#xff1b;而二楼则作为传统的生活区&#xff0c;突破了原有空间限制&#xff0c;为居住者提供更多自由活动空间。 改造亮点&#xff1a; ①…