数据列表(报表)作为一种数据集中展示方式, 在信息系统中充当着重要的角色, 是系统重要的组成部分. 用户的需求有很大部分来源于对数据展示的需求. 传统的开发方式是逐一为数据列表编码, 为满足特定需求逐一开发界面, 开发出来的是静态数据列表. 静态数据列表存在以下3方面的缺点.
(1) 可重用性低, 开发效率低. 即便列表高度相似, 也必须单独实现, 代码、界面无法重用, 开发人员把大量时间浪费在相似代码的编写、调试上, 无法专注于核心业务逻辑的设计与实现, 导致开发效率不高.
(2) 可扩展性低, 部署能力弱. 用户在系统上线使用后, 往往都会有对列表展示、列表查询变更的需求, 此时要进行调整就比较麻烦, 必须由开发人员修改源码, 甚至重新发布系统.
(3) 开发门槛高, 参与程度低. 传统方式要求人员具备一定的开发能力, 所以在开发和后期运维调整过程中, 只能全程由开发人员参与, 将业务人员隔绝在开发之外, 不能充分参与开发与运维, 导致人力资源的浪费.
为了解决数据列表中的上述问题, 陈传波等提出一种Web报表模型, 通过Crystal Reports引擎和ADO.NET对报表底层接口进行控制, 实现报表自定义[1]. 金雨等人把报表分解为若干结构单元, 建立了通用报表模型, 并在Dephi开发环境下阐述了其设计和研发过程[2]. 为了进一步提升报表的柔性, 部分研究的工作基于XML来展开. 高红艳通过一种可视化的报表设计器来解决报表样式复杂的问题, 并提出一种基于XML的无插件方案来解决报表数据问题[3]. 卢笑天等设计了基于XML的可定制查询报表系统, 满足报表查询条件和查询字段的动态可定制, 同时提供了可拖动可视化的定制界面[4]. 符云清等利用Excel设计自定义报表, 程序读取上传的Excel并解析为HTML文件和XML文件, 系统根据Excel中配置的字段生成SQL, 实现报表格式和数据分离[5]. 汤加等利用XML实现动态创建数据表, 并在此基础上配置数据源及其字段与报表单元格之间的关系, 系统将据此自动拼装出相应的SQL, 从而快速生成各类基于单数据源的报表[6].
上述的部分文献通过XML实现报表的灵活定制, 但从开发者和用户的角度看, 尚不能完全满足以下四个数据列表典型需求:
(1) 组合查询定制
开发者可以很方便地增加或者修改查询条件, 而尽可能少的修改系统, 最好不要修改后台代码, 从而减少因重新部署导致系统停机风险, 同时降低开发工作量, 提升效率, 这方面已经有许多相关工作[7,8].
(2) 数据列自定义
用户可以对不关注的数据列设置隐藏, 尤其在数据列过多的情况下, 通过自定义列的显示与隐藏, 突显用户关注的信息, 屏蔽不重要信息, 提升用户体验.
(3) 数据列渲染
一个功能完备的数据列表, 不局限于数据的展示和查询, 还应该具备交互功能, 比如按钮列、选择列、超链接列、状态列等, 这些列需要通过表格组件的渲染将原始数据“翻译”并呈现在用户面前, 为用户提供交互功能. 对开发者来说, 可配置的列渲染大大节省数据列表开发工作量.
(4) 数据范围控制
对拥有不同权限的用户来说, 同一个数据列表应该有不同的数据范围, 以人员管理为例: 公司总裁可以查询公司所有人员信息, 部门经理只能查询本部门人员信息.
针对传统静态数据列表的不足, 本文提出一种动态数据查询技术, 以满足信息系统动态数据列表需求.
1 相关技术简介 1.1 XML技术XML是一种数据交换格式, 它以一种开放的自我描述方式定义数据结构, 在描述数据内容的同时能突出对结构的描述, 从而体现数据之间的关系. XML具有扩展性好、结构性好、与平台无关的特点, 它提供了统一的方法来描述和交换独立于应用程序或供应商的结构化数据.
之所以选择XML配置数据列表正是因为其灵活、可定制的结构性和自描述性. 其结构性可以显著降低数据列表配置的门槛, 能够使得管理人员和业务人员都可以参与到数据列表的配置开发. 同时, XML的自描述性能够让其既作为数据源, 又可以作为一种设计文档, 它正好弥补了传统设计文档和实现之间不一致性, 因为XML所做的修改会及时反映到实现中, 时刻保持着与实现同步. 其作为模型存储、数据存储的文件, 已经得到了广泛的应用[9–13].
1.2 Apache Digester 解析XML技术Digester是Apache的一个组件, Digester包可以配置一个从XML文件到Java对象映射[14]. 其底层采用SAX来解析XML文件. Digester维持了一个对象栈, 可以看作对象转换平台, 用来存放转换中生成的、或是为转换临时创建的Java对象. 由于Digester屏蔽了底层实现细节, 使用者只需关注操作本身, 大大简化了转换操作. 使用Digester的注解模式, 通过建立与XML内容相互映射的JavaBean来解析XML. 为了简化使用, 它通过匹配模式来定位要解析的XML标签. 示例如下:
<?xml version="1.0" encoding="UTF-8"?>
<queryContext>
<query>
<column id='name' name='姓名' />
<column id='id' name='单位' />
…
</query>
</queryContext>
</xml>
每个标签与相应的匹配模式对应如表1 (仅列出部分):
如果将XML文件结构视为一棵树的话, 那么每个标签的匹配模式就是从根元素到这个元素的路径, 除了使用具体的标签, 还可以使用通配符.
使用匹配模式可以很方便地定位需要处理的元素, 规则在匹配模式被找到时起作用. 所有的规则都是从org.apache.commons.digester.Rule派生的. 可通过@ObjectCreate、@SetProperty、@BeanPropertySetter等注解和Digester解析功能就可以将节点queryContext, query, column分别映射到类QueryContext, Query, Column上.
1.3 dhtmlxGrid控件dhtmlxGrid是一个灵活、智能、容易使用的JavaScript表格控件, 它允许使用者以Ajax交互方式实现表格, 使其具有单元格编辑、固定多表头、固定多表尾、列宽可变、列可排序、列可拖动、冻结列等功能. 其丰富的功能足以满足用户对数据列表的各种展示需求.
dhtmlxGrid控件支持XML、JSON,官方网站提供了详细的英文API文档. 为了更好、更方便地在动态数据查询技术中应用dhtmlxGrid控件, 本文使用已改进的dhtmlxGrid控件, 并对其进行二次封装, 二次封装的控件以下简称CommonGrid.
2 动态数据查询设计与实现 2.1 总体框架动态数据查询的设计理念是: 将数据列表抽象化, 分为表级别配置与列级别配置, 并以XML保存该配置; 系统初始化后加Apache Digester读取XML配置并对象化缓存到内存中, 缓存时分为Product模式和Debug模型, Product模式不会监听XML配置的变化, 而Debug模式将动态监听XML配置并及时更新缓存. 后端动态查询引擎根据前端发送请求, 动态获取配置和数据, 返回到前端, 前端CommonGrid组件对数据进行展示和渲染, 总体框架如图1.
动态数据查询框架基于Spring Boot实现, 分为Web层、业务逻辑层、数据访问层、实体层, 各层之间相互衔接, 且明确地完成了自身职责. ① 前端页面创建CommonGrid表格控件, 加载查询条件, 向服务器发送数据加载、查询请求, Web获取请求参数并查询缓存的XML配置. ② 调用业务逻辑层的动态查询引擎处理, 进行配置解析, 将配置解析成可执行的查询操作. ③ 调用持久化方法, 执行查询操作. ④ 查询结果映射到JavaBean对象. ⑤ 服务器处理完请求, 将数据、分页、列等信息以JSON格式传送前端CommonGrid处理.
2.2 数据列表抽象与解析一个基本的数据列表是由数据列、数据行组成的. 同时, 数据列表还附带着一些功能, 比如列表分页、数据查询、数据导出、合并行、合并列、多表头、多表尾、拖拽列等. 通过对数据列表的分析和抽象, 以及结合CommonGrid的特点, 构建数据列表模型如图2所示. 其模型的构建步骤如下:
(1) 列表抽象: 是对整个列表的整体抽象, 对应的是Query类, 用于配置列表的全局属性. 其属性包括表的ID、表名、初始化分页信息(是否允许分页、每页记录数)、冻结列数、初始化排序信息. 为了获取数据, 根据不同的数据获取方式, 需要配置对应的SQL或者映射数据指向的className. 列表中也配置了些冗余属性, 如用于缓存数据列信息的columnList, 用于缓存调用方法信息的callList等.
(2) 数据列抽象: 对数据列的抽象, 对应Column类, 用于配置列属性, 是数据列表中非常重要的部分. 前端组件CommonGrid根据数据列配置动态渲染列, 其属性配置如表2所示.
(3) 数据行抽象: 从后台获取的数据行抽象, 对应Row类, 包括两个属性: 行ID和对象数组. 数据行的集合构成了数据列表的数据, 并以JSON格式返回给前端组件.
(4) 执行方法抽象: 为了灵活定制表格组件在初始化前、初始化中、初始化后等不同的时间节点要执行的前端方法, 定义了BeforeCall、Call、AfterCall类, 类提供了方法名和参数两个属性, CommonGrid组件在对应时间节点获取这些在后台组装好BeforeInit、CallList、AfterInit的的钩子方法列表并执行.
(5) 查询条件抽象: 用于接收前端查询交互时的信息, 对应QueryCondition类, 包括的信息有分页信息pageInfo、查询条件信息contionMap、查询配置主键queryId等. 在数据列表首次加载时, 从后端缓存的XML配置中获取信息并组装. 非首次查询时, 由前端实时参数和前端缓存中获取, 如果两者均存在, 优先从前端实时参数中获取, 并更新到前端缓存. 数据列表的其他需要交互的实时参数, 如通用导出的Excel表信息也可以在QueryCondtion类中定义, 如表名sheetName, 表头标题sheetTitle.
2.3 动态查询引擎对前端而言, 动态查询引擎就是个黑盒, 只要接收参数, 动态查询引擎就以一定的格式向客户端输出满足条件的JSON数据. 图3为其工作流程示意图.
动态查询引擎支持三种查询方式: SQL、HQL、接口. SQL查询用于对多表关联的复杂查询, HQL常用于单表对象化查询, 接口查询是在SQL和HQL无法满足需求情况下推荐的方式, 比如条件参数的获取需要复杂的业务逻辑计算才能得到. 这三种方式都需要查询条件、分页信息、排序信息作为输入、查询数据和配置作为输出. 在界面首次加载时, 分页信息和排序信息从XML配置中读取, 而当用户发送查询请求、换页请求、排序请求时, 分页信息和排序信息由客户端传参而来.
下面以SQL查询为例, 说明动态查询引擎的具体工作过程:
(1) 组装分页信息: 如果客户端传入分页信息, 则优先读取客户端分页信息, 否则读取XML配置中的分页信息;
(2) 组装查询条件: 获取客户端传入的查询条件, 逐一遍历查询条件, 若传入值为空, 则跳过. 否则将获取的查询条件名、查询操作符(between、or、like、in、eq、not_eq等)、传入值在引擎中组装, 直至遍历结束;
(3) 组装排序信息: 如果客户端传入排序信息, 则读取客户端排序信息, 否则读取XML配置的排序信息, 并将排序组装到SQL中;
(4) 组装数据范围条件: 在权限管理中, 获取用户对列表的授权配置, 并组装到SQL中;
(5) 查询执行: 组装的两个查询SQL, 一个是获取数据的SQL, 传入的参数为查询条件, 分页信息; 一个是获取数据总记录数的SQL, 传入参数为查询条件;
(6) 数据对象化、格式化: 通过反射将数据映射到JavaBean对象, 并按照XML配置格式化, 生成CommonGrid控件能够接收的JSON格式的数据列表;
(7) 拼接列属性: 形成CommonGrid要执行的方法列表. 将各列的配置属性, 按照方法名和参数拼接起来, 形成CommonGrid可执行的方法名和列表;
(8) 数据渲染与展示: 将分页信息、数据列表、CommonGrid执行方法列表以JSON格式发送给客户端.
3 应用效果基于该动态查询引擎实现的动态列表实现了组合查询定制、数据列自定义、数据列渲染、数据权限控制等常见用户自定义数据列表需求, 下面以人事信息系统为例, 说明其应用效果.
3.1 组合查询定制需求: 针对用户对数据列表查询条件频繁变更的需求, 动态查询引擎仅需要在前端页面添加或者修改相应的控件, 而无须修改后端代码, 也无须对系统进行重启.
实现: 将查询条件配置在查询框中, 也可以配置在列头, 查询引擎解析查询条件, 自动获取并展示. 如图4所示.
3.2 数据列自定义
需求: 用户可自定义数据列的显示与隐藏, 只显示自己关注的列.
实现: 如图5所示, 用户使用拖拽自定义列的显示与隐藏并保存到数据库中, 查询引擎将XML配置与用户自定义进行比对, 从而决定显示哪些数据列.
3.3 数据列渲染
需求: 用户需要和数据列表进行交互, 比如按钮列、选择列等, 同时需要将部分原始数据翻译成用户可看懂的列, 比如将人员状态字段的“0”、“1”翻译成对应的“在职”、“离职”, 对长文本而言, 还需要截断, 在鼠标悬停时显示全部文本等.
实现: 在XML配置列通过render属性或者fnRender配置回调函数, 支持以下类型:
(1) render (type=eq) 固定值的翻译;
(2) render (type=window) 弹出窗体;
(3) render (type=link) 超链接;
(4) tooltip 鼠标悬停提示;
(5) fnRender 渲染回调函数. 前端CommonGrid解析配置, 按需进行数据渲染.
3.4 数据范围控制需求: 不同权限的用户针对同一数据列表拥有不同的数据范围, 通过系统配置实现, 避免类似的代码开发多次, 这样可以提升系统可维护性.
实现: 在系统后台的权限管理中动态配置数据权限, 查询引擎解析配置, 组合到查询条件中, 形成对应权限的数据范围, 如图6所示.
除以上比较典型的功能外, 动态查询还集成了通用导出、冻结列、自定义多表头等.
4 结论与展望本文在Spring Boot框架下提出一个动态数据查询技术, 从技术实现的总体框架、数据列表抽象与解析、动态查询引擎三个方面阐述了其设计和实现细节. 并从开发者和用户对动态数据列表的典型需求考虑, 选择组合查询定制、数据列自定义、数据列渲染、数据范围控制展示了系统的实现效果. 该技术已经应用于某机构的多个系统中, 在人事系统中的应用效果证明, 它能极大地提高开发效率、降低开发门槛、提高数据列表的扩展性和可维护性. 该技术具有一定的推广性和实用性, 在企业信息系统研发中, 进一步结合代码生成器使用, 只需编写一个实体即可快速完成一个数据列表的增删改查功能, 且可以灵活扩展.
[1] |
陈传波, 黄刚, 刘清慧. 一种基于ASP.NET的自定义报表的设计与实现. 计算机工程与科学, 2006, 28(6): 112-114. DOI:10.3969/j.issn.1007-130X.2006.06.037 |
[2] |
金雨, 张旭堂, 刘文剑. 用户自定义通用报表打印的设计与实现. 计算机应用与软件, 2008, 25(3): 132-134. DOI:10.3969/j.issn.1000-386X.2008.03.049 |
[3] |
高红艳. Web报表开发中的若干关键技术研究与应用[硕士学位论文]. 西安: 西安电子科技大学, 2011.
|
[4] |
卢笑天, 唐慧佳. 基于XML的可定制查询报表系统的设计与应用. 计算机工程与设计, 2014, 35(5): 1847-1852. DOI:10.3969/j.issn.1000-7024.2014.05.067 |
[5] |
符云清, 肖文婷, 廖希, 等. 基于Excel和XML的自定义报表设计与实现. 计算机科学, 2013, 40(7S): 147-149, 163. |
[6] |
汤加, 符云清, 万煊民. 一种基于单数据源的可视化自定义报表模型. 计算机科学, 2017, 44(5): 184-188. DOI:10.11896/j.issn.1002-137X.2017.05.033 |
[7] |
唐伟, 施永香, 文巨峰. 基于.NET的通用查询组件的开发. 计算机工程与设计, 2006, 27(14): 2708-2710. DOI:10.3969/j.issn.1000-7024.2006.14.064 |
[8] |
邵雨舟. 应用系统中通用组合查询功能的实现方法. 电脑知识与技术, 2018, 14(20): 7-9. |
[9] |
信俊昌, 王国仁, 李国徽, 等. 数据模型及其发展历程. 软件学报, 2019, 30(1): 142-163. |
[10] |
陈佳铭, 王风立, 邓君湘, 等. 基于XML语言的导弹防御系统HSTPN博弈模型存储与加载. 计算机应用与软件, 2018, 35(12): 12-15. |
[11] |
王永娜, 赵奎, 王鸿亮, 等. 针对异构协议的动态解析器模型. 计算机系统应用, 2017, 26(1): 251-254. DOI:10.15888/j.cnki.csa.005533 |
[12] |
张文宇, 许明健, 薛昱. 论spring的零配置与XML配置. 计算机系统应用, 2015, 24(2): 270-275. DOI:10.3969/j.issn.1003-3254.2015.02.052 |
[13] |
Nassiri H, Machkour M, Hachimi M. One query to retrieve XML and Relational Data. Procedia Computer Science, 2018, 134: 340-345. DOI:10.1016/j.procs.2018.07.201 |
[14] |
徐秀华, 汪诚波, 毕硕本. Digester解析XML文档研究. 计算机系统应用, 2005(1): 86-88. DOI:10.3969/j.issn.1003-3254.2005.01.027 |