Skip to content

条件构造器

前言

C: 在前两篇 MP 的 CRUD API 演示中,笔者一直刻意忽略了一些带有条件构造器的 API,你说哪些是带有条件构造器的 API ?看下面。

  • update(T entity, Wrapper<T> updateWrapper) : int
  • delete(Wrapper<T> queryWrapper) : int
  • selectOne(Wrapper<T> queryWrapper) : T
  • selectCount(Wrapper<T> queryWrapper) : Integer
  • selectList(Wrapper<T> queryWrapper) : List<T>
  • selectMaps(Wrapper<T> queryWrapper) : List<Map<String, Object>>
  • selectObjs(Wrapper<T> queryWrapper) : List<Object>
  • selectPage(E page, Wrapper<T> queryWrapper) : E
  • selectMapsPage(E page, Wrapper<T> queryWrapper) : E

看到了吧?上述这些 API 中,都有一个显著的特点,即要求传递一个 Wrapper 类型的参数,而这个 Wrapper 类型就是笔者提到的条件构造器。

预热了两篇之久,本篇,笔者将带着你入门 MP 的条件构造器,看看传统的 SQL 语句条件如何用 条件构造器API 组装出来。

202101241302860

概述

Wrapper 类型,顾名思义就是一个包装类,但它又不是我们常说的 Integer 这种包装类概念,在 MP 中它是用于包装各种丰富的查询条件的包装类。

它是 MP 中条件包装类的顶级类,笔者在下方给你列出了它的类层次结构。当然,枝繁叶茂的它不可能就这几个子类,笔者只不过是挑重点的展示一下而已。

202101241303960.

但Wrapper 类是一个抽象类,所以我们不可能直接使用它,而是需要使用它的子类,那么哪些子类是我们常用的呢?一共有4个。

  • QueryWrapper:查询条件构造器

  • UpdateWrapper:更新条件构造器

  • LambdaQueryWrapper:使用 Lambda 语法的查询条件构造器

  • LambdaUpdateWrapper:使用 Lambda 语法的更新条件构造器

接下来笔者就以 QueryWrapperLambdaQueryWrapper 为例,带着大家体验一下条件构造器。至于其他两个,使用方法没有太大差别,所以笔者不再单独讲解了。

我们还是先CV一个专用于条件构造器的单元测试类。

202101241303970

QueryWrapper

QueryWrapper 是 MP 中主要用于查询操作的条件包装类,我们将借助《快速入门》篇提到过的查询实体列表 API 来测试一下它。

java
// 其他 API 略
public interface BaseMapper<T> extends Mapper<T> {
    
    /**
     * 根据 entity 条件,查询全部记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
}

下方的基础 SQL 查询语法,大家应该都不陌生吧?

sql
SELECT
  [ALL | DISTINCT] -- 是否去重
  {* | table_name.* | [table_name.field1 [AS alias1] [, table_name.field2 [AS  alias2]][, ...]]} -- 查询哪些列
FROM
  table_name [AS table_ alias] [, ...] -- 从哪些表查询
  [LEFT | RIGHT | INNER JOIN table_name2 [AS table_alias] ON 关联条件] -- 连接查询
[WHERE ...] -- 根据条件来完成上述表数据的筛选
[GROUP BY …] -- 根据一个或多个列分组
[HAVING …]	-- 分组查询后的筛选条件
[ORDER BY… ] -- 根据一个或多个列排序
[LIMIT  {[offset,] row_count}]  -- 限制查询结果数量和范围

笔者就是要以 QueryWrapper ,带大家体验一下如何用条件构造器,来实现上述的基础 SQL 查询。

指定查询的列

在写查询类 SQL 时,我们都要先指定好要查询哪些列。在 MP 的查询条件构造器中,可以通过 select() 方法来实现查询列的指定。

  • select(String... columns) : QueryWrapper<T>
  • ...

测试代码:

java
@SpringBootTest
class MybatisPlusWrapperTests {

    @Autowired
    private UserMapper userMapper;
    
    // 需求:查询用户的姓名和年龄信息
    @Test
    void testSelectList1() {
        // 创建查询条件构造器对象,通过泛型指定查询结果类型
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 指定查询 name、age 两列数据
        queryWrapper.select("name", "age");

        // 执行用户列表查询
        List<User> userList = userMapper.selectList(queryWrapper);

        // 遍历用户列表
        userList.forEach(System.out::println);
    }
}

控制台输出:

java
==>  Preparing: SELECT name,age FROM user WHERE is_delete=0
==> Parameters: 
<==    Columns: name, age
<==        Row: Jone, 18
<==        Row: Jack, 20
<==        Row: Tom, 28
<==        Row: Sandy, 21
<==        Row: Billie, 24
<==      Total: 5
User(id=null, name=Jone, age=18, email=null, createTime=null, updateTime=null, isDelete=null)
User(id=null, name=Jack, age=20, email=null, createTime=null, updateTime=null, isDelete=null)
User(id=null, name=Tom, age=28, email=null, createTime=null, updateTime=null, isDelete=null)
User(id=null, name=Sandy, age=21, email=null, createTime=null, updateTime=null, isDelete=null)
User(id=null, name=Billie, age=24, email=null, createTime=null, updateTime=null, isDelete=null)

而且,你还可以借助这个方法实现结果去重功能,只需要在第一个指定的列名前添加 DISTINCT 关键字即可。

java
// 创建查询条件构造器对象,通过泛型指定查询结果类型
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 指定查询 name、age 两列数据,并去重结果
queryWrapper.select("DISTINCT name", "age");

最终执行的 SQL 就变成了这样:

sql
SELECT DISTINCT name,age FROM user WHERE is_delete=0

笔者说

聪明如你,一定能举一反三,如果未来需要给查询列起别名、或者使用一些聚合函数,直接在 select() 方法中写就可以。

where条件子句

查询类 SQL 中,在指定了查询哪些列之后,另一个非常重要的就是指定筛选结果数据的条件。

where 条件子句就是 用于检索数据表中符合条件记录 的,条件子句可以由一个或多个逻辑表达式组成,结果一般为真或假。

关系运算符

下方是 where 条件子句中常用关系运算 和 MP 中对应方法的对照表。

SQL 运算符含义MP 对应方法笔者帮你强化理解
=等于eq(R column, Object val) : Childreneq:equal
<> 或 !=不等于ne(R column, Object val) : Childrenne:not equal
>大于gt(R column, Object val) : Childrengt:greater than
<小于lt(R column, Object val) : Childrenlt:less than
>=大于等于ge(R column, Object val) : Childrenge:greater than or equal
<=小于等于le(R column, Object val) : Childrenle:less than or equal
is null等于nullisNull(R column) : Children
is not null不等于nullisNotNull(R column) : Children

测试代码:

java
@SpringBootTest
class MybatisPlusWrapperTests {

    @Autowired
    private UserMapper userMapper;

    // 需求:查询年龄 >= 21 的用户数据
    @Test
    void testSelectList2() {
        // 创建查询条件构造器对象,通过泛型指定查询结果类型
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // ge:greater equals 大于等于
        queryWrapper.ge("age", 21);

        // 执行用户列表查询
        List<User> userList = userMapper.selectList(queryWrapper);

        // 遍历用户列表
        userList.forEach(System.out::println);
    }

}

控制台输出:

sql
==>  Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE is_delete=0 AND (age >= ?)
==> Parameters: 21(Integer)
<==    Columns: id, name, age, email, create_time, update_time, is_delete
<==        Row: 3, Tom, 28, [email protected], 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
<==        Row: 4, Sandy, 21, [email protected], 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
<==        Row: 5, Billie, 24, [email protected], 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
<==      Total: 3
User(id=3, name=Tom, age=28, [email protected], createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
User(id=4, name=Sandy, age=21, [email protected], createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
User(id=5, name=Billie, age=24, [email protected], createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)

其他的笔者就不再测试了,你可以自己挨个试试。

逻辑运算符

普通逻辑
SQL 运算符含义MP 对应方法
AND逻辑与,同时为真,结果才为真默认使用 and 作为多个条件之间的关系表示
嵌套 andand(Consumer<Param> consumer) : Children
OR逻辑或,只要一个为真,则结果为真or() : Children
嵌套 oror(Consumer<Param> consumer) : Children
NOT逻辑非,若操作数为假,结果则为真not(Consumer<Param> consumer) : Children

测试代码:

java
@SpringBootTest
class MybatisPlusWrapperTests {

    @Autowired
    private UserMapper userMapper;

    // 需求:查询年龄 >= 21 并且姓名为Tom的用户数据
    @Test
    void testSelectList3() {
        // 创建查询条件构造器对象,通过泛型指定查询结果类型
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 可以分开设置
        // queryWrapper.ge("age", 21);
        // queryWrapper.eq("name", "Tom");
        // 也可以链式操作
        queryWrapper.ge("age", 21)
                    .eq("name", "Tom");
        
        // 执行用户列表查询
        List<User> userList = userMapper.selectList(queryWrapper);

        // 遍历用户列表
        userList.forEach(System.out::println);
    }

}

控制台输出:

sql
==>  Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE is_delete=0 AND (age >= ? AND name = ?)
==> Parameters: 21(Integer), Tom(String)
<==    Columns: id, name, age, email, create_time, update_time, is_delete
<==        Row: 3, Tom, 28, [email protected], 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
<==      Total: 1
User(id=3, name=Tom, age=28, [email protected], createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)

当你编写了多个条件表达式的时候,MP 默认认为它们之间采用 and 关系。但如果你想实现 or 关系的需求,那就必须得使用 or() 方法了。

测试代码:

java
@SpringBootTest
class MybatisPlusWrapperTests {

    @Autowired
    private UserMapper userMapper;

    // 需求:查询年龄 = 18 或者 年龄 = 24 的用户数据
    @Test
    void testSelectList4() {
        // 创建查询条件构造器对象,通过泛型指定查询结果类型
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 或者关系
        queryWrapper.eq("age", 18)
                    .or()
                    .eq("age", 24);

        // 执行用户列表查询
        List<User> userList = userMapper.selectList(queryWrapper);

        // 遍历用户列表
        userList.forEach(System.out::println);
    }
    
}

控制台输出:

sql
==>  Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE is_delete=0 AND (age = ? OR age = ?)
==> Parameters: 18(Integer), 24(Integer)
<==    Columns: id, name, age, email, create_time, update_time, is_delete
<==        Row: 1, Jone, 18, [email protected], 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
<==        Row: 5, Billie, 24, [email protected], 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
<==      Total: 2
User(id=1, name=Jone, age=18, [email protected], createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
User(id=5, name=Billie, age=24, [email protected], createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
嵌套逻辑

我们看到上方的对照表中,还有一种是用于嵌套 or 或 嵌套 and 关系的方法,有些同学可能有点懵,看看下面这行代码是不是就明白了呢?

sql
select xx from xx where xx = xx or (xx = xx and xx = xx)

测试代码:

java
@SpringBootTest
class MybatisPlusWrapperTests {

    @Autowired
    private UserMapper userMapper;

    // 需求:查询姓名中带有o字母 或者 (年龄 = 21 并且 姓名以S字母开头) 的用户数据
    @Test
    void testSelectList7() {
        // 创建查询条件构造器对象,通过泛型指定查询结果类型
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("name", "o")
                    .or(i -> i.eq("age", 21).likeRight("name", "S"));

        // 执行用户列表查询
        List<User> userList = userMapper.selectList(queryWrapper);

        // 遍历用户列表
        userList.forEach(System.out::println);
    }
    
}

控制台输出:

sql
==>  Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE is_delete=0 AND (name LIKE ? OR (age = ? AND name LIKE ?))
==> Parameters: %o%(String), 21(Integer), S%(String)
<==    Columns: id, name, age, email, create_time, update_time, is_delete
<==        Row: 1, Jone, 18, [email protected], 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
<==        Row: 3, Tom, 28, [email protected], 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
<==        Row: 4, Sandy, 21, [email protected], 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
<==      Total: 3
User(id=1, name=Jone, age=18, [email protected], createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
User(id=3, name=Tom, age=28, [email protected], createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
User(id=4, name=Sandy, age=21, [email protected], createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)

范围查询

上面的 or 关系需求中,是对相同列的多个值的查询,这种情况在 SQL 中我们可以采用 in 查询来优化。而如果是对相同列的连续范围值的查询,则可以采用 between ... and ...查询来优化。

下方是 in 查询 和 between ... and ...查询与 MP 中方法的对照表。

SQL 运算符含义MP 对应方法
in (值1, 值2, ...)在某个列表之间in(R column, Object... values) : Children
not in (值1, 值2, ...)不在某个列表之间notIn(R column, Object... value) : Children
between ... and ...在某个范围之间between(R column, Object val1, Object val2) : Children
not between ... and ...不在某个范围之间notBetween(R column, Object val1, Object val2) : Children

测试代码:

java
@SpringBootTest
class MybatisPlusWrapperTests {

    @Autowired
    private UserMapper userMapper;

    // 需求:查询年龄 = 18 或者 年龄 = 24 的用户数据
    @Test
    void testSelectList5() {
        // 创建查询条件构造器对象,通过泛型指定查询结果类型
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 如果是对相同列的多个值查询,使用in查询优化
        queryWrapper.in("age", 18, 24);
        
        // 执行用户列表查询
        List<User> userList = userMapper.selectList(queryWrapper);

        // 遍历用户列表
        userList.forEach(System.out::println);
    }

}

控制台输出:

sql
==>  Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE is_delete=0 AND (age IN (?,?))
==> Parameters: 18(Integer), 24(Integer)
<==    Columns: id, name, age, email, create_time, update_time, is_delete
<==        Row: 1, Jone, 18, [email protected], 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
<==        Row: 5, Billie, 24, [email protected], 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
<==      Total: 2
User(id=1, name=Jone, age=18, [email protected], createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
User(id=5, name=Billie, age=24, [email protected], createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)

模糊查询

除了精确的查询之外, 模糊查询也是使用频率很高的。下方是模糊查询 和 MP 中方法的对照表。

SQL 运算符含义MP 对应方法
like '%A%'在某个范围之间like(R column, Object val) : Children
not like '%A%'不在某个范围之间notLike(R column, Object val) : Children
like '%A'查询likeLeft(R column, Object val) : Children
like 'A%'不在某个列表之间likeRight(R column, Object val) : Children

测试代码:

java
@SpringBootTest
class MybatisPlusWrapperTests {

    @Autowired
    private UserMapper userMapper;

    // 需求:查询姓名中带有 o 字母的用户数据
    @Test
    void testSelectList6() {
        // 创建查询条件构造器对象,通过泛型指定查询结果类型
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("name", "o");

        // 执行用户列表查询
        List<User> userList = userMapper.selectList(queryWrapper);

        // 遍历用户列表
        userList.forEach(System.out::println);
    }
    
}

控制台输出:

sql
==>  Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE is_delete=0 AND (name LIKE ?)
==> Parameters: %o%(String)
<==    Columns: id, name, age, email, create_time, update_time, is_delete
<==        Row: 1, Jone, 18, [email protected], 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
<==        Row: 3, Tom, 28, [email protected], 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
<==      Total: 2
User(id=1, name=Jone, age=18, [email protected], createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
User(id=3, name=Tom, age=28, [email protected], createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)

分组查询

好了,写了这么多个查询示例,笔者相信聪明的你已经悟了。我们本篇毕竟不是去讲解 SQL 语句的,所以笔者最后把分组查询和排序查询的 API 再列一下,你想测试就自行测试一下吧。

SQL 运算符含义MP 对应方法示例
group by 列1, 列2根据一个列或多个列分组groupBy(R column) : Children例: groupBy("id", "name")
having ...分组查询后的筛选条件having(String sqlHaving, Object... params) : Children例1: having("sum(age) > 10") 例2:having("sum(age) > {0}", 10)

排序查询

SQL 运算符含义MP 对应方法示例
order by 列 ASC按指定列升序排序orderByAsc(R... columns) : Children例: orderByAsc("id", "name")
order by 列 DESC按指定列降序排序orderByDesc(R... columns) : Children例: orderByDesc("id", "name")

连接查询

MP 的确很强大,但是它封装的接口都是针对单表的,所以如果我们要多表查询的时候,还是需要采用原生的 MyBatis 写法,这里笔者就不再介绍了。

LambdaQueryWrapper

LambdaQueryWrapper 和 QueryWrapper 只差一个 Lambda 语法的使用而已,不知道你是否还记得《快速入门》篇中介绍的 MP 特点,其中有一条就是:支持 Lambda 形式调用 :通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错。[1]

测试代码:

java
@SpringBootTest
class MybatisPlusWrapperTests {

    @Autowired
    private UserMapper userMapper;

    // 需求:查询年龄 >= 21 的用户数据
    @Test
    void testSelectList8() {
        // 创建查询条件构造器对象,通过泛型指定查询结果类型
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        // Lambda 语法无需再担心字段写错
        queryWrapper.ge(User::getAge, 21);

        // 执行用户列表查询
        List<User> userList = userMapper.selectList(queryWrapper);

        // 遍历用户列表
        userList.forEach(System.out::println);
    }
    
}

控制台输出:

sql
==>  Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE is_delete=0 AND (age >= ?)
==> Parameters: 21(Integer)
<==    Columns: id, name, age, email, create_time, update_time, is_delete
<==        Row: 3, Tom, 28, [email protected], 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
<==        Row: 4, Sandy, 21, [email protected], 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
<==        Row: 5, Billie, 24, [email protected], 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
<==      Total: 3
User(id=3, name=Tom, age=28, [email protected], createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
User(id=4, name=Sandy, age=21, [email protected], createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)
User(id=5, name=Billie, age=24, [email protected], createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)

allEq

最后,笔者再介绍一个比较实用的方法:allEq,当你得到的查询条件都被封装好在一个 Map 集合中时,为了使用 MP 查询这些条件,难道还要一个个的将数据取出来封装为 Wrapper 吗?显然我们是拒绝的。

allEq 可以解决此问题。

测试代码:

java
@SpringBootTest
class MybatisPlusWrapperTests {

    @Autowired
    private UserMapper userMapper;

    // 需求:查询年龄 = 28 并且姓名为Tom的用户数据
    @Test
    void testSelectList9() {
        // 创建查询条件构造器对象,通过泛型指定查询结果类型
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();

        // 模拟一个封装好查询条件的 Map 集合
        Map<String, Object> map = new HashMap<>();
        map.put("age", 28);
        map.put("name", "Tom");
        
        // 将Map直接传给Wrapper的allEq方法,它会自行识别查询条件
        queryWrapper.allEq(map);
        
        // 执行用户列表查询
        List<User> userList = userMapper.selectList(queryWrapper);

        // 遍历用户列表
        userList.forEach(System.out::println);
    }

}

控制台输出:

==>  Preparing: SELECT id,name,age,email,create_time,update_time,is_delete FROM user WHERE is_delete=0 AND (name = ? AND age = ?)
==> Parameters: Tom(String), 28(Integer)
<==    Columns: id, name, age, email, create_time, update_time, is_delete
<==        Row: 3, Tom, 28, [email protected], 2021-01-23 17:47:52, 2021-01-23 17:47:52, 0
<==      Total: 1
User(id=3, name=Tom, age=28, [email protected], createTime=2021-01-23T17:47:52, updateTime=2021-01-23T17:47:52, isDelete=0)

笔者说

需要注意的是 allEq() 方法 只能用于具有 and 关系且都是等值判断的情况

参考文献

[1]MyBatis Plus 官网. 指南[EB/OL]. https://baomidou.com/guide/. 2021-01-18

后记

C: 好了,MP 的条件构造器就介绍完了。笔者有很多 API 没有带大家测试,但我想你也能明白,即便我全部测试了,它也不一定能被你用上,灵活使用它们是需要时间积累的。所以说,讲些典型的,让你 "悟了" MP 的大致规律和节奏,这才是最重要的。

除了 API 之外,实际上,MP 还有一些功能,因为时间原因,笔者在本次系列并没有提及,你可以前往官网查阅一下,或者等后面笔者时间允许的时候,会再去介绍下它的其他功能。

最后的最后,笔者个人建议,在日常开发中,如果遇到的需求需要写一个比较复杂的 SQL,那还是直接使用 MyBatis 原生写法去 XML 中写吧,毕竟 MyBatis 最强大的优势还是体现在 XML 的 SQL 中。

笔者说

对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。

所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。