MybatisPlus-核心功能

目录

条件构造器

QueryWrapper

UpdateWrapper

LambdaQueryWrapper

自定义SQL

基本用法

多表关联

Service接口

CRUD

基本用法 

Lambda

批量新增

条件构造器

除了新增以外,修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以id作为where条件以外,还支持更加复杂的where条件。

参数中的Wrapper就是条件构造的抽象类,其下有很多默认实现,继承关系如图:

Wrapper的子类AbstractWrapper提供了where中包含的所有条件构造方法:

而QueryWrapper在AbstractWrapper的基础上拓展了一个select方法,允许指定查询字段:

而UpdateWrapper在AbstractWrapper的基础上拓展了一个set方法,允许指定SQL中的SET部分:

接下来就利用Wrapper实现复杂查询 。

QueryWrapper

无论是修改、删除、查询,都可以使用QueryWrapper来构建查询条件。接下来看一些例子:

查询:查询出名字中带o的,存款大于等于1000元的人。代码如下:

@Test
void testQueryWrapper() {// 1.构建查询条件 where name like "%o%" AND balance >= 1000QueryWrapper<User> wrapper = new QueryWrapper<User>().select("id", "username", "info", "balance").like("username", "o").ge("balance", 1000);// 2.查询数据List<User> users = userMapper.selectList(wrapper);users.forEach(System.out::println);
}

更新:更新用户名为jack的用户的余额为2000,代码如下:

@Test
void testUpdateByQueryWrapper() {// 1.构建查询条件 where name = "Jack"QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "Jack");// 2.更新数据,user中非null字段都会作为set语句User user = new User();user.setBalance(2000);userMapper.update(user, wrapper);
}

UpdateWrapper

基于BaseMapper中的update方法更新时只能直接赋值,对于一些复杂的需求就难以实现。 例如:更新id为1,2,4的用户的余额,扣200,对应的SQL应该是:

UPDATE user SET balance = balance - 200 WHERE id in (1, 2, 4)

SET的赋值结果是基于字段现有值的,这个时候就要利用UpdateWrapper中的setSql功能了:

@Test
void testUpdateWrapper() {List<Long> ids = List.of(1L, 2L, 4L);// 1.生成SQLUpdateWrapper<User> wrapper = new UpdateWrapper<User>().setSql("balance = balance - 200") // SET balance = balance - 200.in("id", ids); // WHERE id in (1, 2, 4)// 2.更新,注意第一个参数可以给null,也就是不填更新字段和数据,// 而是基于UpdateWrapper中的setSQL来更新userMapper.update(null, wrapper);
}

LambdaQueryWrapper

无论是QueryWrapper还是UpdateWrapper在构造条件的时候都需要写死字段名称,会出现字符串魔法值。这在编程规范中显然是不推荐的。 那怎么样才能不写字段名,又能知道字段名呢?其中一种办法是基于变量的gettter方法结合反射技术。因此我们只要将条件对应的字段的getter方法传递给MybatisPlus,它就能计算出对应的变量名了。而传递方法可以使用JDK8中的方法引用Lambda表达式。 因此MybatisPlus又提供了一套基于Lambda的Wrapper,包含两个:

  • LambdaQueryWrapper

  • LambdaUpdateWrapper

分别对应QueryWrapper和UpdateWrapper

其使用方式如下:

@Test
void testLambdaQueryWrapper() {// 1.构建条件 WHERE username LIKE "%o%" AND balance >= 1000QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.lambda().select(User::getId, User::getUsername, User::getInfo, User::getBalance).like(User::getUsername, "o").ge(User::getBalance, 1000);// 2.查询List<User> users = userMapper.selectList(wrapper);users.forEach(System.out::println);
}

自定义SQL

在演示UpdateWrapper的案例中,我们在代码中编写了更新的SQL语句:

这种写法在某些企业也是不允许的,因为SQL语句最好都维护在持久层,而不是业务层。就当前案例来说,由于条件是in语句,只能将SQL写在Mapper.xml文件,利用foreach来生成动态SQL。 这实在是太麻烦了。假如查询条件更复杂,动态SQL的编写也会更加复杂。所以,MybatisPlus提供了自定义SQL功能,可以让我们利用Wrapper生成查询条件,再结合Mapper.xml编写SQL

基本用法

总结来说思路就是把wrapper传到自定义的方法中,wrapper中的条件使用特殊占位符标在SQL注解中,并且方法参数要有@Param(“ew”),这里的ew只能叫ew,这是MP官方指定的字符串。

所以,刚刚的案例,即更新id为1,2,4的用户的余额,进行扣处200元的操作,可以写成如下:

@Test
void testCustomWrapper() {// 1.准备自定义查询条件List<Long> ids = List.of(1L, 2L, 4L);QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);// 2.调用mapper的自定义方法,直接传递WrapperuserMapper.deductBalanceByIds(200, wrapper);
}

然后在UserMapper中自定义SQL:

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.mp.domain.po.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import org.apache.ibatis.annotations.Param;public interface UserMapper extends BaseMapper<User> {@Select("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}")void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper);
}

这样就省去了编写复杂查询条件的烦恼了。

总结来说就是,在业务逻辑层中我们封装好wrapper条件,然后以参数的形式在自定义的方法中传递到下游接口访问层。需要注意的是,接口访问层的属性如果是wrapper及其子接口的话,需要用@Param("ew")进行标注,ew是MP官方的写法,不可以取其他名字,只能是@Param("ew");或者可以将ew写成MP封装好的常量类,@Param(Constants.WRAPPER);如果是其他字段的话,直接点明上游传来的属性值即可(如上面的int类型的money,直接绑定为上游的money字段)。另外就是,{ew.customSqlSegment} 是一个动态拼接的 SQL 片段,它通常是通过 QueryWrapper 生成的条件语句,例如 WHERE id IN (1, 2, 3)。由于这个片段是一个字符串,且需要直接拼接到 SQL 语句中,因此使用 ${} 是合适的。这种自定义SQL+Wrapper的方式等于把where条件查询封装到Wrapper,其余的自己写,然后在访问方法的时候进行SQL语句的拼接。

这样开发就省去了编写复杂查询条件的烦恼了。 

多表关联

理论上来讲MyBatisPlus是不支持多表查询的,不过我们可以利用Wrapper中自定义条件结合自定义SQL来实现多表查询的效果。 例如,我们要查询出所有收货地址在北京的并且用户id在1、2、4之中的用户 要是自己基于mybatis实现SQL,大概是这样的:

<select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User">SELECT *FROM user uINNER JOIN address a ON u.id = a.user_idWHERE u.id<foreach collection="ids" separator="," item="id" open="IN (" close=")">#{id}</foreach>AND a.city = #{city}</select>

可以看出其中最复杂的就是WHERE条件的编写,如果业务复杂一些,这里的SQL会更变态。但是基于自定义SQL结合Wrapper的玩法,我们就可以利用Wrapper来构建查询条件,然后手写SELECT及FROM部分,实现多表查询。

查询条件这样来构建:

@Test
void testCustomJoinWrapper() {// 1.准备自定义查询条件QueryWrapper<User> wrapper = new QueryWrapper<User>().in("u.id", List.of(1L, 2L, 4L)).eq("a.city", "北京");// 2.调用mapper的自定义方法List<User> users = userMapper.queryUserByWrapper(wrapper);users.forEach(System.out::println);
}

然后在UserMapper中自定义方法:

@Select("SELECT u.* FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}")
List<User> queryUserByWrapper(@Param("ew")QueryWrapper<User> wrapper);

当然,也可以在UserMapper.xml中写SQL:

<select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User">SELECT * FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}
</select>

将上面的思路整理一下:

  1. 在业务逻辑层中,我们定义一个wrapper 对象,用来存储我们SQL语句中的where子句里的条件,然后将其以参数的形式在外面自定义的方法中传递到我们的接口访问层中进行处理。在这里,我们的写法是u.id,a.city,指代用户的id和地址的城市,这是因为我们在mapper里面定义了别名,"SELECT * FROM user u INNER JOIN address a ON u.id = a.user_id",wrapper是后面手写sql语句的一部分,自然可以调用sql语句中的user表别名u。
  2. 在mapper接口访问层中,对于wrapper的条件构造器对象,我们必须用QueryWrapper<T> wrapper类型的对象对wrapper进行接收,其中T是我们Java中的实体类对象,需要根据业务换成我们要操作的对象;另外,必须加上参数注解,@Param("ew")或者@Param(Constants.WRAPPER),这两个的注解的作用是一样的,任选其一即可(ew是MP指定的写法,不可以换成其他名字,不然MP识别不了)。
  3. 在mapper的具体代码上,我们通过${ew.customSqlSegment}")来替换SQL语句中的where子句,因为条件我们已经封装到了wrapper当中,MP会拼接代码,我们直接使用即可。

Service接口

MybatisPlus不仅提供了BaseMapper,还提供了通用的Service接口及默认实现,封装了一些常用的service模板方法。 通用接口为IService,默认实现为ServiceImpl,其中封装的方法可以分为以下几类:

  • save:新增

  • remove:删除

  • update:更新

  • get:查询单个结果

  • list:查询集合结果

  • count:计数

  • page:分页查询

CRUD

我们先俩看下基本的CRUD接口。

新增

  • save是新增单个元素

  • saveBatch是批量新增

  • saveOrUpdate是根据id判断,如果数据存在就更新,不存在则新增

  • saveOrUpdateBatch是批量的新增或修改

删除: 

  • removeById:根据id删除

  • removeByIds:根据id批量删除

  • removeByMap:根据Map中的键值对为条件删除

  • remove(Wrapper<T>):根据Wrapper条件删除

  • ~~removeBatchByIds~~:暂不支持

修改:

  • updateById:根据id修改

  • update(Wrapper<T>):根据UpdateWrapper修改,Wrapper中包含setwhere部分

  • update(T,Wrapper<T>):按照T内的数据修改与Wrapper匹配到的数据

  • updateBatchById:根据id批量修改

 Get:

  • getById:根据id查询1条数据

  • getOne(Wrapper<T>):根据Wrapper查询1条数据

  • getBaseMapper:获取Service内的BaseMapper实现,某些时候需要直接调用Mapper内的自定义SQL时可以用这个方法获取到Mapper

 List:

  • listByIds:根据id批量查询

  • list(Wrapper<T>):根据Wrapper条件查询多条数据

  • list():查询所有

Count

  • count():统计所有数量

  • count(Wrapper<T>):统计符合Wrapper条件的数据数量

getBaseMapper

当我们在service中要调用Mapper中自定义SQL时,就必须获取service对应的Mapper,就可以通过这个方法:

基本用法 

方法总览(方法基本上见名知意,直接用就行):

由于Service中经常需要定义与业务有关的自定义方法,因此我们不能直接使用IService,而是自定义Service接口,然后继承IService以拓展方法。同时,让自定义的Service实现类继承ServiceImpl,这样就不用自己实现IService中的接口了。

首先,定义IUserService,继承IService

package com.itheima.mp.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;public interface IUserService extends IService<User> {// 拓展自定义方法
}

然后,编写UserServiceImpl类,继承ServiceImpl,实现UserService

package com.itheima.mp.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.po.service.IUserService;
import com.itheima.mp.mapper.UserMapper;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}

项目结构如下:

思路如下:

其中的泛型需要指定为自己项目的实体类和mapper接口名。 

至此,项目整合Service接口完成了,我们可以直接调用对应的方法来简化开发。

Lambda

IService中还提供了Lambda功能来简化我们的复杂查询及更新功能。

案例一:实现一个根据复杂条件查询用户的接口,查询条件如下:

  • name:用户名关键字,可以为空

  • status:用户状态,可以为空

  • minBalance:最小余额,可以为空

  • maxBalance:最大余额,可以为空

可以理解成一个用户的后台管理界面,管理员可以自己选择条件来筛选用户,因此上述条件不一定存在,需要做判断。我们首先需要定义一个查询条件实体,UserQuery实体:

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery {@ApiModelProperty("用户名关键字")private String name;@ApiModelProperty("用户状态:1-正常,2-冻结")private Integer status;@ApiModelProperty("余额最小值")private Integer minBalance;@ApiModelProperty("余额最大值")private Integer maxBalance;
}

其实这就是个DTO类,咋也不明白为啥不以DTO结尾,传输对象

接下来我们在UserController中定义一个controller方法:

@GetMapping("/list")
@ApiOperation("根据id集合查询用户")
public List<UserVO> queryUsers(UserQuery query){// 1.组织条件String username = query.getName();Integer status = query.getStatus();Integer minBalance = query.getMinBalance();Integer maxBalance = query.getMaxBalance();LambdaQueryWrapper<User> wrapper = new QueryWrapper<User>().lambda().like(username != null, User::getUsername, username).eq(status != null, User::getStatus, status).ge(minBalance != null, User::getBalance, minBalance).le(maxBalance != null, User::getBalance, maxBalance);// 2.查询用户List<User> users = userService.list(wrapper);// 3.处理voreturn BeanUtil.copyToList(users, UserVO.class);
}

在这段代码里,传输前面没有加上@Requestbody注解,这是因为这个方法是get请求,请求参数是json格式的,因此可以不加注解。

在组织查询条件的时候,我们加入了 username != null 这样的参数,意思就是当条件成立时才会添加这个查询条件,类似Mybatis的mapper.xml文件中的<if>标签。这样就实现了动态查询条件效果了。不过,上述条件构建的代码太麻烦了。 因此Service中对LambdaQueryWrapperLambdaUpdateWrapper的用法进一步做了简化。我们无需自己通过new的方式来创建Wrapper,而是直接调用lambdaQuerylambdaUpdate方法:

基于Lambda查询:

@GetMapping("/list")
@ApiOperation("根据id集合查询用户")
public List<UserVO> queryUsers(UserQuery query){// 1.组织条件String username = query.getName();Integer status = query.getStatus();Integer minBalance = query.getMinBalance();Integer maxBalance = query.getMaxBalance();// 2.查询用户List<User> users = userService.lambdaQuery().like(username != null, User::getUsername, username).eq(status != null, User::getStatus, status).ge(minBalance != null, User::getBalance, minBalance).le(maxBalance != null, User::getBalance, maxBalance).list();// 3.处理voreturn BeanUtil.copyToList(users, UserVO.class);
}

可以发现lambdaQuery方法中除了可以构建条件,还需要在链式编程的最后添加一个list(),这是在告诉MP我们的调用结果需要是一个list集合。这里不仅可以用list(),可选的方法有:

  • .one():最多1个结果

  • .list():返回集合结果

  • .count():返回计数结果

MybatisPlus会根据链式编程的最后一个方法来判断最终的返回结果。

与lambdaQuery方法类似,IService中的lambdaUpdate方法可以非常方便的实现复杂更新业务。

例如下面的需求:

需求:改造根据id修改用户余额的接口,要求如下

  • 如果扣减后余额为0,则将用户status修改为冻结状态(2)

也就是说我们在扣减用户余额时,需要对用户剩余余额做出判断,如果发现剩余余额为0,则应该将status修改为2,这就是说update语句的set部分是动态的。

@Override
@Transactional
public void deductBalance(Long id, Integer money) {// 1.查询用户User user = getById(id);// 2.校验用户状态if (user == null || user.getStatus() == 2) {throw new RuntimeException("用户状态异常!");}// 3.校验余额是否充足if (user.getBalance() < money) {throw new RuntimeException("用户余额不足!");}// 4.扣减余额 update tb_user set balance = balance - ?int remainBalance = user.getBalance() - money;lambdaUpdate().set(User::getBalance, remainBalance) // 更新余额.set(remainBalance == 0, User::getStatus, 2) // 动态判断,是否更新status.eq(User::getId, id).eq(User::getBalance, user.getBalance()) // 乐观锁.update();
}

set方法的第一个参数可以设置,表示只有满足什么情况才会去执行该语句,如上代码所示,表示只有当余额等于0的时候,才会将status设置为冻结状态。需要注意的是,lambdaUpdate()方法只是构建条件,只有在最后加上类似于update()的方法,才会去执行语句。

批量新增

IService中的批量新增功能使用起来非常方便,但有一点注意事项,我们先来测试一下。 首先我们测试逐条插入数据:

@Test
void testSaveOneByOne() {long b = System.currentTimeMillis();for (int i = 1; i <= 100000; i++) {userService.save(buildUser(i));}long e = System.currentTimeMillis();System.out.println("耗时:" + (e - b));
}private User buildUser(int i) {User user = new User();user.setUsername("user_" + i);user.setPassword("123");user.setPhone("" + (18688190000L + i));user.setBalance(2000);user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");user.setCreateTime(LocalDateTime.now());user.setUpdateTime(user.getCreateTime());return user;
}

执行结果如下:

可以看到速度非常慢。

然后再试试MybatisPlus的批处理:

@Test
void testSaveBatch() {// 准备10万条数据List<User> list = new ArrayList<>(1000);long b = System.currentTimeMillis();for (int i = 1; i <= 100000; i++) {list.add(buildUser(i));// 每1000条批量插入一次if (i % 1000 == 0) {userService.saveBatch(list);list.clear();}}long e = System.currentTimeMillis();System.out.println("耗时:" + (e - b));
}

 执行最终耗时如下:

可以看到使用了批处理以后,比逐条新增效率提高了10倍左右,性能还是不错的。

不过,我们简单查看一下MybatisPlus源码:

@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveBatch(Collection<T> entityList, int batchSize) {String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
}
// ...SqlHelper
public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {Assert.isFalse(batchSize < 1, "batchSize must not be less than one");return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, sqlSession -> {int size = list.size();int idxLimit = Math.min(batchSize, size);int i = 1;for (E element : list) {consumer.accept(sqlSession, element);if (i == idxLimit) {sqlSession.flushStatements();idxLimit = Math.min(idxLimit + batchSize, size);}i++;}});
}

可以发现其实MybatisPlus的批处理是基于PrepareStatement的预编译模式,然后批量提交,最终在数据库执行时还是会有多条insert语句,逐条插入数据。SQL类似这样:

Preparing: INSERT INTO user ( username, password, phone, info, balance, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? )
Parameters: user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01
Parameters: user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01
Parameters: user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01

而如果想要得到最佳性能,最好是将多条SQL合并为一条,像这样:

INSERT INTO user ( username, password, phone, info, balance, create_time, update_time )
VALUES 
(user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01),
(user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01),
(user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01),
(user_4, 123, 18688190004, "", 2000, 2023-07-01, 2023-07-01);

MySQL的客户端连接参数中有这样的一个参数:rewriteBatchedStatements。顾名思义,就是重写批处理的statement语句。参考文档:

Docshttps://b11et3un53m.feishu.cn/wiki/PsyawI04ei2FQykqfcPcmd7Dnsc#SSsSd1nENoCFcSx6ttGc2ocLnlc

这个参数的默认值是false,我们需要修改连接参数,将其配置为true

修改项目中的application.yml文件,在jdbc的url后面添加参数&rewriteBatchedStatements=true:

spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=truedriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: MySQL123

再次测试插入10万条数据,可以发现速度有非常明显的提升:

ClientPreparedStatementexecuteBatchInternal中,有判断rewriteBatchedStatements值是否为true并重写SQL的功能.

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

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

相关文章

RHCE综合项目:分布式LNMP私有博客服务部署

一、项目概述本次项目基于LNMP&#xff08;linux&#xff0c;nginx&#xff0c;mariadb&#xff0c;php&#xff09;搭建了一个私有的博客平台&#xff0c;本篇博客详细记录了该博客平台的服务部署全流程。在该项目中&#xff0c;使用了两台linux&#xff08;openeuler&#xf…

5种安全方法:如何删除三星手机上的所有内容

随着新的三星设备不断推出&#xff0c;在出售或捐赠旧手机之前&#xff0c;彻底清除旧手机上的数据以保护隐私至关重要。许多人不知道的是&#xff0c;简单的删除操作并不能完全清除三星设备上的数据&#xff0c;被删除的文件可能会处于不可见状态。本文介绍了如何彻底删除三星…

Vue 3 入门教程 2- Vue 组件基础与模板语法

一、Vue 组件基础在 Vue 中&#xff0c;组件是构建用户界面的基本单位&#xff0c;它可以将页面拆分成多个独立、可复用的部分。一个 Vue 组件通常以 .vue 文件名结尾&#xff0c;包含三个核心部分&#xff1a;模板&#xff08;Template&#xff09;、脚本&#xff08;Script&a…

Linux 进程管理与计划任务详解

Linux 进程管理与计划任务详解 一、程序与进程的基本概念 程序&#xff1a;保存在外部存储介质中的可执行机器代码和数据的静态集合&#xff0c;是静态的文件实体进程&#xff1a;在 CPU 及内存中处于动态执行状态的计算机程序&#xff0c;是程序的动态执行实例关联关系&#x…

分层解耦(Controller,Service,Dao)

1. 三层架构核心职责层级职责说明关键技术 / 注解Controller&#xff08;控制器&#xff09;1. 接收前端请求&#xff08;HTTP&#xff09; 2. 封装参数、校验 3. 调用 Service 处理业务 4. 返回视图 / 数据给前端Controller、GetMapping等Service&#xff08;业务层&#xff0…

镁金属接骨螺钉注册检测:骨科植入安全的科学基石

在骨科治疗领域&#xff0c;镁金属接骨螺钉凭借其可降解性与生物相容性&#xff0c;成为传统金属植入物的革新替代方案。然而&#xff0c;作为Ⅲ类高风险无源植入器械&#xff08;分类编码13-01-01&#xff09;&#xff0c;其注册检测需覆盖生物相容性、化学表征、降解性能、力…

模具开发和管理系统(c#)

以前编写的一个管理模具开发和进度的程序&#xff0c;可以跟踪模具开发进度&#xff0c;可以查询模具具体情况&#xff0c;也可以用水晶报表查询。OS&#xff1a;microsoft windows IDE&#xff1a;microsoft visual studio programming language&#xff1a;C# DataBase&#…

【WRF-Chem 实例1】namelist.input 详解- 模拟CO2

目录 &time_control(时间控制) &physics(物理过程参数化方案) &fdda(四维数据同化) 工作机制简述 &dynamics(WRF 动力核心的数值方法和选项) &bdy_control(边界控制设置) &chem(WRF-Chem 主要化学设置) &namelist_quilt(并行 I/O 控制…

数据中心-时序数据库InfluxDB

目录 一、InfluxDB介绍 1.1 什么是InfluxDB&#xff1f; 1.2 应用场景 1.3 特点 1.4 版本差异 二、数据模型和存储架构 2.1 相关概念 2.2 存储架构 三、InfluxDB基础操作 3.1 数据库操作 3.2 数据表操作 显示所有表 新建表 删除表 3.3 数据保存策略 查看保存策…

webpack-高级配置

多入口文件 如何输出多个html文件 输入位置 需要写两个entryoutput位置也要改一下 加一个name避免重名 在生成html时 要根据每一个入口都写一个插件 并且chunks要写好 当前html引入哪些文件如何抽离压缩css文件 安装插件在rules里面添加插件plugins中添加css抽离代码压缩css抽离…

WinForm组件之Label 控件

Label 控件Label 控件是 WinForm 中最基础、最常用的控件之一&#xff0c;主要用于在界面上显示文本信息&#xff0c;通常作为说明、提示或标题&#xff0c;不直接接受用户输入。它是构建用户界面的基础组件&#xff0c;在引导用户操作、展示状态信息等方面发挥重要作用。Label…

鸿蒙中相册权限弹窗

model.json5配置权限{"name": ohos.permission.READ_MEDIA,"reason":"$string:permission_reason_IMG","usedScene": {}}ui使用const url albumClass.onRequestCameraPermission()类import { abilityAccessCtrl, common, PermissionR…

智能车辆热管理测试方案——提升效能与保障安全

车辆热管理在能源危机出现、汽车排放法规日益严格以及人们对汽车舒适性要求更高的背景下应运而生。将各个系统或部件如冷却系统、润滑系统和空调系统等集成一个有效的热管理系统&#xff1b;控制和优化车辆的热量传递过程&#xff0c;保证各关键部件和系统良好运行&#xff1b;…

如何提升 TCP 传输数据的性能?详解

TCP 会保证每一个报文都能够抵达对方&#xff0c;它的机制是这样&#xff1a;报文发出去后&#xff0c;必须接收到对方返回的确认报文 ACK&#xff0c;如果迟迟未收到&#xff0c;就会超时重发该报文&#xff0c;直到收到对方的 ACK 为止 所以&#xff0c;TCP 报文发出去后&…

WiFi连接简单流程

WiFi连接流程与Debug方法一、WiFi连接全流程与详细日志解读 WiFi连接是一个多阶段、跨层次的复杂过程&#xff0c;涉及物理层、链路层、网络层和应用层的多种协议协作。整个流程包括AP初始化、终端扫描、认证、关联、四次握手、DHCP获取IP、网络可用与后续服务。1. AP初始化与参…

Python——Pandas库,超详细教程

前言1、Python的Pandas是一个基于Python构建的开源数据分析库&#xff0c;它提供了强大的数据结构和运算功能。2、Series&#xff1a;一维数组&#xff0c;类似于Numpy中的一维array&#xff0c;但具有索引标签&#xff0c;可以保存不同类型的数据&#xff0c;如字符串、布尔值…

go语言的gRPC教程-protobuf基础

一、前言 RPC&#xff0c;全称Remote Procedure Call&#xff0c;中文译为远程过程调用。通俗地讲&#xff0c;使用RPC进行通信&#xff0c;调用远程函数就像调用本地函数一样&#xff0c;RPC底层会做好数据的序列化与传输&#xff0c;从而能使我们更轻松地创建分布式应用和服…

Linux基本指令,对路径的认识

引言简单介绍一些Linux的基本指令&#xff0c;快速上手Linux操作系统。一、ls指令语法&#xff1a;ls [选项] [目录或文件]功能&#xff1a;&#xff1a;对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件件&#xff0c;将列出文件名以及其他信息常用选项&a…

25. html 使用的字符集是什么,有什么特点

总结 utf-8&#xff0c;支持所有语言一、HTML 默认使用的字符集✅ HTML 页面推荐使用 UTF-8 字符集<meta charset"UTF-8" />这是 HTML5 中推荐的标准字符编码&#xff0c;用于定义网页中字符的编码方式。二、什么是字符集&#xff08;Character Encoding&#…

MySQL 读写分离(含示例代码)

背景 面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性…