JSqlParser学习笔记 快速使用JSqlParser
文章目录
- 前言
- 本章节源码
- 官方文档信息
- 认识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):博主个人独立项目,包含详细部署上线视频,已开源
- 学习与生活-专栏:可以了解博主的学习历程
- 算法专栏:算法收录
更多博客与资料可查看👇🏻获取联系方式👇🏻,🍅文末获取开发资源及更多资源博客获取🍅