# 查询
本小节主要讲解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-01
、2022-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,这里就不多做演示了。