java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java使用JSqlParser

Java使用JSqlParser解析SQL语句应用场景

作者:刘子丙

JSqlParser是一个功能全面的Java库,用于解析SQL语句,支持多种SQL方言,它可以轻松集成到Java项目中,并提供灵活的操作方式,本文介绍Java使用JSqlParser解析SQL语句总结,感兴趣的朋友一起看看吧

简述

Java解析SQL语句有很多工具都可以做到,比如Mybatis、Druid、目前用来用去最全面的仍然是Jsqlparser,它是一个Github上的开源项目,JSqlParser是一个用于解析SQL语句的Java库,它可以帮助开发者分析和操作SQL语句的结构。无论是从事数据库开发、SQL性能优化,还是需要解析SQL语句以进行其他操作,JSqlParser都能提供强大的支持

特点

支持多种SQL方言:JSqlParser支持多种数据库的SQL方言,包括MySQL、Oracle、PostgreSQL等,这使得在不同数据库之间进行SQL语句解析变得更加方便。

灵活的操作:JSqlParser以多种方式操作SQL语句,例如修改查询条件、提取表名、列名等,甚至整个SQL语句中使用到的函数,从而满足各种需求。

易于集成:JSqlParser可以轻松集成到您的Java项目中,只需将其作为依赖项添加到项目中即可。

社区支持:JSqlParser拥有一个活跃的社区,许多开发者为其提供贡献,使得这个工具不断得到改进和优化,它的主要操刀人manticore-projects (github.com) 也非常负责并愿意解答各种问题和参与讨论

环境准备

将Jsqlparser直接添加到项目中

Maven:

<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>4.9</version>
</dependency>

Gradle:

implementation("com.github.jsqlparser:jsqlparser:4.9")

快速使用

使用原则

假设现在有一条简单的SQL语句需要拿来解析,首先应该保证这个SQL在结构上是没有问题的,最好是放在数据库中可以直接运行的,不夹杂不应该的标点符号,那样解析起来才不会出错

使用案例:

以下是一个简单的SQL语句,并且这句SQL没有违反原则,是一条基本可以正常运行的SQL语句

SELECT id,name,nickname,age,job,department FROM staff_member WHERE nickname= "刘"

解析SQL语句

接下来使用Jsqlparser去解析语句,其中第二行则是最基本的,将SQL语句字符串拿来解析,如果这句SQL语句违反了原则,例如存在特殊标点符号或者不符合SQL语句,那么在第二行就会产生异常

String sql = "SELECT id,name,nickname,age,job,department FROM staff_member WHERE nickname= '刘'";
// Parse SQL
Statement statement = CCJSqlParserUtil.parse(sql);
Select selectStatement = (Select) statement;
log.info("==> JsqlParser SQL: {}", selectStatement.toString());

正常情况下,将得到一个包含各种属性的statement,这意味着一条SQL成功被解析,并被赋予到一个对象的各个属性中

认识Statement

熟悉JDBC的程序员一般都知道Statement,其实就是语句的意思,不过在Jsqlparser中Statement已经面向对象,被设计成了一个interface,之所以设计成interface大概都可以猜到,因为Jsqlparser既然要去解析SQL,那必然要对SQL语句做区分,到底是Select、还是Insert、还是Delete、甚至是Create,而Jsqlparser对每种语句都做了一个封装,它们都继承了Statement

所以一条SQL语句,根据不同情况,都有适配的对象,例如Select语句对应着 net.sf.jsqlparser.statement.select.Select对象,而Insert也有自己的对象,所以我们都可以通过将Statement强转为它所对应的对象来获取或改变其中的属性,这也是解析SQL的一大目的

其实在Jsqlparser成功解析SQL语句之后,statement就已经有了它的类型

String sql = "SELECT id,name,nickname,age,job,department FROM staff_member WHERE nickname= '刘'";
        // Parse SQL
 Statement statement = CCJSqlParserUtil.parse(sql);
 if(statement instanceof Select){
       Select selectStatement = (Select) statement;
      log.info("==> JsqlParser SQL: {}", selectStatement.toString());
}
if(statement instanceof Insert){
       Insert insertStatement = (Insert) statement;
       log.info("==> JsqlParser SQL: {}", insertStatement.toString());
}
if(statement instanceof Update){
      Update updateStatement = (Update) statement;
      log.info("==> JsqlParser SQL: {}", updateStatement.toString());
}
if (statement instanceof Delete) {
      Delete deleteStatement = (Delete) statement;
      log.info("==> JsqlParser SQL: {}", statement.toString());
}

分析语句

查询语句

在statement成功解析SQL语句之后,通过PlainSelect就可以拿到SQL语句中的各个元素

String sql = "SELECT id,name,nickname,age,job,department FROM staff_member WHERE nickname= '刘'";
// Parse SQL
Statement 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());
}

运行结果:

==> JsqlParser SQL: SELECT id, name, nickname, age, job, department FROM staff_member WHERE nickname = '刘'
==> FromItem: staff_member
==> SelectItem: [id, name, nickname, age, job, department]
==> Where: nickname = '刘'

PlainSelect常用方法:

新增语句

新增语句和查询语句一样,只不过由于Insert没有Select语句那么复杂,所以Jsqlparsert并没有专门设计一个类似PlainSelect extend Select这样一个类,而是直接通过Insert对象就可以获取和操作,Insert语句中的内容

String sql = "INSERT INTO employees (employee_id, employee_name, department) VALUES (1, 'John Doe', 'Human Resources')";
// Parse SQL
Statement 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());
}

运行结果:

 ==> JsqlParser SQL: INSERT INTO employees (employee_id, employee_name, department) VALUES (1, 'John Doe', 'Human Resources')
 ==> Table: employees
 ==> Columns: employee_id, employee_name, department
 ==> ItemsList: VALUES (1, 'John Doe', 'Human Resources')

Insert常用方法

更新语句

Update和Insert是一样的,内容相对于Select较为简单,通过Update对象即可获得相关内容

String sql = "UPDATE employees SET department = 'Human Resources' WHERE employee_id = 1";
// Parse SQL
Statement 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());
    Expression where = updateStatement.getWhere();
    log.info("==> Where: {}", where.toString());
}

运行结果

==> JsqlParser SQL: UPDATE employees SET department = 'Human Resources' WHERE employee_id = 1
Table Name: employees
==> Columns: department
==> Expression: department
==> ItemsList: 'Human Resources'
==> Where: employee_id = 1

删除语句

        String sql = "DELETE FROM table_name WHERE condition";
        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());
        }

运行结果:

Table Name: table_name
Where Condition: condition

从SQL语句中提取表名

Statement statement = CCJSqlParserUtil.parse("SELECT * FROM MY_TABLE1");
Select selectStatement = (Select) statement;
TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
List<String> tableList = tablesNamesFinder.getTableList(selectStatement);

最终tableList里将存入所有给出的SQL语句中的表名,以上案例只有一个表名

为SQL语句各个字段表达式添加别名

String sql = "SELECT id,name,nickname,age,job,department FROM staff_member WHERE nickname= '刘'";
// Parse SQL
Statement statement = CCJSqlParserUtil.parse(sql);
if(statement instanceof Select ){
    Select selectStatement = (Select) statement;
    final AddAliasesVisitor instance = new AddAliasesVisitor();
    instance.setPrefix("t");
    selectStatement.accept(instance);
    log.info("==> JSqlParser finalSQL: {}", selectStatement);
}

动态加字段加表达式加条件

使用SelectUtils,为一个Select语句,增加查询的字段

Select select = (Select) CCJSqlParserUtil.parse("select mydate from mytable");
SelectUtils.addExpression(select, new Column("mylocation"));

增加一个表达式

Select select = (Select) CCJSqlParserUtil.parse("select a from mytable");
SelectUtils.addExpression(select, new Column("b"));
assertEquals("SELECT a, b FROM mytable", select.toString());
Addition add = new Addition();
add.setLeftExpression(new LongValue(5));
add.setRightExpression(new LongValue(6));
SelectUtils.addExpression(select, add);
assertEquals("SELECT a, b, 5 + 6 FROM mytable", select.toString());

增加一个Join

动态添加Join,可以为Join增加表达式,以及设置Join的表,并且通过setLeft()、setRight()、setInner()可以设置join的方向,最终它会生成对应的SQL语句

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);
assertEquals("SELECT a FROM mytable LEFT JOIN mytable2 ON a = b", select.toString());

用SelectUtils构建一个SQL语句

下面是SelectUtils里面的一些方法,可以看到不光是为查询语句增加表达式、Join和分组,其次还可以使用build等方法去构建一个SQL语句

这里是一个案例,构建了一个查询语句,其中也使用到了addGroupBy

Select select = SelectUtils.buildSelectFromTableAndExpressions(new Table("mytable"),
                new Column("a"), new Column("b"));
SelectUtils.addExpression(select, new Column("c"));
final EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(new Column("id"));
equalsTo.setRightExpression(new Column("1"));
SelectUtils.addGroupBy(select, new Column("d"));
log.info("==> JsqlParser Build SQL: {}", select.toString());

输出结果:

==> JsqlParser Build SQL: SELECT a, b, c FROM mytable GROUP BY d

简短的总结

上面的代码虽然不少,但实际上真正需要熟悉的只有一个,就是直接调用CCJSqlParserUtil.parse(sql);去获得Statement,然后通过Statement去操作和获取解析后的SQL中的内容,非常简单方便

实际应用场景

说了那么多JSQLPARSER的使用,或许很多朋友并不能联想到有哪些具体可以用到它的地方,实际上想要开发一个优秀的软件产品,那么细节是少不了的,SQL是BS软件的本质之一,那么针对SQL,我们能做的还有很多,以下列举几个常见的场景

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语句,以实现数据的导入和导出。

在Springboot+Mybaits中使用

如果使用纯原生Mybatis那么我们需要手动在maven中加入Jsqlparser的支持,但如果使用Mybatis plus,那么就无需自己再引用,Mybaits plus自带Jsqlparser

上面举的很多例子都很简单,拿一个SQL语句解析而已,这种情况是手动化的,通常见于单元测试等情况,但如果在项目中想要通过被动的方式,让项目自己去解析SQL语句,就需要分析项目的具体情况,例如在Mybatis中通过Interceptor就可以获得到项目中真正去执行的SQL语句,详见:mybatis 自定义实现拦截器插件Interceptor示例

通过Mybatis的拦截器,我们拿到了项目执行的SQL语句,再通过Jsqlparser去解析,并做一定的处理,例如以上提到的那些实际应用场景

高级特性(很实用)

Jsqlparser在解析SQL语句的过程中,每一个节点都会被解析成一个叫SimpleNode的对象,它包含着各个节点的属性,这仿佛就像Dom4j解析XML的时候所有的元素都视为Node一样,解析之后的内容都是节点,而循环这些节点,Jsqlparser给出了相应的方法,提供了用于遍历节点的接口CCJSqlParserVisitor,而它的默认实现则是CCJSqlParserDefaultVisitor,在这里创建一个自己的类,并通过继承 CCJSqlParserDefaultVisitor 重写它的 visit 方法,便可以实现自己的策略,更加方便的去操作解析内容

public class SQLModifier extends CCJSqlParserDefaultVisitor {
    @Override
    public 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;
        }
        return super.visit(node, data);
    }
}

调用自定义的Visitor

String originalSql = "select * from user where id = 1";
CCJSqlParser parser = CCJSqlParserUtil.newParser(originalSql);
Statement statement = parser.Statement();
parser.getASTRoot().jjtAccept(sqlTestModifier, null);

以上代码做了一个自定义的visitor,重写的visit方法中可以看到形参SimpleNode,而调用这个自定义的Visitor之后,语句则会被拆解,依次进入到visit方法中,通过node.jjtGetValue可以获得节点信息,而node.getId()实则是获取节点的类型,而Switch-case中的常量分别代表了在解析SQL语句时,生成的抽象语法树AST (abstract syntax tree)中不同类型的节点,每个节点对应一个特定的SQL构造,如SELECT、FROM、WHERE等。下面是对这些常量代表的SQL构造的简要说明:

Visit常见应用场景

目前我们知道,通过Mybatis 的 interceptor可以拦截到所有执行的SQL语句,而在 自定义的interceptor中调用自定义的visit,就可以对项目中所有运行的SQL做一个拦截并处理,那么具体可以做哪些骚操作呢

总结

Jsqlparser非常容易上手使用,而它也解决了解析SQL语句的问题,结合Springboot 和 mybatis,可以设计自定义插件,就像Mybatis plus的分页插件那样,可以开发自己系统需求的业务处理功能,方便项目业务的时间,甚至可以拿来提高效率,毕竟总有一些时候,对SQL的解析是绕不开的。

到此这篇关于Java使用JSqlParser解析SQL语句总结的文章就介绍到这了,更多相关Java使用JSqlParser内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文