一、背景
对于用户来讲查询功能按易用性分三个层次:
1. 最简单查询操作是一个输入框,全文检索,如百度,后台实现技术使用搜索引擎,需要设计和建立索引,技术较为复杂,适用于文档和信息数据库检索,但是结果很难精确控制。
2. 其次是定义字段查询,很多企业信息系统大多用的是这种查询,针对模块特定字段的查询有针对性、使用门坎低,适用于企业内部信息管理系统模块定制。
3. 最后一种是专门针对数据模型灵活的查询编辑器,使用难度最高,但是查询结果可以灵活和精确的控制,适用于有一定IT知识并对数据相当了解的用户,同时又可以避免直接将数据库暴露给用户带来的不安全隐患。
大家不难发现一个好的系统软件的查询基本会涵盖上述三种类型的查询功能
二、jeecg实现原理
Jeecg系统中模块主要使用第二种方式的查询功能,使用hibernate的QBC来封装前端查询条件,针对字段的定制过滤条件,最后转换为sql执行数据库查询。
现有方案优点:
1)默认无须复杂开发即可实现字段检索,支持范围、模糊、精确匹配查询
2)内置简单表达式支持:!*,实现非、模糊、数组等
现有方案缺点:
1)不支持or条件
2)不支持字段间操作field1=field2
3)不支持非hibernate关联表联合查询
4)单一字段条件只能出现一次
5)不支持sql嵌套
6)要支持上述功能需要额外开发定制工作
某些特定场景下,用户想要通过模块获得相关信息必须借助于第三方报表模块功能或求助于开发人员,无形中对报表模块开发带来一定压力。比如,用户要从模块中查询特殊的部分数据进行操作,现有查询功能无法做到,报表模块又不能操作编辑,这个时候就是高级查询器定制查询功能派上用场的时候了。
三、类似应用举例
我们来看看Outlook邮件查询设计:
1.全文检索型
2.字段定制型
3.高级查找
微软Team Foundation Server查询编辑器:
四、Jeecg查询器设计与实现
UI设计:首先要实现高级查询,必须要对数据表元信息metadata进行封装才可以通用化,我们利用datagrid标签中的columnList来自动生成字段下拉列表。作为高级查询,从易用的性和使用频率的角度功能所占UI比例不能太多,采用弹出窗口实现,如图:
这里的表格我使用了treegrid,因为更适合表达sql语法树,但没有实现树形结构,条件只加了大于、小于、包含等常用操作符,有待后续扩展sql嵌套。
右侧有查询历史,将每次查询的json条件保存在一个数组中实现快速重查,这个历史查询记录使用HTML5的localstorage保存在客户端缓存中,下次登录仍然有效,对于不支持localstorage的浏览器将使用cookie存储。
前后端交互:从到向前兼容性和对框架升级改动最小的因素考虑,将查询器组装的所有内容封装到一个json字符串,作为一个参数_sqlbuidler传递到后台,由后台控制条件组装,更加安全,防止sql注入等漏洞
json格式示例:
[{“id”:101,”field”:”user_name”,”condition”:”like”,”value”:”%王%”,”relation”:”and”},{“id”:101,”field”:”user_name”,”condition”:”like”,”value”:”%王%”,”relation”:”and”}]
后台封装处理java对象QueryCondition
package org.jeecgframework.web.demo.entity.test; import java.util.List; public class QueryCondition { String field; String type; String condition; String value; String relation; List<QueryCondition> children; public List<QueryCondition> getChildren() { return children; } public void setChildren(List<QueryCondition> children) { this.children = children; } public String getField() { return field; } public void setField(String field) { this.field = field; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getCondition() { return condition; } public void setCondition(String condition) { this.condition = condition; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public String getRelation() { return relation; } public void setRelation(String relation) { this.relation = relation; } public String toString(){ StringBuffer sb =new StringBuffer(); sb.append(this.relation).append(" "); sb.append(this.field).append(" ") .append(this.condition).append(" "); if("java.util.Date".equals(this.type)){ sb.append("to_date(‘").append(this.value).append("‘,‘yyyy-MM-dd‘)"); }else if("java.lang.Number".equals(this.type) ||this.condition.indexOf("in")>0){//TODO 需要按类型处理 sb.append(this.value); }else{ sb.append("‘").append(this.value).append("‘");//TODO 需要处理特殊字符 } return sb.toString(); } } 后台解析处理代码,修改org.jeecgframework.core.extend.hqlsearch.HqlGenerateUtil: public static void installHql(CriteriaQuery cq, Object searchObj, Map<String, String[]> parameterMap) { installHqlJoinAlias(cq, searchObj, getRuleMap(), parameterMap, ""); // --增加一个特殊sql参数处理---------------------------------- try{ if(StringUtil.isNotEmpty(parameterMap.get("_sqlbuilder"))){ List<QueryCondition> list = JSONHelper.toList( parameterMap.get("_sqlbuilder")[0] , QueryCondition.class); String sql=getSql(list,""); System.out.println("DEBUG sqlbuilder:"+sql); cq.add(Restrictions.sqlRestriction(sql)); } }catch(Exception e){ e.printStackTrace(); } // --增加一个特殊sql参数处理---------------------------------- cq.add(); }
五、实现约束
1)只支持标准命名的表名,因为是通过java驼峰命名转换带下划线的数据库表名,如果表名不是这个规则字段名会转换错误;
2)Sql前端界面在输入时并没有做太多约束和控制,因此非专业用户使用时会出现非法的语句而查询无果,建议在懂sql的人员指导下使用不要直接交给用户。
六、标签使用
Datagrid标签中已经对高级查询器做了封装,js变量也根据每个模块的id做了命名处理防止冲突。
使用时只需在datagrid标签加一个属性queryBuilder="true",例如
<t:datagrid name="jeecgDemoList" title="DEMO示例列表"
autoLoadData="true" actionUrl="jeecgDemoController.do?datagrid"
sortName="userName" fitColumns="true" idField="id" fit="true" queryMode="group" checkbox="true"
queryBuilder="true">
模块UI效果如下,重置后面会多一个按钮:
七、TODO
UI方面:易用性还可以提升,比如选中某字段和条件=时,值自动根据数据库表中该列实际值枚举一部分候选项;字段为date类型时,值编辑框变成date控件;字段为整形时,值编辑框有校验或只能输入整数的spinbox来防呆
功能:List<QueryCondition>对象转换为sql getSql()函数中仅仅实现拼装原生sql,这块还有很大的改进空间,可以增加字段类型(int,string,date)的识别和处理、操作符(正则匹配)、内置表达式和函数(类似TFS)等扩展。
安全:模块按钮还没有跟权限绑定,只是通过标签属性来开关,应该加一个动态权限由系统配置来控制