MyBatis-Plus日常工作学习
一: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:
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日常工作学习的更多相关文章
- MyBatis持久层框架学习之01 MyBatis的起源和发展
一.MyBatis的简介 MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架. MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集. MyB ...
- 数据科学工作者(Data Scientist) 的日常工作内容包括什么
数据科学工作者(Data Scientist) 的日常工作内容包括什么 众所周知,数据科学是这几年才火起来的概念,而应运而生的数据科学家(data scientist)明显缺乏清晰的录取标准和工作内容 ...
- 阿里小二的日常工作要被TA们“接管”了!
昨天有人偷偷告诉我说 阿里巴巴其实是一家科技公司! 我想了整整一夜 究竟是谁走漏了风声 那么重点来了,阿里到底是如何在内部的办公.生活中,玩转“黑科技”的呢? AI取名:给你专属的“武侠”花名 花名是 ...
- 关于git你日常工作中会用到的一些东西
前言 git是一个版本控制工具, 版本控制主要的好处有三点: 从当前版本回退到任意版本 查看历史版本 对比两个版本差异 git 相关术语 repository 仓库 branch 分支 summary ...
- DBA日常工作内容和职责
1.统计数据库总容量 按表空间分别统计: 总容量(单位为M): 2.计算用户下所有对象大小 3计算USERS表空间的大小 4计算该USERS表空间对象大小 ---------------------- ...
- git宝典—应付日常工作使用足够的指北手册
最近公司gitlab又迁移,一堆git的命令骚操作,然鹅git命令,感觉还是得复习下——其实,git现在界面操作工具蛮多,比如intellij 自带的git操作插件就不错,gitlab github ...
- Atitit 常见每日流程日程日常工作.docx v4
Atitit 常见每日流程日程日常工作.docx v4 ----早晨 签到 晨会,每天或者隔天 每日计划( )项目计划,日常计划等. mailbox读取检查 每日趋势 推库 -----下午 签退 每日 ...
- awbeci—一个帮助你快速处理日常工作的网址收集网站
大家好,我是awbeci作者,awbeci网站是一个能够快速处理日常工作的网址收集网站,为什么这样说呢?下面我将为大家介绍这个网站的由来,以及设计它的初衷和如何使用以及对未来的展望和计划,以及bug反 ...
- 【Java】能提高日常工作效率的一些Java函数
自编工具总是临时抱佛脚来得顺溜,宜常备手边以提高工作效率: package com.hy; import java.io.File; /** * 日常工作常用的一些工具方法 * @author 逆火 ...
随机推荐
- window.locationbar
window.locationbar demo https://cdn.xgqfrms.xyz/#/operate-tool/select-seat-system/select-detail?temp ...
- nodejs stream 创建读写流
const fs = require("fs"); const { Writable, Readable, Duplex, Transform } = require(" ...
- vue & $router & History API
vue & $router gotoTemplateManage(e) { e.preventDefault(); this.$router.push({ path: `/operate-to ...
- WEB 面向开发者的结构化数据
通常用于google搜索 See also: video 探索搜索库
- 我眼中的价值币——NGK(下)
跨链交互方案并不是区块链世界中的一个新课题.自比特币诞生揭开智能合约的序幕之后,跨链交互的需求便产生了.但是,经过十年的发展,市场中的跨链解决方案进展缓慢,究之原因有以下几个方面. 首先,区块链的去中 ...
- postman功能介绍
- 开源OA办公系统的“应用市场”,能够为协同办公开拓什么样的“前路”?
在我们的日常生活中,应用市场这个词,总是与智能手机划上等号,不管使用的是iPhone还是安卓,总会接触到手机上的APP应用市场,我们可以在应用市场中,选择自己所需要的APP应用软件,下载使用后,可以让 ...
- 必知必会之Java注解
必知必会之Java注解 目录 不定期更新中-- 元注解 @Documented @Indexed @Retention @Target 常用注解 @Deprecated @FunctionalInte ...
- Vue项目的创建、路由、及生命周期钩子
目录 一.Vue项目搭建 1.环境搭建 2.项目的创建 3.pycharm配置并启动vue项目 4.vue项目目录结构分析 5.Vue根据配置重新构建依赖 二.Vue项目创建时发生了什么 三.项目初始 ...
- C++构造函数的选择
构造函数用来初始化类对象.构造函数有好几种,在编程时如何选择不同的构造函数呢?做个总结 一.默认构造函数 默认构造函数是在程序员没有声明任何构造函数的时,编译器为了初始化类对象自己进行的默认构造函数. ...