Skip to content

MyBatis-Flex 的查询和分页

基础查询

在 MyBatis-Flex 的 BaseMapper 中,提供了如下的功能用于查询数据库的数据:

  • selectOneById(id):根据主键查询数据。
  • selectOneByEntityId(entity):根据实体主键查询数据,便于对复合主键实体类的查询。
  • selectOneByMap(whereConditions):根据 Map 构建的条件来查询数据。
  • selectOneByCondition(whereConditions):根据查询条件查询数据。
  • selectOneByQuery(queryWrapper):根据查询条件来查询 1 条数据。
  • selectOneByQueryAs(queryWrapper, asType):根据查询条件来查询 1 条数据。
  • selectOneWithRelationsByMap(whereConditions):根据 Map 构建的条件来查询 1 条数据。
  • selectOneWithRelationsByCondition(whereConditions):根据查询条件查询 1 条数据。
  • selectOneWithRelationsByQuery(queryWrapper):根据查询条件来查询 1 条数据。
  • selectOneWithRelationsByQueryAs(queryWrapper, asType):根据查询条件来查询 1 条数据。
  • selectListByIds(ids):根据多个主键来查询多条数据。
  • selectListByMap(whereConditions):根据 Map 来构建查询条件,查询多条数据。
  • selectListByMap(whereConditions, count):根据 Map 来构建查询条件,查询多条数据。
  • selectListByCondition(whereConditions):根据查询条件查询多条数据。
  • selectListByCondition(whereConditions, count):根据查询条件查询多条数据。
  • selectListByQuery(queryWrapper):根据查询条件查询数据列表。
  • selectListByQuery(queryWrapper, consumers):根据查询条件查询数据列表。
  • selectCursorByQuery(queryWrapper):根据查询条件查询游标数据,该方法必须在事务中才能正常使用,非事务下无法获取数据。
  • selectRowsByQuery(queryWrapper):根据查询条件查询 Row 数据。
  • selectListByQueryAs(queryWrapper, asType):根据查询条件查询数据列表,要求返回的数据为 asType。这种场景一般用在 left join 时,有多出了实体类本身的字段内容,可以转换为 dto、vo 等场景。
  • selectListByQueryAs(queryWrapper, asType, consumers):根据查询条件查询数据列表,要求返回的数据为 asType 类型。
  • selectListWithRelationsByQuery(queryWrapper):查询实体类及其 Relation 注解字段。
  • selectListWithRelationsByQueryAs(queryWrapper, asType):查询实体类及其 Relation 注解字段。
  • selectListWithRelationsByQueryAs(queryWrapper, asType, consumers):查询实体类及其 Relation 注解字段。
  • selectAll():查询全部数据。
  • selectAllWithRelations():查询全部数据,及其 Relation 字段内容。
  • selectObjectByQuery(queryWrapper):查询第一列返回的数据,QueryWrapper 执行的结果应该只有 1 列,例如:QueryWrapper.create().select(ACCOUNT.id).where(...);
  • selectObjectByQueryAs(queryWrapper, asType):查询第一列返回的数据,QueryWrapper 执行的结果应该只有 1 列,例如:QueryWrapper.create().select(ACCOUNT.id).where(...);
  • selectObjectListByQuery(queryWrapper):查询第一列返回的数据集合,QueryWrapper 执行的结果应该只有 1 列,例如:QueryWrapper.create().select(ACCOUNT.id).where(...);
  • selectObjectListByQueryAs(queryWrapper, asType):查询第一列返回的数据集合,QueryWrapper 执行的结果应该只有 1 列,例如:QueryWrapper.create().select(ACCOUNT.id).where(...);
  • selectCountByQuery(queryWrapper):查询数据量。
  • selectCountByCondition(whereConditions):根据条件查询数据总量。

select..As 使用注意事项:

假设项目中有 User.java 的 Entity 类以及 UserVo.java 两个类。而 User.java 的代码如下

java
public class User {

    @Column(typeHandler=xxxHandler.class)
    private String attr1;

    //getter setter
}

User.javaattr1 属性配置了 typeHandler,当我们通过 userMapper.select...As(UserVo.class) 查询得到 UserVo 的时候, 也同样需要在 UserVoattr1 属性中也配置上 @Column(typeHandler=xxxHandler.class),两者才能得到相同的结果。

游标查询

我们对大量数据进行处理时,为防止方法内存泄漏情况,应该使用游标(Cursor)方式进行数据查询并处理数据。 在 BaseMapper 中,存在如下的游标查询方法:

java
Cursor<T> selectCursorByQuery(QueryWrapper queryWrapper);

其使用方法如下:

java
Db.tx(() -> {
    Cursor<Account> accounts = accountMapper.selectCursorByQuery(query);
    for (Account account : accounts) {
        System.out.println(account);
    }
    return true;
});

以上的示例中,数据库并不是把所有的数据一次性返回给应用,而是每循环 1 次才会去数据库里拿 1 条数据,这样,就算有 100w 级数据,也不会导致我们应用内存溢出,同时,在 for 循环中, 我们可以随时终止数据读取。

但由于游标查询是在 for 循环的时候,才去数据库拿数据。因此必须保证 selectCursorByQuery 方法及其处理必须是在事务中进行,才能保证其链接并未与数据库断开。

以下场景经常需要用到游标查询功能:

  • 1、数据查询并写入到缓存
  • 2、Excel 导出等

你还可以使用 Mybatis-Flex-Reactor (flex 响应式拓展包),内部将游标操作封装成了 Flux 对象,可以有更好的游标操作体验。 详情可见: GiteeGithub

查询 Map 集合

java
List<Row> selectRowsByQuery(QueryWrapper queryWrapper);

Relations 注解查询

Relations 注解查询指的是用于查询带有注解 @RelationOneToOne@RelationOneToMany@RelationManyToOne@RelationManyToMany 的查询。

  • selectOneWithRelationsByMap(whereConditions):根据 Map 构建的条件来查询 1 条数据。
  • selectOneWithRelationsByCondition(whereConditions):根据查询条件查询 1 条数据。
  • selectOneWithRelationsByQuery(queryWrapper):根据查询条件来查询 1 条数据。
  • selectOneWithRelationsByQueryAs(queryWrapper, asType):根据查询条件来查询 1 条数据。
  • selectListWithRelationsByQuery(queryWrapper):查询实体类及其 Relation 注解字段。
  • selectListWithRelationsByQueryAs(queryWrapper, asType):查询实体类及其 Relation 注解字段。
  • selectListWithRelationsByQueryAs(queryWrapper, asType, consumers):查询实体类及其 Relation 注解字段。
  • selectAllWithRelations():查询全部数据,及其 Relation 字段内容。
  • paginateWithRelations(pageNumber, pageSize, queryWrapper):分页查询,及其 Relation 字段内容。
  • paginateWithRelations(pageNumber, pageSize, whereConditions):分页查询,及其 Relation 字段内容。
  • paginateWithRelations(pageNumber, pageSize, totalRow, queryWrapper):分页查询,及其 Relation 字段内容。
  • paginateWithRelations(pageNumber, pageSize, totalRow, whereConditions):分页查询,及其 Relation 字段内容。
  • paginateWithRelations(page, queryWrapper):分页查询,及其 Relation 字段内容。
  • paginateWithRelations(page, queryWrapper, consumers):分页查询,及其 Relation 字段内容。
  • paginateWithRelationsAs(pageNumber, pageSize, queryWrapper, asType):分页查询,及其 Relation 字段内容。
  • paginateWithRelationsAs(pageNumber, pageSize, totalRow, queryWrapper, asType):分页查询,及其 Relation 字段内容。
  • paginateWithRelationsAs(page, queryWrapper, asType):分页查询,及其 Relation 字段内容。
  • paginateWithRelationsAs(page, queryWrapper, asType, consumers):分页查询,及其 Relation 字段内容。

多表查询(关联查询)

BaseMapper 中,提供了 selectOneByQueryAsselectListByQueryAspaginateAs 等方法,用于处理关联查询的场景。

假设有 tb_account 用户表和 tb_article 文章表,他们的字段分别如下:

sql
CREATE TABLE IF NOT EXISTS `tb_account`
(
    `id`        INTEGER PRIMARY KEY auto_increment,
    `user_name` VARCHAR(100),
    `age`       Integer,
    `birthday`  DATETIME
);

CREATE TABLE IF NOT EXISTS `tb_article`
(
    `id`         INTEGER PRIMARY KEY auto_increment,
    `account_id` Integer,
    `title`      VARCHAR(100),
    `content`    text
);

当我们进行关联查询时,可以通过如下 3 种方式进行。

方式 1

1、定义 ArticleDTO 类,ArticleDTO 里定义 tb_account 表的字段映射。

java
public class ArticleDTO {

    private Long id;
    private Long accountId;
    private String title;
    private String content;

    //以下用户相关字段
    private String userName;
    private int age;
    private Date birthday;
}

2、使用 QueryWrapper 构建 left join 查询,查询结果通过 ArticleDTO 类型接收。

java
QueryWrapper query = QueryWrapper.create()
        .select(ARTICLE.ALL_COLUMNS)
        .select(ACCOUNT.USER_NAME,ACCOUNT.AGE,ACCOUNT.BIRTHDAY)
        .from(ARTICLE)
        .leftJoin(ACCOUNT).on(ARTICLE.ACCOUNT_ID.eq(ACCOUNT.ID))
        .where(ACCOUNT.ID.ge(0));

List<ArticleDTO> results = mapper.selectListByQueryAs(query, ArticleDTO.class);
System.out.println(results);

方式 2

假设 ArticleDTO 定义的属性和 SQL 查询的字段不一致时,例如:

java
public class ArticleDTO {

  private Long id;
  private Long accountId;
  private String title;
  private String content;

  //以下用户字段 和 用户表定义的列不一致,表定义的列为 user_name
  private String authorName;
  private int authorAge;
  private Date birthday;
}

那么, QueryWrapper 需要添加 as,修改如下:

java
QueryWrapper query = QueryWrapper.create()
    .select(ARTICLE.ALL_COLUMNS)
    .select(ACCOUNT.USER_NAME.as(ArticleDTO::getAuthorName)
            ,ACCOUNT.AGE.as(ArticleDTO::getAuthorAge)
            ,ACCOUNT.BIRTHDAY
    )
    .from(ARTICLE)
    .leftJoin(ACCOUNT).on(ARTICLE.ACCOUNT_ID.eq(ACCOUNT.ID))
    .where(ACCOUNT.ID.ge(0));

List<ArticleDTO> results = mapper.selectListByQueryAs(query, ArticleDTO.class);
System.out.println(results);

方式 3 ^ v1.3.3

1、定义 ArticleDTO 类, 在 ArticleDTO 定义 Account 实体类属性。 例如:

java
public class ArticleDTO {

    private Long id;
    private Long accountId;
    private String title;
    private String content;

    //直接定义 Account 对象
    private Account account;
}

2、使用 QueryWrapper 构建 left join 查询,查询结果通过 ArticleDTO 类型接收。

java
QueryWrapper query = QueryWrapper.create()
        .select(ARTICLE.ALL_COLUMNS)
        .select(ACCOUNT.USER_NAME,ACCOUNT.AGE,ACCOUNT.BIRTHDAY)
        .from(ARTICLE)
        .leftJoin(ACCOUNT).on(ARTICLE.ACCOUNT_ID.eq(ACCOUNT.ID))
        .where(ACCOUNT.ID.ge(0));

List<ArticleDTO> results = mapper.selectListByQueryAs(query, ArticleDTO.class);
System.out.println(results);

其他注意事项:

关联查询(selectOneByQueryAsselectListByQueryAspaginateAs 等方法)中的 asType 参数类型(比如:ArticleDTO), 一样支持使用 @Column@ColumnMask 注解以及 @TableonInsertonUpdateonSet 配置。

分页查询

在 MyBatis-Flex 的 BaseMapper 中,提供了如下的分页查询功能:

  • paginate(pageNumber, pageSize, queryWrapper):分页查询。
  • paginateWithRelations(pageNumber, pageSize, queryWrapper):分页查询,及其 Relation 字段内容。
  • paginate(pageNumber, pageSize, whereConditions):分页查询。
  • paginateWithRelations(pageNumber, pageSize, whereConditions):分页查询,及其 Relation 字段内容。
  • paginate(pageNumber, pageSize, totalRow, queryWrapper):分页查询。
  • paginateWithRelations(pageNumber, pageSize, totalRow, queryWrapper):分页查询,及其 Relation 字段内容。
  • paginate(pageNumber, pageSize, totalRow, whereConditions):分页查询。
  • paginateWithRelations(pageNumber, pageSize, totalRow, whereConditions):分页查询,及其 Relation 字段内容。
  • paginate(page, queryWrapper):分页查询。
  • paginate(page, queryWrapper, consumers):分页查询。
  • paginateWithRelations(page, queryWrapper):分页查询,及其 Relation 字段内容。
  • paginateWithRelations(page, queryWrapper, consumers):分页查询,及其 Relation 字段内容。
  • paginateAs(pageNumber, pageSize, queryWrapper, asType):分页查询。
  • paginateAs(pageNumber, pageSize, totalRow, queryWrapper, asType):分页查询。
  • paginateAs(page, queryWrapper, asType):分页查询。
  • paginateAs(page, queryWrapper, asType, consumers):分页查询。
  • paginateWithRelationsAs(pageNumber, pageSize, queryWrapper, asType):分页查询,及其 Relation 字段内容。
  • paginateWithRelationsAs(pageNumber, pageSize, totalRow, queryWrapper, asType):分页查询,及其 Relation 字段内容。
  • paginateWithRelationsAs(page, queryWrapper, asType):分页查询,及其 Relation 字段内容。
  • paginateWithRelationsAs(page, queryWrapper, asType, consumers):分页查询,及其 Relation 字段内容。

参数说明:

  • pageNumber: 当前页码,从 1 开始
  • pageSize: 每 1 页的数据量
  • totalRow: 非必须值,若传入该值,mybatis-flex 则不再去查询总数据量(若传入小于 0 的数值,也会去查询总量)。
  • queryWrapper: 查询条件
  • queryCondition: 查询条件

totalRow 的说明

在一般的分页场景中,只有第一页的时候有必要去查询数据总量,第二页以后是没必要的(因为第一页已经拿到总量了),因此, 第二页的时候,我们可以带入 totalRow,这样能提高程序的查询效率。

paginate 的返回值为 Page 对象,Page 类的定义如下:

java
public class Page<T> implements Serializable {
    private List<T> records;                // list result of this page
    private int pageNumber;              // page number
    private int pageSize;                // result amount of this page
    private long totalPage;              // total page
    private long totalRow;               // total row
}