# Query查询

Query是一个查询参数类,配合Mapper一起使用。

# 参数介绍

Query里面封装了一系列查询参数,主要分为以下几类:

  • 条件参数:设置查询条件
  • 分页参数:设置分页
  • 排序参数:设置排序字段

下面逐个讲解每个参数的用法。

# 条件参数

条件参数是用的最多一个,因为在查询中往往需要加入各种条件。 fastmybatis在条件查询上面做了一些封装,这里不做太多讲解,只讲下基本的用法,以后会单独开一篇文章来介绍。感兴趣的同学可以自行查看源码,也不难理解。

条件参数使用非常简单,Query对象封装一系列常用条件查询。

  • 等值查询eq(String columnName, Object value),columnName为数据库字段名,value为查询的值

假设我们要查询姓名为张三的用户,可以这样写:

Query query = Query.create();
query.eq("username","张三");
List<User> list = mapper.list(query);

还可以通过条件动态判断是否需要添加条件

String username = ..
Query query = Query.create();
// 如果username不为空则生成where条件
query.eq(StringUtils.hasText(username), "username", username);
List<User> list = mapper.list(query);

等同于:

String username = ..
Query query = Query.create();
// 如果username不为空则生成where条件
if (StringUtils.hasText(username)) {
    query.eq("username", username);
}
List<User> list = mapper.list(query);

显然第一种更加简洁。

通过方法名即可知道eq表示等于'=',同理lt表示小于<,gt表示大于>

查询方式 说明
eq 等于=
gt 大于>
lt 小于<
ge 大于等于>=
le 小于等于<=
notEq 不等于<>
like 模糊查询
in in()查询
notIn not in()查询
between between查询
isNull NULL值查询
notNull IS NOT NULL
notEmpty 字段不为空,非NULL且有内容
isEmpty 字段为NULL或者为''

如果上述方法还不能满足查询需求的话,我们可以使用自定sql的方式来编写查询条件,方法为:

Query query = Query.create();
query.sql(" username='Jim' OR username='Tom'");

注意:sql()方法不会处理sql注入问题,因此尽量少用。

# OR查询

// WHERE id=6 or username>'jim'
Query query = Query.create()
        .eq("id", 6)
        .orEq("username", "jim");

# 子条件

// WHERE id = ? OR username = ?
Query query = Query.create()
  .eq("id", 6)
  .orEq("username", "jim");

// WHERE (id = ? OR id between ? and ?) AND ( money > ? OR state = ? )
Query query = Query.create()
  .and(q -> q.eq("id", 3).orBetween("id", 4, 10))
  .and(q -> q.gt("money", 1).orEq("state", 1));

// WHERE ( id = ? AND username = ? ) OR ( money > ? AND state = ? )
Query query = Query.create()
  .and(q -> q.eq("id", 3).eq("username", "jim"))
  .or(q -> q.gt("money", 1).eq("state", 1));

# LambdaQuery

Query query = Query.lambda(TUser.class)
    .eq(TUser::getId, 1)
    .in(TUser::getId, Arrays.asList(1,2))
    .between(TUser::getId, 1, 2)
    .sql("id=1");
List<TUser> list = mapper.list(query);

子表达式

/*
        SELECT ... FROM table WHERE id = 6 
        and (username = 'jim' or state = 1) 
        or (id = 1 or id between 1 and 90)
        or username = 'tom'
 */
Query query = Query.lambda(TUser.class)
    .eq(TUser::getId, 6)
    // 子表达式
    .andLambda(q -> q.eq(TUser::getUsername, "jim")
    .orEq(TUser::getState, 1)
    )
    .orLambda(q -> q.eq(TUser::getId, 1).orBetween(TUser::getId, 1, 90))
    .orEq(TUser::getUsername, "tom");
List<TUser> list = mapper.list(query);

# 分页参数

一般来说分页的使用比较简单,通常是两个参数, pageIndex:当前页索引,pageSize:每页几条数据。 Query类使用**page(pageIdnex, pageSize)**方法来设置。 假如我们要查询第二页,每页10条数据,代码可以这样写:

Query query = Query.create();
query.page(2, 10);
List<User> list = dao.list(query);

如果要实现不规则分页,可以这样写:

Query query = Query.create();
query.limit(3, 5) // 对应mysql:limit 3,5

# 排序参数

orderby(String sortname, Sort sort)

其中sortname为数据库字段,非javaBean属性

  • orderby(String sortname, Sort sort)则可以指定排序方式,Sort为排序方式枚举 假如要按照添加时间倒序,可以这样写:
Query query = Query.create();
query.orderby("create_time", Sort.DESC); // 或 query.orderByDesc("create_time");
mapper.list(query);

query.orderByDesc("create_time");

添加多个排序字段可以在后面追加:

query.orderby("create_time",Sort.DESC).orderby("id",Sort.ASC);

# 对象转换Query

在实际开发中Controller层需要声明一个对象用来接收参数,fastmybatis提供了几个方法将查询参数直接封装到Query对象中

  • 方式1:实现IParam接口
public class UserParam extends IParam {

    @Condition
    private Integer id;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }
}
// http://localhost:8080/page4?pageIndex=1&pageSize=10
@GetMapping("page4")
public PageInfo<TUser> page4(UserParam param) {
    Query query = param.toQuery();
    PageInfo<TUser> pageInfo = mapper.page(query);
    return pageInfo;
}
  • 方式2:Query.build(bean)

如果不想实现IParam接口可以直接调用Query.build(bean)方法生成Query对象

Query query = Query.build(param);

# 重写Query中的方法(3.0.19)

有时候我们要重写Query中的方法,比如下面这个例子

    @Override
    public LambdaQuery<T> in(Getter<T, ?> getter, Collection<?> value) {
        // 去重
        Set<Object> uniqueValues = new HashSet<>(value);
        return super.in(getter, uniqueValues);
    }

Query新增了两个函数Query.setQueryBuilderQuery.setLambdaQueryBuilder方便我们扩展Query和LambdaQuery

下面是一个扩展LambdaQuery的例子,实现了两个功能:

  • 当使用in查询时去重
  • 使用eq查询兼容更多的数据类型
public class MyQuery<T> extends LambdaQuery<T> {

  public MyQuery(Class<T> entityClass) {
    super(entityClass);
  }


  @Override
  public LambdaQuery<T> in(Getter<T, ?> getter, Collection<?> value) {
    // 去重
    Set<Object> uniqueValues = new HashSet<>(value);
    return super.in(getter, uniqueValues);
  }

  @Override
  public LambdaQuery<T> eq(Getter<T, ?> getter, Object value) {
    // 兼容Collection
    if (value instanceof Collection) {
      return this.in(getter, (Collection<?>) value);
    }
    // 兼容布尔
    if (Objects.equals(value, true)) {
      return super.eq(getter, 1);
    }
    if (Objects.equals(value, false)) {
      return super.eq(getter, 0);
    }
    // 兼容枚举
    if (value instanceof IEnum) {
      return super.eq(getter, ((IEnum<?>) value).getValue());
    }

    return super.eq(getter, value);
  }
}

在启动的时候执行Query.setLambdaQueryBuilder(MyQuery::new);加载自定义builder,替换默认的builder

@Configuration
public class Config {

    static {
        Query.setLambdaQueryBuilder(MyQuery::new);
    }

}

单元测试:

@Test
public void test2() {
    List<UserInfo> list = userInfoMapper.query()
            .in(UserInfo::getId, Lists.newArrayList(1, 1, 2, 3, 3))
            .list();
    System.out.println(list);
}

# group by查询(3.1.0)

从3.1.0开始支持group by查询

@Test
public void groupBy() {
  List<StateVO> list = userMapper.query()
          // 指定返回结果,别名可以指定下划线,也可以指定驼峰形式,如:count(*) as stateCnt
          // 返回字段名需要跟StateVO中的字段保持一致
          .select("state, count(*) as state_cnt")
          // 查询条件
          .gt(TUser::getId, 1)
          // 字段分组
          .groupBy(TUser::getState)
          // 分组条件
          .having("state_cnt > 0")
          // 查询并转换
          .list(StateVO.class);
  System.out.println(list);

  // 只返回一个值
  Query query = Query.create()
          .select("sum(money)")
          .gt("id", 1);
  
  // 返回一个基本类型值,如果查出多条记录会抛出异常
  BigDecimal value = userMapper.getPrimitive(query, BigDecimal.class).getOrThrow();
  System.out.println(value);

  BigDecimal val = userMapper.query()
          .select("sum(money)")
          .gt(TUser::getId, 1)
          .getPrimitive(BigDecimal.class)
          .getOrThrow();
  System.out.println(val);
}

public static class StateVO {
  private Integer state;
  private Long stateCnt;

  public Integer getState() {
    return state;
  }

  public void setState(Integer state) {
    this.state = state;
  }

  public Long getStateCnt() {
    return stateCnt;
  }

  public void setStateCnt(Long stateCnt) {
    this.stateCnt = stateCnt;
  }

  @Override
  public String toString() {
    return "StateVO{" +
            "state=" + state +
            ", stateCnt=" + stateCnt +
            '}';
  }
}