当前很多开源的低代码平台框架多对mybatis-puls进行了查询的封装使得使用起来很方便如:jeecg-boot, bladex等通过前后端协商约定基于mybatis-pulus 构造QueryWrapper能够快速实现各种条件的查询。但是存在局限性因为mybatis-pulus 的基础查询方法目前只支持单表查询,这对一些复杂的查询场景而言就比较难以支持必须自己写mapper 组织组织查询条件。
那有没有一种可以关联表的工具也支持LambdaWrapper? 答案是存在的做连接查询的插件Mybatis-plus-join使用和mybatis-pulus基本类似,但是也是局限于实体对象的连表查询,这里不做过度介绍有兴趣的可自行度。 这里主要通过一种自定义的方式动态组装查询条件。
@Log4j2
public class SwaQueryInterceptor implements InnerInterceptor {
/****
* 获取指定的条件参数 这里是我们自定义的参数对象 SwaQueryParam
* @param parameterObject
* @return
*/
public static Optional<SwaQueryParam> findSwaQueryParam(Object parameterObject) {
if (parameterObject != null) {
if (parameterObject instanceof Map) {
Map<?, ?> parameterMap = (Map<?, ?>) parameterObject;
for (Map.Entry entry : parameterMap.entrySet()) {
if (entry.getValue() != null && entry.getValue() instanceof SwaQueryParam) {
return Optional.of((SwaQueryParam) entry.getValue());
}
}
} else if (parameterObject instanceof SwaQueryParam) {
return Optional.of((SwaQueryParam) parameterObject);
}
}
return Optional.empty();
}
/***
* 子执行sql 语句之前对 sql 语句进行改造
*/
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
//step1. 查询是否存在需要构建高级查询的参数 ,如果没有就直接返回
SwaQueryParam swaQueryParam = findSwaQueryParam(parameter).orElse(null);
// 没有参数直接返回
if (null == swaQueryParam) {
return;
}
//step2. 获取到当前需要执行的sql
String buildSql = boundSql.getSql();
//step3. 循环参数 拼接查询条件
if (ObjectUtil.isNotEmpty(swaQueryParam.getSuperQueryParams())) {
//因为get请求传递了复杂的参数,所以需要encode ,这里需要解码为 json 格式
String superQueryParams = URLDecoder.decode(swaQueryParam.getSuperQueryParams(), CharsetUtil.CHARSET_UTF_8);
List<QueryCondition> conditions = JSON.parseArray(superQueryParams, QueryCondition.class);
if (conditions != null) {
List<String> conditionList = conditions.stream()
.map(this::generatorFilter).filter(x->{
return StringUtils.isNotEmpty(x);
}).collect(Collectors.toList());
//拼接的条件,这里是关键 把sql 语句包一层 ,这样就不需要关心是 查询那张表的哪个字段
String whereSql = StringUtils.join(conditionList, swaQueryParam.getSuperQueryMatchType()+" ");
buildSql = String.format("select temp.* from ( %s ) as temp where %s ", buildSql,whereSql);
}
}
//排序sql
if(StringUtils.isNotEmpty(swaQueryParam.getColumn())) {
buildSql = String.format("%s order by %s %s",buildSql, oConvertUtils.camelToUnderline(swaQueryParam.getColumn()),StringUtils.defaultString(swaQueryParam.getOrder(), "desc"));
}
//修改完成的sql 再设置回去
PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
mpBoundSql.sql(buildSql);
}
/****
* 拼接查询条件
*
* @param condition
* @return
*/
public String generatorFilter(QueryCondition condition) {
//其中一个为空就返回空
if (ObjectUtil.isEmpty(condition.getField())
|| ObjectUtil.isEmpty(condition.getRule())
|| ObjectUtil.isEmpty(condition.getVal())) {
return null;
}
boolean isString = true;
//value
Object value = condition.getVal();
if("date".equals(condition.getType())){
value = "STR_TO_DATE('"+condition.getVal()+"','%Y-%m-%d')";
isString = false;
}else if("datetime".equals(condition.getType())){
value = "STR_TO_DATE('"+condition.getVal()+"',''%Y-%m-%d %H:%i:%s'')";
isString = false;
}
//QueryRuleEnum
QueryRuleEnum rule = QueryRuleEnum.getByValue(condition.getRule());
String name = oConvertUtils.camelToUnderline(condition.getField());
String sql = QueryGenerator.getSingleSqlByRule(rule, name, value, isString);
return sql;
}
QueryGenerator.getSingleSqlByRule 的实现,具体可以根据的规则调整 ,主要是构造一个 name like %xxx% 的sql 查询条件。如果是对sql 注入要求较高的,这里可以可以添加sql注入的校验。
public static String getSingleSqlByRule(QueryRuleEnum rule,String field,Object value,boolean isString, String dataBaseType) {
String res = "";
switch (rule) {
case GT:
res =field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
break;
case GE:
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
break;
case LT:
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
break;
case LE:
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
break;
case EQ:
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
break;
case EQ_WITH_ADD:
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
break;
case NE:
res = field+" <> "+getFieldConditionValue(value, isString, dataBaseType);
break;
case IN:
res = field + " in "+getInConditionValue(value, isString);
break;
case LIKE:
res = field + " like "+getLikeConditionValue(value);
break;
case LEFT_LIKE:
res = field + " like "+getLikeConditionValue(value);
break;
case RIGHT_LIKE:
res = field + " like "+getLikeConditionValue(value);
break;
default:
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
break;
}
return res;
}
@Configuration
@MapperScan(value={"xxx.**.mapper*","xxx.**.mapper*"})
public class MybatisPlusConfig {
private static final List<String> tenantTable = new ArrayList<String>();
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
@Override
public Expression getTenantId() {
String tenant_id = oConvertUtils.getString(TenantContext.getTenant(),"0");
return new LongValue(tenant_id);
}
// 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
@Override
public boolean ignoreTable(String tableName) {
if(tenantTable.contains(tableName)){
return false;
}
return true;
}
}));
//自定义高级查询
interceptor.addInnerInterceptor(new SwaQueryInterceptor());
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
@Bean
public ISqlInjector getSqlInjector() {
return new CustomizedSqlInjector();
}
}
interceptor.addInnerInterceptor(new SwaQueryInterceptor()); 注意要在分页拦截器之后。
这里以jeecg-boot在不改变原有前端参数的情况如果扩张查询支持多表的自定义高级查询为例。
注意:这里只是提供一种解决方案不局限于任何平台和框架。
查询查询通常是get请求 ,通过queryParam 参数传递
superQueryParams: %5B%7B%22rule%22:%22like%22,%22type%22:%22string%22,%22val%22:%22EXCEL%22,%22field%22:%22deliveryType%22%7D%5D
superQueryMatchType: and
column: createTime
order: desc
pageNo: 1
pageSize: 10
[{"rule":"like","type":"string","val":"a","field":"accountName"},{"rule":"like","type":"string","val":"a","field":"saleOwner"}]
其中rule 为:>,>=,<=,< like 等 ,type: 为string ,date ,datetime, number. field 查询的字段名称 ,val 查询的值多个值“,”号分割
superQueryMatchType: 条件组合类型支持 and 和 or
column: 排序字段
order:排序规则 desc,asc
pageNo,pageSize 分页查询需要传递
Controller
@GetMapping(value = "/list")
public Result> queryPageList(SwaQueryParam swaQueryParam) {
Page page = new Page(swaQueryParam.getPageNo(), swaQueryParam.getPageSize());
IPage pageList = accountDeliveryService.pageQueryParam(page,swaQueryParam);
return Result.OK(pageList);
}
Service
省略
Mapper
IPage pageQueryParam(Page page, SwaQueryParam swaQueryParam);
mapper.xml
上述即可完成 页面传递动态参数的高级查询。
关键点就在于我们可以通过 mapper 文件自定义复杂的sql 查询获得一个查询视图 ,在查询视图的基础上可实现动态的查询条件拼接 ,这就避免了很多重复的if else 条件的 拼接判断 在 mapper文件中。
上面的例子中的sql 只是简单的 select * from a,实际的应该过程中 select a.* ,b.* from a ,b 慎重更加复杂的查询多是没有问题的
查询反的属性 自定义的别名不要使用 驼峰 如果 as userName, 正确的为 as user_name .mybtais 会自动转换user_name 到 userName 的对象属性上。
对查询性能要求比较好的查询还是不建议采用这方式,通常列表分页查询返回数据量少可采用。