一:Mybatis-Plus概述

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

(1):特性

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

具体代码下载:

git clone https://gitee.com/antLaddie/mybatis_plus_demo.git

二:MapperCRUD接口操作

  我们在Mapper层上使用CRUD时就需要在具体的接口上继承BaseMapper接口,为Mybatis-Plus启动时自动解析实体表关系映射转换为Mybatis内部对象注入容器;其中Searializable为任意类型的主键,Mybatis-Plus不推荐使用复合主键约定每一张表都有自己的唯一ID主键

Mybatis_Plus基本必备注解说明:
①:@TableId: 此注解代表当前实体字段对应数据库表主键
type:设置主键生成类型
value:设置实体字段与数据库字段映射不一致
②:@TableField 此注解代表当前实体字段对应数据库表普通字段
value:设置实体字段与数据库字段映射不一致
fill: 设置填充模式
③:@TableName 映射到数据库表名称
value:具体映射表名

1:Insert插入方法

// 插入一条记录  T entity 传入对应的实体类即可
int insert(T entity);

注意事项:在使用保存或根据ID查询、更新、删除时,主键字段必须添加@TableId

/**
* @Auther: xiaoYang
* @Date: 2021/4/6 12:38
* @Description: 学生实体类
*/
//下面四个都是lombok插件注解
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Student implements Serializable { @TableId //必须添加此注解代表当前实体的主键
private Long sid; //学生ID
private String sname; //姓名
private String ssex; //性别
private Integer sage; //年龄
private String saddress; //住址
private Integer isDel; //是否被删除 0未删除 1删除
private Date createTime; //数据行创建时间
private Date updateTime; //数据行更新时间
private Integer version; //版本(用于乐观锁)
}

在实体类上必加@TableId

2:Select查询方法

  Mapper层上也实现了特别多的基本查询方法,如id查询、条件查询、分页查询、统计查询...

## 参数类型
类型 参数名 描述
Serializable id 主键ID
Wrapper
<T> queryWrapper 实体对象封装操作类(可以为 null)
Collection<? extends Serializable> idList 主键ID列表(不能为 null 以及 empty)
Map<String, Object> columnMap 表字段 map 对象
IPage<T>
page 分页查询条件(可以为 RowBounds.DEFAULT) ## 具体方法
// 根据 ID 查询
T selectById(Serializable id);
// 根据 entity 条件,查询一条记录
// 指定的条件查询查询到多条数据则出现异常
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
// 批量查询 底层使用的是 where xx in ( *** ) IN关键字
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 entity 条件,查询全部记录 全表查询则传入null
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据 columnMap 条件)
// 传入map类型,多个键值则为 aaa = xxx AND bbb = xxx
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根据 Wrapper 条件,查询全部记录
//以键值对的形式返回 [ {xx=xx,...},{xx=xx,...} ]
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);

// ### 示例
@Test
public void selectList() {
//使用条件构造器模糊查询带 %壮%的名称
QueryWrapper<Student> wrapper = new QueryWrapper<Student>().like("sname", "壮");
List<Student> students = studentMapper.selectList(wrapper);
students.forEach(System.out::println);
}

Select基本示例

3:Update更新方法

  Mapper也提供了更新方法,可以根据ID来更新一条,或者根据条件查询更新多条

// 根据 ID 修改    
int updateById(@Param(Constants.ENTITY) T entity);
// 根据 whereEntity 条件,更新记录
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);

    // 根据 ID 修改    一次更新一个
// int updateById(@Param(Constants.ENTITY) T entity);
@Test
public void updateById() {
//设置基本信息
Student stu = new Student();
stu.setSname("蚂蚁小哥");
stu.setSsex("男");
stu.setSaddress("上海浦东");
stu.setSid(1379438319679049730L); //必须设置ID 要不然找不到
int i = studentMapper.updateById(stu);
System.out.println("更新记录条数:" + i);
} // 根据 whereEntity 条件,更新记录 可以一次性更新多个
// int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
@Test
public void update() {
//设置基本信息 更改年龄66
Student stu = new Student();
stu.setSage(66);
int i = studentMapper.update(stu, new QueryWrapper<Student>().eq("ssex", "保密"));
System.out.println("更新记录条数:" + i);
}

Update基本示例

4:Delete删除方法

  Mapper其实也提供了删除方法,如根据ID删除、批量删除、条件删除....

// 根据 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);

    // 根据 entity 条件,删除记录
// int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
@Test
public void delete() {
// 删除名字中带 %壮% 的名字
int i = studentMapper.delete(new QueryWrapper<Student>().like("sname", "壮"));
System.out.println("删除个数:" + i);
} // 删除(根据ID 批量删除)
// int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
@Test
public void deleteBatchIds() {
// 传入集合 删除ID为 2 4 6 8
int i = studentMapper.deleteBatchIds(Arrays.asList(2, 4, 6, 8));
System.out.println("删除个数:" + i);
}

Delete基本示例

5:Page分页查询及分页插件

  细心的话,大家会发现,写完了分页查询后却不起效果,其实这是因为分页查询是需要配置Mybatis-Plus指定插件的;但要注意的是,分页查询和我们上面的普通查询是不一样的,我们得配置mybatis-plus特有的分页插件才可以使用,具体配置如下:

@Configuration
@MapperScan(basePackages = "cn.xw.mapper") //映射mapper扫描
public class MybatisPlusConfig { //在这个Bean对象里面配置我们所需的插件,这里我们只需要配置一个分页插件即可
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//拦截器对象
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//创建分页插件 并指定为mysql数据库方言 也可以通过对象set设置各种属性
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
//配置的每页最多显示多少条数据
pageInterceptor.setMaxLimit(10L);
// 放入添加分页拦截器插件
interceptor.addInnerInterceptor(pageInterceptor);
return interceptor;
}
}

自定义config配置类==>分页

@SpringBootTest
public class PageTests { @Autowired
private StudentMapper studentMapper; // 根据 entity 条件,查询全部记录(并翻页)
// IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
@Test
public void selectPage() {
//分页对象
Page<Student> page = new Page<>(1, 10);
//分页查询
Page<Student> pageResult = studentMapper.selectPage(page, null);
System.out.println("总记录数:" + pageResult.getTotal());
System.out.println("每页总数" + pageResult.getSize());
System.out.println("当前页:" + pageResult.getCurrent());
System.out.println("排序字段信息:" + pageResult.getOrders());
pageResult.getRecords().forEach(System.out::println);
} // 根据 Wrapper 条件,查询全部记录(并翻页)
// IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
@Test
public void selectMapsPage() {
//分页对象
Page<Map<String, Object>> page = new Page<>(1, 5);
//分页查询
Page<Map<String, Object>> pageResult = studentMapper.selectMapsPage(page, null);
System.out.println("总记录数:" + pageResult.getTotal());
System.out.println("每页总数" + pageResult.getSize());
System.out.println("当前页:" + pageResult.getCurrent());
System.out.println("排序字段信息:" + pageResult.getOrders());
List<Map<String, Object>> list = pageResult.getRecords();
for (Map<String, Object> li : list) {
System.out.println("姓名:" + li.get("sname") + " 地址:" + li.get("saddress"));
}
}
}

使用mybatis-plus插件来分页测试

6:Sequence主键增长策略及主键生成策略

  其实mybatis-plus内置了几个主键增长策略,我们只需要通过@TableId注解里的type来完成对其设置,也内置了主键增长策略

##  ASSIGN_ID(雪花算法)

  如果我们不设置主键类型值,默认则使用 IdType.ASSIGN_ID 策略(自3.3.0起)该策略会使用雪花算法自动生成主键ID,主键类型为长整型或字符串(分别对应的MySQL的表字段为BIGINT和VARCHAR)

  该策略使用接口IdentifierGenerator的方法nextId(以实现类为DefaultIdentifierGenerator雪花算法)

  雪花算法(雪花)是微博开源的分布式ID生成算法其核心思想就是:使用一个64位的长整型的数字作为全局唯一ID。在分布式系统中的应用十分广泛,且ID引入了时间戳,基本上保持自增的

@Data
public class Student implements Serializable {
@TableId(type = IdType.ASSIGN_ID) //使用雪花算法生成主键 不写也是使用雪花算法
private Long sid; //学生ID
private String sname; //姓名
 ......省略
}

##  ASSIGN_UUID(去除了中横线的UUID)

  如果使用 IdType.ASSIGN_UUID 策略,并重新自动生成排除中划线的UUID作为主键。主键类型为String,对应MySQL的表分段为VARCHAR(32)

  该策略使用接口IdentifierGenerator的方法nextUUID

@Data
public class Student implements Serializable {
@TableId(type = IdType.ASSIGN_UUID) //使用了UUID(没有中横线)的方式生成主键 注意的是主键字段是varchar(32)才行
private Long sid; //学生ID
private String sname; //姓名
 ......省略
}

##  AUTO(数据库自增长 适用于MySQL)

  对于MySQL适用于这种自增长,但是我们在创建数据库字段主键时别忘了设置auto_increment

@Data
public class Student implements Serializable {
// 对于像MySQL这样的支持主键自动递增的数据库,我们可以使用IdType.AUTO策略
@TableId(type = IdType.AUTO) // 特别注意 数据库要加 auto_increment
private Long sid; //学生ID
private String sname; //姓名
 ......省略
}

##  INPUT(插入前自行设置主键自增)

  针对有序列的数据库:Oracle,SQLServer等,当需要建立一个自增序列时,需要用到序列

  在Oracle 11g中,设置自增扩,需要先创建序列(SQUENCE)再创建一个触发器(TRIGGER)

  在Oracle 12c中,只需要使用IDENTITY属性就可以了,和MySQL一样简单

    Mybatis -Plus已经定义好了常见的数据库主键序列,我们首先只需要在@Configuration类中定义好@Bean:

  Mybatis -Plus内置了如下数据库主键序列(如果内置支持不满足你的需求,可实现IKeyGenerator接口来进行扩展):

  DB2KeyGenerator、H2KeyGenerator、KingbaseKeyGenerator、OracleKeyGenerator、PostgreKeyGenerator

// 配置类
@SpringBootConfiguration
@MapperScan(basePackages = "cn.xw.mapper") //映射包扫描
public class MybatisPlusConfig {
// 配置主键序列方式
@Bean
public IKeyGenerator keyGenerator() {
//创建Oracle序列
return new OracleKeyGenerator();
}
}

  然后实体类配置主键Sequence,指定主键策略为IdType.INPUT即可;支持父类定义@KeySequence子类使用,这样就可以几个表共享一个Sequence

  如果主键是String类型的,也可以使用;如何使用序列作为主键,但是实体主键类型是字符串开头,表的主键是varchar2,但是需要从序列中取值

  实体定义@KeySequence注解clazz指定类型String.class

  实体定义主键的类型字符串

  注意:oracle的序列返回的是Long类型,如果主键类型是Integer,可能会引起ClassCastException

@KeySequence(value = "SQL_TEST", clazz = String.class)
public class Student implements Serializable {
@TableId(type = IdType.INPUT)
private Long sid; //学生ID
private String sname; //姓名
...后面省略
}

7:自动填充功能

  我们试想这样一个场景,我们每次添加数据时有个字段是记录数据创建时间的createTime;那么我们修改数据时是要由updateTime来记录我们什么时候修改了数据;前提我们使用自动填充功能实现时数据库创建类型不要以timestamp时间戳,因为这样数据库层面就可以实现此功能

  自动填充功能不光可以实现时间的自动填充,其它的我们有需求都是可以实现的

  实现元对象处理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler

  注解填充字段 @TableField(.. fill = FieldFill.INSERT) 生成器策略部分也可以配置

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@TableName(value = "student") // 数据库表名称
public class Student implements Serializable {
//雪花ID
@TableId(type = IdType.ASSIGN_ID)private Long sid; //学生ID
private String sname; //姓名
private String ssex; //性别
private Integer sage; //年龄
private String saddress; //住址
private Integer isDel; //是否被删除 0未删除 1删除
@TableField(value = "create_time", fill = FieldFill.INSERT)
private Date createTime; //数据行创建时间
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private Date updateTime; //数据行更新时间
private Integer version; //版本(用于乐观锁)
}

@Slf4j  //日志
@Component //加入容器
public class MyMetaObjectHandler implements MetaObjectHandler { // 添加数据时的填充方案
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
Date currentDate = new Date();
// 插入创建时间
if (metaObject.hasSetter("createTime")) {
this.strictInsertFill(metaObject, "createTime", Date.class, currentDate);
}
// 同时设置修改时间为当前插入时间
if (metaObject.hasSetter("updateTime")) {
this.strictUpdateFill(metaObject, "updateTime", Date.class, currentDate);
}
//this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
} // 更新数据时的填充方案
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictInsertFill(metaObject, "updateTime", Date.class, new Date()); // 起始版本 3.3.0(推荐使用)
}
}

配置自定义handler,填充类实现方案

public enum FieldFill {  //取值范围
//默认不处理
DEFAULT,
//插入填充字段
INSERT,
//更新填充字段
UPDATE,
//插入和更新填充字段
INSERT_UPDATE
}

8:逻辑删除

  其实逻辑删除不是真正来删除具体的数据,而是要对删除的数据使用某个字段来记录当前数据是否被删除了,我们通常使用 is_del 来记录 0 代表未删除 1 代表被删除;此时我的表是存在 is_del 字段的,所以我们就可以认为每次删除就等于更新 is_del 字段值;但是我们得对mybatis_plus配置后才可以生效

在application.yml里配置mybatis-plus:标红的才是我们要配置的
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 日志输出到控制台
global-config:
db-config:
logic-delete-field: isDel
# 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略实体上不配置 @TableLogic)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
在实体类上添加 @TableLogic 注解:
@TableName(value = "student")
public class Student implements Serializable {
//使用雪花算法生成主键 不写也是使用雪花算法
@TableId(type = IdType.ASSIGN_ID)
@TableField(value = "sid")
private Long sid; //学生ID
  ......省略部分
@TableLogic // 在配置里配置了logic-delete-field属性后可不加此注解 在3.3.0版本以上才可以不加
private Integer isDel; //是否被删除 0未删除 1删除
......省略部分
}

  还有一个问题就是,我们在添加数据的时候,我们得设置 is_del 字段数据值为 0 (未删除)吗?其实这可以使用自动填充功能完成,但是最好使用在数据库定义默认值 is_del  int  default 0

三:Mybatis-Plus插件

  在学习插件时首先介绍一下 MybatisPlusInterceptor 它是核心插件 目前代理了 Executor#query 和 Executor#update 和StatementHandler#prepare 方法

MybatisPlusInterceptor 属性:
private List<InnerInterceptor> interceptors = new ArrayList<>();
InnerInterceptor:它是一个接口,我们提供的插件都将基于此接口来实现功能
目前以实现此接口的类:
自动分页
: PaginationInnerInterceptor
多租户: TenantLineInnerInterceptor
动态表名: DynamicTableNameInnerInterceptor
乐观锁: OptimisticLockerInnerInterceptor
sql性能规范: IllegalSQLInnerInterceptor
防止全表更新与删除: BlockAttackInnerInterceptor

  注意:

  使用多个功能需要注意顺序关系,建议使用如下顺序

  • 多租户,动态表名
  • 分页,乐观锁
  • sql性能规范,防止全表更新与删除

   总结: 对sql进行单次改造的优先放入,不对sql进行改造的最后放入#使用方式(以分页插件举例)

1:分页插件

  在上面的分页查询有过介绍

2:乐观锁插件

  乐观锁采用了更加宽松的加锁机制,他相信其它人不会来和它争抢锁的,所以乐观锁更倾向于开发运用,不对数据进行加锁,可以提高数据库的性能,而悲观锁就有点悲观了,利用数据库的锁机制实现,把要操作的数据或者表进行加锁,其它人不给操作,只有等它操作完了才轮到后面人使用,这往往来说就加大了数据库的性能开销

在mybatis-plus里的乐观锁实现方式:
①:取出记录时,获取当前version字段值
②:更新时,带上version字段值
③:执行更新时比对获取的version和数据库已有的version是否一致
set version = newVersion where version = oldVersion
④:如果提交的version和数据库里的version不对应则更新失败

首先配置乐观锁插件:

@SpringBootConfiguration    //声明为配置类
@MapperScan(basePackages = "cn.xw.mapper") //映射包扫描
public class MybatisPlusConfig {
//Mybatis_plus拦截器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//创建插件核心类对象(拦截器)
MybatisPlusInterceptor plusInterceptor = new MybatisPlusInterceptor();
//创建乐观锁插件 对更新生效 并添加到核心插件对象实力里
OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor
= new OptimisticLockerInnerInterceptor();
plusInterceptor.addInnerInterceptor(optimisticLockerInnerInterceptor);
//返回
return plusInterceptor;
}
}

然后在实体类上的version(名字随便)一定要加上@Version注解

4:防止全表更新与删除插件

  其实我们在进行更新或者删除数据时可能会一不小心删除了全部,这都是因为我们忘传条件,或者全匹配条件,这样就会造成数据的全部更新或者删除,这显然是不可以的,所以mybatis-plus为我们提供了防止全表删除或者更新插件

 首先我们去配置类配置 防止全表删除更新插件:

@SpringBootConfiguration    //声明为配置类
@MapperScan(basePackages = "cn.xw.mapper") //映射包扫描
public class MybatisPlusConfig { //Mybatis_plus拦截器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//创建插件核心类对象(拦截器)
MybatisPlusInterceptor plusInterceptor = new MybatisPlusInterceptor();
//创建乐观锁插件 对更新生效 并添加到核心插件对象实力里
OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor
= new OptimisticLockerInnerInterceptor();
plusInterceptor.addInnerInterceptor(optimisticLockerInnerInterceptor);
//创建 防止全表更新与删除插件(顺序必须在乐观锁插件后面)并添加到核心插件对象实力里
BlockAttackInnerInterceptor blockAttackInnerInterceptor = new BlockAttackInnerInterceptor();
plusInterceptor.addInnerInterceptor(blockAttackInnerInterceptor);
//返回
return plusInterceptor;
}
}

    //演示批量删除或者批量修改
@Test
void delTest() {
int delete = studentMapper.delete(null);
System.out.println("批量删除了:" + delete + " 条数据");
// 防止全表更新与删除插件加上后执行批量删除
//### The error occurred while executing an update //解释:执行更新时发生错误
//### Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException:
     //  Prohibition of full table deletion //解释禁止全表删除
}

测试 防止全表删除更新插件

5:总结插件

  我们在使用插件时和上述操作大同小异,直接创建指定插件并add添加到核心插件对象里即可

四:条件构造器

  条件构造器抽象类其实是 AbstractWrapper 这个抽象类里面有特别多的条件方法,而它的实现类有 QueryWrapper 查询条件构造器和 UpdateWrapper 更新条件构造器,我们在查询的时候就用QueryWrapper;在接下来的例子中我都以QueryWrapper实现类来说明

具体条件构造器请参考官方文档

allEq:         多条件匹配查询全部      isNull:        字段 IS NULL
eq: 等于 = isNotNull: 字段 IS NOT NULL
ne: 不等于 <> in: 字段 IN (v0, v1, ...)
gt: 大于 > notIn: 字段 NOT IN (v0, v1, ...)
ge: 大于等于 >= or: 拼接 OR
lt: 小于 < and: AND 嵌套
le: 小于等于 <= inSql: 字段 IN ( sql语句 )
between: BETWEEN 值1 AND 值2 notInSql: 字段 NOT IN ( sql语句 )
notBetween: NOT BETWEEN 值1 AND 值2 groupBy: 分组:GROUP BY 字段, ...
like: LIKE '%值%' orderByAsc: 排序:ORDER BY 字段, ... ASC
notLike: NOT LIKE '%值%' orderByDesc: 排序:ORDER BY 字段, ... DESC
likeLeft: LIKE '%值' orderBy: 排序:ORDER BY 字段, ...
likeRight: LIKE '值%' having: HAVING ( sql语句 ) func: func 方法(主要方便在出现if...else下调用不同方法能不断链)
nested: 正常嵌套 不带 AND 或者 OR
apply: 拼接 sql
last: 无视优化规则直接拼接到 sql 的最后
exists: 拼接 EXISTS ( sql语句 )
notExists: 拼接 NOT EXISTS ( sql语句 )

AbstractWrapper条件查询方法

1:QueryWrapper查询条件构造器

allEq  =  ;  eq = 

    allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
eq(R column, Object val)
eq(boolean
condition, R column, Object val) 参数说明:
Map<R, V> params :传入Map键值对 R 表字段 V 值
boolean null2IsNull:传入的map键值对中,为true是,如果有键值为null的话是否以isNull查询
是否参数为 null 自动执行 isNull 方法, false 则忽略这个字段
boolean condition:执行条件默认true,false 忽略我们条件

    // 多条件查询匹配条件 allEq
@Test
void allEqTest() {
//创建查询对象条件
QueryWrapper<Student> wrapper = new QueryWrapper<>();
//创建查询条件参数
Map<String, Object> map = new HashMap<>();
map.put("sage", 20);
map.put("ssex", null);
wrapper.allEq(true, map, true);
List<Student> students = studentMapper.selectList(wrapper);
students.forEach(System.out::println);
} // 条件查询 等于 =
@Test
void eqTest() {
//创建查询对象条件全部为男生数据 并查询
List<Student> students = studentMapper
.selectList(new QueryWrapper<Student>().eq("ssex", "男"));
students.forEach(System.out::println);
}

allEq、eq测试

ne <> ; gt > ; ge >= ; lt < ; le <=

ne(R column, Object val)
ne(boolean condition, R column, Object val)
gt(R column, Object val)
gt(boolean condition, R column, Object val)
ge(R column, Object val)
ge(boolean condition, R column, Object val)
lt(R column, Object val)
lt(boolean condition, R column, Object val)
le(R column, Object val)
le(boolean condition, R column, Object val)

 // 都差不多,以 gt为例
// ne <> ; gt > ; ge >= ; lt < ; le <= 查询
// .... FROM student WHERE (sage > ? AND ssex <> ?)
@Test
void gtTest(){
List<Student> students = studentMapper
.selectList(new QueryWrapper<Student>()
.gt("sage",21)
.ne("ssex","保密"));
students.forEach(System.out::println);
}

测试代码

综合使用

 //综合编写
@Test
void sumTest(){
//创建查询对象条件
QueryWrapper<Student> wrapper = new QueryWrapper<>();
//条件:查询学生id在5~15之间
wrapper.between("sid",4,15);
//条件:模糊查询学生中不带 %壮% 子的名字
wrapper.notLike("sname","%壮%");
List<Student> students = studentMapper.selectList(wrapper);
students.forEach(System.out::println);
}

综合操作

五:执行 SQL 分析打印

  该功能依赖 p6spy 组件,可以完美输出打印SQL执行时常及其它信息

<!--性能测试-->
<dependency>
  <groupId>p6spy</groupId>
  <artifactId>p6spy</artifactId>
  <version>3.9.1</version>
</dependency>

  更改配置application.yml配置:

spring:
# 设置数据库连接信息
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://localhost:3306/demo_m......
.....

  在resources资源目录下创建spy.properties配置:

#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2

spy.properties 配置文件

然后就和我们正常使用一样来执行SQL语句查询等其它操作;在控制台会打印当前的运行sql执行时间等信息 如下打印:

Consume Time:17 ms 2021-04-12 19:37:25
Execute SQL:SELECT sid ...... FROM student WHERE (sid BETWEEN 4 AND 15 AND sname NOT LIKE '%%壮%%')

六:代码生成器

  AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率

  在使用代码生成器之前我们需要导入相应坐标

    <!--代码生成器坐标  我mybatis-plus版本是3.4.1-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!--下面两个根据自己选择的模板导入其中一个即可-->
<!--模板坐标 freemarker-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
<!--模板坐标 velocity-engine-core-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>

在学习代码生成器之前资料的准备

代码生成器源码搜索下载  代码生成器具体配置

public void fun() {
System.out.println("java版本号:" + System.getProperty("java.version")); // java版本号
System.out.println("Java提供商名称:" + System.getProperty("java.vendor")); // Java提供商名称
System.out.println("Java提供商网站:" + System.getProperty("java.vendor.url")); // Java提供商网站
System.out.println("jre目录:" + System.getProperty("java.home")); // Java,哦,应该是jre目录
System.out.println("Java虚拟机规范版本号:" + System.getProperty("java.vm.specification.version")); // Java虚拟机规范版本号
System.out.println("Java虚拟机规范提供商:" + System.getProperty("java.vm.specification.vendor")); // Java虚拟机规范提供商
System.out.println("Java虚拟机规范名称:" + System.getProperty("java.vm.specification.name")); // Java虚拟机规范名称
System.out.println("Java虚拟机版本号:" + System.getProperty("java.vm.version")); // Java虚拟机版本号
System.out.println("Java虚拟机提供商:" + System.getProperty("java.vm.vendor")); // Java虚拟机提供商
System.out.println("Java虚拟机名称:" + System.getProperty("java.vm.name")); // Java虚拟机名称
System.out.println("Java规范版本号:" + System.getProperty("java.specification.version")); // Java规范版本号
System.out.println("Java规范提供商:" + System.getProperty("java.specification.vendor")); // Java规范提供商
System.out.println("Java规范名称:" + System.getProperty("java.specification.name")); // Java规范名称
System.out.println("Java类版本号:" + System.getProperty("java.class.version")); // Java类版本号
System.out.println("Java类路径:" + System.getProperty("java.class.path")); // Java类路径
System.out.println("Java lib路径:" + System.getProperty("java.library.path")); // Java lib路径
System.out.println("Java输入输出临时路径:" + System.getProperty("java.io.tmpdir")); // Java输入输出临时路径
System.out.println("Java编译器:" + System.getProperty("java.compiler")); // Java编译器
System.out.println("Java执行路径:" + System.getProperty("java.ext.dirs")); // Java执行路径
System.out.println("操作系统名称:" + System.getProperty("os.name")); // 操作系统名称
System.out.println("操作系统的架构:" + System.getProperty("os.arch")); // 操作系统的架构
System.out.println("操作系统版本号:" + System.getProperty("os.version")); // 操作系统版本号
System.out.println("文件分隔符:" + System.getProperty("file.separator")); // 文件分隔符
System.out.println("路径分隔符:" + System.getProperty("path.separator")); // 路径分隔符
System.out.println("直线分隔符:" + System.getProperty("line.separator")); // 直线分隔符
System.out.println("操作系统用户名:" + System.getProperty("user.name")); // 用户名
System.out.println("操作系统用户的主目录:" + System.getProperty("user.home")); // 用户的主目录
System.out.println("当前程序所在目录:" + System.getProperty("user.dir")); // 当前程序所在目录
}

System.getProperty("xx”);信息填写

package cn.xw;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; import java.util.ArrayList;
import java.util.List; /**
* @Auther: xiaoYang
* @Date: 2021/4/13 14:29
* @Description:
*/
public class GeneratorCodeUtils {
public static void main(String[] args) { //设置作者信息
String author = "xiaoYang";
//数据库URL / username / password
String url = "jdbc:mysql://localhost:3306/demo_mp?useSSL=true&useUnicode=true&" +
"characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&" +
"useLegacyDatetimeCode=false&serverTimezone=GMT%2B8";
String username = "root";
String password = "123";
//这个include 和exclude只能存在一个
String[] include = {"student"};
String[] exclude = {};
//乐观锁属性名称 和数据库表字段保持一致
String version = "version";
//逻辑删除属性名称 和数据库表字段保持一致
String isDel = "is_del"; //代码生成器
AutoGenerator autoGenerator = new AutoGenerator();
//全局相关配置
GlobalConfig gc = new GlobalConfig();
String propertyPath = System.getProperty("user.dir");
gc.setOutputDir(propertyPath + "./src/main/java")
.setAuthor(author)
.setDateType(DateType.ONLY_DATE)
.setEnableCache(false)
.setBaseResultMap(true)
.setBaseColumnList(true)
.setOpen(false)
.setServiceName("%sService");
autoGenerator.setGlobalConfig(gc);
//数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setDbType(DbType.MYSQL);
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUrl(url);
dsc.setUsername(username);
dsc.setPassword(password);
autoGenerator.setDataSource(dsc);
//包配置
PackageConfig pgc = new PackageConfig();
pgc.setParent("cn.xw");
autoGenerator.setPackageInfo(pgc);
//自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
}
};
String templatePath = "/templates/mapper.xml.ftl";
ArrayList<FileOutConfig> focList = new ArrayList<>();
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return propertyPath + "/src/main/resources/mapper/" + pgc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
autoGenerator.setCfg(cfg);
//模板信息配置
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
autoGenerator.setTemplate(templateConfig); //其它信息配置
//策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setInclude(include);
//排除生成的表名称,就是写数据库表名称 String ... include
strategy.setExclude(exclude);
//strategy.setFieldPrefix("");
strategy.setTablePrefix("");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
//表填充字段
List<TableFill> fill = new ArrayList<>();
fill.add(new TableFill("create_time", FieldFill.INSERT));
fill.add(new TableFill("update_time", FieldFill.INSERT_UPDATE));
strategy.setTableFillList(fill);
strategy.setVersionFieldName(version);
strategy.setLogicDeleteFieldName(isDel);
autoGenerator.setStrategy(strategy);
autoGenerator.setTemplateEngine(new FreemarkerTemplateEngine());
autoGenerator.execute();
}
}

如果只是单纯想快速生成,就复杂这里面代码测试跑通就可以了

代码生成器具体详细注释:

public static void main(String[] args) {
//记得生成成功之后,在测试运行的时候要在主启动类上添加@MapperScan(value = “”)
//################## 基本信息准备 ##################
//获取当前程序所在目录 如:h://demo_mybatis_test
String propertyPath = System.getProperty("user.dir"); //创建代码生成器实例对象 (主要)
AutoGenerator autoGenerator = new AutoGenerator(); //################## 全局相关配置 ##################
GlobalConfig gc = new GlobalConfig();
//生成文件的输出目录【默认 D:\\ 盘根目录】
gc.setOutputDir(propertyPath + "./src/main/java");
//设置作者信息
gc.setAuthor("xiaoYang");
//时间类型对应策略
// ONLY_DATE:使用 java.util.date 代替
// SQL_PACK:使用 java.sql 包下的
// TIME_PACK:使用 java.time 包下的 java8 新的时间类型
gc.setDateType(DateType.ONLY_DATE);
//是否在xml中添加二级缓存配置 默认false
gc.setEnableCache(false);
//是否在XML里创建 ResultMap开启 就是创建<resultMap></resultMap>标签 默认false
gc.setBaseResultMap(true);
//是否在XML里创建 columnList开启 就是创建<sql> 表全部字段 </sql>标签 默认false
gc.setBaseColumnList(true);
//是否打开输出目录 就是说创建完是否弹出window文件夹位置
gc.setOpen(false);
//!!!各层文件名称方式,注释的全部是默认的 例如: I%sService 生成 IStudentService
//gc.setEntityName("%s");
//gc.setMapperName("%sMapper");
//gc.setXmlName("%sMapper");
gc.setServiceName("%sService");
//gc.setServiceImplName("%sServiceImpl");
//gc.setControllerName("%sController");
//实体属性 Swagger2 注解
//gc.setSwagger2(true);
// 把全局配置添加到 代码生成器实例里
autoGenerator.setGlobalConfig(gc); //################## 数据源配置 ##################
DataSourceConfig dsc = new DataSourceConfig();
//数据库类型
dsc.setDbType(DbType.MYSQL);
//设置数据库驱动 此处是mysql8.0版本
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
// 数据库链接URL 我这里使用的是MySQL8的链接URL 一样的
dsc.setUrl("jdbc:mysql://localhost:3306/demo_mp?useSSL=true&useUnicode=true&" +
"characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&" +
"useLegacyDatetimeCode=false&serverTimezone=GMT%2B8");
// 设置数据库用户名
dsc.setUsername("root");
// 设置数据库密码
dsc.setPassword("123");
// 架构名称 默认就行
//dsc.setSchemaName("public");
//自定义类型转换 写这个方法是为了mybatis-plus根据数据库类型自动生成的实体类型不符合我们要求是才自定义
//当生成的model实体类,java类型不满足时可以自定义转换
dsc.setTypeConvert(new ITypeConvert() {
//数据库类型datetime默认生成的java类型为localDateTime, 下面示例要改成Date类型
//类型转换(流程类型转换方法)
@Override
public IColumnType processTypeConvert(GlobalConfig globalConfig, String fieldType) {
//获取数据库类型字符串转为小写
String t = fieldType.toLowerCase();
//判断当前获取的类型里是否包含"datetime" 成立的话就转类型返回
if (t.contains("datetime")) {
System.out.println("类型捕捉datetime 将从LocalDateTime类型转Date");
return DbColumnType.DATE;
}
//其它字段采用默认转换(非mysql数据库可以使用其它默认的数据库转换器)
return new MySqlTypeConvert().processTypeConvert(globalConfig, fieldType);
}
});
// 把数据源配置添加到 代码生成器实例里
autoGenerator.setDataSource(dsc); //################## 包配置 ##################
PackageConfig pgc = new PackageConfig();
pgc.setParent("cn.xw") //父包名 默认"com.baomidou"
.setPathInfo(null) //路径配置信息,就是配置各个文件模板的路径信息
.setModuleName("") //父包模块名 默认 ”“
.setEntity("entity") //实体类包名 默认entity
.setService("service") //Service包名 默认”service“
.setServiceImpl("service.impl") //Service里面的实现类位置 默认”service.impl“
.setMapper("mapper") //Mapper映射包名 默认”mapper“
.setXml("mapper.xml") //Mapper XML包名 默认"mapper.xml"
.setController("controller"); //Controller包名 默认”controller“
// 把包配置添加到 代码生成器实例里
autoGenerator.setPackageInfo(pgc); //################## 自定义配置 ##################
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// 这里面可以设置具体的自定义表信息等等 正常不写 特殊百度学习
//https://www.bookstack.cn/read/mybatis-3.3.2/spilt.7.4267953fc3057caf.md
}
};
//注意 freemarker 和 velocity 使用哪个模板就去导入哪个坐标
// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
// String templatePath = "/templates/mapper.xml.vm";
//自定义输出配置 就是说找到并定位到我们生成的xxx(mapper).xml在哪里了
ArrayList<FileOutConfig> focList = new ArrayList<>();
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return propertyPath + "/src/main/resources/mapper/" + pgc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
//设置自定义输出文件(就是上面几行我们配置的)
cfg.setFileOutConfigList(focList);
// 把自定义配置添加到 代码生成器实例里
autoGenerator.setCfg(cfg); //################## 模板信息配置 ##################
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
//把模板信息配置添加到 代码生成器实例里
autoGenerator.setTemplate(templateConfig); //################## 其它信息配置 ##################
//策略配置
StrategyConfig strategy = new StrategyConfig();
//数据库表映射到实体类的命名策略 此时设置下划线转驼峰命名
strategy.setNaming(NamingStrategy.underline_to_camel);
//数据库表字段映射到实体字段的命名策略 此时设置下划线转驼峰命名
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//setInclude和setExclude只能存在一个,否则某一个为空
//setInclude必须写需要生成的数据库表
//setExclude根据我们连接的数据库,排除哪个表不生产,其它全生成
//需要生成的表名称,就是写数据库表名称 String ... include
strategy.setInclude("student");
//排除生成的表名称,就是写数据库表名称 String ... include
//strategy.setExclude("teacher");
//忽略表字段前缀
strategy.setFieldPrefix("s");
// 忽略表名的前缀
strategy.setTablePrefix("");
// 是否跳过视图
strategy.setSkipView(true);
//使用LomBok 默认false ,设置true注意要导入lombok坐标
strategy.setEntityLombokModel(true);
//生成的Controller层使用Rest风格 默认不使用
strategy.setRestControllerStyle(true);
//公共父类 如果你有自己的baseController的控制器可以写上
//strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
// 写于父类中的公共字段
strategy.setSuperEntityColumns("id");
//表填充字段
List<TableFill> fill = new ArrayList<>();
fill.add(new TableFill("create_time", FieldFill.INSERT));
fill.add(new TableFill("update_time", FieldFill.INSERT_UPDATE));
strategy.setTableFillList(fill);
//乐观锁属性名称
strategy.setVersionFieldName("version");
//逻辑删除属性名称
strategy.setLogicDeleteFieldName("is_del"); //把策略配置添加到 代码生成器实例里
autoGenerator.setStrategy(strategy);
//设置那种模板引擎
autoGenerator.setTemplateEngine(new FreemarkerTemplateEngine());
// 执行生成
autoGenerator.execute();
}

.

MyBatis-Plus日常工作学习的更多相关文章

  1. MyBatis持久层框架学习之01 MyBatis的起源和发展

    一.MyBatis的简介  MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.    MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集. MyB ...

  2. 数据科学工作者(Data Scientist) 的日常工作内容包括什么

    数据科学工作者(Data Scientist) 的日常工作内容包括什么 众所周知,数据科学是这几年才火起来的概念,而应运而生的数据科学家(data scientist)明显缺乏清晰的录取标准和工作内容 ...

  3. 阿里小二的日常工作要被TA们“接管”了!

    昨天有人偷偷告诉我说 阿里巴巴其实是一家科技公司! 我想了整整一夜 究竟是谁走漏了风声 那么重点来了,阿里到底是如何在内部的办公.生活中,玩转“黑科技”的呢? AI取名:给你专属的“武侠”花名 花名是 ...

  4. 关于git你日常工作中会用到的一些东西

    前言 git是一个版本控制工具, 版本控制主要的好处有三点: 从当前版本回退到任意版本 查看历史版本 对比两个版本差异 git 相关术语 repository 仓库 branch 分支 summary ...

  5. DBA日常工作内容和职责

    1.统计数据库总容量 按表空间分别统计: 总容量(单位为M): 2.计算用户下所有对象大小 3计算USERS表空间的大小 4计算该USERS表空间对象大小 ---------------------- ...

  6. git宝典—应付日常工作使用足够的指北手册

    最近公司gitlab又迁移,一堆git的命令骚操作,然鹅git命令,感觉还是得复习下——其实,git现在界面操作工具蛮多,比如intellij 自带的git操作插件就不错,gitlab github ...

  7. Atitit 常见每日流程日程日常工作.docx v4

    Atitit 常见每日流程日程日常工作.docx v4 ----早晨 签到 晨会,每天或者隔天 每日计划( )项目计划,日常计划等. mailbox读取检查 每日趋势 推库 -----下午 签退 每日 ...

  8. awbeci—一个帮助你快速处理日常工作的网址收集网站

    大家好,我是awbeci作者,awbeci网站是一个能够快速处理日常工作的网址收集网站,为什么这样说呢?下面我将为大家介绍这个网站的由来,以及设计它的初衷和如何使用以及对未来的展望和计划,以及bug反 ...

  9. 【Java】能提高日常工作效率的一些Java函数

    自编工具总是临时抱佛脚来得顺溜,宜常备手边以提高工作效率: package com.hy; import java.io.File; /** * 日常工作常用的一些工具方法 * @author 逆火 ...

随机推荐

  1. window.locationbar

    window.locationbar demo https://cdn.xgqfrms.xyz/#/operate-tool/select-seat-system/select-detail?temp ...

  2. nodejs stream 创建读写流

    const fs = require("fs"); const { Writable, Readable, Duplex, Transform } = require(" ...

  3. vue & $router & History API

    vue & $router gotoTemplateManage(e) { e.preventDefault(); this.$router.push({ path: `/operate-to ...

  4. WEB 面向开发者的结构化数据

    通常用于google搜索 See also: video 探索搜索库

  5. 我眼中的价值币——NGK(下)

    跨链交互方案并不是区块链世界中的一个新课题.自比特币诞生揭开智能合约的序幕之后,跨链交互的需求便产生了.但是,经过十年的发展,市场中的跨链解决方案进展缓慢,究之原因有以下几个方面. 首先,区块链的去中 ...

  6. postman功能介绍

  7. 开源OA办公系统的“应用市场”,能够为协同办公开拓什么样的“前路”?

    在我们的日常生活中,应用市场这个词,总是与智能手机划上等号,不管使用的是iPhone还是安卓,总会接触到手机上的APP应用市场,我们可以在应用市场中,选择自己所需要的APP应用软件,下载使用后,可以让 ...

  8. 必知必会之Java注解

    必知必会之Java注解 目录 不定期更新中-- 元注解 @Documented @Indexed @Retention @Target 常用注解 @Deprecated @FunctionalInte ...

  9. Vue项目的创建、路由、及生命周期钩子

    目录 一.Vue项目搭建 1.环境搭建 2.项目的创建 3.pycharm配置并启动vue项目 4.vue项目目录结构分析 5.Vue根据配置重新构建依赖 二.Vue项目创建时发生了什么 三.项目初始 ...

  10. C++构造函数的选择

    构造函数用来初始化类对象.构造函数有好几种,在编程时如何选择不同的构造函数呢?做个总结 一.默认构造函数 默认构造函数是在程序员没有声明任何构造函数的时,编译器为了初始化类对象自己进行的默认构造函数. ...