jsqlparser(六):TablesNamesFinder 深度解析與 SQL 格式化實現

在數據庫應用開發中,SQL語句的解析和處理是一項常見而重要的任務。本文將深入探討 JSQLParser 中的 TablesNamesFinder 類,分析其核心原理、與 AST 訪問接口(CCJSqlParserVisitor )的關系、使用場景,并通過實際代碼示例展示如何基于 TablesNamesFinder 實現 SQL 語句的格式化處理。

一、JSQLParser AST 訪問機制概述

JSQLParser 采用訪問者模式(Visitor Pattern)來處理解析后的 SQL 抽象語法樹(AST)。在這個設計中,有幾個核心組件:

1.1 核心訪問接口

CCJSqlParserVisitor 是 JSQLParser 中定義的一個關鍵接口,它是 SQL 抽象語法樹(AST)的訪問接口,為訪問各種 SQL 語法元素提供了統一的方法。此外,JSQLParser 還提供了一系列更具體的訪問接口:

  • StatementVisitor:用于訪問不同類型的 SQL 語句(如 SELECT、UPDATE、DELETE 等)
  • ExpressionVisitor:用于訪問各種表達式(如條件表達式、算術表達式等)
  • SelectVisitor:專門用于訪問 SELECT 語句的各個部分
  • FromItemVisitor:用于訪問 FROM 子句中的各種表引用
  • SelectItemVisitor:用于訪問 SELECT 列表中的各個項目
  • ItemsListVisitor:用于訪問項目列表(如 IN 表達式中的值列表)

1.2 訪問者實現體系

為了簡化開發,JSQLParser 提供了多個適配器類,實現了上述接口的所有方法(通常為空實現),開發者可以繼承這些適配器,只重寫自己關心的方法。

二、TablesNamesFinder 核心原理

TablesNamesFinder 是 JSQLParser 庫中提供的一個實用工具類,它的核心功能是遍歷解析后的 SQL 語句或子句對象(Select,Where…)的所有節點并收集其中引用的所有表名,并提供通過重寫訪問者方法來擴展或定制 SQL 處理邏輯的能力。

2.1 類的繼承關系

TablesNamesFinder 通過繼承一系列適配器類來實現其功能,而不是直接實現 CCJSqlParserVisitor 接口。它的主要繼承路徑為:

// TablesNamesFinder 繼承了多個訪問者接口,用于處理不同類型的 SQL 元素
StatementVisitor, ExpressionVisitor, SelectVisitor, FromItemVisitor, SelectItemVisitor, ItemsListVisitor <-- TablesNamesFinder

2.2 核心功能

  1. 表名收集:自動遍歷 SQL 語句的各個部分,收集所有引用的表名到內部集合中
  2. 可配置性:通過 init() 方法可以配置是否處理表的別名
  3. 便捷訪問:提供 getTableList() 等方法,方便獲取收集到的表名信息
  4. 可擴展性:允許子類重寫特定的 visit 方法,實現自定義的 SQL 處理邏輯

三、TablesNamesFinder 與 CCJSqlParserVisitor 的關系與區別

特性TablesNamesFinderCCJSqlParserVisitor
類型具體工具類SQL抽象語法樹(AST)核心接口
設計目的用于收集SQL語句中的表名,
更重要的是提供了定制化的處理能力
定義訪問SQL抽象語法樹的標準方法
接口實現通過實現不同節點的訪問者接口,
遍歷所有的SQL語法元素(比如 Table,Column,Expression)
頂層接口,定義訪問各種SQL語法原始元素的方法
使用場景提取SQL中使用的表、分析表依賴關系作為實現自定義SQL處理邏輯的基礎接口
執行階段在SQL解析為語法對象上執行SQL解析階段被調用

四、TablesNamesFinder 使用場景

  • SQL 表依賴分析

通過 TablesNamesFinder 可以快速提取 SQL 語句中引用的所有表名,用于分析 SQL 的表依賴關系,這在數據庫遷移、表結構變更影響分析等場景中非常有用。

  • SQL 安全性檢查

可以基于 TablesNamesFinder 構建安全檢查器,識別 SQL 中是否引用了敏感表,或是否包含未授權訪問的表。

  • SQL 格式化和規范化

通過擴展 TablesNamesFinder,可以實現 SQL 語句的格式化和規范化,例如為列名添加表名前綴、標準化表別名等。

  • SQL 重構和轉換

基于 TablesNamesFinder 可以實現 SQL 的重構和轉換,如自動添加 WHERE 條件、替換表名等。

五、基于 TablesNamesFinder 實現 SQL 格式化

下面,我們通過一個實際的代碼示例來展示如何基于 TablesNamesFinder 實現 SQL 語句的格式化。

5.1 代碼實現

以下是一個名為 CanonicalColumnVisitor 的內部類,它繼承自 TablesNamesFinder,用于為 SQL 語句中的列添加表名前綴或別名:

/*** 規范化列訪問器,繼承自 TablesNamesFinder,用于為 SQL 語句中的列添加表名前綴或別名。* 該訪問器會遍歷 SQL 語句中的各個部分,包括 WHERE 子句、JOIN 條件、排序和分組等,* 為缺少表名前綴的列添加指定的表名或其別名,同時為表添加合適的別名。* 在遍歷過程中,還會收集關聯的表信息。*/
private static class CanonicalColumnVisitor extends TablesNamesFinder {private final String tablename;private final Map<String,FromItem> associatedTables = new HashMap<>();private final Function<String,String> aliaFunction = asAliasFunction(associatedTables);/*** 構造CanonicalColumnVisitor實例,關聯表映射使用null值,適用于對完整SQL語句的處理* @param tablename 表名,用于為列名添加表名前綴*/CanonicalColumnVisitor(String tablename) {this(tablename, null);}/*** 構造CanonicalColumnVisitor實例* @param tablename 表名,用于為列名添加表名前綴* @param joinedTables 已連接的表映射,鍵為表名,值為對應的FromItem對象*/CanonicalColumnVisitor(String tablename, Map<String,FromItem> joinedTables) {this.tablename = tablename;if(null != joinedTables && !joinedTables.isEmpty()){this.associatedTables.putAll(joinedTables);}init(true);}@Overridepublic void visit(Column column) {/** 為列名增加表名前綴,優先使用表的別名 */Table table = column.getTable();if (!isNullOrEmpty(tablename)) {if (null == table) {String aliasName = aliaFunction.apply(tablename);column.setTable(new Table(null != aliasName ? aliasName : tablename));}}if (null != table) {Alias alias = table.getAlias();if (null == alias) {String aliasName = aliaFunction.apply(table.getName());if (null != aliasName && !aliasName.equals(table.getName())) {alias = new Alias(aliasName);table.setAlias(alias);}}}super.visit(column);}@Overridepublic void visit(PlainSelect plainSelect) {doVisitForCollectAssociatedTable(associatedTables, plainSelect.getFromItem(), plainSelect.getJoins());super.visit(plainSelect);// 處理JOIN條件、WHERE子句、ORDER BY和GROUP BY等部分if (plainSelect.getJoins() != null) {for (Join join : plainSelect.getJoins()) {for(Expression exp: join.getOnExpressions()) {exp.accept(this);}}}if (plainSelect.getWhere() != null) {plainSelect.getWhere().accept(this);}if (plainSelect.getOrderByElements() != null) {for (OrderByElement item : plainSelect.getOrderByElements()) {item.getExpression().accept(this);}}if (plainSelect.getGroupBy() != null) {plainSelect.getGroupBy().getGroupByExpressionList().accept(this);}}// 其他SQL語句類型的visit方法實現...@Overridepublic void visit(Update update) {doVisitForCollectAssociatedTable(associatedTables, update.getTable(), update.getJoins());super.visit(update);// 處理UPDATE語句的更新列和表達式update.getUpdateSets().forEach(us -> {us.getColumns().forEach(c -> c.accept(this));us.getExpressions().forEach(e -> e.accept(this));});}// Delete、Upsert等其他語句類型的visit方法實現...
}

5.2 關鍵輔助方法

該實現中使用了幾個關鍵的輔助方法:

5.2.1 asAliasFunction 方法
/*** 創建一個用于獲取表別名的函數* 1. 根據輸入的表名,從已JOIN表映射中查找對應的FromItem對象* 2. 若找到FromItem且有別名,則返回別名* 3. 若找到FromItem但無別名,則返回原表名* 4. 若未找到FromItem,則返回null*/
private static Function<String, String> asAliasFunction(Map<String, FromItem> joinedTables) {class AliasFunction implements Function<String, String> {private final Map<String, FromItem> joinedTables;AliasFunction(Map<String, FromItem> joinedTables) {this.joinedTables = null == joinedTables ? Collections.emptyMap() : joinedTables;}@Overridepublic String apply(String name) {FromItem fromItem = null == name ? null : joinedTables.get(name);if (null == fromItem) {return null;}Alias alias = fromItem.getAlias();return (null == alias || alias.getName() == null) ? name : alias.getName();}// hashCode、equals和toString方法實現...}return new AliasFunction(joinedTables);
}
5.2.2 doVisitForCollectAssociatedTable 方法
/*** 遍歷FromItem和JOIN子句,將其中的表信息添加到已連接表映射中*/
private static void doVisitForCollectAssociatedTable(Map<String, FromItem> joinedTables, FromItem fromItem, List<Join> joins) {if(null != fromItem) {joinedTables.put(tablenameOrAliasOf(fromItem), fromItem);}if(null != joins){joins.stream().map(j -> j.getRightItem()).forEach(i->joinedTables.put(tablenameOrAliasOf(i), i));}
}
5.2.3 tablenameOrAliasOf 方法
/*** 獲取 FromItem 對象對應的表名或別名*/
private static String tablenameOrAliasOf(FromItem fromItem) {if(null == fromItem) {return null;}if(fromItem instanceof Table) {return ((Table)fromItem).getName();}Alias alias = fromItem.getAlias();return (null == alias) ? null : alias.getName();
}

5.3 使用示例

以下是如何使用 CanonicalColumnVisitor 來規范化 SQL 語句的示例:

/*** 規范化SQL語句對應的Statement對象* 解析傳入的SQL語句,獲取對應的Statement對象,并使用CanonicalColumnVisitor對其進行訪問,* 為沒有指定表名的字段名自動加上 tablename 指定的表名前綴*/
private static Statement normalizeStatement(String tablename, String sql) throws JSQLParserException {Statement statement = parseStatement(sql);return normalizeStatement(tablename, statement);
}/*** 規范化SQL語句對應的Statement對象* 使用CanonicalColumnVisitor對傳入的Statement對象進行訪問,* 為沒有指定表名的字段名自動加上 tablename 指定的表名前綴*/
private static Statement normalizeStatement(String tablename, Statement statement) throws JSQLParserException {statement.accept(new CanonicalColumnVisitor(tablename));return statement;
}/*** 解析 SQL 語句字符串并返回對應的 Statement 對象*/
private static Statement parseStatement(String sql) throws JSQLParserException {// 解析SQL語句的實現return ParserSupport.parse0(sql, null, null).statement;
}/*** 規范化SQL字符串*/
private static String normalizeSql(String tablename, String sql) {if(isNullOrEmpty(sql)){return sql;}try {return normalizeStatement(tablename, sql).toString();} catch (JSQLParserException e) {// 解析SQL語句失敗,不做任何處理返回原值return sql;}
}

六、實際應用場景分析

6.1 SQL 格式化與規范化

在多表聯合查詢中,為每個列添加表名前綴可以避免列名歧義,提高 SQL 語句的可讀性和可維護性。通過 CanonicalColumnVisitor,我們可以自動為 SQL 語句中的列添加表名前綴。

輸入輸出示例

輸入:

SELECT id, name FROM user WHERE age > 18 ORDER BY create_time;

輸出(使用 user 作為表名前綴):

SELECT user.id, user.name FROM user WHERE user.age > 18 ORDER BY user.create_time;

6.2 SQL 安全增強

基于 TablesNamesFinder,我們可以實現 SQL 安全增強功能,如自動為 SQL 添加訪問控制條件:

/*** 為 SQL 語句添加訪問限制條件*/
private static Statement addLimitForStatement(Statement statement, String joinClause, String limitExpression) {statement.accept(new TablesNamesFinder(){{init(true);}@Overridepublic void visit(PlainSelect plainSelect) {super.visit(plainSelect);doVisitForAddLimit(limitExpression, joinClause, plainSelect::getWhere, plainSelect::getJoins, plainSelect::setWhere, plainSelect::setJoins);}@Overridepublic void visit(Delete delete) {super.visit(delete);doVisitForAddLimit(limitExpression, joinClause, delete::getWhere, delete::getJoins, delete::setWhere, delete::setJoins);}@Overridepublic void visit(Update update) {super.visit(update);doVisitForAddLimit(limitExpression, joinClause, update::getWhere, update::getJoins, update::setWhere, update::setJoins);}});return statement;
}

七、總結

TablesNamesFinder 是 JSQLParser 提供的一個強大工具類,通過繼承和擴展它,我們可以實現各種復雜的 SQL 處理功能。本文通過實際代碼示例,展示了如何基于 TablesNamesFinder 實現 SQL 的格式化和規范化處理。

相對于通用的 CCJSqlParserVisitorTablesNamesFinder 使用更加簡單便捷,特別適合于需要對 SQL 表引用進行分析和處理的場景。

在實際應用中,我們可以根據具體需求,進一步擴展 TablesNamesFinder,實現更復雜的 SQL 處理邏輯,如 SQL 重構、SQL 優化建議、SQL 安全檢查等功能。

jsqlparser系列文章

《jsqlparser(一):基于抽象語法樹(AST)遍歷SQL語句的語法元素》
《jsqlparser(二):實現基于SQL語法分析的SQL注入攻擊檢查》
《jsqlparser(三):基于語法分析實現SQL中的CAST函數替換》
《jsqlparser(四):實現MySQL 函數DATE_FORMAT到Phoenix函數TO_CHAR的替換》
《jsqlparser(五):修改語法定義(JSqlParserCC.jjt)實現UPSERT支持Phoenix語法ON DUPLICATE KEY IGNORE》
《jsqlparser(六):TablesNamesFinder 深度解析與 SQL 格式化實現》

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/94994.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/94994.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/94994.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python訓練營打卡Day49-神經網絡調參指南

知識點回顧&#xff1a;隨機種子內參的初始化神經網絡調參指南 參數的分類調參的順序各部分參數的調整心得 作業&#xff1a;對于day41的簡單cnn&#xff0c;看看是否可以借助調參指南進一步提高精度。 隨機種子 import torch import torch.nn as nn# 定義簡單的線性模型&…

Elasticsearch 常用任務管理命令及實戰應用

常用任務管理命令 列出所有任務 curl -X GET "http://<es_host>:<es_port>/_tasks?detailedtrue&pretty" -H Content-Type: application/json獲取特定類型的任務 curl -X GET "http://<es_host>:<es_port>/_tasks?actions<act…

Java試題-選擇題(26)

Java試題-選擇題(26) 題目 下列有關Thread的描述,哪個是正確的 ? A:啟動一個線程的方法是:thread. run() B:結束一個線程的通常做法是:thread. stop() C:將一個線程標記成daemon線程,意味著當主線程結束,并且沒有其它正在運行的非daemon線程時,該daemon線程也會自…

緩存的原理、引入及設計

開篇寄語&#xff1a;緩存&#xff0c;你真的用對了嗎&#xff1f; 我們為什么要學習緩存呢&#xff1f;有必要學習緩存嗎&#xff1f; 緩存的使用&#xff0c;是提升系統性能、改善用戶體驗的唯一解決之道。 其實&#xff0c;作為互聯網公司&#xff0c;只要有直接面對用戶的業…

單片機如何控制模數轉換芯片

一、介紹單片機控制模數轉換&#xff08;ADC&#xff09;芯片的核心是通過通信接口發送控制指令&#xff0c;并讀取轉換后的數字信號&#xff0c;本質是“指令交互數據傳輸”的協同過程&#xff0c;具體實現需分4步完成&#xff0c;關鍵在于接口匹配和時序同步。二、核心1. 先明…

【Proteus仿真】開關控制系列仿真——開關控制LED/撥碼開關二進制計數/開關和繼電器控制燈滅

目錄 0案例視頻效果展示 0.1例子1&#xff1a;開關控制LED燈亮滅 0.2例子2&#xff1a;數碼管顯示撥碼開關二進制計數(000~255) 0.3例子3&#xff1a;開關和繼電器控制燈亮滅 1基礎知識補充 1.1 74LS245雙總線收發器 1.1.1 引腳及功能 1.1.2應用場景 1.1.3真值表 1.2…

Q1 Top IF 18.7 | 基于泛基因組揭示植物NLR進化

文章DOI: 10.1016/j.chom.2025.07.011 標題&#xff1a;Pangenomic context reveals the extent of intraspecific plant NLR evolution 期刊&#xff1a;Cell Hose & Microbe (https://i-blog.csdnimg.cn/direct/0e31f86b94d348b0a1adb084ec4e49b7.png)(https://i-blog.cs…

技術干貨|Prometheus PromQL查詢語言之聚合操作內置函數

聚合操作 Prometheus還提供了下列內置的聚合操作符,這些操作符作用域瞬時向量。可以將瞬時表達式返回的樣本數據進行聚合,形成一個新的時間序列。 sum (求和) min (最小值) max (最大值) avg (平均值) stddev (標準差) stdvar (標準差異) count (計數) count_values …

Redis 哨兵(Sentinel)全面解析

在2025年的數字化浪潮中&#xff0c;想象這樣一個場景&#xff1a;凌晨3點&#xff0c;電商平臺流量突然暴增&#xff0c;主Redis服務器因硬件故障突然宕機。幾年前&#xff0c;這意味著緊急電話、慌亂的運維人員和不可避免的業務中斷。而今天&#xff0c;用戶甚至沒有察覺任何…

【數學史冷知識】關于行列式的發展史

學習的途中會遇到一些有意思的東西&#xff0c;我想著做一個專欄《艾薩克紀行簡報》&#xff0c;專門寫這些知識發展歷史。可以讓您從繁忙的學習生活中放松&#xff0c;添些耀彩。行列式和微積分一樣&#xff0c;都是兩個人獨立發現的。而且還都有萊布尼茨。1683 年&#xff0c…

【python】python進階——生成器

目錄 一、生成器介紹 1.1 生成器與迭代器的關系 1.2 生成器與return比較 二、創建生成器 方法1: 生成器函數 方法2: 生成器表達式 三、生成器的實際應用場景 3.1 處理大型文件 3.2 生成無限序列 3.3 數據管道處理 四、生成器的高級用法 4.1 使用send()方法傳遞值 …

【Pytorch】生成對抗網絡實戰

GAN框架基于兩個模型的競爭&#xff0c;Generator生成器和Discriminator鑒別器。生成器生成假圖像&#xff0c;鑒別器則嘗試從假圖像中識別真實的圖像。作為這種競爭的結果&#xff0c;生成器將生成更好看的假圖像&#xff0c;而鑒別器將更好地識別它們。 目錄 創建數據集 定…

Java基礎第7天總結(代碼塊、內部類、函數式編程)

代碼塊靜態代碼塊&#xff1a;有static修飾&#xff0c;屬于類&#xff0c;與類一起優先加載&#xff0c;自動執行一次實例代碼塊&#xff1a;無static修飾&#xff0c;屬于對象&#xff0c;每次創建對象時&#xff0c;都會優先執行一次。package com.itheima.code;import java…

文獻綜述寫作指南:從海量文獻到邏輯閉環的實戰模板

文獻綜述往往是學術寫作的“第一關難題”&#xff1a;面對成百上千篇文獻&#xff0c;如何避免“簡單羅列”的陷阱&#xff0c;梳理出有邏輯、有洞見的論述體系&#xff1f;本文結合學術寫作實踐&#xff0c;總結出一套模塊化的文獻綜述“實戰模板”&#xff0c;通過結構化方法…

CuTe C++ 簡介01,從示例開始

這里先僅僅關注 C 層的介紹&#xff0c;python DSL 以后再說。在 ubuntu 22.04 X64 中&#xff0c;RTX 50801. 環境搭建1.1 安裝 cuda1.2 下載源碼git clone https://github.com/NVIDIA/cutlass.git1.3 編譯mkdir build/ cmake .. -DCUTLASS_NVCC_ARCHS"120" -DCMAK…

Python實現異步多線程Web服務器:從原理到實踐

目錄Python實現異步多線程Web服務器&#xff1a;從原理到實踐引言第一章&#xff1a;Web服務器基礎1.1 Web服務器的工作原理1.2 HTTP協議簡介1.3 同步 vs 異步 vs 多線程第二章&#xff1a;Python異步編程基礎2.1 異步I/O概念2.2 協程與async/await2.3 事件循環第三章&#xff…

Deep Think with Confidence:llm如何進行高效率COT推理優化

1. 引言:大模型的推理解碼優化 大型語言模型(LLM)在處理數學、編碼等復雜推理任務時,一種強大但“耗能巨大”的技術是self-consistency,也稱并行思考(parallel thinking)。其核心思想是讓模型對同一個問題生成多條不同的“思考路徑”(reasoning traces),然后通過多數…

vscode克隆遠程代碼步驟

一、直接使用VsCode1.復制git的https鏈接代碼2.在vscode中點擊 代碼管理-克隆倉庫3.粘貼&#xff08;在git里面復制的https鏈接&#xff09;4.選擇需要存儲的文件位置5.確認6.代碼克隆成功二、使用命令行克隆1.確定文件放置位置&#xff0c;右鍵2.復制git的https鏈接代碼3.粘貼…

spi總線

一、介紹SPI總線&#xff08;Serial Peripheral Interface&#xff0c;串行外設接口&#xff09;是一種高速全雙工同步串行通信總線&#xff0c;核心通過“主從架構同步時鐘”實現設備間數據傳輸&#xff0c;因結構簡單、速率高&#xff0c;廣泛用于MCU與傳感器、存儲芯片、顯示…

COLA:大型語言模型高效微調的革命性框架

本文由「大千AI助手」原創發布&#xff0c;專注用真話講AI&#xff0c;回歸技術本質。拒絕神話或妖魔化。搜索「大千AI助手」關注我&#xff0c;一起撕掉過度包裝&#xff0c;學習真實的AI技術&#xff01; 1 COLA技術概述 COLA&#xff08;Chain of LoRA&#xff09;是一種創…