MybatisPlus

一、MybatisPlus概述

1、学习MybatisPlus前所需的知识

​ Mybatis、Spring、SpringMVC

2、为什么要学习MybatisPlus

Mybatis本身就是被用作简化我们CRUD过程的一个框架,而MybatisPlus是和Mybatis配合使用的,可以更加简化我们的CRUD过程,可以自动化完成CRUD。

官网原话:MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。MybatisPlus的愿景是成为 MyBatis 最好的搭档,就像魂斗罗中的 1P、2P一样,基友搭配,效率翻倍。

3、MybatisPlus的特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作

4、官方文档网址

https://mp.baomidou.com/guide/

5、环境介绍

  1. springboot2.X
  2. mybatisplus3.0.5
  3. mysql8.0.12
  4. jdk1.8

6、进入文章前的提示(必读)

由于编写该文章的时候,没有使用最新的MybatisPlus版本,而目前MybatisPlus已经更新了非常多的版本了,其中很多下文设计的操作会有不同,但是总体来说是差不多的,我使用的3.0.5MP展示了更多配置细节,而最新版则省略或者改变了一些配置方式,如果你使用的是最新版的或者和我的版本差距比较大的话只需要去官方文档查看使用方法即可,基本上都是相通的。

二、Hello,MybatisPlus(入门实战)

1、学习思路

学习新组件的通用思路:

  1. 查找应该导入什么依赖
  2. 研究怎么导入,可能有版本不兼容等问题
  3. 尝试如何使用
  4. 使用成功后,研究该组件的特性和与没有该组件的时候相比的扩展/增强

2、开始入门

  1. 创建springboot项目(只需要勾选web即可)

  2. 引入依赖

    这里需要mysql的依赖和mybatisplus的依赖

            <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.0.5</version></dependency>
    
  3. 创建数据库并插入数据,实例数据库创建语句和插入语句如下

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)
)ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC; 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');
  1. 连接数据库

    在application.yml或properties文件中配置数据库参数

spring:datasource:username: rootpassword: 123456#?serverTimezone=UTC解决时区的报错url: jdbc:mysql://localhost:3306/你的数据库名
?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8driver-class-name: com.mysql.cj.jdbc.Driver
  1. 对比传统mybatis和使用了mybatis-plus后的区别

    传统:pojo->dao(使用mybatis进行映射)->service->controller

    mybatis-plus:pojo->mapper(继承BaseMapper类)->即可完成大部分简单的增删改查分页等任务

@Mapper
@Repository
//继承BaseMapper类即可
public interface UserMapper extends BaseMapper<User> {}
    @Autowiredprivate UserMapper userMapper;@Testvoid contextLoads() {List<User> userList = userMapper.selectList(null);userList.forEach(System.out::println);}

​ 测试结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CCs02Flx-1637981062998)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20211120113947137.png)]

三、配置MybatisPlus日志

1、为什么要配置日志

由于我们现在的SQL语句都是自动化的,我们看不见它的运行情况,但我们又时常希望可以看到它是用什么SQL语句来执行的还有很多运行中的其他信息,所以需要日志来帮助我们将对数据库的操作可视化。

2、配置和使用

  1. 在application.yml或properties文件中配置数据库参数

    mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 在控制台输出,也可以用其他的日志方式
    
  2. 测试结果

我们可以看到,执行的SQL语句为:

查询到的信息为:

使用的连接和连接池为:

四、从CRUD引出MP的一些出色的功能

1.1、插入

  1. 我们插入时并没有设置ID,我们观察一下插入后的ID会是什么
        User user = new User();user.setName("dt");user.setAge(3);user.setEmail("123456@126.com");userMapper.insert(user);
  1. 控制台日志

我们发现,插入的时候,mybatis-plus自动给我们设置了一个ID(主键),且这个ID肉眼看起来没有什么规律。

1.2、主键插入策略

我们在插入中,由于ID是主键,所以mp给我们自动用分布式全局唯一ID算法得到一个全局唯一ID。我们阅读文档发现,可以在POJO实体类中的主键属性上增加一个注解 @TableId ,该注解可以设置主键的生成方法/算法,具体参数如下

2.1、更新

  1. 更新某一行数据
        User user = new User();user.setName("faker");user.setId(0L);user.setEmail("123456@qq.com");userMapper.updateById(user);
  1. 控制台,此时的预SQL语句和插入元素为:

  2. 我们还是更新这一行数据,但更新的字段不同(少了email)

        User user = new User();user.setName("rookie");user.setId(0L);userMapper.updateById(user);
  1. 控制台,此时的预SQL语句和插入元素为:

  2. 我们发现,我们的更新函数并没有发生变化,参数也没有发生变化,都是一个user对象,但是执行的SQL语句却不一样,它会根据我们对user赋值的情况来对SQL语句作出改变,也就是动态SQL,MP直接帮我们做了动态SQL这件事,如果使用的还是mybatis则需要我们自己手动写动态SQL,这也是MP相比传统mybatis更加方便的地方

五、自动填充

1、为什么要自动填充

我们在操作数据库表中的数据的时候,往往需要记录被操作的数据的创建时间和修改时间,方便以后查询某个数据是什么时候创建的和什么时候修改的,像阿里巴巴开发手册所说的,他们会在每个表里面加上gmt_create .gmt_modified两个字段来作为创建时间和修改时间。

而这种仅作为记录时间的字段,往往不应该每次操作的时候都调用编写代码来实现,这样过于低效,由于是固定化的任务,我们完全可以让它自动化实现,这就是为什么我们要自动填充字段。

2、mybatisplus给出的自动填充的方法

  1. 在需要填充的POJO实体类的字段上添加注解 @TableField ,该注解中的fill属性就用于控制该字段是否自动填充以及在什么时候填充,如:
    private Date createTime;@TableField(fill = FieldFill.INSERT_UPDATE)private Date updateTime;
  1. 自己编写一个实现MetaObjectHandler接口的类,该接口需要实现两个方法:insertFill和updateFill,用于控制插入和更新的时候自动填充的操作,这里主要要加**@Component**注解,否则不会注入到springboot中
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {@Overridepublic void insertFill(MetaObject metaObject) {this.setFieldValByName("createTime",new Date(),metaObject);this.setFieldValByName("updateTime",new Date(),metaObject);}@Overridepublic void updateFill(MetaObject metaObject) {this.setFieldValByName("updateTime",new Date(),metaObject);}
}
  1. 测试数据
        User user = new User();user.setName("meiko");user.setAge(10);user.setEmail("123456@126.com");userMapper.insert(user);
  1. 测试结果

  1. 可能遇到的问题:时间不对,这很可能是我们数据库的时差参数不对,把数据库配置中的url属性中的serverTimezone参数改为serverTimezone=GMT%2B8即可解决问题

3、另外一种自动填充的方法

也可以直接在数据库创建的时候修改字段为自动填充,或者在需要的时候,往表里添加字段的同时设置为自动填充。

但我不建议用这样的方式来做自动填充,因为真实开发环境中,往往数据库的权限不在开发方的手上,难以对数据库进行直接的修改,而且在数据库表使用后再增加字段是很大的禁忌,一般来说都是不会发生的。我们使用代码来解决问题,更加的灵活,也更加的普适,如果想要了解这种方法的话可以自动查找资料,作为了解即可。

六、乐观锁

1、什么是乐观锁

乐观锁:顾名思义十分乐观,他总是认为不会出现问题,无论干什么都不去上锁,如果出现了问题,就会再次更新值进行测试

有乐观锁也就有悲观锁,悲观锁和乐观锁相对

悲观锁:顾名思义十分悲观,他总是认为出现问题,无论干什么都会先上锁再操作

2、乐观锁的实现原理

首先需要添加乐观锁的数据库表中添加version字段,并在MP中手动乐观锁添加组件

然后:

当需要更新数据的时候,在where语句后匹配version字段是否正确,如果正确则对数据进行修改,并让version自增,否则操作失败,原SQL语句不执行(执行失败)

3、乐观锁的代码实现

1.增加version字段

    @Version    private Integer version;

2.注册乐观锁

@MapperScan("com.xiafan.mapper")
@EnableTransactionManagement
@Configuration // 配置类
public class MybatisPlusConfig {// 注册乐观锁插件@Beanpublic OptimisticLockerInterceptor optimisticLockerInterceptor(){return new OptimisticLockerInterceptor();}
}

3.测试乐观锁,测试串行更新和模拟多线程更新测试

//串行,可以正常运行
@Testvoid testVersion1(){User user = userMapper.selectById(1461964912795344897L);user.setName("ququ");user.setAge(18);userMapper.updateById(user);;}
//模拟多线程,user和user1对同一行数据进行更新,但在user还没更新的时候user1已经获取了原来user获取的对象了,此时两个对象version都是2,但是当user1提交后,version自增为3,再提交user时,由于此时需要验证version为2,但version已经自增为3了,所以该更新语句失效了
@Testvoid testVersion2(){User user = userMapper.selectById(1461964912795344897L);user.setName("qq");user.setAge(15);User user1 = userMapper.selectById(1461964912795344897L);user1.setName("wx");user1.setAge(16);userMapper.updateById(user1);userMapper.updateById(user);}

七、简单单表查询

1、按单个ID单个查询

2、按单个ID多个查询

3、使用Map封装键值对来帮助我们进行简单的条件查询

所调用的方法如下:

两种按ID查询的方法不必多说,重点演示一下如何进行条件查询,演示代码如下:

        Map<String, Object> map = new HashMap<>();//查询 id=1 and age=20 的数据map.put("id",1L);map.put("age",20);userMapper.selectByMap(map);

八、分页查询

1、分页查询的实现方法

  1. 用原始sql语句中的limit关键字实现
  2. 使用如pageHelper这种第三方的插件实现
  3. 在mybatis-plus中配置分页查询插件

2、如何在mybatis-plus中配置分页查询插件

  1. 在配置类中配置插件,和乐观锁插件的配置相似
    //注册分页插件@Beanpublic PaginationInterceptor paginationInterceptor(){return new PaginationInterceptor();}
  1. 直接使用插件内置的Page对象
        @Testvoid testPage(){//第一个参数为当前页面,第二个参数为每一页的数据个数(行数)Page<User> objectPage = new Page<>(1,5);userMapper.selectPage(objectPage,null);objectPage.getRecords().forEach(System.out::println);}

九、删除

1、按单个ID单个删除

2、按单个ID多个删除

3、使用Map封装键值对来帮助我们进行简单的条件删除

所调用的方法如下:

两种按ID删除的方法不必多说,重点演示一下如何进行条件删除,演示代码如下:

        Map<String, Object> map = new HashMap<>();//查询 id=1 and age=20 的数据map.put("id",0L);map.put("name","xiaohu");userMapper.deleteByMap(map);

十、逻辑删除

1、什么是逻辑删除

逻辑删除之所以叫逻辑删除,是因为它和物理删除这种真实删除不一样,区别如下:

物理删除:从存储介质中完全抹去了这个数据,这个数据永远的删除了,无法再得到它

逻辑删除:我们通过某些方法,比如增加一个字段,当字段=0的时候,默认它是存在的,当字段=1的时候,默认它被删除了,但是它其实并没有被删除,只是在做其他操作的时候会被忽略掉,只是在“逻辑上”被删除了,所以被叫做逻辑删除

2、如何实现逻辑删除功能

逻辑删除的实现和乐观锁的实现非常相似。

  1. 在数据库中添加一个deleted字段,默认值置为0(最好这么做,后面会进行配置,可以根据自己的需要改,但是建议使用0)
  2. 在实体类中添加deleted字段
    @TableLogicprivate Integer deleted;
  1. 和乐观锁插件、分页插件类似,在配置类中注册插件
    //注册逻辑删除插件@Beanpublic ISqlInjector sqlInjector(){return new LogicSqlInjector();}
  1. 在springboot的yml文件中编写逻辑删除的相关配置
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 在控制台输出,也可以用其他的日志方式global-config:db-config:logic-delete-value: 1logic-not-delete-value: 0
  1. 测试逻辑删除功能

3、总结

本质上,逻辑删除就是把”删除“变为了”更新“,我们只要增加一个标记字段,然后约定好这个字段为某个值代表它应该被”看见“,为某个别的值代表它不能被”看见“,就可以实现看起来好像删除了,但其实数据还存在我们的存储介质中的效果

十一、条件构造器

1、什么是条件构造器

条件构造器是用来让MP能够进行复杂SQL操作的工具,如模糊查询,分组查询等,这些复杂操作只使用MP的BaseMapper里已经封装好的方法是不够用的(不加Wrapper类参数的话)

2、怎么使用条件构造器

条件构造器的使用是建立在Wrapper类上的,Wrapper类封装了绝大部分SQL语句的操作,且支持链式编程(即可以连续设置参数)

实例:这里只实例一部分方法,其他的去看官方文档即可,用法简单易懂

        //>=@Testvoid test1(){QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.ge("age","28").isNotNull("name").select("name");userMapper.selectList(wrapper).forEach(System.out::println);}//between@Testvoid test2(){QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.between("age",20,30);List<User> userList = userMapper.selectList(wrapper);for (User user : userList){System.out.println(user.getName());}}//模糊查询like@Testvoid test3(){QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.likeRight("name","J").notLike("name","a");List<User> userList = userMapper.selectList(wrapper);for (User user : userList){System.out.println(user.getName());}}

十二、代码自动生成器

本质是使用MP封装好的类对所需生成的代码文件进行配置,这一块没有什么技术上的要求,这里使用官方文档的例子作为说明:

// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {/*** <p>* 读取控制台内容* </p>*/public static String scanner(String tip) {Scanner scanner = new Scanner(System.in);StringBuilder help = new StringBuilder();help.append("请输入" + tip + ":");System.out.println(help.toString());if (scanner.hasNext()) {String ipt = scanner.next();if (StringUtils.isNotBlank(ipt)) {return ipt;}}throw new MybatisPlusException("请输入正确的" + tip + "!");}public static void main(String[] args) {// 代码生成器AutoGenerator mpg = new AutoGenerator();// 全局配置GlobalConfig gc = new GlobalConfig();String projectPath = System.getProperty("user.dir");gc.setOutputDir(projectPath + "/src/main/java");gc.setAuthor("jobob");gc.setOpen(false);// gc.setSwagger2(true); 实体属性 Swagger2 注解mpg.setGlobalConfig(gc);// 数据源配置DataSourceConfig dsc = new DataSourceConfig();dsc.setUrl("jdbc:mysql://localhost:3306/ant?useUnicode=true&useSSL=false&characterEncoding=utf8");// dsc.setSchemaName("public");dsc.setDriverName("com.mysql.jdbc.Driver");dsc.setUsername("root");dsc.setPassword("密码");mpg.setDataSource(dsc);// 包配置PackageConfig pc = new PackageConfig();pc.setModuleName(scanner("模块名"));pc.setParent("com.baomidou.ant");mpg.setPackageInfo(pc);// 自定义配置InjectionConfig cfg = new InjectionConfig() {@Overridepublic void initMap() {// to do nothing}};// 如果模板引擎是 freemarkerString templatePath = "/templates/mapper.xml.ftl";// 如果模板引擎是 velocity// String templatePath = "/templates/mapper.xml.vm";// 自定义输出配置List<FileOutConfig> focList = new ArrayList<>();// 自定义配置会被优先输出focList.add(new FileOutConfig(templatePath) {@Overridepublic String outputFile(TableInfo tableInfo) {// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;}});/*cfg.setFileCreate(new IFileCreate() {@Overridepublic boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {// 判断自定义文件夹是否需要创建checkDir("调用默认方法创建的目录,自定义目录用");if (fileType == FileType.MAPPER) {// 已经生成 mapper 文件判断存在,不想重新生成返回 falsereturn !new File(filePath).exists();}// 允许生成模板文件return true;}});*/cfg.setFileOutConfigList(focList);mpg.setCfg(cfg);// 配置模板TemplateConfig templateConfig = new TemplateConfig();// 配置自定义输出模板//指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别// templateConfig.setEntity("templates/entity2.java");// templateConfig.setService();// templateConfig.setController();templateConfig.setXml(null);mpg.setTemplate(templateConfig);// 策略配置StrategyConfig strategy = new StrategyConfig();strategy.setNaming(NamingStrategy.underline_to_camel);strategy.setColumnNaming(NamingStrategy.underline_to_camel);strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");strategy.setEntityLombokModel(true);strategy.setRestControllerStyle(true);// 公共父类strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");// 写于父类中的公共字段strategy.setSuperEntityColumns("id");strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));strategy.setControllerMappingHyphenStyle(true);strategy.setTablePrefix(pc.getModuleName() + "_");mpg.setStrategy(strategy);mpg.setTemplateEngine(new FreemarkerTemplateEngine());mpg.execute();}}

MybatisPlus入门(涉及大部分常用操作)相关推荐

  1. tableau入门简介和常用操作

    目录  1.tableau的介绍    1)tableau的优势    2)维度和指标    3)展现形式    4)设计形式    5)设计流程  2.数据导入.数据浏览  3.调整tableau中 ...

  2. 微电子新手入门之Cadence常用操作——Cadence layout打散版图

    这里以NMOS管为例,先看一下NMOS管的整体版图 先选中nmos管的版图,然后执行菜单栏命令: 按照如下进行设置 快捷键m,然后即可拖动不同金属层

  3. 微电子新手入门之Cadence常用操作——原理图/版图的导出

    原理图和版图的导出步骤是一致的,这里以导出黑白原理图为例介绍. 1)打开相应的schematic.        2)在Virtusuo Schematic Editor中,[File] - [Pri ...

  4. 微电子新手入门之ADS常用操作——ADS的port与layout不能相连

    在铺好金属线后,接下来进行准备EM电磁仿真,这里需要加入port,在选择对应金属层,然后在金属线的边缘处放入port,确保port与金属线相连接,但是有时候会出现一个问题:port与金属线相连接了,但 ...

  5. 微电子新手入门之Cadence常用操作——ADS电磁仿真后电感的SP参数文件导入到Cadence中

    进入版图页面,点击EM仿真 仿真结束,出现S参数波形图 在波形图最上面的菜单栏上的[Tools]中的[Data File Tool...],并进行相应的设置,最后如图所示,会导出对应的SP文件. 通过 ...

  6. Python入门基础篇(四)字符串的常用操作,全面易懂,简单实用!!!

    请认真阅读哦!!! 字符串几乎是所有编程语言在项目开发过程中涉及最多的一块内容,许多地方都是用到字符串的基本操作,前面讲了列表.元组,这次就来说说字符串吧 文章目录 字符串的常用操作: 1.拼接字符串 ...

  7. mybatis、mybatisplus的常用操作

    文章目录 1. mybatis-批量增加 2. 单条件批量删除 3. 多条件批量删除 4. mp的lambda用法.分组查询 5. mybatis-plus 插入/更新操作 ①:更新操作 ②:插入或更 ...

  8. 数学建模速成! 两小时零基础入门 MATLAB 教程(一)—— Matlab常用操作和基本语法

    Matlab常用操作和基本语法 没有涉及到编程的内容, 就是介绍一些MATLAB软件的操作, 和简单的语法如注释输出之类的 目录 文章目录 Matlab常用操作和基本语法 1. help查看函数说明 ...

  9. Oracle数据库的数据更新语句与视图操作示例(包含大部分常用语句)

    Oracle数据库的数据更新语句与视图操作(包含大部分常用语句) 目标 本文用到的关系模式 语句示例 1.在创建的student, course, teacher, sc, tc表中用SQL语句完成以 ...

最新文章

  1. PYPL 12 月 IDE 榜单:Eclipse 有望超越 Visual Studio
  2. 【原】Coursera—Andrew Ng机器学习—课程笔记 Lecture 17—Large Scale Machine Learning 大规模机器学习...
  3. 如何在Flutter(2021)中创建过滤器搜索列表视图
  4. web前端培训分享Electron之IPC 通信
  5. PHP文件函数 记录日志功能
  6. QT中使用OpenGL绘制图形
  7. 关于技术文章“标题党”一事我想说两句
  8. python高阶函数闭包装饰器_Python自学从入门到就业之高阶函数、嵌套函数、闭包、装饰器...
  9. 知方可补不足~sqlserver中使用ROW_NUMBER进行的快速分页
  10. zoj 2110 dfs,剪枝
  11. python代码写龙卷风_python-打开网站时龙卷风403获取警告
  12. Redis 面试常问问题
  13. 计算机键盘交替换键是,用了这么久电脑,还不知道键盘上的Ctrl、Tab、Alt是啥英文?快来涨知识!...
  14. 我想深入学习Go语言
  15. Vue-全局websocket 实现消息推送
  16. typora的 ctrl shift k 快捷键和搜狗的快捷键冲突了
  17. rec卡刷root吗,卡刷需要root权限吗
  18. 环境建环和给排水工程计算机应用试卷,环境建环和给水排水工程计算机应用教材内容...
  19. 玩转「Wi-Fi」系列之测试工具(三)
  20. Python 经纬度,偏航角,距离计算

热门文章

  1. 用python做视觉检测系统_教你用 Python 做一个物体检测系统
  2. AI在实体零售行业的应用场景
  3. 去除Windows的弹窗广告
  4. 对单链表实现就地逆置
  5. 【算法java版11】:实现求s = a + aa + aaa + aaaa + aa...a 的值,其中a是一个数字,几个数相加由键盘控制
  6. 杰奇cms mysql查询_杰奇cms自动推送链接插件使用方式
  7. 科学家量子计算机时间倒流,腾讯内容开放平台
  8. html5+css3个人相册网页制作
  9. 安卓通讯录【联系人管理】
  10. Docker Swarm从部署到基本操作