文章目錄
- 前言
- 本章節源碼
- 官方文檔信息
- 認識JSqlParser
- How it works? 它是如何工作的?
- 知識點
- 關于statement
- 實際應用場景
- 引入依賴
- Parser 解析SQL
- 解析sql語句
- 解析sql區分sql類型
- 分析增刪改查語句
- 查詢語句
- 認識PlainSelect
- 示范
- 新增語句
- 了解Insert常用方法
- 示范
- 更新語句
- 刪除語句
- 從SQL語句中提取表名
- 為SQL語句各個字段表達式添加別名
- 動態加字段加表達式加條件
- 增加一個表達式(字段 or 表達式)
- 增加一個Join
- SelectUtils構建一個SQL語句
- Building 構建SQL
- **創建一個簡單的select**
- **擴展簡單插入**
- **替換字符串值**
- **語句中字符串值的更一般的替換**
- 使用Java對象API方式構建sql語句(增刪改查sql)
- Analyzing 分析SQL
- 高級特性
- 認識Visitor
- 實際案例
- 自定義Visitor訪問node節點
- 驗證 SQL 語句
- 介紹
- 實際案例
- SQL驗證數據庫類型/版本
- **根據指定語法權限完成SQL驗證**
- 參考文章
- 資料獲取
前言
博主介紹:?目前全網粉絲4W+,csdn博客專家、Java領域優質創作者,博客之星、阿里云平臺優質作者、專注于Java后端技術領域。
涵蓋技術內容:Java后端、大數據、算法、分布式微服務、中間件、前端、運維等。
博主所有博客文件目錄索引:博客目錄索引(持續更新)
CSDN搜索:長路
視頻平臺:b站-Coder長路
本章節源碼
當前文檔配套相關源碼地址:
- gitee:https://gitee.com/changluJava/demo-exer/tree/master/java-sqlparser/demo-JSqlParser/demo-JSqlParser-demo
- github:https://github.com/changluya/Java-Demos/tree/master/java-sqlparser/demo-JSqlParser/demo-JSqlParser-demo
官方文檔信息
JSqlParser:https://github.com/JSQLParser/JSqlParser
官方網站:https://jsqlparser.github.io/JSqlParser/
快速了解和學習:https://github.com/JSQLParser/JSqlParser/wiki#
認識JSqlParser
JSqlParser is a SQL statement parser. It translates SQLs in a traversable hierarchy of Java classes. JSqlParser is not limited to one database but provides support for a lot of specials of Oracle, SqlServer, MySQL, PostgreSQL … To name some, it has support for Oracles join syntax using (+), PostgreSQLs cast syntax using ::, relational operators like != and so on. Then the result can be accessed in a structured way. The generated Java class hierarchy can be navigated using the Visitor Pattern.
JSqlParser是一個SQL語句解析器。它在Java類的可遍歷層次結構中轉換SQL。JSqlParser不限于一個數據庫,而是提供了對Oracle,SqlServer,MySQL,PostgreSQL等許多特殊數據庫的支持。舉一些例子,它支持使用(+)的Oracle連接語法,使用::的PostgreSQL轉換語法,像!=這樣的關系運算符然后可以以結構化的方式訪問結果。生成的Java類層次結構可以使用訪問者模式進行導航。
How it works? 它是如何工作的?
The parser is built using JavaCC. The core JavaCC grammar for SQL has been taken from Guido Draheim’s site and has been changed in order to produce a hierarchy of Java classes. The classes called deparsers are used to build again the SQL text of the class hierarchy.
解析器使用JavaCC構建。SQL的核心JavaCC語法取自Guido Draheim的站點,并已進行了更改,以生成Java類的層次結構。稱為deparser的類用于再次構建類層次結構的SQL文本。
Over the time the grammar was extended and now is a combination of specialities of grammars of various database systems. It is grown by need. So some (not all) Oracle, MySql, SQLServer, PostgreSQL specific aspects can be parsed.
隨著時間的推移,語法得到了擴展,現在是各種數據庫系統的語法專業的組合。它因需要而成長。所以一些(不是全部)Oracle,MySql,SQLServer,PostgreSQL特定的方面可以解析。
知識點
關于statement
熟悉JDBC的程序員一般都知道Statement,其實就是語句的意思,不過在Jsqlparser中Statement已經面向對象,被設計成了一個interface,之所以設計成interface大概都可以猜到,因為Jsqlparser既然要去解析SQL,那必然要對SQL語句做區分,到底是Select、還是Insert、還是Delete、甚至是Create,而Jsqlparser對每種語句都做了一個封裝,它們都繼承了Statement。
在SqlParser中設計了多個statement,根據不同的語法結構:
一條SQL語句,根據不同情況,都有適配的對象,例如Select語句對應著
net.sf.jsqlparser.statement.select.Select對象,而Insert也有自己的對象,所以我們都可以通過將
Statement強轉為它所對應的對象來獲取或改變其中的屬性,這也是解析SQL的一大目的。
在Jsqlparser成功解析SQL語句之后,statement就已經有了它的類型,可見下面快速使用demo。
實際應用場景
SQL審計和分析:審計SQL語句,檢查是否包含潛在的安全漏洞,如SQL注入。分析SQL語句的性能,檢查是否存在可以優化的查詢條件。數據庫遷移和同步:在遷移數據庫時,使用JSqlParser解析源數據庫的SQL語句,并生成目標數據庫的相應語句。數據庫同步工具可以使用JSqlParser來解析和生成SQL語句,以實現數據的同步。動態SQL生成:應用程序需要生成動態SQL語句以執行不同的操作,JSqlParser可以用來解析這些動態生成的SQL語句。SQL測試和驗證:在開發過程中,使用JSqlParser來驗證SQL語句的正確性。單元測試中,使用JSqlParser來解析和執行測試用例中的SQL語句。SQL注入防護:在應用程序中,使用JSqlParser來解析和分析用戶輸入的SQL查詢,以防止SQL注入攻擊。數據庫管理工具:數據庫管理工具可以使用JSqlParser來解析和顯示SQL語句的結構,幫助開發者理解查詢的邏輯。
代碼生成:在生成數據庫訪問層代碼時,使用JSqlParser來解析SQL語句,并生成相應的數據訪問對象(DAO)或查詢對象(DTO)。SQL格式化:使用JSqlParser來格式化SQL語句,使其更易于閱讀和理解。SQL優化:通過分析SQL語句的結構,可以提出性能優化建議。數據處理工具:在數據處理和轉換工具中,使用JSqlParser來解析和生成SQL語句,以實現數據的導入和導出。
引入依賴
說明:JSqlParser在5.0開始需要升級jdk11
<dependency><groupId>com.github.jsqlparser</groupId><artifactId>jsqlparser</artifactId><version>4.9</version>
</dependency>
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><version>5.7.0</version><scope>test</scope>
</dependency>
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.30</version>
</dependency>
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version>
</dependency>
Parser 解析SQL
wiki學習參考:https://github.com/JSQLParser/JSqlParser/wiki/Examples-of-SQL-parsing
解析sql語句
parse過程會去校驗sql語法,同時構建AstNode樹:
package com.changlu.demo;import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.Select;public class demo01 {public static void main(String[] args) throws JSQLParserException {String sql = "SELECT id,name,nickname,age,job,department FROM staff_member WHERE nickname= '劉'";// Parse SQLStatement statement = CCJSqlParserUtil.parse(sql);Select selectStatement = (Select) statement;System.out.println("JsqlParser SQL" + selectStatement.toString());}}
解析sql區分sql類型
package com.changlu.demo;import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.update.Update;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class demo02 {public static final Logger log = LoggerFactory.getLogger(demo02.class);public static void main(String[] args) throws JSQLParserException {String sql = "SELECT id,name,nickname,age,job,department FROM staff_member WHERE nickname= '劉'";// Parse SQLStatement statement = CCJSqlParserUtil.parse(sql);if(statement instanceof Select){Select selectStatement = (Select) statement;log.info("Select==> JsqlParser SQL: {}", selectStatement.toString());}if(statement instanceof Insert){Insert insertStatement = (Insert) statement;log.info("Insert==> JsqlParser SQL: {}", insertStatement.toString());}if(statement instanceof Update){Update updateStatement = (Update) statement;log.info("Update==> JsqlParser SQL: {}", updateStatement.toString());}if (statement instanceof Delete) {Delete deleteStatement = (Delete) statement;log.info("Delete==> JsqlParser SQL: {}", statement.toString());}}
}
分析增刪改查語句
查詢語句
認識PlainSelect
PlainSelect常用方法:
獲取和設置表(From子句):FromItem getFromItem(): 獲取FROM子句中的表或子查詢。void setFromItem(FromItem fromItem): 設置FROM子句中的表或子查詢。獲取和設置選擇項(SelectItems):List<SelectItem> getSelectItems(): 獲取SELECT子句中的選擇項列表。void setSelectItems(List<SelectItem> selectItems): 設置SELECT子句中的選擇項列表。獲取和設置WHERE子句:Expression getWhere(): 獲取WHERE子句的條件表達式。void setWhere(Expression where): 設置WHERE子句的條件表達式。獲取和設置GROUP BY子句:List<Expression> getGroupByColumnReferences(): 獲取GROUP BY子句中的列引用列表。void setGroupByColumnReferences(List<Expression> groupByColumnReferences): 設置GROUP BY子句中的列引用列表。獲取和設置ORDER BY子句:List<OrderByElement> getOrderByElements(): 獲取ORDER BY子句中的排序元素列表。void setOrderByElements(List<OrderByElement> orderByElements): 設置ORDER BY子句中的排序元素列表。獲取和設置LIMIT子句:Limit getLimit(): 獲取LIMIT子句。void setLimit(Limit limit): 設置LIMIT子句。獲取和設置DISTINCT關鍵字:boolean isDistinct(): 檢查SELECT語句是否使用了DISTINCT關鍵字。void setDistinct(boolean distinct): 設置SELECT語句是否使用DISTINCT關鍵字。獲取和設置INTO子句(用于SELECT INTO語句):SubSelect getIntoTables(): 獲取INTO子句中的表。void setIntoTables(SubSelect intoTables): 設置INTO子句中的表。獲取和設置HAVING子句:Expression getHaving(): 獲取HAVING子句的條件表達式。void setHaving(Expression having): 設置HAVING子句的條件表達式。獲取和設置別名:String getAlias(): 獲取SELECT語句的別名。void setAlias(String alias): 設置SELECT語句的別名。獲取和設置子查詢(SubSelect):SubSelect getSubSelect(): 獲取子查詢。void setSubSelect(SubSelect subSelect): 設置子查詢。獲取和設置聯合查詢(Union):List<PlainSelect> getUnion(): 獲取聯合查詢的SELECT語句列表。void setUnion(List<PlainSelect> union): 設置聯合查詢的SELECT語句列表。
示范
在statement成功解析SQL語句之后,通過PlainSelect就可以拿到SQL語句中的各個元素:
package com.changlu.demo;import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class demo03 {public static final Logger log = LoggerFactory.getLogger(demo03.class);public static void main(String[] args) throws Exception{selectDemo();}/*** select demo案例*/public static void selectDemo() throws Exception{String sql = "SELECT id,name,nickname,age,job,department FROM staff_member WHERE nickname= '劉'";// Parse SQLStatement statement = CCJSqlParserUtil.parse(sql);if(statement instanceof Select){Select selectStatement = (Select) statement;log.info("==> JsqlParser SQL: {}", selectStatement.toString());PlainSelect plainSelect = selectStatement.getPlainSelect();log.info("==> FromItem: {}", plainSelect.getFromItem());// 表結構log.info("==> SelectItem: {}",plainSelect.getSelectItems());// 字段選項log.info("==> Where: {}",plainSelect.getWhere());// where字段}}}
新增語句
了解Insert常用方法
Table getTable(): 獲取插入語句中的目標表。
List<Column> getColumns(): 獲取插入語句中要插入的列的列表。
ItemsList getValues(): 獲取插入語句中的值列表,可以是單個值或者子查詢。
String getPrefix(): 獲取INSERT關鍵字前的前綴,如INSERT INTO或者INSERT IGNORE。
void setTable(Table table): 設置插入語句中的目標表。
void setColumns(List<Column> columns): 設置插入語句中要插入的列的列表。
void setValues(ItemsList values): 設置插入語句中的值列表。
void setPrefix(String prefix): 設置INSERT關鍵字前的前綴。
示范
package com.changlu.demo;import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.Values;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class demo03 {public static final Logger log = LoggerFactory.getLogger(demo03.class);public static void main(String[] args) throws Exception{insertDemo();}/*** insert案例demo*/public static void insertDemo() throws Exception{String sql = "INSERT INTO employees (employee_id, employee_name, department) VALUES (1, 'John Doe', 'Human Resources')";// Parse SQLStatement statement = CCJSqlParserUtil.parse(sql);if (statement instanceof Insert) {Insert insertStatement = (Insert) statement;log.info("==> JsqlParser SQL: {}", insertStatement.toString());log.info("==> Table: {}", insertStatement.getTable());log.info("==> Columns: {}", insertStatement.getColumns());log.info("==> ItemsList: {}", insertStatement.getValues());}}}
更新語句
Update和Insert是一樣的,內容相對于Select較為簡單,通過Update對象即可獲得相關內容。
package com.changlu.demo;import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.Values;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.update.UpdateSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.List;public class demo03 {public static final Logger log = LoggerFactory.getLogger(demo03.class);public static void main(String[] args) throws Exception{updateDemo();}/*** update的demo*/public static void updateDemo() throws Exception{String sql = "UPDATE employees SET department = 'Human Resources' WHERE employee_id = 1";
// Parse SQLStatement statement = CCJSqlParserUtil.parse(sql);if (statement instanceof Update) {Update updateStatement = (Update) statement;log.info("==> JsqlParser SQL: {}", updateStatement.toString());// 更新的目標表Table table = updateStatement.getTable();log.info("Table Name: {}", table.getName());log.info("==> Columns: {}", updateStatement.getColumns());// 獲取更新項List<UpdateSet> updateSets = updateStatement.getUpdateSets();for (UpdateSet updateSet : updateSets) {for (Expression expression : updateSet.getColumns()) {log.info("==> Expression: {}", expression.toString());}}// 更新的字段log.info("==> ItemsList: {}", updateStatement.getExpressions());// 更新的where條件Expression where = updateStatement.getWhere();log.info("==> Where: {}", where.toString());}}}
刪除語句
package com.changlu.demo;import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.update.UpdateSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.List;public class demo03 {public static final Logger log = LoggerFactory.getLogger(demo03.class);public static void main(String[] args) throws Exception{deleteDemo();}/*** 刪除demo*/public static void deleteDemo() throws Exception{String sql = "DELETE FROM table_name WHERE a = 1";Statement statement = CCJSqlParserUtil.parse(sql);if (statement instanceof Delete) {Delete deleteStatement = (Delete) statement;// 獲取要刪除的表Table table = deleteStatement.getTable();System.out.println("Table Name: " + table.getName());// 獲取WHERE條件Expression where = deleteStatement.getWhere();System.out.println("Where Condition: " + where.toString());}}}
從SQL語句中提取表名
package com.changlu.demo;import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.util.TablesNamesFinder;import java.util.Set;/*** 從SQL語句中提取表名*/
public class demo04 {public static void main(String[] args) throws Exception{
// String sql = "SELECT * FROM MY_TABLE1";
// String sql = "SELECT * FROM t1 left join t2 on t1.a = t2.a";String sql = "SELECT * FROM t1 union all select * from t2";Statement statement = CCJSqlParserUtil.parse(sql);Select selectStatement = (Select) statement;// 表名查找器TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();Set<String> tables = tablesNamesFinder.getTables(statement);System.out.println(tables);}
}
為SQL語句各個字段表達式添加別名
借助工具AddAliasesVisitor
package com.changlu.demo;import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.util.AddAliasesVisitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class demo05 {public static final Logger log = LoggerFactory.getLogger(demo03.class);public static void main(String[] args) throws Exception{String sql = "SELECT id,name,nickname,age,job,department FROM staff_member WHERE nickname= '劉'";// Parse SQLStatement statement = CCJSqlParserUtil.parse(sql);if(statement instanceof Select){Select selectStatement = (Select) statement;// 借助使用自定義visitor來實現補充別名final AddAliasesVisitor instance = new AddAliasesVisitor();instance.setPrefix("tt");selectStatement.accept(instance);log.info("==> JSqlParser finalSQL: {}", selectStatement);}}
}
動態加字段加表達式加條件
增加一個表達式(字段 or 表達式)
package com.changlu.demo;import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.arithmetic.Addition;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.util.AddAliasesVisitor;
import net.sf.jsqlparser.util.SelectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** 動態加字段加表達式加條件*/
public class demo06 {public static final Logger log = LoggerFactory.getLogger(demo03.class);public static void main(String[] args) throws Exception{test01();}// 增加一個表達式public static void test01() throws Exception{// 解析得到statement// 測試:select a from mytable union all select b from test 不支持 Not supported yet.Select select = (Select) CCJSqlParserUtil.parse("select a from mytable");// 添加表達式,這里添加一個字段SelectUtils.addExpression(select, new Column("b"));System.out.println(select);// 添加一個字段表達式 5+6Addition add = new Addition();add.setLeftExpression(new LongValue(5));add.setRightExpression(new LongValue(6));SelectUtils.addExpression(select, add);System.out.println(select);}}
增加一個Join
動態添加Join,可以為Join增加表達式,以及設置Join的表,并且通過setLeft()、setRight()、setInner()可以設置join的方向,最終它會生成對應的SQL語句。
package com.changlu.demo;import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.arithmetic.Addition;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.util.AddAliasesVisitor;
import net.sf.jsqlparser.util.SelectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** 動態加字段加表達式加條件*/
public class demo06 {public static final Logger log = LoggerFactory.getLogger(demo03.class);public static void main(String[] args) throws Exception{
// test01();test02();}// 增加一個表達式public static void test01() throws Exception{// 解析得到statement// 測試:select a from mytable union all select b from test 不支持 Not supported yet.Select select = (Select) CCJSqlParserUtil.parse("select a from mytable");// 添加表達式,這里添加一個字段SelectUtils.addExpression(select, new Column("b"));System.out.println(select);// 添加一個字段表達式 5+6Addition add = new Addition();add.setLeftExpression(new LongValue(5));add.setRightExpression(new LongValue(6));SelectUtils.addExpression(select, add);System.out.println(select);}/*** 增加一個join* @throws Exception*/public static void test02() throws Exception{Select select = (Select) CCJSqlParserUtil.parse("select a from mytable");final EqualsTo equalsTo = new EqualsTo();equalsTo.setLeftExpression(new Column("a"));equalsTo.setRightExpression(new Column("b"));Join addJoin = SelectUtils.addJoin(select, new Table("mytable2"), equalsTo);addJoin.setLeft(true);// "SELECT a FROM mytable LEFT JOIN mytable2 ON a = b"System.out.println(select.toString());}}
SelectUtils構建一個SQL語句
SelectUtils里面的一些方法,可以看到不光是為查詢語句增加表達式、Join和分組,其次還可以使用build等方法去構建一個SQL語句。
package com.changlu.demo;import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.arithmetic.Addition;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.util.SelectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** SelectUtils 構建sql*/
public class demo07 {public static final Logger log = LoggerFactory.getLogger(demo03.class);public static void main(String[] args) throws Exception{// 構建select sqlSelect select = SelectUtils.buildSelectFromTableAndExpressions(new Table("mytable"),new Column("a"), new Column("b"));// 添加一個字段SelectUtils.addExpression(select, new Column("c"));// 添加一個條件表達式 id = 1final EqualsTo equalsTo = new EqualsTo();equalsTo.setLeftExpression(new Column("id"));equalsTo.setRightExpression(new Column("1"));// 添加一個group bySelectUtils.addGroupBy(select, new Column("d"));log.info("==> JsqlParser Build SQL: {}", select.toString());}}
Building 構建SQL
wiki學習參考:https://github.com/JSQLParser/JSqlParser/wiki/Examples-of-SQL-building
創建一個簡單的select
package com.changlu.demo.building;import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.util.SelectUtils;/*** 創建一個簡單的select*/
public class Demo01 {public static void main(String[] args) throws JSQLParserException {// SELECT * FROM mytableSelect select = SelectUtils.buildSelectFromTable(new Table("mytable"));System.out.println(select);// select包含select * from mytable。select = SelectUtils.buildSelectFromTableAndExpressions(new Table("mytable"), new Column("a"), new Column("b"));System.out.println(select);// 或者更簡單,如果你不想構建正確的表達式樹,你可以提供簡單的文本表達式,它將被解析并包含在你的選擇中。// 示范:SELECT a + b, test FROM mytableselect = SelectUtils.buildSelectFromTableAndExpressions(new Table("mytable"), "a+b", "test");System.out.println(select);}}
擴展簡單插入
package com.changlu.demo.building;import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.ExpressionVisitor;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.FromItemVisitor;/*** 擴展簡單插入*/
public class Demo02 {public static void main(String[] args) throws JSQLParserException {Insert insert = (Insert) CCJSqlParserUtil.parse("insert into mytable (col1) values (1)");System.out.println(insert.toString());//adding a columninsert.getColumns().add(new Column("col2"));System.out.println(insert);//adding another columninsert.getColumns().add(new Column("col3"));System.out.println(insert);//adding a value using a visitor// tag為jsqlparser-1.2.0 有該實現,當前4.9.0沒有
// insert.getItemsList().accept(new ItemsListVisitor() {
//
// public void visit(SubSelect subSelect) {
// throw new UnsupportedOperationException("Not supported yet.");
// }
//
// public void visit(ExpressionList expressionList) {
// expressionList.getExpressions().add(new LongValue(5));
// }
//
// public void visit(MultiExpressionList multiExprList) {
// throw new UnsupportedOperationException("Not supported yet.");
// }
// });}}
替換字符串值
Somebody wanted to publish some SQLs but wanted to scramble all concrete values. So here is a little example of how to achieve this. In short a visitor scans through the complete tree, finds all StringValues and replaces the current value with XXXX.
有些人想要發布一些SQL,但是想要打亂所有具體的值。這里有一個小例子來說明如何實現這一點。簡而言之,訪問者掃描整個樹,找到所有StringValues并將當前值替換為String。
package com.changlu.demo.building;import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.util.deparser.ExpressionDeParser;
import net.sf.jsqlparser.util.deparser.SelectDeParser;/*** 替換字符串值* 背景:有些人想要發布一些SQL,但是想要打亂所有具體的值。這里有一個小例子來說明如何實現這一點。簡而言之,訪問者掃描整個樹,找到所有StringValues并將當前值替換為String。* 這段代碼的核心是使用 JSQLParser 庫將 SQL 語句解析為抽象語法樹,然后通過自定義的訪問者模式遍歷樹中的節點,將所有字符串值替換為 "XXXX",最后輸出修改后的 SQL 語句。*/
public class Demo03 {public static void main(String[] args) throws Exception{String sql ="SELECT NAME, ADDRESS, COL1 FROM USER WHERE SSN IN ('11111111111111', '22222222222222');";try {// 使用 CCJSqlParserUtil 類的 parse 方法將 SQL 字符串解析為 Select 對象。// 該方法會將 SQL 語句解析為一個抽象語法樹(AST),方便后續對其進行操作。Select select = (Select) CCJSqlParserUtil.parse(sql);// 創建一個 StringBuilder 對象,用于存儲最終修改后的 SQL 語句。// StringBuilder 是一個可變的字符序列,適合動態拼接字符串。StringBuilder buffer = new StringBuilder();// 創建一個 ExpressionDeParser 對象,它是 JSQLParser 中的一個解析器,用于處理表達式。重寫其 visit 方法來實現對字符串值的替換。ExpressionDeParser expressionDeParser = new ExpressionDeParser() {// 重寫 visit 方法,當訪問到 StringValue 對象時會調用該方法。 StringValue 對象代表 SQL 語句中的字符串值。@Overridepublic void visit(StringValue stringValue) {// 當訪問到字符串值時,將 "XXXX" 追加到 buffer 中,而不是原來的字符串值。這樣就實現了將所有字符串值替換為 "XXXX" 的功能。this.getBuffer().append("?");}// 字段名值@Overridepublic void visit(Column tableColumn) {this.getBuffer().append("?");}};// 創建一個 SelectDeParser 對象,它是 JSQLParser 中用于解析 SELECT 語句的解析器。// 構造函數接受兩個參數:ExpressionDeParser 對象和 StringBuilder 對象。// ExpressionDeParser 用于處理表達式,buffer 用于存儲最終的 SQL 語句。SelectDeParser deparser = new SelectDeParser(expressionDeParser, buffer);// 將 SelectDeParser 對象設置為 ExpressionDeParser 的 SelectVisitor。// 這樣在解析過程中,ExpressionDeParser 會將 SELECT 語句的處理委托給 SelectDeParser。expressionDeParser.setSelectVisitor(deparser);// ExpressionDeParser 在處理表達式時會將結果存儲到該緩沖區中。expressionDeParser.setBuffer(buffer);// 調用 select 對象的 getSelectBody 方法獲取 SELECT 語句的主體部分。調用 accept 方法,將 SelectDeParser 對象作為訪問者傳入。// 這會觸發 SelectDeParser 對 SELECT 語句的解析和處理,同時 ExpressionDeParser 會處理其中的表達式。select.getSelectBody().accept(deparser);System.out.println(buffer);}catch (Exception ex) {ex.printStackTrace();}}}
語句中字符串值的更一般的替換
This is a more general approach for replacing string and long values in all kinds of statements.
這是一種更通用的方法,用于替換所有類型語句中的字符串和長值。
package com.changlu.demo.building;import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.util.deparser.ExpressionDeParser;
import net.sf.jsqlparser.util.deparser.SelectDeParser;
import net.sf.jsqlparser.util.deparser.StatementDeParser;public class Demo04 {// 自定義訪問者類型 替換String字段值 & Long類型值static class ReplaceColumnAndLongValues extends ExpressionDeParser {@Overridepublic void visit(StringValue stringValue) {this.getBuffer().append("?");}@Overridepublic void visit(LongValue longValue) {this.getBuffer().append("?");}}public static String cleanStatement(String sql) throws JSQLParserException {StringBuilder buffer = new StringBuilder();ExpressionDeParser expr = new ReplaceColumnAndLongValues();// 綁定ExpressionDeParser & StringBuilderSelectDeParser selectDeparser = new SelectDeParser(expr, buffer);// 表達式解析起設置select訪問器expr.setSelectVisitor(selectDeparser);expr.setBuffer(buffer);// 將表達式綁定到stmtDeparser中,后續使用astnode去進行accept操作StatementDeParser stmtDeparser = new StatementDeParser(expr, selectDeparser, buffer);Statement stmt = CCJSqlParserUtil.parse(sql);// 間接執行訪問者模式操作stmt.accept(stmtDeparser);return stmtDeparser.getBuffer().toString();}public static void main(String[] args) throws JSQLParserException {System.out.println(cleanStatement("SELECT 'abc', 5 FROM mytable WHERE col='test'"));System.out.println(cleanStatement("UPDATE table1 A SET A.columna = 'XXX' WHERE A.cod_table = 'YYY'"));System.out.println(cleanStatement("INSERT INTO example (num, name, address, tel) VALUES (1, 'name', 'test ', '1234-1234')"));System.out.println(cleanStatement("DELETE FROM table1 where col=5 and col2=4"));}}
使用Java對象API方式構建sql語句(增刪改查sql)
通過Java代碼進行SQL構建。
package com.changlu;import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.util.SelectUtils;
import org.junit.jupiter.api.Test;import java.util.Arrays;
import java.util.List;public class JsqlparserTest {/*** 簡單的構建單表查詢** @throws JSQLParserException*/@Testpublic void buildSelectSql() throws JSQLParserException {Select select01 = SelectUtils.buildSelectFromTable(new Table("test"));System.err.println(select01.getSelectBody().toString()); // SELECT * FROM testSelect select02 = SelectUtils.buildSelectFromTableAndExpressions(new Table("test"), new Column("col1"), new Column("col2"));System.err.println(select02.getSelectBody().toString()); // SELECT col1, col2 FROM testSelect select03 = SelectUtils.buildSelectFromTableAndExpressions(new Table("mytable"), "col1", "col2");System.err.println(select03.getSelectBody().toString()); // SELECT col1, col2 FROM test}/*** 構建插入語句*/@Testpublic void buildInsertSql() {// 創建表對象設置表名Table table = new Table();table.setName("table");// 創建插入對象Insert insert = new Insert();insert.setTable(table); // 設置插入對象的表對象// 設置插入列List<Column> columnList = Arrays.asList(new Column("col01"), new Column("col02"));insert.setColumns(columnList);// 設置插入值MultiExpressionList multiExpressionList = new MultiExpressionList();multiExpressionList.addExpressionList(Arrays.asList(new StringValue("1"), new StringValue("2")));insert.setItemsList(multiExpressionList);System.err.println(insert); // INSERT INTO table (col01, col02) VALUES ('1', '2')}/*** 構建更新語句*/@Testpublic void buildUpdateSql() {// 創建表對象設置表名Table table = new Table();table.setName("table");// 創建更新對象Update update = new Update();update.setTable(table);// 設置更新對象的表對象// 設置更新列List<Column> columnList = Arrays.asList(new Column("col01"), new Column("col02"));update.setColumns(columnList);// 設置更新值update.setExpressions(Arrays.asList(new StringValue("1"), new StringValue("2")));// 添加Where條件EqualsTo equalsTo = new EqualsTo(); // 等于表達式equalsTo.setLeftExpression(new Column(table,"user_id")); // 設置表達式左邊值equalsTo.setRightExpression(new StringValue("123456"));// 設置表達式右邊值update.setWhere(equalsTo); // 設置Where}/*** 構建刪除語句*/@Testpublic void buildDeleteSql() {// 創建表對象設置表名Table table = new Table();table.setName("table");// 創建更新對象Delete delete = new Delete();delete.setTable(table);// 設置更新對象的表對象// 添加Where條件EqualsTo equalsTo = new EqualsTo(); // 等于表達式equalsTo.setLeftExpression(new Column(table,"user_id")); // 設置表達式左邊值equalsTo.setRightExpression(new StringValue("123456"));// 設置表達式右邊值delete.setWhere(equalsTo); // 設置Where// 輸入語句System.err.println(delete);}
}
Analyzing 分析SQL
高級特性
認識Visitor
Jsqlparser在解析SQL語句的過程中,每一個節點都會被解析成一個叫SimpleNode的對象,它包含著各個節點的屬性,這仿佛就像Dom4j解析XML的時候所有的元素都視為Node一樣,解析之后的內容都是節點,而循環這些節點,Jsqlparser給出了相應的方法,提供了用于遍歷節點的接口CCJSqlParserVisitor,而它的默認實現則是CCJSqlParserDefaultVisitor。
在這里創建一個自己的類,并通過繼承 CCJSqlParserDefaultVisitor 重寫它的visit 方法,便可以實現自己的策略,更加方便的去操作解析內容。
重寫的visit方法中可以看到形參SimpleNode,而調用這個自定義的Visitor之后,語句則會被拆解,依次進入到visit方法中,通過node.jjtGetValue可以獲得節點信息,而node.getId()實則是獲取節點的類型,而Switch-case中的常量分別代表了在解析SQL語句時,生成的抽象語法樹AST (abstract syntax tree)中不同類型的節點,每個節點對應一個特定的SQL構造,如SELECT、FROM、WHERE等。下面是對這些常量代表的SQL構造的簡要說明:
JJTSTATEMENT: 代表一個SQL語句。
JJTVOID: 可能代表一個空語句或者不返回結果的語句。
JJTBLOCK: 代表一個語句塊,可能包含多個語句。
JJTSTATEMENTS: 代表一個包含多個語句的列表。
JJTCOLUMN: 代表一個列名。
JJTTABLENAME: 代表一個表名。
JJTSELECT: 代表一個SELECT查詢。
JJTPARENTHESEDSELECT: 代表被括號包圍的SELECT查詢。
JJTLATERALVIEW: 代表LATERAL VIEW子句,常用于Hive SQL。
JJTFORCLAUSE: 代表FOR子句。
JJTLATERALSUBSELECT: 代表LATERAL子查詢。
JJTPLAINSELECT: 代表一個簡單的SELECT查詢(不包含UNION等)。
JJTSETOPERATIONLIST: 代表一個集合操作列表,比如UNION, EXCEPT, INTERSECT。
JJTWITHITEM: 代表WITH子句中的單個項。
JJTSELECTITEM: 代表SELECT子句中的一個項,可能是列名、表達式等。
JJTJOINEREXPRESSION: 代表JOIN操作的表達式。
JJTLIMITWITHOFFSET: 代表LIMIT和OFFSET子句。
JJTPLAINLIMIT: 代表一個簡單的LIMIT子句。
JJTEXPRESSION: 代表一個表達式。
JJTREGULARCONDITION: 代表一個常規條件(如WHERE子句中的條件)。
JJTINEXPRESSION: 代表IN表達式。
JJTLIKEEXPRESSION: 代表LIKE表達式。
JJTSIMILARTOEXPRESSION: 代表SIMILAR TO表達式。
JJTISDISTINCTEXPRESSION: 代表IS DISTINCT FROM表達式。
JJTEXPRESSIONLIST: 代表一個表達式列表。
JJTPRIMARYEXPRESSION: 代表一個主要表達式。
JJTCONNECTBYROOTOPERATOR: 代表CONNECT BY ROOT操作符。
JJTCASEWHENEXPRESSION: 代表CASE WHEN表達式。
JJTFUNCTION: 代表一個函數調用。
JJTSEQUENCE: 代表一個序列。
JJTSYNONYM: 代表一個同義詞。
實際案例
自定義Visitor訪問node節點
package com.changlu.demo.demo08;import net.sf.jsqlparser.parser.*;
import net.sf.jsqlparser.statement.Statement;public class SQLModifier extends CCJSqlParserDefaultVisitor {@Overridepublic Object visit(SimpleNode node, Object data) {Object value = node.jjtGetValue();switch (node.getId()) {case CCJSqlParserTreeConstants.JJTTABLENAME:break;case CCJSqlParserTreeConstants.JJTCOLUMN:break;case CCJSqlParserTreeConstants.JJTFUNCTION:break;default:break;}System.out.println(node.getId());return super.visit(node, data);}public static void main(String[] args) throws ParseException {String originalSql = "select * from user where id = 1";CCJSqlParser parser = CCJSqlParserUtil.newParser(originalSql);Statement statement = parser.Statement();parser.getASTRoot().jjtAccept(new SQLModifier(), null);}}
驗證 SQL 語句
介紹
Examples of SQL Validation(官方案例):https://github.com/JSQLParser/JSqlParser/wiki/Examples-of-SQL-Validation
StatementValidator 是 JSqlParser 中用于驗證 SQL 語句的工具,它可以幫助你檢查解析后的 SQL 語句是否符合某些規則或約束。
下面是官方原文翻譯:
從4.0開始,JSQLParser框架包含了一個validaton框架。
驗證框架映射了幾種類型的驗證,它們是ValidationCapability接口的實現。
目前存在以下實現:
-
ParseCapability:檢查語句是否可以被解析(總是包含在Validation#validate()中)
-
FeatureSetValidation****數據集驗證:
-
- DatabaseType:檢查分析的語句相對于支持的數據庫語法是否有效
- Version 版本:檢查分析的語句對于特定的數據庫版本是否有效。
- FeaturesAllowed: Checks if a statement only uses syntax elements which are allowed.(檢查語句是否只使用允許的語法元素。)
-
DatabaseMetaDataValidation:驗證Meta數據(如表、視圖、列的名稱)是否存在
實際案例
SQL驗證數據庫類型/版本
檢查分析的語句對于所選數據庫是否有效
String sql = "DROP INDEX IF EXISTS idx_tab2_id;";// validate statement if it's valid for all given databases.
Validation validation = new Validation(Arrays.asList(DatabaseType.SQLSERVER, DatabaseType.MARIADB,DatabaseType.POSTGRESQL, DatabaseType.H2), sql);
List<ValidationError> errors = validation.validate();
檢查分析的語句對于特定的數據庫版本是否有效:
// validate statement if it's valid for all given databases.
Validation validation = new Validation(Arrays.asList(PostgresqlVersion.V10), sql);
List<ValidationError> errors = validation.validate();
根據指定語法權限完成SQL驗證
根據預定義的錯誤允許的.DML集進行檢查(下面使用數據定義語言(DDL)語句,所以驗證時會產生錯誤):
// validate against pre-defined FeaturesAllowed.DML set
String sql = "CREATE TABLE tab1 (id NUMERIC(10), val VARCHAR(30))";
// 這行注釋表明代碼的意圖是根據預定義的 FeaturesAllowed.DML 特性集合對 SQL 語句進行驗證。FeaturesAllowed.DML 代表只允許執行數據操作語言(如 INSERT、UPDATE、DELETE 等)相關的 SQL 語句。
Validation validation = new Validation(Arrays.asList(FeaturesAllowed.DML), sql);
List<ValidationError> errors = validation.validate();
// 調用 Validation 對象的 validate() 方法對 SQL 語句進行驗證,該方法會返回一個包含驗證錯誤信息的列表。如果 SQL 語句符合允許的特性集合,列表將為空;否則,列表中會包含相應的錯誤信息。
// only DML is allowed, got error for using a DDL statement
log.error (errors);
根據預定義的錯誤允許.SELECT集進行查詢:
public static void main(String[] args) throws Exception{String sql = "SELECT * FROM myview v JOIN secondview v2 ON v.id = v2.ref";
// 滿足條件指明支持SELECTValidation validation = new Validation(Arrays.asList(FeaturesAllowed.SELECT), sql);List<ValidationError> errors = validation.validate();
// no errors, select - statement is allowedif (errors.isEmpty()) {// do something else with the parsed statementsStatements statements = validation.getParsedStatements();System.out.println(statements);}
}
Validate against your own FeatureSet針對您自己的隱私設置:
// 根據你自己定義的特性集合(FeatureSet)來進行驗證。這里的 “特性集合” 是指你可以自定義一組規則或特性,然后用這組規則去檢查 SQL 語句是否符合這些特性要求。
FeaturesAllowed exec = new FeaturesAllowed("EXECUTE", Feature.execute).unmodifyable();
Combine multiple pre-defined FeaturesAllowed set’s 將多個預定義的 FeaturesAllowed
集合組合起來:
FeaturesAllowed myAllowedFeatures = new FeaturesAllowed("myAllowedFeatures").add (FeaturesAllowed.DDL, FeaturesAllowed.DML);
參考文章
[1]. JSqlParser入門系列專欄:https://blog.csdn.net/qq_43437874/category_10895724.html
[2]. 【JSqlParser】Java使用JSqlParser解析SQL語句總結:https://blog.csdn.net/m0_74823364/article/details/144870400
資料獲取
大家點贊、收藏、關注、評論啦~
精彩專欄推薦訂閱:在下方專欄👇🏻
- 長路-文章目錄匯總(算法、后端Java、前端、運維技術導航):博主所有博客導航索引匯總
- 開源項目Studio-Vue—校園工作室管理系統(含前后臺,SpringBoot+Vue):博主個人獨立項目,包含詳細部署上線視頻,已開源
- 學習與生活-專欄:可以了解博主的學習歷程
- 算法專欄:算法收錄
更多博客與資料可查看👇🏻獲取聯系方式👇🏻,🍅文末獲取開發資源及更多資源博客獲取🍅