当前位置: 首页 > article >正文

《通俗易懂 · JSqlParser 解析和构造SQL》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗

🌻 希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

    • 写在前面的话
    • 技术简介
      • 基础间接
      • 特色说明
      • 核心功能
      • 竞品分析
    • 技术扩展
      • 基础入门
      • 主要模块
      • 解析SQL能力
      • 构建SQL能力
    • 企业实战
      • 运用场景
      • 企业实战 - SQL 解析
      • 企业实战 - SQL 生成
    • 总结陈词

写在前面的话

博主公司在近期新产品的技术选型当中,涉及到SQL解析与生成等场景,这部分选择了JSqlParser,这里进行简单的介绍说明,大家可以按需选择。

参考:JSqlParser 官网


技术简介

基础间接

JSqlParser 是一个功能强大的 Java 库,专为解析、修改和生成 SQL 语句而设计。它不仅能够处理复杂的 SQL 语法,还提供了丰富的 API,使得开发者可以轻松地对 SQL 语句进行各种操作。JSqlParser 的主要目标是为开发人员提供一个可靠的工具,以便在应用程序中高效地管理和优化 SQL 语句。

JSqlParser 的设计理念是简单易用,同时具备高度的灵活性和扩展性。无论是初学者还是经验丰富的开发人员,都可以通过 JSqlParser 快速上手并解决实际问题。该库支持多种 SQL 方言,包括 MySQL、PostgreSQL、Oracle 和 SQL Server 等,这使得它在多数据库环境中具有广泛的应用前景。

特色说明

1、功能丰富

JSqlParser 提供了全面的 SQL 解析功能,可以解析几乎所有的 SQL 语句类型,包括但不限于 SELECT、INSERT、UPDATE、DELETE、CREATE TABLE 和 ALTER TABLE 等。此外,它还支持复杂的子查询、联合查询和嵌套查询等高级语法。通过 JSqlParser,开发人员可以轻松地将 SQL 语句转换为抽象语法树(AST),从而方便地进行进一步的操作和分析。

2、易于使用与扩展

JSqlParser 的 API 设计简洁明了,使得开发人员可以快速上手并集成到现有的项目中,解析或生成一个简单的 SQL 语句只需要几行代码。

3、高度可扩展

JSqlParser 不仅提供了丰富的内置功能,还允许开发人员根据需要进行扩展。例如,可以通过实现自定义的 Visitor 接口来遍历和修改抽象语法树。这种灵活性使得 JSqlParser 可以适应各种复杂的应用场景,如 SQL 语句的动态生成、查询优化和安全性检查等。

4、支持多种 SQL 方言

JSqlParser 支持多种主流的 SQL 方言,包括 MySQL、PostgreSQL、Oracle 和 SQL Server 等。这意味着开发人员可以在不同的数据库环境中使用同一个库,而无需担心兼容性问题。这种跨平台的支持使得 JSqlParser 成为了一个多数据库应用的理想选择。

核心功能

前面已经写了太多文字了,核心功能不展开了。

无非就是:SQL解析、SQL调整、SQL生成。

再简化一下:SQL解析和生成!

竞品分析

JSqlParser 同类型的工具,用于解析 SQL 语句,主要有以下几种:

  • **SQLparser: **一个轻量级的 Java 库,专注于 SQL 解析,支持多种 SQL 方言,功能相对 JSqlParser 较为精简。
  • **Apache Calcite: **一个用于 SQL 查询优化的框架,也包含 SQL 解析器, 更侧重于查询优化和执行,解析功能作为其一部分。
  • **ANTLR: ** 一个强大的语法分析工具,可以用于构建自定义的 SQL 解析器,灵活性高,但需要用户定义语法规则。
  • **SQL-Java: **一个用于解析和操作 SQL 语句的 Java 库,功能与 JSqlParser 类似,但可能在特定方面有不同侧重。

温馨提示:个人认为,这种工具类的,不用花费大量时间去纠结技术选型,能满足开发需求,用的顺手即可。


技术扩展

基础入门

Tips:截止编写日期,最新版本是5.0,需要使用JDK11以上版本。

Tips:针对后端框架没有限制,可以当作 Hutools 那样的工具类使用。

Step1、引入Maven依赖

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

Step2、编写SQL解析的测试方法

@Test
public void parseSqlTemp() throws JSQLParserException {
    String sql = "SELECT id,name FROM staff_member WHERE nickname= '刘'";
    Statement statement = CCJSqlParserUtil.parse(sql);
    if (statement instanceof Select selectStatement) {
        PlainSelect plainSelect = selectStatement.getPlainSelect();
        log.info("==> JsqlParser SQL: {}", selectStatement);
        log.info("==> FromItem: {}", plainSelect.getFromItem());
        log.info("==> SelectItem: {}", plainSelect.getSelectItems());
        log.info("==> Where: {}", plainSelect.getWhere());
        log.info("==> Joins: {}", plainSelect.getJoins());
    }
}

// 输出信息:
==> JsqlParser SQL: SELECT id, name FROM staff_member WHERE nickname = '刘'
==> FromItem: staff_member
==> SelectItem: [id, name]
==> Where: nickname = '刘'

Step3、编写SQL生成的测试方法

@Test
public void testSql1() {
    Table t1 = new Table("tab1").withAlias(new Alias("t1").withUseAs(true)); // 表1
    Table t2 = new Table("tab2").withAlias(new Alias("t2", false)); // 表2
    PlainSelect plainSelect = new PlainSelect().addSelectItems(new AllColumns())
            .withFromItem(t1); // SELECT * FROM tab1 AS t1
    Join join = new Join(); // 创建Join对象
    join.withRightItem(t2); // 添加Join的表 JOIN t2 =>JOIN tab2 t2
    EqualsTo equalsTo = new EqualsTo(); // 添加 = 条件表达式  t1.user_id  = t2.user_id
    equalsTo.setLeftExpression(new Column(t1, "user_id "));
    equalsTo.setRightExpression(new Column(t2, "user_id "));
    join.withOnExpression(equalsTo);// 添加ON
    plainSelect.addJoins(join);
    System.err.println(plainSelect); 
    // SELECT * FROM tab1 AS t1 JOIN tab2 t2 ON t1.user_id  = t2.user_id
}

主要模块

1. Expression

expression 包含了 SQL 中的各种表达式类,这些类用于构建和表示 SQL 语句中的条件和表达式。常见的表达式类包括:

EqualsTo:表示等于操作符(=)。

InExpression:表示 IN 操作符,用于检查某个值是否在一组值中。

BinaryExpression:表示二元操作符(如加法、减法等)。

Function:表示 SQL 函数调用。

Column:表示 SQL 中的列。

这些表达式类使得用户可以方便地构建复杂的 SQL 查询条件。

2. Schema

schema 模块主要用于表示数据库的结构,包括表、列、约束等。常见的类包括:

Table:表示数据库中的表。

Column:表示表中的列,通常包含列名、数据类型等信息。

Index:表示表上的索引。

ForeignKey:表示表之间的外键关系。

这些类帮助用户理解和操作数据库的结构,便于进行元数据的管理和查询。

3. Parser

parser 模块包含了 SQL 解析的相关类,主要负责将 SQL 字符串解析为 Java 对象。常见的类包括:

CCJSqlParserUtil:提供了静态方法来解析 SQL 语句,返回相应的语法树。

SQLParser:核心解析器,负责将 SQL 语句分解为不同的部分。

ExpressionParser:用于解析 SQL 中的表达式部分。

通过这些类,用户可以轻松地将 SQL 语句转换为可操作的 Java 对象。

4. Statement

statement 模块封装了各种数据库操作的对象,表示不同类型的 SQL 语句。常见的类包括:

Select:表示 SELECT 查询语句。

Insert:表示 INSERT 操作。

Update:表示 UPDATE 操作。

Delete:表示 DELETE 操作。

CreateTable:表示创建表的语句。

这些类使得用户可以方便地构建、修改和执行 SQL 语句。

解析SQL能力

JSqlParser可以解析SQL为Java对象,以便于获取SQL中的相关信息或可进行修改。

一般通过 CCJSqlParserUtil 和 CCJSqlParserManager 解析SQL。

CCJSqlParserUtil 适合于简单的情况,CCJSqlParserManager 则拥有更强大的功能。

【示例代码】

这边来一段示例代码,其实自己准备一个SQL,尝试一下很容易理解。

基本步骤:使用CCJSqlParserUtil解析sql,获得Statement,如果是查询就转换PlainSelect类型,然后调其相关方法获取更详细的内容。

@Test
public void testSelectAll() throws JSQLParserException {

    // 准备一段基础SQL
    String sql = """
        SELECT t.pres_no,
               t.patient_id,
               t.exec_dept_code,
               t1.patient_id,
               t1.patient_name,
               t1.patient_sex,
               (SELECT a.DEPT_NAME
                  FROM ZOEDICT.DIC_DEPT_DICT a
                 WHERE a.DEPT_CODE = t.exec_dept_code) AS "EXEC_DEPT_NAME"
          FROM ZOEPRES.PRES_OUTP_PRES_MASTER t
          JOIN zoepatient.pat_basic_info t1 ON t.patient_id = t1.patient_id
         WHERE t.patient_id = '5000022933'
           AND t.event_no = 'D3389411'
        """;

    // 利用 CCJSqlParserUtil 解析SQL
    Statement statementTemp = CCJSqlParserUtil.parse(sql);
    if (statementTemp instanceof Select selectStatement) {
        PlainSelect plainSelect = selectStatement.getPlainSelect();
        log.info("===> JsqlParser SQL: {}", selectStatement);
        log.info("===> FromItem: {}", plainSelect.getFromItem());
        log.info("===> SelectItem: {}", plainSelect.getSelectItems());
        log.info("===> Where: {}", plainSelect.getWhere());
        log.info("===> Join: {}", plainSelect.getJoins());
    }

    // 创建 TablesNamesFinder 实例,提取表名
    // 这里会连JOIN和子查询的表一起输出,总共三张
    TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
    List<String> tableList = tablesNamesFinder.getTableList(statementTemp);
    System.out.println(tableList);
}

【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语句列表。

构建SQL能力

构建SQL其实相当于解析SQL的逆过程,是构造组装一个PlainSelect的过程,进而得到SQL语句。

可以从无到有构造SQL,也可以基于原有SQL,解析出PlainSelect对象后,再进行修改。

操作方法很简单,直接看下方示例,企业实战中,通常会搭配自定义语义解析模板使用。

【示例代码】

@Test
public void buildSqlTemp() throws JSQLParserException {

    // 定义表
    Table table = new Table().withName("zoepres.pres_outp_pres_master")
            .withAlias(new Alias("t", false));

    // 定义查询列
    Column columnA = new Column().withColumnName("patient_id");
    Column columnB = new Column().withColumnName("pres_no");
    Column columnC = new Column().withColumnName("event_no");

    // 定义查询列的条件
    Expression whereExpression = new EqualsTo().withLeftExpression(columnA)
            .withRightExpression(new StringValue("5000022928"));

    // 定义查询
    PlainSelect select = new PlainSelect().addSelectItem(new LongValue(123))
            .withFromItem(table)
            .withWhere(whereExpression);

    // 增加函数表达式
    select.addSelectItem(columnB, new Alias("presNo"));

    // 增加子查询
    Expression expr2 = CCJSqlParserUtil.parseExpression("(select count(1) from dual)");
    select.addSelectItem(expr2, new Alias("id124"));

    // 增加函数处理逻辑
    Function function = new Function();
    function.setName("REPLACE");
    List<Expression> parameters = new ArrayList<>();
    parameters.add(columnC);
    parameters.add(new StringValue("a"));
    parameters.add(new StringValue("b"));
    function.setParameters(new ExpressionList(parameters));
    select.addSelectItem(function);

    System.out.println(select);
}

//输出信息如下:
SELECT 123,
       pres_no AS presNo,
       (SELECT count(1) FROM dual) AS id124,
       REPLACE(event_no, 'a', 'b')
  FROM zoepres.pres_outp_pres_master t
 WHERE patient_id = '5000022928'

企业实战

运用场景

Java SQL 解析器(如 JSQLParser)在多个场景中发挥着重要作用,以下是一些具体的应用场景:
1、构建数据库管理工具:在开发数据库客户端或管理界面时,Java SQL 解析器可以解析用户输入的 SQL 查询。这使得工具能够执行相应的操作,例如执行查询、更新数据库结构或管理数据,从而提升用户体验和操作效率。
2、实现自定义的 SQL 分析工具:当需要对大量 SQL 查询进行深入分析时,Java SQL 解析器可以帮助解析这些查询,以识别查询模式、性能瓶颈等。通过编写自定义分析逻辑,开发者可以获得更深入的洞察,优化数据库性能。
3、定制 SQL 解析和执行逻辑:在某些情况下,标准的数据库接口可能无法满足特定需求。此时,Java SQL 解析器可以解析 SQL 查询,并允许开发者编写自定义的执行逻辑,以实现更复杂的功能或满足特定的业务需求。
4、实现查询优化器:如果希望深入了解查询优化器的工作原理,Java SQL 解析器可以解析 SQL 查询,并基于解析结果实现自己的查询优化器。这为开发者提供了一个实验和学习的机会,以优化查询性能。
5、实现 SQL 注入检测工具:SQL 注入是常见的安全漏洞之一。为了防止 SQL 注入攻击,开发者可使用Java 的SQL解析器解析用户输入的 SQL 查询,并检测其中是否包含潜在的注入漏洞。这种检测机制有助于增强应用程序的安全性,保护数据库免受攻击。
通过这些应用场景,可以看出 Java SQL 解析器在数据库管理、性能优化、安全检测等方面的重要性,帮助开发者更高效地处理 SQL 查询和相关操作。

企业实战 - SQL 解析

【场景说明】

博主所在公司开发了数据中心产品线,涉及较多与数据打交道的数据中心工具和产品,诸如票据设计器、报表系统、床位导航等,这些产品都涉及可视化配置界面,需要通过工程人员编写的SQL语句,作为页面的数据来源,同时需要具备从SQL中解析出各项元素,进行后续操作。例如,提取出SELECT后面的元素,生成返回值表格列,允许设置表名、宽度、类型等内容。

【场景使用】

针对上述场景,使用JSQLParser来实现,就相当简单了,很轻易的将SQL语法进行剖析,分解出各个组成部分,一览无余,按需使用。

只能说使用JSQLParser来解析SQL,真是庖丁解牛、游刃有余。

企业实战 - SQL 生成

【场景说明】

无独有偶,博主所在公司继续开发低代码平台,部分产品线决定不通过写SQL完成配置,而是完全采用通俗易懂的拖拉拽和可视化表单编辑等方式,生成页面逻辑。为此,我们需要将前端元素转换为一个可执行的SQL语法。

【场景使用】

针对上述场景,使用JSQLParser来实现,也是相当简单了。

当然,我们没有直接解析前端传参,然后进行JSQLParser处理,而是在中间增加了一层JSON模板,前端根据该模板约定组装入参,后端增加将JSON模板使用JSQLParser解析成相应SQL。通常这种方式,使查询语义可以在各个产品线更好的共享,增强了扩展性。


总结陈词

本篇文章大体介绍了JSqlParser实现SQL的解析和生成,由于篇幅所限,仅展示简单示例分析。

💗 后续也会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。


http://www.kler.cn/a/411239.html

相关文章:

  • 前向概率与隐马尔可夫模型的解码问题
  • CSS —— 子绝父相
  • 系统设计时应时刻考虑设计模式基础原则
  • ORB-SLAM2源码学习:Initializer.cc:Initializer::ComputeF21地图初始化——计算基础矩阵
  • git 命令之只提交文件的部分更改
  • 深度学习模型入门与实践:从零开始理解 AI 的核心
  • Java【多线程】(1)进程与线程
  • YOLO系列论文综述(从YOLOv1到YOLOv11)【第1篇:概述物体检测算法发展史、YOLO应用领域、评价指标和NMS】
  • 基于数据融合的智能家居环境监测系统研究与设计(论文+源码)
  • 在Hadoop上实现分布式深度学习
  • 网络安全风险评估
  • ubuntu设置程序开机自启动
  • 互联网视频推拉流EasyDSS视频直播点播平台视频转码有哪些技术特点和应用?
  • w056基于web的教学资源库
  • 一个计算频率的模块
  • 网络安全事件管理
  • TCP IP协议和网络安全
  • 11.26作业
  • 【ONE·基础算法 || 动态规划(二)】
  • 【DL笔记】神经网络轻量化(CV方向)的一些论文记录
  • PyQt6+pyqtgraph折线图绘制显示
  • 计算机毕业设计Python+大模型美食推荐系统 美食可视化 美食数据分析大屏 美食爬虫 美团爬虫 机器学习 大数据毕业设计 Django Vue.js
  • 【k8s】资源限制管理:Namespace、Deployment与Pod的实践
  • Javaee的网络编程初识
  • SLF4J日志快速上手
  • TCP/IP协议攻击与防范