Mybatis-puls自定义查询扩展方案

前言

当前很多开源的低代码平台框架多对mybatis-puls进行了查询的封装使得使用起来很方便如:jeecg-boot, bladex等通过前后端协商约定基于mybatis-pulus 构造QueryWrapper能够快速实现各种条件的查询。但是存在局限性因为mybatis-pulus 的基础查询方法目前只支持单表查询,这对一些复杂的查询场景而言就比较难以支持必须自己写mapper 组织组织查询条件。
那有没有一种可以关联表的工具也支持LambdaWrapper? 答案是存在的做连接查询的插件Mybatis-plus-join使用和mybatis-pulus基本类似,但是也是局限于实体对象的连表查询,这里不做过度介绍有兴趣的可自行度。 这里主要通过一种自定义的方式动态组装查询条件。

实现原理

实现mybatis-puls 拦截器
@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;
	}
注入拦截器到MybatisPlusInterceptor
@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
  1. superQueryParams: 查询条件参数urlencode之后的字符串, urldecode结构为:
[{"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 查询的值多个值“,”号分割

  1. superQueryMatchType: 条件组合类型支持 and 和 or

  2. column: 排序字段

  3. order:排序规则 desc,asc

  4. pageNo,pageSize 分页查询需要传递

后端开发查询接口

Controller

    @GetMapping(value = "/list")
    public Result<?> queryPageList(SwaQueryParam swaQueryParam) {
        Page<ReportAccountDeliveryPlan> page = new Page<ReportAccountDeliveryPlan>(swaQueryParam.getPageNo(), swaQueryParam.getPageSize());
        IPage<ReportAccountDeliveryPlan> pageList = accountDeliveryService.pageQueryParam(page,swaQueryParam);
        return Result.OK(pageList);
    }

Service
省略

Mapper

IPage<ReportAccountDeliveryPlan> pageQueryParam(Page<ReportAccountDeliveryPlan> page, SwaQueryParam swaQueryParam);

mapper.xml

	<select id="pageQueryParam" resultType="com.sandalwood.report.modules.delivery.entity.ReportAccountDeliveryPlan">
	  select * from report_account_delivery_plan
	</select>

上述即可完成 页面传递动态参数的高级查询。
关键点就在于我们可以通过 mapper 文件自定义复杂的sql 查询获得一个查询视图 ,在查询视图的基础上可实现动态的查询条件拼接 ,这就避免了很多重复的if else 条件的 拼接判断 在 mapper文件中。

上面的例子中的sql 只是简单的 select * from a,实际的应该过程中 select a.* ,b.* from a ,b 慎重更加复杂的查询多是没有问题的

实战记录

页面操作
在这里插入图片描述

后台输出
在这里插入图片描述

注意事项

  1. 查询反的属性 自定义的别名不要使用 驼峰 如果 as userName, 正确的为 as user_name .mybtais 会自动转换user_name 到 userName 的对象属性上。

  2. 对查询性能要求比较好的查询还是不建议采用这方式,通常列表分页查询返回数据量少可采用。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值