mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-09-19 01:48:47 +08:00
多条件多表查询功能优化
This commit is contained in:
parent
8dc4b5cf6b
commit
3838decd78
@ -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 "";
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -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字段都会默认参与条件拼接,可以根据自己需要,考虑是否需要必须使用注解才参与
|
||||
Loading…
Reference in New Issue
Block a user