前言
用 MyBatis-Plus框架 的 QueryWrapper,可以更方便、清晰、安全、易维护地构建动态查询条件,避免了手动拼接 SQL 字符串的繁琐和潜在的安全问题。特别对于刚使用新手或者习惯了编写SQL语句的猿来说,在面对复杂的查询逻辑条件,可能会有点蒙圈。这里本人就于实践中整了一点案例进行分享。本文是基于一个医生预约筛选查询的功能,其界面大致如下:
其中大致的逻辑是,根据名称、类型、领域等字段进行常规的查询,其中复杂的点在预约的三种方式和价格区间的组合查询,任选一/多种预约类型,传入价格区间进行查询,这里选择的任一预约类型的价格在区间内,就返回该条记录。记录价格间是或(or)的关系。
实现
这里就是采用了 MyBatis-Plus框架 的 QueryWrapper来实现,首先要定义医生的实体类型:
@Data
@TableComment("医生")
@TableName(autoResultMap = true)
public class Doctor {
@ApiModelProperty("ID,创建勿填")
@TableId(value = "id",type = IdType.ASSIGN_ID)
@Column(comment = "ID")
private Long id;
@ApiModelProperty("姓名")
@Column(comment = "姓名")
private String name;
@ApiModelProperty("性别 0:女 1:男")
@Column(type = MySqlTypeConstant.TINYINT,defaultValue = "0",comment = "性别 0:女 1:男")
private Integer sex;
@ApiModelProperty("医生类型")
@Column(comment = "医生类型")
private Long doctorType;
@ApiModelProperty("擅长领域")
@Column(comment = "擅长领域")
private Long expertIn;
@ApiModelProperty("开启图文会诊")
@Column(comment = "开启图文会诊", defaultValue = "false")
private Boolean isText;
@ApiModelProperty("图文会诊价格")
@Column(comment = "图文会诊价格")
private Long textPrice;
@ApiModelProperty("开启电话会诊")
@Column(comment = "开启电话会诊", defaultValue = "false")
private Boolean isPhone;
@ApiModelProperty("电话会诊价格")
@Column(comment = "电话会诊价格")
private Long phonePrice;
@ApiModelProperty("开启线下会诊")
@Column(comment = "开启线下会诊", defaultValue = "false")
private Boolean isOffline;
@ApiModelProperty("线下会诊价格")
@Column(comment = "线下会诊价格")
private Long offlinePrice;
}
然后根据查询的参数来设计查询的参数类型:
@Data
public class DoctorFilterParam {
@ApiModelProperty("姓名")
private String name;
@ApiModelProperty("医生类型")
private Long doctorType;
@ApiModelProperty("擅长领域")
private Long expertIn;
@ApiModelProperty("筛选图文会诊")
private Boolean isText;
@ApiModelProperty("筛选电话会诊")
private Boolean isPhone;
@ApiModelProperty("筛选线下会诊")
private Boolean isOffline;
@ApiModelProperty("价格区间--开始")
private Float startPrice;
@ApiModelProperty("价格区间--结束")
private Float endPrice;
}
这样参数的文档说明如下:
最后就是用QueryWrapper的lambda来编写实现查询的代码:
public List<Doctor> getFilterList(DoctorFilterParam param)
{
QueryWrapper<Doctor> objectQueryWrapper = new QueryWrapper<>(Doctor.class);
objectQueryWrapper.lambda()
.and(param.getDoctorType() != null,
dt -> dt.eq(Doctor::getDoctorType, param.getDoctorType()))
.and(param.getExpertIn() != null,
dt -> dt.eq(Doctor::getExpertIn, param.getExpertIn()))
.and(param.getName() != null,
dt -> dt.like(Doctor::getName, param.getName()))
.and(param.getIsPhone()
|| param.getIsOffline()
|| param.getIsText(),
lt -> lt.or(param.getIsPhone(),
pc -> pc.eq(Doctor::getIsPhone,1)
.between(Doctor::getPhonePrice,param.getStartPrice(),param.getEndPrice()))
.or(param.getIsOffline(),
ol -> ol.eq(Doctor::getIsOffline,1)
.between(Doctor::getOfflinePrice,param.getStartPrice(),param.getEndPrice()))
.or(param.getIsText(),
tc -> tc.eq(Doctor::getIsText,1)
.between(Doctor::getTextPrice,param.getStartPrice(),param.getEndPrice()))
);
return this.list(objectQueryWrapper);
}
如上的代码实现查询的条件嵌套,主要就是or和and的嵌套,包含部分的区间查询between,没做全部的容错处理,传参差异可能有异常,仅供参考。
下面就可以通过构建查询的参数进行一次查询测试:
DoctorFilterParam param = new DoctorFilterParam();
param.setIsPhone(true);
param.setIsOffline(true);
param.setIsText(true);
param.setName("牛");
param.setStartPrice(20.0f);
param.setEndPrice(20000.0f);
System.out.println(doctorService.getFilterList(param));
这个参数的查询条件就是查询名称含牛的、开通了电话、线下、图文会诊而且任一一直方式的价格在20至20000区间的医生。
然后转换后的SQL语句以及参数如下:
Preparing: SELECT id,name,sex,doctor_type,expert_in,is_text,text_price,is_phone,phone_price,is_offline,offline_price FROM oas_doctor WHERE ((name LIKE ?) AND ((is_phone = ? AND phone_price BETWEEN ? AND ?) OR (is_offline = ? AND offline_price BETWEEN ? AND ?) OR (is_text = ? AND text_price BETWEEN ? AND ?)))
Parameters: %牛%(String), 1(Integer), 20.0(Float), 20000.0(Float), 1(Integer), 20.0(Float), 20000.0(Float), 1(Integer), 20.0(Float), 20000.0(Float)
查询的结果格式也是一个又医生数据组成的数组: