目錄
前言
一、問題重現
1、環境說明
2、重現步驟
3、錯誤信息
二、關于LATERAL
1、Lateral作用場景
2、在四至場景中使用
三、問題解決之道
1、源碼追蹤
2、關閉sql合并
3、改寫處理SQL
四、總結
前言
????????在博客:【寫在創作紀念日】基于SpringBoot和PostGIS的各省東西南北四至極點區縣可視化中,我們詳細而具體的講解了如何在SpringBoot中與PostGIS進行交互,實現省域的四至(東西南北)各區縣的空間檢索及可視化。后臺計算程序運行友好,功能也都還算正常,在地圖界面上點擊具體的省份、行政區劃等可以正常的查詢出東南西北四至點及所在的區縣信息,功能正常。
????????前臺看起來一切正常,但是在我們的后臺輸出日志當中,依然發現了一些不正常本地log日志。如下圖所示:
????????雖然報錯的信息對程序的功能并沒有實質的影響。但依然值得我們關注,一定是有地方執行有問題。本文即作為排查和解決報錯信息的教程,逐步來排查這個錯誤。通過對問題的重現,讓大家了解出現相關問題的場景,重現步驟等信息,為大家在實際項目中解決問題提供相關背景參考。接著講解在空間分析中Lateral關鍵字的作用,以及在省域四至問題求解中的具體使用,最后來講解如何在Mybatis-Plus中集成Druid時修復異常的信息,通過講解兩種解決辦法,讓大家了解如果在工項目中真實遇到了,可以有哪些解決辦法。
一、問題重現
????????本節將講解問題的出現場景,同時詳細列出本博客參考時使用的相關依賴的版本,也許新的依賴包已經將這些問題統統都解決。
1、環境說明
序號 | 依賴組件 | 版本 | 說明 |
1 | JDK | 1.8 | JDK依賴 |
2 | Mybatis-Plus | 3.5.2 | ORM操作 |
3 | druid | 1.2.11 | 數據庫連接池 |
2、重現步驟
????????在省域的四至所屬區縣分析中,在頁面的右側邊欄中,有全國的所有省份列表,通過點擊省份列表操作欄中的“定位”按鈕,可以實現對當前省份的四至所屬區縣求解。功能操作示意圖如下所示:
3、錯誤信息
????????這里將更多的后臺運行錯誤貼出來,看大家在實際的項目中是否也曾經遇到類似的問題。錯誤信息如下所示:
22:32:31.176 [http-nio-8080-exec-94] ERROR c.a.d.f.s.StatFilter - [mergeSql,150] - merge sql error, dbType postgresql, druid-1.2.11, sql : WITH temp_area AS ( xxx) SELECT direction,id,province_code,province_name,city_code,city_name,area_code, area_name,type, st_asgeojson(geom) geomJson, x lon,y lat FROM bounds_info
com.alibaba.druid.sql.parser.ParserException: syntax error, error in :'T,LATERAL ST_DumpPoints ( T.geom ) AS dp', expect ), actual (, pos 208, line 1, column 208, token (at com.alibaba.druid.sql.parser.SQLParser.printError(SQLParser.java:838)at com.alibaba.druid.sql.parser.SQLParser.accept(SQLParser.java:846)at com.alibaba.druid.sql.dialect.postgresql.parser.PGSelectParser.query(PGSelectParser.java:62)at com.alibaba.druid.sql.parser.SQLSelectParser.select(SQLSelectParser.java:61)at com.alibaba.druid.sql.parser.SQLStatementParser.parseWithQuery(SQLStatementParser.java:5458)at com.alibaba.druid.sql.dialect.postgresql.parser.PGSQLStatementParser.parseWith(PGSQLStatementParser.java:421)at com.alibaba.druid.sql.parser.SQLStatementParser.parseStatementList(SQLStatementParser.java:186)at com.alibaba.druid.sql.parser.SQLStatementParser.parseStatementList(SQLStatementParser.java:101)at com.alibaba.druid.sql.visitor.ParameterizedOutputVisitorUtils.parameterize(ParameterizedOutputVisitorUtils.java:163)at com.alibaba.druid.sql.visitor.ParameterizedOutputVisitorUtils.parameterize(ParameterizedOutputVisitorUtils.java:134)at com.alibaba.druid.filter.stat.StatFilter.mergeSql(StatFilter.java:148)at com.alibaba.druid.filter.stat.StatFilter.createSqlStat(StatFilter.java:672)at com.alibaba.druid.filter.stat.StatFilter.statementPrepareAfter(StatFilter.java:325)at com.alibaba.druid.filter.FilterEventAdapter.connection_prepareStatement(FilterEventAdapter.java:118)
????????可以很明顯的看到,報錯的信息是在一個統計的過濾器中所拋出來的,過濾器所在類是:com.alibaba.druid.filter.stat.StatFilter。本小節內容到此結束,對問題的暴露介紹到這里。下面將逐步來說明這些問題。
二、關于LATERAL
????????本節將介紹一下載PostGIS空間數據庫中的Lateral關鍵字,主要將介紹Lateral關鍵字的作用場景以及在省域四至所屬區縣的問題求解過程中的作用。
1、Lateral作用場景
????????在 PostgreSQL 中,LATERAL
關鍵字用于指定一個子查詢,該子查詢可以引用出現在 FROM
子句中前面的表。它允許子查詢訪問外部查詢中的列,這在需要根據外部查詢的行動態生成結果時非常有用。
主要作用
-
關聯子查詢:
LATERAL
子查詢可以引用FROM
子句中前面的表中的列。這使得子查詢可以根據外部查詢的行動態生成結果。 -
提高查詢靈活性:
LATERAL
允許在FROM
子句中使用動態的子查詢,使得查詢更加靈活,可以處理更復雜的數據關系。 -
優化性能:在某些情況下,使用
LATERAL
可以減少重復的計算,提高查詢效率。
典型應用場景
-
與表值函數結合使用:
LATERAL
常用于與表值函數(如unnest
、generate_series
等)結合,以便根據外部查詢的行生成動態的結果。 -
幾何函數:在 PostGIS 中,
LATERAL
常用于與幾何函數(如ST_DumpPoints
)結合,將幾何對象分解為點集合。 -
多對多關系:在處理多對多關系時,
LATERAL
可以動態生成中間表數據。
2、在四至場景中使用
????????先來看一下在省域四至所屬區縣的求解場景中的具體SQL實現:
WITH temp_area AS ( SELECT * FROM biz_area T WHERE T.province_code = '510000' ),
bounds_info AS (( SELECT '最東' AS direction,T.*,ST_X ( dp.geom ) AS x,ST_Y ( dp.geom ) AS y FROM temp_area T,LATERAL ST_DumpPoints ( T.geom ) AS dp ORDER BY x DESC LIMIT 1
) UNION ALL
( SELECT '最西' AS direction,T.*,ST_X ( dp.geom ) AS x,ST_Y ( dp.geom ) AS y
FROM temp_area T,LATERAL ST_DumpPoints ( T.geom ) AS dp ORDER BY x ASC LIMIT 1
) UNION ALL
( SELECT '最南' AS direction,T.*,ST_X ( dp.geom ) AS x,ST_Y ( dp.geom ) AS y
FROM temp_area T, LATERAL ST_DumpPoints ( T.geom ) AS dp ORDER BY y ASC LIMIT 1
) UNION ALL
(SELECT '最北' AS direction,T.*,ST_X ( dp.geom ) AS x,ST_Y ( dp.geom ) AS y
FROM temp_area T, LATERAL ST_DumpPoints ( T.geom ) AS dp ORDER BY y DESC LIMIT 1 ) ) SELECT direction,id,province_code,province_name,city_code,city_name,area_code,area_name,type, st_asgeojson(geom) geomJson, x lon,y lat FROM bounds_info;
????????在這個例子中,LATERAL
子查詢 dp
調用 ST_DumpPoints
函數,將幾何對象 T.geom
分解為點集合,并提取每個點的坐標信息。在求解具體的區縣信息時,會將曲線的Polygon數據轉變為點,才能在查詢結果中使用St_Y計算對應的經緯度值。 當然上面的這條sql完全是正常的,可以在客戶端中正常執行,完全可以在Navicat客戶端中進行調試。調試過程暫且不表,大家可以自行嘗試驗證。
三、問題解決之道
????????既然遇到了問題,雖然不會造成特別重大的影響,但是有運行強迫癥的我們還是要關注下,畢竟是Error,不是普通的程序運行日志。因此我們動手來解決一下遇到的問題。要解決問題,首先要明確出現的問題。這里主要分享兩個解決問題的辦法,從關閉SQL合并的設置到改寫處理SQL兩個方向來進行。
1、源碼追蹤
????????本著按圖索驥的原則,根據在控制臺中異常信息所在的代碼類,即:com.alibaba.druid.filter.stat.StatFilter中,異常拋出的行數是150行,所處理的程序邏輯如下:
????????源代碼如下所示:
public String mergeSql(String sql, DbType dbType) {if (!mergeSql) {return sql;}try {sql = ParameterizedOutputVisitorUtils.parameterize(sql, dbType, null, null, null);} catch (Exception e) {LOG.error("merge sql error, dbType " + dbType + ", druid-" + VERSION.getVersionNumber() + ", sql : " + sql, e);}return sql;
}
?????????從代碼上看,代碼不是很復雜,首先判斷要執行的SQL是否需要合并,如果不需要合并,直接返回執行SQL,否則對sql進行合并處理。當然,為了不影響后續的執行,如果轉換有問題,也對合并前的SQL原樣返回。而對于更復雜的處理,大家可以看下druid的處理方式,這里截個圖做個示例,還有一些沒截圖完整的,需要大家去了解。
2、關閉sql合并
????????了解了Druid的程序合并邏輯之后,就可以對陣下藥了。在前面的源碼追蹤內容中有介紹,如果當前的Druid應用程序設置了不需要合并sql,sql將直接原樣返回,不會做任何的處理。因此,如果不想在程序運行過程中發生異常,可以將合并SQL的開關關閉,這樣也能解決問題。在Druid中關閉sql合并的方法簡單,在Druid聲明數據庫連接池的地方關閉選項即可,代碼如下:
????????在這個配置文件中,把merge-sql:true的配置修改一下,改成false后再重新運行程序,同樣來點擊各省份,會發現在控制臺中已經沒有了合并sql的錯誤信息。控制臺窗口如下所示:
3、改寫處理SQL
????????在上面的例子中,我們選擇了關閉sql合并的設置,雖然能將問題暫時解決掉,但是對于其它需要合并的SQL就無法進行合并處理了。難道問題就無法解決了嗎?當然不是,這里我們將分享第二種方法,通過改寫處理SQL的方式來解決不關閉sql合并的問題。回到我們的查詢SQL中,可以發現,在我們的查詢需求中,我們需要求解的四至點位,其實最后都會變成一個點,即Limit 1。因此其實可以不使用Lateral的關鍵字,直接進行查詢,通過對查詢結果集排序后選擇第一條,依然是滿足我們的實際需求的,首先將查詢SQL做如下改寫,其它內容不變:
static final String FIND_ESWNAREA_BYPROVINCE_SQL = "<script>"
+ " WITH temp_area AS ( SELECT * FROM biz_area T WHERE T.province_code = #{province_code} ), "
+ " bounds_info AS ("
+ " ( SELECT '最東' AS direction,T.*,ST_X ( dp.geom ) AS x,ST_Y ( dp.geom ) AS y "
+ " FROM temp_area T,ST_DumpPoints ( T.geom ) AS dp ORDER BY x DESC LIMIT 1 "
+ " ) UNION "
+ " ( SELECT '最西' AS direction,T.*,ST_X ( dp.geom ) AS x,ST_Y ( dp.geom ) AS y "
+ " FROM temp_area T,ST_DumpPoints ( T.geom ) AS dp ORDER BY x ASC LIMIT 1 "
+ " ) UNION "
+ " ( SELECT '最南' AS direction,T.*,ST_X ( dp.geom ) AS x,ST_Y ( dp.geom ) AS y "
+ " FROM temp_area T,ST_DumpPoints ( T.geom ) AS dp ORDER BY y ASC LIMIT 1 "
+ " ) UNION "
+ " (SELECT '最北' AS direction,T.*,ST_X ( dp.geom ) AS x,ST_Y ( dp.geom ) AS y "
+ " FROM temp_area T,ST_DumpPoints ( T.geom ) AS dp ORDER BY y DESC LIMIT 1 ) "
+ " ) SELECT direction,id,province_code,province_name,city_code,city_name,area_code, "
+ " area_name,type, st_asgeojson(geom) geomJson, x lon,y lat FROM bounds_info "
+ "</script>";
????????將應用程序啟動后,再次點擊相關按鈕,程序控制成功無任何異常,如下所示:
????????頁面正常,且控制臺無任何異常輸出,成功解決以上問題。?
四、總結
????????以上就是本文的主要內容,本文即作為排查和解決報錯信息的教程,逐步來排查這個錯誤。通過對問題的重現,讓大家了解出現相關問題的場景,重現步驟等信息,為大家在實際項目中解決問題提供相關背景參考。接著講解在空間分析中Lateral關鍵字的作用,以及在省域四至問題求解中的具體使用,最后來講解如何在Mybatis-Plus中集成Druid時修復異常的信息,通過講解兩種解決辦法,讓大家了解如果在工項目中真實遇到了,可以有哪些解決辦法。
? ? ? ? 當然在實際的項目當中,選用哪種解決辦法,需要大家根據實際的需求來決定。需要大家知曉的是:當 mergeSql
設置為 true
時,Druid 會將相同的 SQL 語句進行合并,以減少統計信息的數量,這在性能統計和內存占用方面可能有所幫助,但可能會影響調試和統計信息的準確性。如果需要對某個特定的方法或場景禁用 SQL 合并處理,可以在該方法的調用環境中動態調整 Druid 的配置,或者通過其他方式(如使用不同的數據源)來實現。行文倉促,定有不足之處,歡迎各位朋友在評論區批評指正,不勝感激。