多条件多表查询功能优化

This commit is contained in:
kevin 2024-11-28 16:25:52 +08:00
parent 8dc4b5cf6b
commit 3838decd78
3 changed files with 443 additions and 0 deletions

View File

@ -0,0 +1,40 @@
package org.dromara.common.mybatis.query;
import java.lang.annotation.*;
/**
* 查询条件
* 修饰查询类qo
*
*/
@Target(value = {ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface QueryCriteria {
/**
* 条件 QueryUtil.eq
*/
String criteria() default QueryUtil.eq;
/**
* 字段 默认为字段名 多表联查可通过 @QueryCriteria(name = "table.fieldName")配置具有别名的字段
*/
String name() default "";
/**
* 跳过当前字段
*/
boolean exit() default false;
/**
* 是否必填
*/
boolean required() default false;
/**
* 排序方式
* eq: QueryUtil.asc or QueryUtil.desc
*/
String order() default "";
}

View File

@ -0,0 +1,276 @@
package org.dromara.common.mybatis.query;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.dromara.common.core.exception.ServiceException;
import org.springframework.util.ObjectUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Optional;
/**
* 参数处理
*
* @author zjmm
*/
public class QueryUtil {
/**
* 等价于 =: eq("name", "老王") ---> name = '老王'
*/
public final static String q_eq = "=";
/**
* 等价于 <>: ne("name", "老王") ---> name <> '老王'
*/
public final static String q_ne = "<>";
/**
* 等价于 >: gt("name", "老王") ---> name > '老王'
*/
public final static String q_gt = ">";
/**
* 等价于 >=: ge("name", "老王") ---> name >= '老王'
*/
public final static String q_ge = ">=";
/**
* 等价于 <: lt("name", "老王") ---> name < '老王'
*/
public final static String q_lt = "<";
/**
* 等价于 <=: le("name", "老王") ---> name <= '老王'
*/
public final static String q_le = "<=";
/**
* 等价于 =: eq("name", "老王") ---> name = '老王'
*/
public final static String eq = "eq";
/**
* 等价于 <>: ne("name", "老王") ---> name <> '老王'
*/
public final static String ne = "ne";
/**
* 等价于 >: gt("name", "老王") ---> name > '老王'
*/
public final static String gt = "gt";
/**
* 等价于 >=: ge("name", "老王") ---> name >= '老王'
*/
public final static String ge = "ge";
/**
* 等价于 <: lt("name", "老王") ---> name < '老王'
*/
public final static String lt = "lt";
/**
* 等价于 <=: le("name", "老王") ---> name <= '老王'
*/
public final static String le = "le";
/**
* 等价于 between a and b, between("age", 18, 30) ---> age between 18 and 30
*/
public final static String between = "between";
/**
* 等价于 not between a and b, notBetween("age", 18, 30) ---> age not between 18 and 30
*/
public final static String notBetween = "notBetween";
/**
* 等价于 字段 IN (v0, v1, ...),: in("age",{1,2,3}) ---> age in (1,2,3)
*/
public final static String in = "in";
/**
* 等价于 字段 NOT IN (v0, v1, ...), : notIn("age",{1,2,3}) ---> age not in (1,2,3)
*/
public final static String notIn = "notIn";
/**
* 等价于 字段 IN (sql 语句), : inSql("id", "select id from table where id < 3") ---> id in (select id from table where id < 3)
*/
public final static String inSql = "inSql";
/**
* 等价于 字段 NOT IN (sql 语句)
*/
public final static String notInSql = "notInSql";
/**
* 等价于 LIKE '%%': like("name", "") ---> name like '%%'
*/
public final static String like = "like";
/**
* 等价于 NOT LIKE '%%': notLike("name", "") ---> name not like '%%'
*/
public final static String notLike = "notLike";
/**
* 等价于 LIKE '%': likeLeft("name", "") ---> name like '%'
*/
public final static String likeLeft = "likeLeft";
/**
* 等价于 LIKE '%': likeRight("name", "") ---> name like '%'
*/
public final static String likeRight = "likeRight";
/**
* 等价于 IS NULL: isNull("name") ---> name is null
*/
public final static String isNull = "isNull";
/**
* 等价于 IS NOT NULL: isNotNull("name") ---> name is not null
*/
public final static String isNotNull = "isNotNull";
public final static String asc = "asc";
public final static String desc = "desc";
public static <T, M> QueryWrapper<T> toWrapper(QueryWrapper<T> wrapper, M m) {
Class<?> aClass = m.getClass();
Field[] declaredFields = aClass.getDeclaredFields();
for (Field field : declaredFields) {
//判断field是否被static修饰
if (Modifier.isStatic(field.getModifiers())) {
continue;
}
field.setAccessible(true);
//获取字段名
QueryCriteria annotation = field.getAnnotation(QueryCriteria.class);
String name = StrUtil.toUnderlineCase(Optional.ofNullable(annotation)
.map(QueryCriteria::name)
.filter(StrUtil::isNotBlank)
.orElseGet(field::getName));
//获取排序方式
String order = Optional.ofNullable(annotation).map(QueryCriteria::order).orElse("");
if (asc.equalsIgnoreCase(order)) {
wrapper.orderByAsc(name);
}
if (desc.equalsIgnoreCase(order)) {
wrapper.orderByDesc(name);
}
if (Optional.ofNullable(annotation).map(QueryCriteria::exit).orElse(false)) {
continue;
}
//获取比较方式
String criteria = Optional.ofNullable(annotation).map(QueryCriteria::criteria).orElse(QueryUtil.eq);
Object value = getValue(m, field);
if (ObjectUtils.isEmpty(value)) {
if (Optional.ofNullable(annotation).map(QueryCriteria::required).orElse(false)) {
throw new ServiceException(name + "参数内容必填");
} else {
continue;
}
}
//添加条件
addWrapper(wrapper, name, criteria, value);
}
return wrapper;
}
private static <M> Object getValue(M m, Field field) {
try {
return field.get(m);
} catch (IllegalAccessException e) {
return null;
}
}
/**
* 有查询条件调用
*
* @param m qo对象
* @return wrapper
*/
public static <T, M> QueryWrapper<T> toWrapper(M m) {
if (m == null) {
return new QueryWrapper<T>();
}
return toWrapper(new QueryWrapper<T>(), m);
}
/**
* 无查询条件调用
*/
public static <T> QueryWrapper<T> newWrapper() {
return new QueryWrapper<T>();
}
private static <T> void addWrapper(QueryWrapper<T> wrapper, String name, String criteria, Object value) {
switch (criteria) {
case q_eq, eq -> wrapper.eq(name, value);
case q_ne, ne -> wrapper.ne(name, value);
case q_gt, gt -> wrapper.gt(name, value);
case q_ge, ge -> wrapper.ge(name, value);
case q_lt, lt -> wrapper.lt(name, value);
case q_le, le -> wrapper.le(name, value);
case between -> {
if (value instanceof List<?> list) {
if (list.size() >= 2) {
wrapper.between(name, list.get(0), list.get(1));
break;
}
} else if (value instanceof Object[] arr) {
if (arr.length >= 2) {
wrapper.between(name, arr[0], arr[1]);
break;
}
}
throw new ServiceException(name + "字段传值请用list");
}
case notBetween -> {
if (value instanceof List<?> list) {
if (list.size() >= 2) {
wrapper.notBetween(name, list.get(0), list.get(1));
break;
}
} else if (value instanceof Object[] arr) {
if (arr.length >= 2) {
wrapper.notBetween(name, arr[0], arr[1]);
break;
}
}
throw new ServiceException(name + "字段传值请用list");
}
case in -> {
if (value instanceof List<?> list) {
Object[] array = list.toArray();
wrapper.in(name, array);
} else {
wrapper.in(name, value);
}
}
case notIn -> wrapper.notIn(name, value);
case inSql -> wrapper.inSql(name, value.toString());
case notInSql -> wrapper.notInSql(name, value.toString());
case like -> wrapper.like(name, value);
case notLike -> wrapper.notLike(name, value);
case likeLeft -> wrapper.likeLeft(name, value);
case likeRight -> wrapper.likeRight(name, value);
case isNull -> wrapper.isNull(name);
case isNotNull -> wrapper.isNotNull(name);
}
}
}

View File

@ -0,0 +1,127 @@
## 多条件多表查询功能优化
- 解决的问题
在单表列表查询多表联查列表查询需要手动拼接条件容易出错这里引入工具类QueryUtil通过Bo对象自动拼接查询条件减少手动拼接条件代码提高开发效率。
```java
//项目内存在的代码片段
public List<SysDeptVo> selectDeptList(SysDeptBo dept) {
LambdaQueryWrapper<SysDept> lqw = buildQueryWrapper(dept);
return baseMapper.selectDeptList(lqw);
}
private LambdaQueryWrapper<SysDept> buildQueryWrapper(SysDeptBo bo) {
LambdaQueryWrapper<SysDept> lqw = Wrappers.lambdaQuery();
lqw.eq(SysDept::getDelFlag, SystemConstants.NORMAL);
lqw.eq(ObjectUtil.isNotNull(bo.getDeptId()), SysDept::getDeptId, bo.getDeptId());
lqw.eq(ObjectUtil.isNotNull(bo.getParentId()), SysDept::getParentId, bo.getParentId());
lqw.like(StringUtils.isNotBlank(bo.getDeptName()), SysDept::getDeptName, bo.getDeptName());
lqw.like(StringUtils.isNotBlank(bo.getDeptCategory()), SysDept::getDeptCategory, bo.getDeptCategory());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysDept::getStatus, bo.getStatus());
lqw.orderByAsc(SysDept::getAncestors);
lqw.orderByAsc(SysDept::getParentId);
lqw.orderByAsc(SysDept::getOrderNum);
lqw.orderByAsc(SysDept::getDeptId);
return lqw;
}
```
以上面这段代码为例查询部门列表buildQueryWrapper方法中使用了eq,like, orderByAsc等wapper中的条件方法直接返回wrapper无需再手动添加条件。
而在每个查询时都需要写这buildQueryWrapper方法每次写条件拼接易出错
这里改进为扩展Bo, 为字段添加注解
```java
@Data
public class SysDeptBo extends BaseEntity {
/**
* 祖级列表
*/
@QueryCriteria(exit = true, order = "asc")
private String ancestors;
/**
* 父部门ID
*/
@QueryCriteria(order = "asc")
private Long parentId;
/**
* 显示顺序
*/
@NotNull(message = "显示顺序不能为空")
@QueryCriteria(exit = true, order = "asc")
private Integer orderNum;
/**
* 部门id
*/
@QueryCriteria(order = "asc")
private Long deptId;
/**
* 部门名称
*/
@NotBlank(message = "部门名称不能为空")
@Size(min = 0, max = 30, message = "部门名称长度不能超过{max}个字符")
@QueryCriteria(criteria = QueryUtil.like)
private String deptName;
/**
* 部门类别编码
*/
@Size(min = 0, max = 100, message = "部门类别编码长度不能超过{max}个字符")
@QueryCriteria(criteria = QueryUtil.like)
private String deptCategory;
/**
* 负责人
*/
private Long leader;
/**
* 联系电话
*/
@Size(min = 0, max = 11, message = "联系电话长度不能超过{max}个字符")
private String phone;
/**
* 邮箱
*/
@Email(message = "邮箱格式不正确")
@Size(min = 0, max = 50, message = "邮箱长度不能超过{max}个字符")
private String email;
/**
* 部门状态0正常 1停用
*/
private String status;
/**
* 删除标志0代表存在 2代表删除
*/
private String delFlag;
}
public List<SysDeptVo> selectDeptList(SysDeptBo dept) {
dept.setDelFlag(SystemConstants.NORMAL);
return baseMapper.selectDeptList(QueryUtil.toWrapper(dept));
}
//为什么Bo中字段顺序改了因为排序有前后顺序
//为什么需要添加dept.setDelFlag(SystemConstants.NORMAL);这行代码,因为删除标志是默认的,在查询时,需要添加该条件,否则会查出所有数据
```
以上两种写法结果等价
```
使用前提:
1.domain添加qo或者使用已有的Bo甚至就使用domain
2.使用@QueryCriteria注解修饰Qo中的实例字段如果字段不配置注解则使用默认配置
3.使用mybatis-plus wrapper查询列表
```
```
原理和使用介绍:
1、通过@QueryCriteria注解的参数criteria可配置条件包含多种sql查询类型如eq,like,gt,lt,in,isNull,between等详情查看QueryUtil工具类注释
2、若字段不需要被加入sql条件中注解参数exit设置为true
3、当qo中的实例字段的值为null时该字段的查询条件将不被加入sql条件
4、当表中的字段如dept_id在一个接口中出现场景<1>筛选dept_id为null <2>筛选dept_id=xxx可在qo中加入两个实例字段名字自定义注解参数name都设为dept_id即可同时满足场景<1>和场景<2>
5、通过注解参数order,可实现字段排序
6、当实例字段和注解参数name相同时可省略注解参数name多表联查可通过 @QueryCriteria(name = "table.fieldName")配置具有别名的字段
7、or条件不支持需自行拼接wapper
```
### 根据自身的业务需求,可自定义规则,如:
- 前端不需要多余的参数那么就可以扩展一个qo类那么在前端的接口文档中就无需显示多余的参数减少前后的沟通成本
- QueryUtil.toWrapper(查询对象) 查询对象中的非static字段都会默认参与条件拼接可以根据自己需要考虑是否需要必须使用注解才参与