江日念, 許 錕, 林 霞
(中國石油勘探開發研究院 計算機應用技術研究所,北京 100083)
數據列表(報表)作為一種數據集中展示方式,在信息系統中充當著重要的角色,是系統重要的組成部分. 用戶的需求有很大部分來源于對數據展示的需求.傳統的開發方式是逐一為數據列表編碼,為滿足特定需求逐一開發界面,開發出來的是靜態數據列表. 靜態數據列表存在以下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) 數據范圍控制
對擁有不同權限的用戶來說,同一個數據列表應該有不同的數據范圍,以人員管理為例:公司總裁可以查詢公司所有人員信息,部門經理只能查詢本部門人員信息.
針對傳統靜態數據列表的不足,本文提出一種動態數據查詢技術,以滿足信息系統動態數據列表需求.
XML是一種數據交換格式,它以一種開放的自我描述方式定義數據結構,在描述數據內容的同時能突出對結構的描述,從而體現數據之間的關系. XML具有擴展性好、結構性好、與平臺無關的特點,它提供了統一的方法來描述和交換獨立于應用程序或供應商的結構化數據.
之所以選擇XML配置數據列表正是因為其靈活、可定制的結構性和自描述性. 其結構性可以顯著降低數據列表配置的門檻,能夠使得管理人員和業務人員都可以參與到數據列表的配置開發. 同時,XML的自描述性能夠讓其既作為數據源,又可以作為一種設計文檔,它正好彌補了傳統設計文檔和實現之間不一致性,因為XML所做的修改會及時反映到實現中,時刻保持著與實現同步. 其作為模型存儲、數據存儲的文件,已經得到了廣泛的應用[9-13].
Digester是Apache的一個組件,Digester包可以配置一個從XML文件到Java對象映射[14]. 其底層采用SAX來解析XML文件. Digester維持了一個對象棧,可以看作對象轉換平臺,用來存放轉換中生成的、或是為轉換臨時創建的Java對象. 由于Digester屏蔽了底層實現細節,使用者只需關注操作本身,大大簡化了轉換操作. 使用Digester的注解模式,通過建立與XML內容相互映射的JavaBean來解析XML. 為了簡化使用,它通過匹配模式來定位要解析的XML標簽.示例如下:


每個標簽與相應的匹配模式對應如表1 (僅列出部分):

表1 標簽匹配模式
如果將XML文件結構視為一棵樹的話,那么每個標簽的匹配模式就是從根元素到這個元素的路徑,除了使用具體的標簽,還可以使用通配符.
使用匹配模式可以很方便地定位需要處理的元素,規則在匹配模式被找到時起作用. 所有的規則都是從org.apache.commons.digester.Rule派生的. 可通過@ObjectCreate、@SetProperty、@BeanProperty-Setter等注解和Digester解析功能就可以將節點queryContext,query,column分別映射到類Query-Context,Query,Column上.
dhtmlxGrid是一個靈活、智能、容易使用的JavaScript表格控件,它允許使用者以Ajax交互方式實現表格,使其具有單元格編輯、固定多表頭、固定多表尾、列寬可變、列可排序、列可拖動、凍結列等功能. 其豐富的功能足以滿足用戶對數據列表的各種展示需求.
dhtmlxGrid控件支持XML、JSON,官方網站提供了詳細的英文API文檔. 為了更好、更方便地在動態數據查詢技術中應用dhtmlxGrid控件,本文使用已改進的dhtmlxGrid控件,并對其進行二次封裝,二次封裝的控件以下簡稱CommonGrid.
動態數據查詢的設計理念是:將數據列表抽象化,分為表級別配置與列級別配置,并以XML保存該配置;系統初始化后加Apache Digester讀取XML配置并對象化緩存到內存中,緩存時分為Product模式和Debug模型,Product模式不會監聽XML配置的變化,而Debug模式將動態監聽XML配置并及時更新緩存.后端動態查詢引擎根據前端發送請求,動態獲取配置和數據,返回到前端,前端CommonGrid組件對數據進行展示和渲染,總體框架如圖1.

圖1 動態數據查詢框架
動態數據查詢框架基于Spring Boot實現,分為Web層、業務邏輯層、數據訪問層、實體層,各層之間相互銜接,且明確地完成了自身職責. ① 前端頁面創建CommonGrid表格控件,加載查詢條件,向服務器發送數據加載、查詢請求,Web獲取請求參數并查詢緩存的XML配置. ② 調用業務邏輯層的動態查詢引擎處理,進行配置解析,將配置解析成可執行的查詢操作. ③ 調用持久化方法,執行查詢操作. ④ 查詢結果映射到JavaBean對象. ⑤ 服務器處理完請求,將數據、分頁、列等信息以JSON格式傳送前端CommonGrid處理.
一個基本的數據列表是由數據列、數據行組成的.同時,數據列表還附帶著一些功能,比如列表分頁、數據查詢、數據導出、合并行、合并列、多表頭、多表尾、拖拽列等. 通過對數據列表的分析和抽象,以及結合CommonGrid的特點,構建數據列表模型如圖2所示. 其模型的構建步驟如下:
(1) 列表抽象:是對整個列表的整體抽象,對應的是Query類,用于配置列表的全局屬性. 其屬性包括表的ID、表名、初始化分頁信息(是否允許分頁、每頁記錄數)、凍結列數、初始化排序信息. 為了獲取數據,根據不同的數據獲取方式,需要配置對應的SQL或者映射數據指向的className. 列表中也配置了些冗余屬性,如用于緩存數據列信息的columnList,用于緩存調用方法信息的callList等.
(2) 數據列抽象:對數據列的抽象,對應Column類,用于配置列屬性,是數據列表中非常重要的部分.前端組件CommonGrid根據數據列配置動態渲染列,其屬性配置如表2所示.

圖2 數據列表模型(XML結構抽象)

表2 數據列Column的屬性
(3) 數據行抽象:從后臺獲取的數據行抽象,對應Row類,包括兩個屬性:行ID和對象數組. 數據行的集合構成了數據列表的數據,并以JSON格式返回給前端組件.
(4) 執行方法抽象:為了靈活定制表格組件在初始化前、初始化中、初始化后等不同的時間節點要執行的前端方法,定義了BeforeCall、Call、AfterCall類,類提供了方法名和參數兩個屬性,CommonGrid組件在對應時間節點獲取這些在后臺組裝好BeforeInit、CallList、AfterInit的的鉤子方法列表并執行.
(5) 查詢條件抽象:用于接收前端查詢交互時的信息,對應QueryCondition類,包括的信息有分頁信息pageInfo、查詢條件信息contionMap、查詢配置主鍵queryId等. 在數據列表首次加載時,從后端緩存的XML配置中獲取信息并組裝. 非首次查詢時,由前端實時參數和前端緩存中獲取,如果兩者均存在,優先從前端實時參數中獲取,并更新到前端緩存. 數據列表的其他需要交互的實時參數,如通用導出的Excel表信息也可以在QueryCondtion類中定義,如表名sheetName,表頭標題sheetTitle.
對前端而言,動態查詢引擎就是個黑盒,只要接收參數,動態查詢引擎就以一定的格式向客戶端輸出滿足條件的JSON數據. 圖3為其工作流程示意圖.

圖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格式發送給客戶端.
基于該動態查詢引擎實現的動態列表實現了組合查詢定制、數據列自定義、數據列渲染、數據權限控制等常見用戶自定義數據列表需求,下面以人事信息系統為例,說明其應用效果.
需求:針對用戶對數據列表查詢條件頻繁變更的需求,動態查詢引擎僅需要在前端頁面添加或者修改相應的控件,而無須修改后端代碼,也無須對系統進行重啟.
實現:將查詢條件配置在查詢框中,也可以配置在列頭,查詢引擎解析查詢條件,自動獲取并展示. 如圖4所示.

圖4 組合查詢定制(人事系統)
需求:用戶可自定義數據列的顯示與隱藏,只顯示自己關注的列.
實現:如圖5所示,用戶使用拖拽自定義列的顯示與隱藏并保存到數據庫中,查詢引擎將XML配置與用戶自定義進行比對,從而決定顯示哪些數據列.

圖5 自定義數據列
需求:用戶需要和數據列表進行交互,比如按鈕列、選擇列等,同時需要將部分原始數據翻譯成用戶可看懂的列,比如將人員狀態字段的“0”、“1”翻譯成對應的“在職”、“離職”,對長文本而言,還需要截斷,在鼠標懸停時顯示全部文本等.
實現:在XML配置列通過render屬性或者fnRender配置回調函數,支持以下類型:
(1) render (type=eq) 固定值的翻譯;
(2) render (type=window) 彈出窗體;
(3) render (type=link) 超鏈接;
(4) tooltip 鼠標懸停提示;
(5) fnRender 渲染回調函數. 前端CommonGrid解析配置,按需進行數據渲染.
需求:不同權限的用戶針對同一數據列表擁有不同的數據范圍,通過系統配置實現,避免類似的代碼開發多次,這樣可以提升系統可維護性.
實現:在系統后臺的權限管理中動態配置數據權限,查詢引擎解析配置,組合到查詢條件中,形成對應權限的數據范圍,如圖6所示.

圖6 配置數據范圍
除以上比較典型的功能外,動態查詢還集成了通用導出、凍結列、自定義多表頭等.
本文在Spring Boot框架下提出一個動態數據查詢技術,從技術實現的總體框架、數據列表抽象與解析、動態查詢引擎三個方面闡述了其設計和實現細節.并從開發者和用戶對動態數據列表的典型需求考慮,選擇組合查詢定制、數據列自定義、數據列渲染、數據范圍控制展示了系統的實現效果. 該技術已經應用于某機構的多個系統中,在人事系統中的應用效果證明,它能極大地提高開發效率、降低開發門檻、提高數據列表的擴展性和可維護性. 該技術具有一定的推廣性和實用性,在企業信息系統研發中,進一步結合代碼生成器使用,只需編寫一個實體即可快速完成一個數據列表的增刪改查功能,且可以靈活擴展.