文章目录

  • MyBatis-Plus
    • MyBatisPlus概述
    • 快速开始
    • 配置日志
    • CRUD扩展
      • 插入操作
      • 主键生成策略
      • 更新操作
      • 自动填充
      • 乐观锁
      • 查询操作
      • 分页查询
      • 删除操作
      • 逻辑删除
      • 性能分析插件
    • 条件构造器
    • 代码自动生成器

MyBatis-Plus

MyBatisPlus概述

MyBatisPlus可以节省我们大量工作时间,所有的CRUD代码它都可以自动化完成!

简介

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

特性

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

框架结构

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

快速开始

步骤

1、创建数据库 demo2

2、创建user表

DROP TABLE IF EXISTS user;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)
);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');

3、编写项目,初始化项目

4、添加依赖

<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.0.5</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency>

5、连接数据库

spring.datasource.username=root
sprint.datasource.password=password
spring.datasource.url=jdbc:mysql://localhost:3306/demo2?serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

6、pojo-dao(连接mybatis,配置mapper.xml文件)-service-controller

pojo:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {Long id;String name;Integer age;String email;
}

mapper:

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.khazix.mybatis_plus.pojo.User;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper extends BaseMapper<User> {}

测试类中实现:

@SpringBootTest
class MybatisPlusApplicationTests {@Autowiredprivate UserMapper userMapper;@Testvoid contextLoads() {// 参数是一个Wrapper,条件构造器//查询全部用户List<User> users = userMapper.selectList(null);users.forEach(System.out::println);}
}

结果:

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

配置日志

# 配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

CRUD扩展

插入操作

Insert插入

@Testpublic void testInsert() {User user = new User();user.setName("信智学院");user.setAge(3);user.setEmail("1206509062@qq.com");int insert = userMapper.insert(user);System.out.println(insert);System.out.println(user);}

结果:

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

主键生成策略

默认ID_WORKER全局唯一id

分布式系统唯一id生成:https://www.cnblogs.com/haoxinyue/p/5208136.html

雪花算法

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。

主键自增

我们需要配置主键自增:

1、实体类字段上:@TableId(type = IdType.AUTO)

2、数据库上一定要有自增

3、再次测试插入即可!

其余的源码解释

public enum IdType {AUTO(0),   //数据库id自增NONE(1),   //未设置主键INPUT(2),  //手动输入ID_WORKER(3), //默认的全局唯一idUUID(4),  //全局唯一id uuidID_WORKER_STR(5); //ID_WORKER 字符串表示法
}

更新操作

@Testpublic void testUpdate() {User user = new User();user.setId(5L);user.setName("信智学院");user.setAge(3);user.setEmail("1206509062@qq.com");int i = userMapper.updateById(user);System.out.println(i);}

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

自动填充

创建时间、修改时间!这些操作都是自动化完成的,我们不希望手动更新。

方式一:数据库级别(工作中不允许修改数据库,不建议使用)

1、在表中新增字段create_time,update_time

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

2、再次测试插入方法,我们需要先把实体类同步

    private Date createTime;private Date updateTime;

3、再次更新查看结果

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

方式二:代码级别

1、删除数据库的默认值、更新操作

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

2、实体类字段属性上需要增加注解

    //字段添加填充内容@TableField(fill = FieldFill.INSERT)private Date createTime;@TableField(fill = FieldFill.INSERT_UPDATE)private Date updateTime;

3、编写处理器来处理这个注解即可!

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;import java.util.Date;@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {// 插入时的填充策略@Overridepublic void insertFill(MetaObject metaObject) {log.info("start insert fill......");this.setFieldValByName("createTime",new Date(),metaObject);this.setFieldValByName("updateTime",new Date(),metaObject);}// 更新时的填充策略@Overridepublic void updateFill(MetaObject metaObject) {log.info("start update fill......");this.setFieldValByName("updateTime",new Date(),metaObject);}
}

4、测试插入

5、测试更新、观察时间

乐观锁

乐观锁: 它总是认为不会出现问题,无论干什么都不去上锁!如果出现了问题,再次更新值操作。

悲观锁:它总是认为总是出现问题,无论干什么都会上锁!再去操作!

乐观锁实现方式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

测试mybatis-plus乐观锁插件

1、添加version字段

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

2、我们实体类加对应的字段

 @Versionprivate Integer version;

3、注册组件

@MapperScan("com.khazix.mybatis_plus.mapper")
@EnableTransactionManagement
@Configuration
public class MyBatisPlusConfig {//注册乐观锁插件@Beanpublic OptimisticLockerInterceptor optimisticLockerInterceptor() {return new OptimisticLockerInterceptor();}
}

4、测试

    // 测试乐观锁成功!单线程@Testpublic void testOptimisticLock() {// 1、查询用户信息User user = userMapper.selectById(1L);// 2、修改用户信息user.setName("syq");user.setEmail("1206509062@qq.com");// 3、执行更新操作userMapper.updateById(user);}// 测试乐观锁失败!多线程下@Testpublic void testOptimisticLock2() {// 线程1User user1 = userMapper.selectById(1L);user1.setName("JSON1");user1.setEmail("1206509062@qq.com");//模拟另外一个线程执行了插队操作User user2 = userMapper.selectById(1L);user2.setName("JSON2");user2.setEmail("1206509062@qq.com");userMapper.updateById(user2);userMapper.updateById(user1); //如果没有乐观锁就会覆盖插队线程的值}

查询操作

// 测试查询@Testpublic void testSelectById() {User user = userMapper.selectById(1L);System.out.println(user);}// 测试批量查询@Testpublic void testSelectByBatchId() {List<User> users = userMapper.selectBatchIds(Arrays.asList(1,2,3));users.forEach(System.out::println);}// 条件查询 map操作@Testpublic void testSelectByBatchIds() {HashMap<String,Object> map = new HashMap<>();// 自定义要查询map.put("name","jack");List<User> users = userMapper.selectByMap(map);users.forEach(System.out::println);}

分页查询

1、原始的limit进行分页

2、pageHelper第三方插件

3、Mybatis-Plus也内置了分页插件

如何使用

1、配置拦截器

// 分页插件@Beanpublic PaginationInterceptor paginationInterceptor() {return new PaginationInterceptor();}

2、直接使用Page对象即可

    //测试分页查询@Testpublic void testPage() {Page<User> page = new Page<>(1, 5);IPage<User> userIPage = userMapper.selectPage(page, null);page.getRecords().forEach(System.out::println);}

删除操作

基本的删除操作

    // 测试删除@Testpublic void testDeleteById() {userMapper.deleteById(1367742328890036227L);}// 批量删除@Testpublic void testDeleteBatchId() {userMapper.deleteBatchIds(Arrays.asList(1,2));}// 通过map删除@Testpublic void testDeleteMap() {HashMap<String,Object> map = new HashMap<>();map.put("name","Tom");userMapper.deleteByMap(map);}

逻辑删除

物理删除:从数据库中直接移除

逻辑删除:在数据库中没有移除,而是通过一个变量来让他失效!

1、在数据表中添加一个deleted字段

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

2、实体类中增加属性

 @TableLogic // 逻辑删除private Integer deleted;

3、配置插件

   // 逻辑删除插件@Beanpublic ISqlInjector sqlInjector() {return new LogicSqlInjector();}
# 配置逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0

4、测试删除

 @Testpublic void testDeleteBatchId() {userMapper.deleteBatchIds(Arrays.asList(4,5));}

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

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

性能分析插件

Mybatis-plus提供了性能分析插件,如果超过这个时间就停止运行!

作用:性能分析拦截器,用于输出每条SQL语句及其执行时间。

1、导入插件

/*** SQL执行效率插件*/
@Bean
@Profile({"dev","test"}) // 设置 dev test环境开启
public PerformanceInterceptor performanceInterceptor() {PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();performanceInterceptor.setMaxTime(100);performanceInterceptor.setFormat(true);return performanceInterceptor;
}
# 设置开发环境
spring.profiles.active=dev

2、测试使用

    @Testvoid contextLoads() {// 参数是一个Wrapper,条件构造器//查询全部用户List<User> users = userMapper.selectList(null);users.forEach(System.out::println);}

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

使用性能分析插件,可以帮助我们提高效率!

条件构造器

我们写一些复杂的sql就可以用它来替代!

测试一:

 @Testvoid contextLoads() {// 查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于12QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.isNotNull("name").isNotNull("email").ge("age",12);userMapper.selectList(wrapper);}

测试二:

 @Testpublic void test2(){// 查询名字 智能学院QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.eq("name","智能学院");User user = userMapper.selectOne(wrapper);System.out.println(user);}

测试三:

@Test
public void test4() {// 模糊查询QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.notLike("name","n").likeRight("email","1");userMapper.selectMaps(wrapper);}

测试四:

@Test
public void test5() {QueryWrapper<User> wrapper = new QueryWrapper<>();// id 在子查询中查出来wrapper.inSql("id","select id from user where id<7");List<Object> objects = userMapper.selectObjs(wrapper);objects.forEach(System.out::println);
}

测试五:

@Test
public void test6() {QueryWrapper<User> wrapper = new QueryWrapper<>();// 通过id进行排序wrapper.orderByDesc("id");List<User> users = userMapper.selectList(wrapper);users.forEach(System.out::println);
}

代码自动生成器

导入依赖:

<dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>2.0</version>
</dependency>
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;import java.util.ArrayList;// 代码生成器
public class khazix {public static void main(String[] args) {// 需要构建一个代码生成器AutoGenerator mpg = new AutoGenerator();// 配置策略// 1、全局配置GlobalConfig gc = new GlobalConfig();String projectPath = System.getProperty("user.dir");gc.setOutputDir(projectPath+"/src/main/java");gc.setAuthor("khazix");gc.setOpen(false);gc.setFileOverride(false);gc.setServiceName("%sService");gc.setIdType(IdType.ID_WORKER);gc.setDateType(DateType.ONLY_DATE);gc.setSwagger2(true);mpg.setGlobalConfig(gc);// 2、设置数据源DataSourceConfig dsc = new DataSourceConfig();dsc.setDriverName("com.mysql.cj.jdbc.Driver");dsc.setUrl("jdbc:mysql://localhost:3306/demo2?serverTimezone=UTC");dsc.setUsername("root");dsc.setPassword("password");dsc.setDbType(DbType.MYSQL);mpg.setDataSource(dsc);// 3、包的配置PackageConfig pc = new PackageConfig();pc.setModuleName("blog");pc.setParent("com.khazix");pc.setEntity("entity");pc.setMapper("mapper");pc.setService("service");pc.setController("controller");mpg.setPackageInfo(pc);// 4、策略配置StrategyConfig strategy = new StrategyConfig();strategy.setInclude("user"); // 设置要映射的表名strategy.setNaming(NamingStrategy.underline_to_camel);strategy.setColumnNaming(NamingStrategy.underline_to_camel);strategy.setEntityLombokModel(true); // 自动Lombokstrategy.setLogicDeleteFieldName("deleted"); //逻辑删除//自动填充TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);TableFill gmtUpdate = new TableFill("gmt_update", FieldFill.UPDATE);ArrayList<TableFill> tableFills = new ArrayList<>();tableFills.add(gmtCreate);tableFills.add(gmtUpdate);strategy.setTableFillList(tableFills);// 乐观锁strategy.setVersionFieldName("version");strategy.setRestControllerStyle(true);strategy.setControllerMappingHyphenStyle(true);mpg.setStrategy(strategy);mpg.execute();}
}strategy.setEntityLombokModel(true); // 自动Lombokstrategy.setLogicDeleteFieldName("deleted"); //逻辑删除//自动填充TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);TableFill gmtUpdate = new TableFill("gmt_update", FieldFill.UPDATE);ArrayList<TableFill> tableFills = new ArrayList<>();tableFills.add(gmtCreate);tableFills.add(gmtUpdate);strategy.setTableFillList(tableFills);// 乐观锁strategy.setVersionFieldName("version");strategy.setRestControllerStyle(true);strategy.setControllerMappingHyphenStyle(true);mpg.setStrategy(strategy);mpg.execute();}
}

初学MyBatis-Plus相关推荐

  1. 初学MyBatis PageHelper.startPage(m,n)报错,自动添加limit

    问题描述 初学MyBatis,使用分页插件的时候,输入PageHelper.startPage(m,n)会报如下错误,导致报红不分页 org.apache.ibatis.exceptions.Pers ...

  2. 初学MyBatis(踩坑)Error querying database.

    最近在学习Mybatis,代码全部根据教程写好了,一运行结果报了一个错误,主要错误内容: Caused by: org.apache.ibatis.exceptions.PersistenceExce ...

  3. 初学mybatis时出现 org.apache.ibatis.binding.BindingException报错

    错误分析:由于每一个Mapper.xml都需要在mybatis核心配置文件中注册 解决方法: ①检测mapper是否声明 在mybatis-config.xml配置文件中添加下面几行代码(注意路径一定 ...

  4. mybatis中的Example_Where_Clause

    1.初学mybatis时的疑惑 mybatis中的generatorConfig.xml在生成entity时会生成每个POJO(例如User.java)对应的example POJO: UserExa ...

  5. mybatis的映射文件中的mapper空间名不能自定义名字,自定义后爆红

    初学mybatis,遇到很多奇葩问题,很多时候第一次编辑的时候没出现任何问题,第二次复习的时候各种报错,疯狂爆红. 这里说的就是映射文件中mapper空间名字不能自定义的问题,第一次操作时 没有任何问 ...

  6. Type interface com.haroro.dao.UserDao is not known to the MapperRegistry——MyBatis问题

    初学MyBatis的时候,往往出现的错误都是一些细节上引起的.今天就遇到了一个问题,在运行测试类的时候,出现了以下的错误提示: org.apache.ibatis.binding.BindingExc ...

  7. mybatis框架的介绍和使用

    1. 正文 1. 介绍mybatis框架. 2. 为什么使用mybatis框架. 3. 如何使用mybatis框架. 4. mybatis完成crud操作. 5. mybatis的一些优化和一些插件的 ...

  8. mybatis传入map参数

    初学mybatis产生两个问题: mybatis传入参数执行SQL语句好像并不是很智能?xml文件中指定parameterType只能是一种数据类型.假设我现在需要执行一条SQL语句,里面有两个参数: ...

  9. 2020年总结:携梦而行,无怨无悔

    文章目录 前言 2020于追梦 1.梦的起点 2.追梦的由来与团队的组建 3.学习与沉淀 4.困境 5.希望 6.再遇困境 7.关于梦的思考 8.展望未来 2020于技术 我的技术之旅 关于对技术的思 ...

  10. 解决org.apache.ibatis.binding.BindingException Invalid bound statement (not found) com.fs.mapper.UserM

    首先我们来看看我的修改前的Mapper.xml文件 <mapper namespace="com.fs.mapper.UserMapper"><select id ...

最新文章

  1. Java Script 第四节课 Java Script的隐式转换
  2. oracle 用户 多个表空间
  3. mysql连接的空闲时间超过8小时后 MySQL自动断开该连接解决方案
  4. fir.im 全名 Fly It Remotely ,是一个为移动开发者服务,FIR一个免费的App托管平台
  5. 360怎么看电脑配置_Win10系统自带杀毒和垃圾清理好么?需不需要安装360卫士
  6. u-boot分析之启动简介(一)
  7. FPGA学习之路—Vivado与Modelsim联合仿真
  8. form 窗体增加边框_C#控件美化之路(13):美化Form窗口(上)
  9. oracle util_mail,Oracle UTL_MAIL邮件包程序使用实践
  10. 《C Primer Plus 第五版》读书笔记
  11. SpringCloud学习笔记022---SpringBoot中集成使用MongoDb进行增删改查
  12. 一个非常标准的Java连接Oracle数据库的示例代码
  13. 存储过程可重用的代码块_如何使您的代码可重用
  14. 基于SSM的家具商城系统
  15. 联想笔记本连不上手机热点_联想笔记本连不上无线_联想笔记本连不上热点
  16. 银行对公业务和对私业务
  17. 高度坍塌的几种解决方法
  18. 解决:Excel 下拉项数据报 输入内容不能大于255个字符
  19. Synopsys Formality Workshop 2013
  20. 2022中级Android开发面试解答,当上项目经理才知道

热门文章

  1. 联合索引最左匹配原则成因
  2. 2022-2028年中国热塑性聚酯PBT工程塑料行业市场全景调查及发展趋势分析报告
  3. 斯坦福CS224n、CMU NLP公开课 播放地址
  4. dropout,batch norm 区别 顺序
  5. Http请求之优雅的RestTemplate
  6. Mobileye独创性创新
  7. 结构感知图像修复:ICCV2019论文解析
  8. 人脸标记检测:ICCV2019论文解析
  9. CSS grid 的用法
  10. AndroidStudio 在工具栏上设置显示前进和后台的方法