本人想作一個cms查詢框架,用於解決實際的業務問題,順便鍛鍊下能力 #1 背景介紹算法
在一個配置管理系統即cms系統中,有不少的實體,實體間有不少的關聯關係,這些實體就是構建成了一張網。以下圖所示: sql
目前面臨的問題是,如何輕鬆應對其餘用戶對實體的各類各樣的查詢需求?數據庫
#2 需求整理json
站在用戶的角度來分析需求緩存
##2.1 用戶的輸入服務器
###2.1.1 用戶要查詢的數據框架
###2.1.2 查詢條件線程
各類各樣的查詢條件,如debug
如 =、 !=、>、<、like、in、exists 等查詢條件,時間段查詢等
查詢條件間的and or 關係,以及多層條件嵌套設計
如 a>2 and (b>3 or c<4)
##2.2 查詢輸出
###2.2.1 字段對應的值的格式化
如某個表的type字段的值爲0或1,須要將這些0或1轉化成有意義的值,如 0表示online 1表示offline
###2.2.2 返回的數據形式的格式化
sql查詢的結果是平鋪的形式,然而返回給用戶的但願是一個格式良好的形式,因此要求必須具有格式化的功能。
目前可能的格式化形式以下所示:
形式1 a下的全部的b(外層主體內容是a,而後包含一個b的集合)
{ "aName":"name1", "bs":[ { "bName":"name2" }, { "bName":"name3" } ] }
形式2 b的信息(外層主體是b,而後包含一個a的信息)
{ "bName":"name2", "a":{ "aName":"name1" } }
形式3 a下全部的b、c(外層主體是a,而後包含一個b的集合,也包含一個c的集合)
{ "aName":"name1", "bs":[ { "bName":"name2" }, { "bName":"name3" } ], "cs":[ { "cName":"name4" }, { "cName":"name5" } ] }
#3 大概的設計
1 用戶查詢封裝成QueryBody
2 對QueryBody進行解析,解析成sql
3 根據sql查詢出對應的結果
4 對sql查詢的結果進行值的格式化和形式的格式化,返回滿意的結果
##3.1 用戶查詢封裝成QueryBody
QueryBody就是配置用戶需求的地方。它的來源有兩種方式,分別以下:
方式1 用戶配置QueryBody的一些參數直接進行http請求
這種方式的狀況下,用戶須要瞭解QueryBody的配置意義,同時用戶能夠自行進行各類各樣的查詢
方式2 根據用戶的查詢需求,在後臺配置QueryBody的參數,並映射對應的key,而後讓用戶拿着key來進行查詢,以下
{ "key1":{QueryBody1的配置}, "key2":{QueryBody2的配置} }
用戶拿着key1來查詢,即咱們使用key1對應的QueryBody配置來進行查詢。這種方式,用戶不須要關心QueryBody的配置,但只能按照咱們後臺所配置的參數進行查詢了。方式1就不須要維護信息,方式2就須要維護key對應的QueryBody的配置信息
以上的這兩種方式均可能會出現,因此都要支持。
##3.2 對QueryBody進行解析,解析成sql
這一部分其實就是一個sql生成器。這裏須要說明的是,咱們並非去設計一個複雜的sql生成器,而是針對cms系統常見的查詢操做可以生成sql就好了。因此不要期望這個查詢框架能自動幫你生成複雜的sql。 可是有不少地方要注意:
###3.2.1 對解析要進行緩存
方便下一次相同的請求直接跳過解析過程,加快搜索。然而一旦服務器關閉,緩存就消失,因此是否要考慮將解析緩存持久化起來,在服務器啓動的時候,就去先加載解析緩存。
同時容許清空緩存等操做
###3.2.2 普通查詢sql的幾個要素
普通sql以下所示:
select 表1.column_a,表1.column_b,表2.column_c,表3.column_d from 表1 表2 表3 where 表1.column_e>4 and 表2.column_f='test'
主要分紅三大部分:
第一部分 要查詢的column
這一部分,可讓用戶本身輸入,還要進行下配置映射,避免對外暴漏數據庫中的表和字段名
上述方式通常不多,因此大部分的時候仍是,查詢表1 表2 表3 的所有有用字段的信息,因此須要在後臺配置哪些表的哪些字段須要對外暴漏。
上面的兩種方式也都是要支持的
第二部分 表之間的鏈接關係
一種方式就是,直接配置表與表之間的鏈接關係(簡單粗暴,可是會有不少的重複配置信息)
另外一種方式就是,只配置兩兩表之間的鏈接關係,經過一個針對圖的算法模塊來找到表之間的鏈接關係。如博客開頭的圖片中,算法模塊可以自動計算出entity1和entity4之間的鏈接關係是 entity1->entity2->entity3->entity4。這種方式大大減小了配置的冗餘度。
雖然算法這一塊是美好的,仍然會存在不少的問題。算法要找出最短路徑,可是最短路徑的鏈接關係不必定是用戶想要的,因此有時候又不能來依靠算法,還須要人爲的干預配置。如上圖中的entity1到entity7有2條途徑,算法只能給咱們推薦一個最短的路徑,但這並不必定是用戶想要的,因此在這個時候,咱們是須要配置使用哪條路徑的
第三部分 查詢參數部分
用戶的查詢條件就是一個json對象,咱們要把這個json對象轉化成sql中的where部分
用戶的查詢條件是各類各樣的,同時查詢條件是能夠嵌套的,以下兩種查詢條件
{ "a.name":"lg", "b.age@>":24 }
這裏就表示查詢 where a.name='lg' and b.age>24
{ "a.name":"lg", "$or":{ "b.age@>":24, "c.id@in":[1,2,3] } }
這裏就表示查詢 where a.name='lg' and (b.age>24 or c.id in (1,2,3) )
因此但願可以作到上述的查詢效果,這就須要設計一系列內置的查詢參數解析器,同時方便用戶自定義擴展查詢參數解析器,來支持更加複雜的查詢
##3.3 根據sql查詢出對應的結果
這一塊就須要藉助於如Spring的JdbcTemplate來執行相應的sql,或者藉助於其餘,這就須要思考如何更加方便的接入呢?
##3.4 對sql查詢的結果進行值的格式化和形式的格式化,返回滿意的結果
sql的查詢結果是平鋪的,這時候就須要進行聚合操做,聚合操做就須要依據外層實體的主鍵做爲聚合的重要依據了。
對數據進行格式化處理,就須要遍歷查詢結果,一一進行處理。而後返回給用戶,若是用戶還要進行相應的處理操做,又要遍歷一次,形成浪費,因此該框架還要支持用戶配置一些處理操做。
#4 還涉及的問題
##4.1 日誌
該框架應該不能定死所使用的日誌系統,因此須要採用slf4j這個統一的日誌接口
對於程序中哪部分的日誌採用debug級別,哪部分的日誌採用info級別須要仔細考量。簡單來講就是,程序的主要執行過程採用info級別,使得咱們可以迅速定位錯誤緣由就能夠了,而一些詳細的解析過程採用debug級別就夠了。
##4.2 配置文件的監控模塊
爲了避免用重啓服務器,就能方便的發佈新的API、或者改動老的API,就須要對這些配置文件進行監控。
由於這些各類各樣的配置文件會不少,因此就須要把監控單獨寫成一個模塊,方便外界隨意的添加監控項。
同時還須要監控的總開關和每一個監控項的子開關,來隨時關閉或者打開某個監控項。
##4.3 上下文環境Context
在解析的過程當中,用戶的查詢QueryBody,會在不少地方都要用到,若是都是做爲方法的參數傳遞來傳遞去,將很是難看和難以維護,很明顯這裏就須要用到ThreadLocal形式了,將相似QueryBody和一些對應的緩存存儲到綁定的對應的線程中,經過ThreadLocal對象來隨時隨地獲取這些數據。最好是弄一個上下文環境來封裝數據。
##4.4 異常處理
配置文件加載、解析產生的異常要進行規範的自定義處理
##4.5 集成與配置
如何更加方便的使用與配置這個cms查詢框架
#5 最終達成的效果
仍是如文章最上面的圖
1 用戶輸入以下:
{ "entites":["entity1","entity2s@listentity2","entity3s@listentity3"], "params":{ 這裏放置查詢參數 } }
則表明查詢的是全部的entity1,以及它所包含的entity2和entity3,返回的數據格式是以下形式的:
{ "entity1Name":"aaa", "entity2s":[ {"entity2Name":"xxx"}, {"entity2Name":"xxx"} ], "entity3s":[ {"entity3Name":"xxx"} ] }
2 若是用戶輸入以下:
{ "entites":["entity2","entity1@mapentity1"], "params":{ 這裏放置查詢參數 } }
則表示用戶想查詢entity2的信息,而且想知道每一個entity2所屬的entity1信息,是以下格式的:
{ "entity2Name":"aaa", "entity1":{ "entity1Name":"xxx" } }