# 查询

本小节主要讲解fastmybatis的查询功能。fastmybatis提供丰富的查询方式,满足日常查询所需。

# 分页查询

# 方式1

前端传递两个分页参数pageIndex,pageSize

	// http://localhost:8080/page1?pageIndex=1&pageSize=10
	@GetMapping("page1")
	public List<TUser> page1(int pageIndex,int pageSize) {
		Query query = new Query();
		query.page(pageIndex, pageSize);
		List<TUser> list = mapper.list(query);
		return list;
	}

# 方式2

PageParam里面封装了pageIndex,pageSize参数

	// http://localhost:8080/page2?pageIndex=1&pageSize=10
	@GetMapping("page2")
	public List<TUser> page2(PageParam param) {
		Query query = param.toQuery();
		List<TUser> list = mapper.list(query);
		return list;
	}

# 返回结果集和总记录数

fastmybatis提供一种更简洁的方式来处理:

// http://localhost:8080/page4?pageIndex=1&pageSize=10
	@GetMapping("page4")
	public PageInfo<TUser> page4(PageParam param) {
        Query query = param.toQuery();
		PageInfo<TUser> pageInfo = mapper.page(query);
		return pageInfo;
	}

PageInfo里面包含了List,total信息,还包含了一些额外信息,完整数据如下:

{
    "currentPageIndex": 1, // 当前页
    "firstPageIndex": 1, // 首页
    "lastPageIndex": 2, // 尾页
    "list": [     // 结果集
        {},
        {}
    ],
    "nextPageIndex": 2, // 下一页
    "pageCount": 2, // 总页数
    "pageIndex": 1, // 当前页
    "pageSize": 10, // 每页记录数
    "prePageIndex": 1, // 上一页
    "start": 0,
    "total": 20 // 总记录数
}
  • 结果集数据处理

对查询出来的结果做进一步加工

    Query query = new Query();
    // 添加查询条件
    query.eq("username", "张三")
        .page(1, 2) // 分页查询,按页码分,通常使用这种。
    ;

    // 分页信息
    PageInfo<TUser> pageInfo = mapper.page(query, tUser -> {
        // 对每行数据进行转换
        String username = tUser.getUsername();
        if ("张三".equals(username)) {
            tUser.setUsername("法外狂徒");
        }
        return tUser;
    });

状态字段添加说明:

    PageInfo<UserVO> pageInfo = mapper.page(query, tUser -> {
        // 对每行数据进行转换
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(tUser, userVO);
        Byte state = tUser.getState();
        switch (state) {
            case 0:
                userVO.setStateName("未启用");
                break;
            case 1:
                userVO.setStateName("已启用");
                break;
            case 2:
                userVO.setStateName("已禁用");
                break;
            default:{
                userVO.setStateName("未知状态");
            }
        }
        return userVO;
    });

# 使用普通bean查询

假设有个User类如下

public class User {
	private Integer id;
	private String userName;

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}
}

我们将这个类作为查询参数,那么在springmvc中可以这样写:

@GetMapping(path="findUserBean.do")
public List<User> findUser(User user) {
	Query query = Query.build(user);
	List<User> list = dao.list(query);
	return list;
}

Query query = Query.build(user);这句是将User中的属性转换成对应条件,假设userName的值为"jim",那么会封装成一个条件where user_name='jim'

浏览器输入链接:http://localhost:8080/fastmybatis-springmvc/findUserBean.do?userName=jim 后台将会执行如下SQL:

SELECT id,user_name FROM user t WHERE t.user_name = ?

?的值为jim

# @Condition注解

@Condition注解用来强化查询,有了这个注解可以生成各种查询条件。

@Condition注解属性如下:

属性 类型 说明 默认值
joint Joint 表达式之间的连接符,AND、OR Joint.AND
column String 数据库字段名,可选 空字符串
operator Operator 连接符枚举,存放了等于、大于、小于等连接符 Operator.nil
ignore boolean 是否忽略,设置true,@Condition将不起作用 false
ignoreEmptyString boolean 否忽略空字符串,设置true,忽略空字符串的字段 true
emptyStringWithTrim boolean 默认true情况下查询参数检查空白字符会先进行trim操作 true
ignoreValue String[] 设置忽略的值,如果字段值跟设置的值匹配,则不会生成条件。 比如前端传一个0,表示未选择,这样需要查询出所有数据,此时需要设置ignoreValue="0"。 如果不设置的话,会生成条件where status = 0导致查不到数据。 空数组
index int 决定WHERE后面表达式顺序,值小的靠左,可设置该值调整WHERE后面的条件顺序。 Integer.MAX_VALUE
handlerClass Class 值处理器,用来返回条件值 DefaultConditionValueHandler.class

如果要查询id大于2的用户只需在get方法上加上一个@Condition注解即可:

@Condition(operator=Operator.gt)
private Integer id;

这样,当id有值时,会封装成一个where id>2的条件

使用@Condition可以生产更加灵活的条件查询,比如需要查询日期为2017-12-1~2017-12-10日的记录,我们可以这样写:

@Condition(column="add_date",operator=Operator.ge)
private Date startDate;

@Condition(column="add_date",operator=Operator.lt)
private Date endDate;

转换成SQL语句:

t.add_date>='2017-12-1' AND t.add_date<'2017-12-10'

# 忽略某个值

@Condition(ignoreValue = "-1")
private Integer name;

客户端传-1将会被忽略,也可以写多个

@Condition(ignoreValue = {"0","-1"}) // 忽略0,-1两个值

这个功能配合前端select控件将会非常有用,假设控件内容如下

<select name="name">
    <option value="0">-请选择-</option>
    <option value="1">足球</option>
    <option value="2">篮球</option>
</select>

-请选择-的情况查询全部内容,此时需要忽略0这个值。

# 格式化查询参数

@Condition注解中的handlerClass属性可以指定自定义返回结果

/** 备注, 数据库字段:remark */
@Condition(handlerClass = RemarkHander.class)
private String remark;

指定的类需要实现ConditionValueHandler接口

public static class RemarkHander implements ConditionValueHandler {
    @Override
    public Object getConditionValue(Object defaultValue, String fieldName, Object target) {
        return defaultValue + "1";
    }
}

这里的示例是在原来的值上加1

  • handlerClass在日期上的处理

在做日期查询时,前端会传一个日期范围:开始日期、结束日期,如:2022-02-012022-02-02,此时对应数据库查询的日期范围是:2022-02-01 00:00:00 ~ 2022-02-02 23:59:59

此时我们需要对结束时间做一下修改:

public class EndDateConditionValueHandler implements ConditionValueHandler {  

    public Object getConditionValue(Object defaultValue, String fieldName, Object target) {
        if (defaultValue == null) {
            return null;
        } else if (defaultValue instanceof Date) {
            // 设置时间部分
            return setHMS((Date)defaultValue, 23, 59, 59);
        } else {
            return defaultValue instanceof LocalDateTime ? setHMS((LocalDateTime)defaultValue, 23, 59, 59) : defaultValue;
        }
    }

    /**
     * 设置时间部分
     *
     * @param date   日期
     * @param hour   时,0~23
     * @param minute 分,0~59
     * @param second 秒,0~59
     * @return 返回新的对象
     */
    public static Date setHMS(Date date, int hour, int minute, int second) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.HOUR_OF_DAY, hour);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, second);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar.getTime();
    }

    /**
     * 设置时间部分
     *
     * @param date   日期
     * @param hour   时,0~23
     * @param minute 分,0~59
     * @param second 秒,0~59
     * @return 返回新的对象
     */
    public static LocalDateTime setHMS(LocalDateTime date, int hour, int minute, int second) {
        return date.withHour(hour).withMinute(minute).withSecond(second).withNano(0);
    }
}

然后使用

/**
 * 有效期,结束时间
 * @mock 2021-12-09
 */
@Condition(column = "end_time", operator = Operator.le, handlerClass = EndDateConditionValueHandler.class)
private Date effectiveEndTime;
  • 调整条件顺序

使用@Condition.index属性可以调整where后面的顺序,index值小靠前,如下面两个

/**
 * 有效期,开始时间
 * @mock 2021-12-02
 */
@Condition(column = "begin_time", operator = Operator.ge, index = 2)
private Date effectiveBeginTime;

/**
 * 有效期,结束时间
 * @mock 2021-12-09
 */
@Condition(column = "end_time", operator = Operator.le, handlerClass = EndDateConditionValueHandler.class, index = 3)
private Date effectiveEndTime;

生成where条件就是:where begin_time >= ? and end_time <= ?

# 忽略空字符串

默认情况下忽略空字符串,如果需要某个字段不忽略空字符串可以做如下配置:

@Condition(ignoreEmptyString = false)
private String channelId;

如果channelId是""(空字符串),该字段会加入到条件当中。

如果要全局不忽略空字符串,可以配置FastmybatisConfig.ignoreEmptyString=false;

springboot可以配置属性:mybatis.ignore-empty-string=false

  • 检查空字符串去除首尾空白字符

默认情况下查询参数检查空白字符会先进行trim操作,如:传递一个空格,将被认为是空字符串。关闭此功能可以配置@Condition(emptyStringWithTrim = false)

全局关闭可以:FastmybatisConfig.emptyStringWithTrim=false;

或者springboot配置属性:

mybatis.empty-string-with-trim=false

# IN查询

假设前端页面传来多个值比如checkbox勾选多个id=[1,2],那么我们在User类里面可以用Integer[]List<Integer>来接收.

@Condition(column="id")
private Integer[] idArr;

这样会生成where id IN(1,2)条件。

# 排序查询

// 根据添加时间倒序

Query query = new Query();
query.orderby("create_time",Sort.DESC);
dao.list(query);

# 根据数组查询

// SELECT xx FROM t_user t WHERE id IN (1,2,3)
List<TUser> users = mapper.listByArray("id", new Integer[]{1, 2, 3});
System.out.println(JSON.toJSONString(users));

或者

List<TUser> users = mapper.listByCollection("id", Arrays.asList(1,2,3));
System.out.println(JSON.toJSONString(users));

# 返回指定字段

有时候只需要查询几个字段,并不需要返回所有字段

/**
 * 返回自定义字段,并转换成自定义类集合
 * 
 * <pre>
 * SELECT id, user_address FROM `t_user` t WHERE username = ?
 * </pre>
 */
@Test
public void testGivenColumns2() {
    Query query = new Query();
    // 添加查询条件
    query.eq("username", "张三");

    // 数据库字段
    List<String> columns = Arrays.asList("id", "user_address");
    // 查询,自定义集合
    List<UserVO> list = mapper.listBySpecifiedColumns(columns, query, UserVO.class);

    for (UserVO obj : list) {
        System.out.println(obj);
    }
}

@Data
public class UserVO {
    private Integer id;
    private String userAddress;
}

如果只返回一个字段

Query query = new Query();
// 添加查询条件
query.eq("username", "张三");

// 返回id列
List<Integer> idList = mapper.listBySpecifiedColumns(Collections.singletonList("id"), query, Integer.class/* 或int.class */);
for (Integer id : idList) {
    System.out.println(id);
}

// 返回id列,并转换成String
List<String> strIdList = mapper.listBySpecifiedColumns(Collections.singletonList("id"), query, String.class);

for (String id : strIdList) {
    System.out.println("string:" + id);
}

// 返回username列
List<String> usernameList = mapper.listBySpecifiedColumns(Collections.singletonList("username"), query, String.class);
for (String username : usernameList) {
    System.out.println(username);
}

// 返回时间列
List<Date> dateList = mapper.listBySpecifiedColumns(Collections.singletonList("add_time"), query, Date.class);
for (Date date : dateList) {
    System.out.println(date);
}

// 返回decimal列
List<BigDecimal> moneyList = mapper.listBySpecifiedColumns(Collections.singletonList("money"), query, BigDecimal.class);
for (BigDecimal money : moneyList) {
    System.out.println(money);
}

# 返回指定字段并分页

Query query = new Query()
        .eq("state", 0)
        .page(1, 6);
PageInfo<UserVO> mapPageInfo = mapper.pageBySpecifiedColumns(Arrays.asList("id", "username"), query, UserVO.class);
System.out.println(mapPageInfo);

# 多表关联查询(xml)【推荐】

有多张表需要关联查询,并且分页

mapper接口申明方法:

    List<UserInfoDO> listUserInfo(@Param("query") Query query);

    Long getUserInfoCount(@Param("query") Query query);

xml添加查询记录以及查询总数sql

<!-- 演示联表查询并分页 -->
	<select id="listUserInfo"
			parameterType="com.gitee.fastmybatis.core.query.Query"
			resultType="com.myapp.entity.UserInfoDO">
		SELECT
			t.id,
			t.username,
			t.state,
			t2.city,
			t2.address
		FROM t_user t LEFT JOIN user_info t2 ON t.id = t2.user_id
		<include refid="common.where" />
		<include refid="common.orderBy" />
		<include refid="common.limit" />
	</select>

	<!-- 查询总数 -->
	<select id="getUserInfoCount"
			parameterType="com.gitee.fastmybatis.core.query.Query"
			resultType="Long">
		SELECT
			count(*)
		FROM t_user t LEFT JOIN user_info t2 ON t.id = t2.user_id
		<include refid="common.where" />
	</select>

使用MapperUtil工具类查询:

    @Autowired
    TUserMapper tUserMapper;

    /**
     * 演示联表查询且分页
     */
    @Test
    public void joinPage() {
        Query query = new Query()
                // 联表查询需要带上表别名t.
                .gt("t.id", 1)
                .eq("t2.city", "杭州")
                .page(1, 10);

        // 分页查询只需要返回总数,以及当前分页内容
        PageInfo<UserInfoDO> pageInfo = MapperUtil.query(query, tUserMapper::getUserInfoCount, tUserMapper::listUserInfo);

        List<UserInfoDO> list = pageInfo.getList(); // 结果集
        long total = pageInfo.getTotal(); // 总记录数
        int pageCount = pageInfo.getPageCount(); // 共几页

        System.out.println("total:" + total);
        System.out.println("pageCount:" + pageCount);
        list.forEach(System.out::println);
    }

推荐使用这种方式,将SQL定义在xml中可读性好,方便维护,虽然使用硬编码能力也能查询出来但是可读性查。

# 多表关联查询(code)

多表关联查询使用的地方很多,比如需要关联第二张表,获取第二张表的几个字段,然后返回给前端。

fastmybatis的用法如下:

假如我们需要关联第二张表user_info,筛选出user_info中的城市为杭州的数据。

Query query = new Query()
        // 左连接查询,主表的alias默认为t
        .join("LEFT JOIN user_info t2 ON t.id = t2.user_id").page(1, 5)
        .eq("t2.city","杭州");

List<TUser> list = mapper.list(query);

System.out.println("==============");
for (TUser user : list) {
    System.out.println(user.getId() + " " + user.getUsername());
}
System.out.println("==============");

这种方式可读性差,不推荐

# 多表关联返回指定字段

有时候不需要全部字段,需要取表1中的几个字段,然后取表2中的几个字段,fastmybatis实现方式如下:

Query query = new Query();
// 左连接查询,主表的alias默认为t
query.join("LEFT JOIN user_info t2 ON t.id = t2.user_id");
// 指定返回字段
List<String> column = Arrays.asList("t2.user_id as userId", "t.username", "t2.city");
// 查询结果返回到map中
List<Map<String, Object>> mapList = mapper.listMap(column, query);
// 再将map转换成实体bean
List<UserInfoVo> list = MyBeanUtil.mapListToObjList(mapList, UserInfoVo.class);

执行的SQL语句对应如下:

SELECT t2.user_id as userId , t.username , t2.city
FROM `t_user` t 
LEFT JOIN user_info t2 ON t.id = t2.user_id

# 使用@Select查询

@Select注解是mybatis官方提供的一个功能,fastmybatis可以理解为是官方的一种扩展,因此同样支持此功能。 在Mapper中添加如下代码:

@Select("select * from t_user where id=#{id}")
TUser selectById(@Param("id") int id);

编写测试用例

@Test
public void testSelectById() {
    TUser user = dao.selectById(3);

    System.out.println(user.getUsername());
}

对于简单的SQL,可以用这种方式实现。除了@Select之外,还有@Update,@Insert,@Delete,这里就不多做演示了。