refactor: 重构 QueryHelper => QueryWrapperHelper,正式支持多列查询

This commit is contained in:
2024-01-24 20:41:43 +08:00
parent f16b968b3f
commit 13a6809e2a
5 changed files with 192 additions and 201 deletions

View File

@@ -236,10 +236,10 @@ ContiNew Starter 的分支目前分为下个大版本的开发分支和上个大
### 特别鸣谢
- 感谢 <a href="https://www.jetbrains.com/" target="_blank">JetBrains</a> 提供的 <a href="https://www.jetbrains.com/shop/eform/opensource" target="_blank">非商业开源软件开发授权</a>
- 感谢 <a href="https://www.jetbrains.com/" target="_blank">JetBrains</a> 提供的 <a href="https://jb.gg/OpenSourceSupport" target="_blank">非商业开源软件开发授权</a>
- 感谢 <a href="https://github.com/baomidou/mybatis-plus" target="_blank">MyBatis Plus</a><a href="https://github.com/dromara/sa-token" target="_blank">Sa-Token</a><a href="https://github.com/alibaba/jetcache" target="_blank">JetCache</a><a href="https://github.com/opengoofy/crane4j" target="_blank">Crane4j</a><a href="https://github.com/xiaoymin/knife4j" target="_blank">Knife4j</a><a href="https://github.com/dromara/hutool" target="_blank">Hutool</a> 等开源组件作者为国内开源世界作出的贡献
- 感谢 <a href="https://github.com/elunez/eladmin" target="_blank">ELADMIN</a><a href="https://github.com/dromara/RuoYi-Vue-Plus" target="_blank">RuoYi-Vue-Plus</a><a href="https://gitee.com/herodotus/dante-engine" target="_blank">Dante-Engine</a>,致敬各位作者为开源脚手架领域作出的贡献
- e.g. 扩展于 ELADMIN 项目开源的 QueryHelper 组件
- e.g. 起源于 ELADMIN 项目开源的 QueryHelper 组件
- e.g. 扩展于 RuoYi-Vue-Plus 项目封装的 SaToken 相关认证鉴权配置
- e.g. 扩展于 Dante-Engine 项目封装的 Redisson 相关配置
- 感谢项目使用或未使用到的每一款开源组件,致敬各位开源先驱 :fire:

View File

@@ -23,7 +23,6 @@ import java.lang.annotation.*;
*
* @author Charles7c
* @author Jasmine
* @author Zheng Jie<a href="https://gitee.com/elunez/eladmin">ELADMIN</a>
* @since 1.0.0
*/
@Target(ElementType.FIELD)
@@ -32,7 +31,7 @@ import java.lang.annotation.*;
public @interface Query {
/**
* 列名
* 列名(注意:列名是数据库字段名,而不是实体类字段名。如果命名是数据库关键字的,请使用转义符包裹)
*
* <p>
* columns 为空时,默认取值字段名(自动转换为下划线命名);<br>

View File

@@ -1,186 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.data.mybatis.plus.query;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import top.charles7c.continew.starter.core.exception.BadRequestException;
import top.charles7c.continew.starter.core.util.ReflectUtils;
import top.charles7c.continew.starter.core.util.validate.ValidationUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* 查询助手
*
* @author Charles7c
* @author Jasmine
* @author Zheng Jie<a href="https://gitee.com/elunez/eladmin">ELADMIN</a>
* @since 1.0.0
*/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class QueryHelper {
/**
* 根据查询条件构建查询条件封装对象
*
* @param query 查询条件
* @param <Q> 查询条件数据类型
* @param <R> 查询数据类型
* @return 查询条件封装对象
*/
public static <Q, R> QueryWrapper<R> build(Q query) {
QueryWrapper<R> queryWrapper = new QueryWrapper<>();
// 没有查询条件,直接返回
if (null == query) {
return queryWrapper;
}
// 解析并拼接查询条件
List<Field> fieldList = ReflectUtils.getNonStaticFields(query.getClass());
fieldList.forEach(field -> buildWrapper(query, field, queryWrapper));
return queryWrapper;
}
/**
* 构建查询条件封装对象
*
* @param query 查询条件
* @param field 字段
* @param queryWrapper 查询条件封装对象
* @param <Q> 查询条件数据类型
* @param <R> 查询数据类型
*/
public static <Q, R> void buildWrapper(Q query, Field field, QueryWrapper<R> queryWrapper) {
boolean accessible = field.canAccess(query);
try {
field.setAccessible(true);
// 如果字段值为空,直接返回
Object fieldValue = field.get(query);
if (ObjectUtil.isEmpty(fieldValue)) {
return;
}
// 建议:数据库规范中列建议采用下划线连接法命名,程序规范中变量建议采用驼峰法命名
String fieldName = field.getName();
String columnName = StrUtil.toUnderlineCase(fieldName);
// 没有 @Query 注解,默认等值查询
Query queryAnnotation = field.getAnnotation(Query.class);
if (null == queryAnnotation) {
queryWrapper.eq(columnName, fieldValue);
return;
}
// 解析单列查询
String[] columns = queryAnnotation.columns();
final int columnLength = ArrayUtil.length(columns);
if (columnLength == 0 || columnLength == 1) {
columnName = columnLength == 1 ? columns[0] : columnName;
parse(queryAnnotation.type(), columnName, fieldValue, queryWrapper);
return;
}
// 解析多列查询
QueryType queryType = queryAnnotation.type();
queryWrapper.nested(wrapper -> {
for (String column : columns) {
switch (queryType) {
case EQ -> queryWrapper.or().eq(column, fieldValue);
case NE -> queryWrapper.or().ne(column, fieldValue);
case GT -> queryWrapper.or().gt(column, fieldValue);
case GE -> queryWrapper.or().ge(column, fieldValue);
case LT -> queryWrapper.or().lt(column, fieldValue);
case LE -> queryWrapper.or().le(column, fieldValue);
case BETWEEN -> {
List<Object> between = new ArrayList<>((List<Object>)fieldValue);
ValidationUtils.throwIf(between.size() != 2, "[{}] 必须是一个范围", fieldName);
queryWrapper.or().between(column, between.get(0), between.get(1));
}
case LIKE -> queryWrapper.or().like(column, fieldValue);
case LIKE_LEFT -> queryWrapper.or().likeLeft(column, fieldValue);
case LIKE_RIGHT -> queryWrapper.or().likeRight(column, fieldValue);
case IN -> {
ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", fieldName);
queryWrapper.or().in(column, (List<Object>)fieldValue);
}
case NOT_IN -> {
ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", fieldName);
queryWrapper.or().notIn(column, (List<Object>)fieldValue);
}
case IS_NULL -> queryWrapper.or().isNull(column);
case IS_NOT_NULL -> queryWrapper.or().isNotNull(column);
default -> throw new IllegalArgumentException(String.format("暂不支持 [%s] 查询类型", queryType));
}
}
});
} catch (BadRequestException e) {
log.error("Build query occurred an validation error: {}. Query: {}, Field: {}.", e
.getMessage(), query, field, e);
throw e;
} catch (Exception e) {
log.error("Build query occurred an error: {}. Query: {}, Field: {}.", e.getMessage(), query, field, e);
} finally {
field.setAccessible(accessible);
}
}
/**
* 解析查询条件
*
* @param queryType 查询类型
* @param columnName 驼峰字段名
* @param fieldValue 字段值
* @param queryWrapper 查询条件封装对象
* @param <R> 查询数据类型
*/
private static <R> void parse(QueryType queryType,
String columnName,
Object fieldValue,
QueryWrapper<R> queryWrapper) {
switch (queryType) {
case EQ -> queryWrapper.eq(columnName, fieldValue);
case NE -> queryWrapper.ne(columnName, fieldValue);
case GT -> queryWrapper.gt(columnName, fieldValue);
case GE -> queryWrapper.ge(columnName, fieldValue);
case LT -> queryWrapper.lt(columnName, fieldValue);
case LE -> queryWrapper.le(columnName, fieldValue);
case BETWEEN -> {
List<Object> between = new ArrayList<>((List<Object>)fieldValue);
ValidationUtils.throwIf(between.size() != 2, "[{}] 必须是一个范围", columnName);
queryWrapper.between(columnName, between.get(0), between.get(1));
}
case LIKE -> queryWrapper.like(columnName, fieldValue);
case LIKE_LEFT -> queryWrapper.likeLeft(columnName, fieldValue);
case LIKE_RIGHT -> queryWrapper.likeRight(columnName, fieldValue);
case IN -> {
ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", columnName);
queryWrapper.in(columnName, (List<Object>)fieldValue);
}
case NOT_IN -> {
ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", columnName);
queryWrapper.notIn(columnName, (List<Object>)fieldValue);
}
case IS_NULL -> queryWrapper.isNull(columnName);
case IS_NOT_NULL -> queryWrapper.isNotNull(columnName);
default -> throw new IllegalArgumentException(String.format("暂不支持 [%s] 查询类型", queryType));
}
}
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.continew.starter.data.mybatis.plus.query;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import top.charles7c.continew.starter.core.exception.BadRequestException;
import top.charles7c.continew.starter.core.util.ReflectUtils;
import top.charles7c.continew.starter.core.util.validate.ValidationUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
/**
* QueryWrapper 助手
*
* @author Charles7c
* @author Jasmine
* @since 1.0.0
*/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class QueryWrapperHelper {
/**
* 构建 QueryWrapper
*
* @param query 查询条件
* @param <Q> 查询条件数据类型
* @param <R> 查询数据类型
* @return QueryWrapper
*/
public static <Q, R> QueryWrapper<R> build(Q query) {
QueryWrapper<R> queryWrapper = new QueryWrapper<>();
// 没有查询条件,直接返回
if (null == query) {
return queryWrapper;
}
// 获取查询条件中所有的字段
List<Field> fieldList = ReflectUtils.getNonStaticFields(query.getClass());
return build(query, fieldList, queryWrapper);
}
/**
* 构建 QueryWrapper
*
* @param query 查询条件
* @param fields 查询条件字段列表
* @param queryWrapper QueryWrapper
* @param <Q> 查询条件数据类型
* @param <R> 查询数据类型
* @return QueryWrapper
*/
public static <Q, R> QueryWrapper<R> build(Q query, List<Field> fields, QueryWrapper<R> queryWrapper) {
// 没有查询条件,直接返回
if (null == query) {
return queryWrapper;
}
// 解析并拼接查询条件
for (Field field : fields) {
List<Consumer<QueryWrapper<R>>> consumers = buildWrapperConsumer(query, field);
queryWrapper.and(CollUtil.isNotEmpty(consumers), q -> consumers.forEach(q::or));
}
return queryWrapper;
}
/**
* 构建 QueryWrapper Consumer
*
* @param query 查询条件
* @param field 查询条件字段
* @param <Q> 查询条件数据类型
* @param <R> 查询数据类型
* @return QueryWrapper Consumer
*/
public static <Q, R> List<Consumer<QueryWrapper<R>>> buildWrapperConsumer(Q query, Field field) {
List<Consumer<QueryWrapper<R>>> consumers = new ArrayList<>();
boolean accessible = field.canAccess(query);
try {
field.setAccessible(true);
// 如果字段值为空,直接返回
Object fieldValue = field.get(query);
if (ObjectUtil.isEmpty(fieldValue)) {
return consumers;
}
// 建议:数据库表列建议采用下划线连接法命名,程序变量建议采用驼峰法命名
String fieldName = field.getName();
// 没有 @Query 注解,默认等值查询
Query queryAnnotation = field.getAnnotation(Query.class);
if (Objects.isNull(queryAnnotation)) {
consumers.add(q -> q.eq(StrUtil.toUnderlineCase(fieldName), fieldValue));
return consumers;
}
// 解析单列查询
QueryType queryType = queryAnnotation.type();
String[] columns = queryAnnotation.columns();
final int columnLength = ArrayUtil.length(columns);
if (columnLength <= 1) {
String columnName = columnLength == 1 ? columns[0] : StrUtil.toUnderlineCase(fieldName);
parse(queryType, columnName, fieldValue, consumers);
return consumers;
}
// 解析多列查询
for (String column : columns) {
parse(queryType, column, fieldValue, consumers);
}
} catch (BadRequestException e) {
log.error("Build query wrapper occurred an validation error: {}. Query: {}, Field: {}.", e
.getMessage(), query, field, e);
throw e;
} catch (Exception e) {
log.error("Build query wrapper occurred an error: {}. Query: {}, Field: {}.", e
.getMessage(), query, field, e);
} finally {
field.setAccessible(accessible);
}
return consumers;
}
/**
* 解析查询条件
*
* @param queryType 查询类型
* @param columnName 列名
* @param fieldValue 字段值
* @param <R> 查询数据类型
*/
private static <R> void parse(QueryType queryType,
String columnName,
Object fieldValue,
List<Consumer<QueryWrapper<R>>> consumers) {
switch (queryType) {
case EQ -> consumers.add(q -> q.eq(columnName, fieldValue));
case NE -> consumers.add(q -> q.ne(columnName, fieldValue));
case GT -> consumers.add(q -> q.gt(columnName, fieldValue));
case GE -> consumers.add(q -> q.ge(columnName, fieldValue));
case LT -> consumers.add(q -> q.lt(columnName, fieldValue));
case LE -> consumers.add(q -> q.le(columnName, fieldValue));
case BETWEEN -> {
List<Object> between = new ArrayList<>((List<Object>)fieldValue);
ValidationUtils.throwIf(between.size() != 2, "[{}] 必须是一个范围", columnName);
consumers.add(q -> q.between(columnName, between.get(0), between.get(1)));
}
case LIKE -> consumers.add(q -> q.like(columnName, fieldValue));
case LIKE_LEFT -> consumers.add(q -> q.likeLeft(columnName, fieldValue));
case LIKE_RIGHT -> consumers.add(q -> q.likeRight(columnName, fieldValue));
case IN -> {
ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", columnName);
consumers.add(q -> q.in(columnName, (List<Object>)fieldValue));
}
case NOT_IN -> {
ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", columnName);
consumers.add(q -> q.notIn(columnName, (List<Object>)fieldValue));
}
case IS_NULL -> consumers.add(q -> q.isNull(columnName));
case IS_NOT_NULL -> consumers.add(q -> q.isNotNull(columnName));
default -> throw new IllegalArgumentException(String.format("暂不支持 [%s] 查询类型", queryType));
}
}
}

View File

@@ -40,7 +40,7 @@ import top.charles7c.continew.starter.core.util.ReflectUtils;
import top.charles7c.continew.starter.core.util.validate.CheckUtils;
import top.charles7c.continew.starter.core.util.validate.ValidationUtils;
import top.charles7c.continew.starter.data.mybatis.plus.base.BaseMapper;
import top.charles7c.continew.starter.data.mybatis.plus.query.QueryHelper;
import top.charles7c.continew.starter.data.mybatis.plus.query.QueryWrapperHelper;
import top.charles7c.continew.starter.extension.crud.annotation.TreeField;
import top.charles7c.continew.starter.extension.crud.model.query.PageQuery;
import top.charles7c.continew.starter.extension.crud.model.query.SortQuery;
@@ -81,7 +81,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseDO,
@Override
public PageResp<L> page(Q query, PageQuery pageQuery) {
QueryWrapper<T> queryWrapper = handleQueryWrapper(query);
QueryWrapper<T> queryWrapper = this.handleQueryWrapper(query);
IPage<T> page = baseMapper.selectPage(pageQuery.toPage(), queryWrapper);
PageResp<L> pageResp = PageResp.build(page, listClass);
pageResp.getList().forEach(this::fill);
@@ -134,7 +134,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseDO,
* @return 列表信息
*/
protected <E> List<E> list(Q query, SortQuery sortQuery, Class<E> targetClass) {
QueryWrapper<T> queryWrapper = handleQueryWrapper(query);
QueryWrapper<T> queryWrapper = this.handleQueryWrapper(query);
// 设置排序
this.sort(queryWrapper, sortQuery);
List<T> entityList = baseMapper.selectList(queryWrapper);
@@ -248,19 +248,14 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseDO,
}
/**
* 封装查询条件
* 处理查询条件
*
* @return 查询条件封装对象
* @return QueryWrapper
*/
protected QueryWrapper<T> handleQueryWrapper(Q query) {
QueryWrapper<T> queryWrapper = new QueryWrapper<>();
// 没有查询条件,直接返回
if (null == query) {
return queryWrapper;
}
// 解析并拼接查询条件
queryFields.forEach(field -> QueryHelper.buildWrapper(query, field, queryWrapper));
return queryWrapper;
return QueryWrapperHelper.build(query, queryFields, queryWrapper);
}
/**