JSqlParser:Java SQL 解析利器

  在开发涉及数据库操作的应用程序时,我们常常需要对 SQL 语句进行解析、分析和处理。例如,我们需要提取 SQL 语句中的表名、字段名、条件表达式等信息,或者需要对 SQL 语句进行修改、优化等操作。手动编写 SQL 解析器既耗时又容易出错,这时候,一个强大的 SQL 解析器就显得尤为重要。

  jsqlparser (Java SQL Parser) 是一个开源的 Java SQL 解析器,它可以将 SQL 语句解析成抽象语法树(Abstract Syntax Tree,AST),然后我们可以通过遍历 AST 来获取 SQL 语句中的各种信息,或者进行其他操作。本文将深入探讨 jsqlparser 的功能和用法,并提供代码示例和最佳实践。

  官网 api 文档和 github 地址如下:

  jsqlparser API 文档

  jsqlparser github 地址

一、为什么选择 JSqlParser?

  1. 功能强大: jsqlparser 支持解析各种复杂的 SQL 语句,包括 SELECT, INSERT, UPDATE, DELETE等。
  2. 支持多种数据库: 虽然是基于标准 SQL 实现的,但它能较好地兼容各种主流数据库的 SQL 方言,如MySQL、PostgreSQL、Oracle 等。
  3. 灵活可扩展: jsqlparser的架构设计良好,易于扩展和定制。你可以根据自己的需要,自定义 AST 节点的处理逻辑。
  4. 活跃的社区: jsqlparser拥有一个活跃的社区,提供了丰富的文档和示例,可以快速上手使用。
  5. 易于集成: 可以轻松地集成到 Java 项目中。

二、JSqlParser 的核心概念

  1. SQL 语句: 你要解析的 SQL 语句字符串。
  2. Parser: jsqlparser 的核心组件,用于将 SQL 语句解析成 AST。
  3. AST (Abstract Syntax Tree): SQL 语句的抽象语法树表示,由各种节点组成,每个节点代表 SQL语句中的一个语法单元(例如,表名、字段名、表达式等)。
  4. Visitor: 用于遍历 AST 的组件,你可以通过实现 Visitor接口,自定义对不同 AST 节点的处理逻辑。
  5. Statement: AST 的根节点,代表一个完整的 SQL 语句。
  6. Expression: 表达式的节点,例如 WHERE 条件中的表达式。

三、实践:使用 JSqlParser 解析 SQL 语句

3.1 添加 jsqlparser 依赖

java 复制代码
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>4.7</version>
</dependency>

请确保版本号与你的项目兼容。mybatis-plus-boot-starter和pagehelper-spring-boot-starter都包含该依赖项。

3.2 使用案例

查询字段

java 复制代码
 private static List<String> test_select_items(String sql) throws JSQLParserException {
     CCJSqlParserManager parserManager = new CCJSqlParserManager();
     Select select = (Select) CCJSqlParserUtil.parse(new StringReader(sql));
     PlainSelect plain = (PlainSelect) select.getSelectBody();
     List<SelectItem> selectitems = plain.getSelectItems();
     List<String> str_items = new ArrayList<String>();
     if (selectitems != null) {
         for (SelectItem selectitem : selectitems) {
             str_items.add(selectitem.toString());
         }
     }
     return str_items;
 }

查询 join

java 复制代码
private static List<String> test_select_join(String sql) throws JSQLParserException {
    Statement statement = CCJSqlParserUtil.parse(sql);
    Select selectStatement = (Select) statement;
    PlainSelect plain = (PlainSelect) selectStatement.getSelectBody();
    List<Join> joinList = plain.getJoins();
    List<String> tablewithjoin = new ArrayList<String>();
    if (joinList != null) {
        for (Join join : joinList) {
            join.setLeft(false);
            tablewithjoin.add(join.toString());
            //注意 , leftjoin rightjoin 等等的to string()区别
        }
    }
    return tablewithjoin;
}

查询表名 table

java 复制代码
private static List<String> test_select_table(String sql) throws JSQLParserException {
    Statement statement = CCJSqlParserUtil.parse(sql);
    Select selectStatement = (Select) statement;
    TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
    return tablesNamesFinder.getTableList(selectStatement);
}

查询 where

java 复制代码
private static String test_select_where(String sql) throws JSQLParserException {
    CCJSqlParserManager parserManager = new CCJSqlParserManager();
    Select select = (Select) parserManager.parse(new StringReader(sql));
    PlainSelect plain = (PlainSelect) select.getSelectBody();
    Expression where_expression = plain.getWhere();
    return where_expression.toString();
}

查询 group by

java 复制代码
private static List<String> test_select_groupby(String sql) throws JSQLParserException {
    CCJSqlParserManager parserManager = new CCJSqlParserManager();
    Select select = (Select) parserManager.parse(new StringReader(sql));
    PlainSelect plain = (PlainSelect) select.getSelectBody();
    List<Expression> GroupByColumnReferences = plain.getGroupByColumnReferences();
    List<String> str_groupby = new ArrayList<String>();
    if (GroupByColumnReferences != null) {
        for (Expression groupByColumnReference : GroupByColumnReferences) {
            str_groupby.add(groupByColumnReference.toString());
        }
    }
    return str_groupby;
}

查询 order by

java 复制代码
private static List<String> test_select_orderby(String sql) throws JSQLParserException {
   CCJSqlParserManager parserManager = new CCJSqlParserManager();
   Select select = (Select) parserManager.parse(new StringReader(sql));
   PlainSelect plain = (PlainSelect) select.getSelectBody();
   List<OrderByElement> OrderByElements = plain.getOrderByElements();
   List<String> str_orderby = new ArrayList<String>();
   if (OrderByElements != null) {
       for (OrderByElement orderByElement : OrderByElements) {
           str_orderby.add(orderByElement.toString());
       }
   }
   return str_orderby;
}

查询 子查询

java 复制代码
private static Map test_select_subselect(SelectBody selectBody) throws JSQLParserException {
    Map<String, String> map = new HashMap<String, String>();

    if (selectBody instanceof PlainSelect) {
        List<SelectItem> selectItems = ((PlainSelect) selectBody).getSelectItems();
        for (SelectItem selectItem : selectItems) {
            if (selectItem.toString().contains("(") && selectItem.toString().contains(")")) {
                map.put("selectItemsSubselect", selectItem.toString());
            }
        }

        Expression where = ((PlainSelect) selectBody).getWhere();
        String whereStr = where.toString();
        if (whereStr.contains("(") && whereStr.contains(")")) {
            int firstIndex = whereStr.indexOf("(");
            int lastIndex = whereStr.lastIndexOf(")");
            CharSequence charSequence = whereStr.subSequence(firstIndex, lastIndex + 1);
            map.put("whereSubselect", charSequence.toString());
        }

        FromItem fromItem = ((PlainSelect) selectBody).getFromItem();
        if (fromItem instanceof SubSelect) {
            map.put("fromItemSubselect", fromItem.toString());
        }

    } else if (selectBody instanceof WithItem) {
        SqlParser.test_select_subselect(((WithItem) selectBody).getSelectBody());
    }
    return map;
}

查询多个sql语句

java 复制代码
String sqls = "SELECT * FROM TABLE1;SELECT * FROM TABLE2";

//方法1
Statements statements = CCJSqlParserUtil.parseStatements(sqls);

//方法2
CCJSqlParser ccjSqlParser = new CCJSqlParser(sqls);
Statements statements = ccjSqlParser.Statements();
List<Statement> statementList = statements.getStatements();

表达式解析

java 复制代码
//表达式
Expression expression = CCJSqlParserUtil.parseExpression("a+b*c");
//条件表达式
Expression expression = CCJSqlParserUtil.parseCondExpression("A='123'");

将别名应用于所有表达式

java 复制代码
Select select = (Select) CCJSqlParserUtil.parse("SELECT A,B,C FROM TABLE1");//此处的运行时类是Select
SelectBody selectBody = select.getSelectBody();
AddAliasesVisitor addAliasesVisitor = new AddAliasesVisitor();
addAliasesVisitor.setPrefix("B");//设置前缀(如不进行设置默认为"A")
selectBody.accept(addAliasesVisitor);
System.out.println(selectBody.toString());//SELECT A AS B1, B AS B2, C AS B3 FROM TABLE1

向SELECT添加一列或表达式

java 复制代码
Select select = (Select) CCJSqlParserUtil.parse("SELECT A FROM TABLE1");
SelectUtils.addExpression(select, new Column("B"));
System.out.println(select);//SELECT A, B FROM TABLE1

四、高级特性

  1. 多表联接:解析和构建复杂的 JOIN 操作。
  2. 子查询支持:处理嵌套查询和其他复杂结构。
  3. DML 和 DDL 支持:不仅限于 SELECT语句,还支持 INSERT、UPDATE、DELETE 以及 CREATE TABLE 等操作。
  4. 事件监听器:可以通过自定义事件监听器监控解析过程中的特定节点。 SQL 方言兼容性:支持多种主流数据库系统的 SQL 方言。

五、实际应用场景

  1. SQL 审计工具:分析和记录应用程序发出的所有 SQL 查询,确保它们符合安全性和性能标准。
  2. 动态查询生成器:根据用户输入动态构建SQL 查询,同时保证语法正确性和安全性。
  3. ORM 框架集成:辅助 ORM 框架实现更加灵活的数据映射和查询优化。
  4. SQL性能调优:识别潜在的性能瓶颈,并提出优化建议。

六、最佳实践

  1. 异常处理: 在解析 SQL 语句时,使用 try-catch 代码块处理可能抛出的异常。
  2. 选择合适的 Visitor:根据实际需要选择合适的 Visitor 接口,例如 StatementVisitorAdapter、ExpressionVisitorAdapter。
  3. 避免修改 AST: 除非有特殊需求,否则不要直接修改 AST,避免破坏 SQL 语句的结构。
  4. 缓存解析结果: 如果需要多次解析相同的 SQL 语句,可以将解析结果缓存起来,提高性能。
  5. 了解 SQL 语法: jsqlparser 的 API 和数据结构与 SQL 语法紧密相关,需要对 SQL 语法有一定的了解才能更好地使用。
  6. 根据实际情况扩展 : jsqlparser 提供了灵活的扩展机制,可以根据实际需求自定义 AST 节点的处理逻辑,例如实现 SQL
    语句的校验、优化、改写等功能。
相关推荐
weixin_440784113 小时前
【HandlerThread实现原理】
android·java·开发语言
天空'之城3 小时前
C 语言工业级通用组件手写 24:互补滤波
c语言·开发语言·互补滤波·嵌入式算法·工业级组件
毛驴赶鹿3 小时前
【金仓数据库征文】KES V9性能调优实战记录:从慢SQL排查到全链路优化
数据库·sql
leoZ2313 小时前
本地跑大模型实战(七):llama.cpp 性能调优,让推理更快更省
java·人工智能·spring·生成对抗网络·语言模型·自然语言处理·llama
XR1234567883 小时前
工厂车间无线网络方案对比:AGV漫游哪家强?
开发语言·php
傻啦嘿哟3 小时前
某短视频平台视频爬虫实战:抓取推荐流视频信息,绕过反爬的3种技巧
开发语言·爬虫·python
現実君4 小时前
【硬件进阶】AD22上位替换JLEDA教程
开发语言·php
mubei-1234 小时前
SpringDAO的用法
java·开发语言·数据库
happymagic4 小时前
java spring boot做的jar包程序,如何实现自动运行启动
java·运维·服务器·spring boot·jar
格林威6 小时前
多相机微秒级对齐:硬件触发 vs PTP(IEEE 1588)方案实战对比
开发语言·人工智能·数码相机·机器学习·计算机视觉·视觉检测·机器视觉