Skip to content

第二部分:JSqlParser 核心功能详解 (Intermediate)

模块三:解析 DML (Data Manipulation Language) 语句

数据操作语言 (DML) 用于管理数据库中的数据。JSqlParser 支持对核心的 DML 语句进行解析,包括 INSERT, UPDATE, 和 DELETE。

1. 解析 INSERT 语句 (Insert)

INSERT 语句用于向表中添加新的数据行。com.github.jsqlparser.statement.insert.Insert 类代表了解析后的 INSERT 语句。

一个 Insert 对象主要包含以下部分:

  • 目标表 (Table):
    • getTable(): 返回一个 Table 对象,表示要插入数据的目标表。
  • 插入列列表 (List<Column>):
    • getColumns(): 返回一个 List<Column> 对象。如果 INSERT 语句指定了要插入的列名 (例如 INSERT INTO users (id, name) VALUES ...),则此列表包含这些 Column 对象。如果未指定列名 (即为表的所有列按默认顺序插入),则此方法可能返回 null 或空列表。
  • 插入的值 (ItemsList):
    • getItemsList(): 返回一个 com.github.jsqlparser.statement.select.ItemsList 接口的实例,表示要插入的数据来源。ItemsList 有两个主要的实现:
      • ExpressionList: 代表 VALUES (expr1, expr2, ...), (exprA, exprB, ...) 这种形式,其中包含一个或多个值元组。
        • getExpressions(): 返回 List<Expression>,其中每个 Expression 通常是一个字面量 (如 StringValue, LongValue, NullValue) 或简单的表达式。如果插入多行,ExpressionList 内部会有一个 List<List<Expression>> 的结构 (通过 getExpressionLists() 获取更精确的多行结构,或者 getExpressions() 会返回一个扁平化的列表,其中每个元素是一个 RowConstructor 或 MultiExpressionList 的实例,代表一行)。
        • JSqlParser 4.7+ 对多行 VALUES 的处理:ExpressionList 的 getExpressions() 会返回一个 List<Expression>,其中每个 Expression 是一个 RowConstructor 对象,代表一行数据。RowConstructor 的 getExprList().getExpressions() 才能拿到该行的具体表达式列表。
      • SubSelect: 代表 INSERT INTO ... SELECT ... 这种形式,其中要插入的数据来源于一个子查询的结果集。
        • 可以通过 getSelectBody() 获取子查询的 SelectBody。
  • ON DUPLICATE KEY UPDATE / ON CONFLICT (特定方言):
    • getOnDuplicateKeyUpdate(): 返回一个 List<UpdateSet> (JSqlParser 4.6 之前是 List<Column> updatedColumnList 和 List<Expression> duplicateUpdateExpressionList),用于 MySQL 的 ON DUPLICATE KEY UPDATE col = expr, ...。
    • getOnConflict(): 返回 OnConflictExpression 对象,用于 PostgreSQL 和 SQLite 的 ON CONFLICT ... DO ...。
      • getConflictAction(): 例如 DO NOTHING 或 DO UPDATE。
      • getUpdateSet(): 如果是 DO UPDATE SET ...,这里包含更新的列和值。
      • getConflictTarget(): 冲突目标,例如 ON CONSTRAINT constraint_name 或 (column_name, ...)。
  • RETURNING / OUTPUT (特定方言):
    • getReturningExpressionList(): 返回 List<SelectExpressionItem>,用于 PostgreSQL 和 Oracle 的 RETURNING ... 子句,返回插入/更新/删除后的数据。
    • JSqlParser 对 SQL Server 的 OUTPUT 子句也有相应的支持。
  • 修饰符:
    • isUseValues(): 是否使用了 VALUES 关键字。
    • isUsePriority(): 是否有 INSERT LOW_PRIORITY / HIGH_PRIORITY (MySQL)。
    • isUseIgnore(): 是否有 INSERT IGNORE (MySQL)。
    • isModifierOracleHint(): 是否有 Oracle 的 INSERT /*+ ... */ ... 提示。

示例代码:

import com.github.jsqlparser.parser.CCJSqlParserUtil;
import com.github.jsqlparser.statement.Statement;
import com.github.jsqlparser.statement.insert.Insert;
import com.github.jsqlparser.statement.select.SelectBody;
import com.github.jsqlparser.statement.select.SubSelect;
import com.github.jsqlparser.expression.Expression;
import com.github.jsqlparser.expression.RowConstructor;
import com.github.jsqlparser.expression.operators.relational.ExpressionList;
import com.github.jsqlparser.schema.Column;
import com.github.jsqlparser.schema.Table;
import com.github.jsqlparser.JSQLParserException;
import java.util.List;

public class ParseInsertDemo {
public static void main(String[] args) {
String sqlInsertValues = "INSERT INTO employees (id, name, department) VALUES (101, 'John Doe', 'Sales'), (102, 'Jane Smith', 'Marketing')";
String sqlInsertSelect = "INSERT INTO archive_employees (id, name) SELECT emp_id, emp_name FROM employees WHERE status = 'archived'";

    try {  
        System.out.println("--- Parsing INSERT ... VALUES \---");  
        parseInsert(sqlInsertValues);

        System.out.println("\\n--- Parsing INSERT ... SELECT \---");  
        parseInsert(sqlInsertSelect);

    } catch (JSQLParserException e) {  
        e.printStackTrace();  
    }  
}

public static void parseInsert(String sql) throws JSQLParserException {  
    Statement statement \= CCJSqlParserUtil.parse(sql);  
    if (statement instanceof Insert) {  
        Insert insertStatement \= (Insert) statement;

        Table table \= insertStatement.getTable();  
        System.out.println("Target Table: " \+ table.getFullyQualifiedName());

        List\<Column\> columns \= insertStatement.getColumns();  
        if (columns \!= null) {  
            System.out.println("Columns to Insert:");  
            for (Column column : columns) {  
                System.out.println("  \- " \+ column.getColumnName());  
            }  
        } else {  
            System.out.println("Columns: All (not explicitly listed)");  
        }

        if (insertStatement.getItemsList() instanceof ExpressionList) {  
            System.out.println("Inserting VALUES:");  
            ExpressionList expressionList \= (ExpressionList) insertStatement.getItemsList();  
            // For JSqlParser 4.7+ and multi-row inserts, expressions are RowConstructors  
            for (Expression rowExpr : expressionList.getExpressions()) {  
                if (rowExpr instanceof RowConstructor) {  
                    RowConstructor row \= (RowConstructor) rowExpr;  
                    System.out.println("  Row Values: " \+ row.getExprList().getExpressions());  
                } else {  
                     // Fallback for single row or older JSqlParser versions might have expressions directly  
                    System.out.println("  Value Expression (single row or older JSqlParser): " \+ rowExpr);  
                }  
            }  
            // For older JSqlParser versions or single row inserts, you might access expressions directly  
            // or use getExpressionLists() if available and appropriate.  
            // Example for older style or if not using RowConstructor:  
            // List\<Expression\> values \= expressionList.getExpressions();  
            // System.out.println("  Values: " \+ values);

        } else if (insertStatement.getItemsList() instanceof SubSelect) {  
            System.out.println("Inserting from SELECT subquery:");  
            SubSelect subSelect \= (SubSelect) insertStatement.getItemsList();  
            SelectBody selectBody \= subSelect.getSelectBody();  
            System.out.println("  SubSelect Query: " \+ selectBody.toString());  
            // You can further analyze the selectBody as shown in Module 2  
        }

        if (insertStatement.getOnDuplicateKeyUpdate() \!= null && \!insertStatement.getOnDuplicateKeyUpdate().isEmpty()) {  
            System.out.println("ON DUPLICATE KEY UPDATE exists.");  
            // Analyze insertStatement.getOnDuplicateKeyUpdate() which is List\<UpdateSet\>  
        }

        if (insertStatement.getReturningExpressionList() \!= null && \!insertStatement.getReturningExpressionList().isEmpty()){  
            System.out.println("RETURNING clause exists: " \+ insertStatement.getReturningExpressionList());  
        }

    } else {  
        System.out.println("Not an INSERT statement: " \+ sql);  
    }  
}  

}

2. 解析 UPDATE 语句 (Update)

UPDATE 语句用于修改表中已存在行的数据。com.github.jsqlparser.statement.update.Update 类代表了解析后的 UPDATE 语句。

一个 Update 对象主要包含以下部分:

  • 目标表 (Table 或 List<Table>):
    • getTable(): 对于单表更新,返回被更新的 Table 对象。
    • getTables(): (JSqlParser 4.0+) 对于多表更新语法 (如 UPDATE t1 JOIN t2 ON ... SET t1.col = ...),此方法返回一个 List<Table>。通常情况下,单表更新时,getTable() 足够。
  • 更新的列和值 (List<UpdateSet>):
    • getUpdateSets(): (JSqlParser 4.7+) 返回一个 List<UpdateSet>。每个 UpdateSet 对象包含:
      • getColumns(): List<Column>,要更新的列 (通常是一个,但支持 (col1, col2) = (expr1, expr2) 或 (col1, col2) = (SELECT ...) 这种元组赋值)。
      • getExpressions(): List<Expression>,对应的新值表达式。
    • 旧版 JSqlParser (4.6 及更早):
      • getColumns(): 返回 List<Column>,代表 SET 子句中被赋值的列。
      • getExpressions(): 返回 List<Expression>,代表赋给对应列的新值。这两个列表的元素一一对应。
  • WHERE 条件 (Expression):
    • getWhere(): 返回一个 Expression 对象,代表 WHERE 子句的过滤条件。如果不存在 WHERE 子句 (即更新所有行),则返回 null。分析方式与 SELECT 语句的 WHERE 条件相同。
  • FROM 子句 (用于连接更新,特定方言如 PostgreSQL, SQL Server):
    • getFromItem(): 返回 FromItem,代表 UPDATE ... FROM ... WHERE ... 语法中的 FROM 部分。
    • getJoins(): 如果 FROM 子句中包含 JOIN,则返回 List<Join>。
  • ORDER BY 和 LIMIT (特定方言如 MySQL):
    • getOrderByElements(): 返回 List<OrderByElement>。
    • getLimit(): 返回 Limit 对象。
  • RETURNING / OUTPUT (特定方言):
    • getReturningExpressionList(): 类似于 INSERT。

示例代码:

import com.github.jsqlparser.parser.CCJSqlParserUtil;
import com.github.jsqlparser.statement.Statement;
import com.github.jsqlparser.statement.update.Update;
import com.github.jsqlparser.statement.update.UpdateSet; // For JSqlParser 4.7+
import com.github.jsqlparser.expression.Expression;
import com.github.jsqlparser.schema.Column;
import com.github.jsqlparser.schema.Table;
import com.github.jsqlparser.JSQLParserException;
import java.util.List;

public class ParseUpdateDemo {
public static void main(String[] args) {
String sqlSingleTable = "UPDATE employees SET salary = salary * 1.1, department = 'Engineering' WHERE years_of_service > 5 AND current_status = 'Active'";
String sqlMultiTableMySQL = "UPDATE items i JOIN item_categories ic ON i.category_id = ic.id SET i.discount_rate = 0.15 WHERE ic.name = 'Electronics'"; // JSqlParser might parse this differently based on strictness

    try {  
        System.out.println("--- Parsing Single-Table UPDATE \---");  
        parseUpdate(sqlSingleTable);

        // Note: JSqlParser's handling of multi-table UPDATE syntax can vary.  
        // The standard SQL way is often through subqueries or CTEs.  
        // For specific vendor syntax, ensure your JSqlParser version supports it or use a more general approach.  
        // System.out.println("\\n--- Parsing Multi-Table UPDATE (MySQL syntax example) \---");  
        // parseUpdate(sqlMultiTableMySQL);

    } catch (JSQLParserException e) {  
        e.printStackTrace();  
    }  
}

public static void parseUpdate(String sql) throws JSQLParserException {  
    Statement statement \= CCJSqlParserUtil.parse(sql);  
    if (statement instanceof Update) {  
        Update updateStatement \= (Update) statement;

        // For single table updates, getTable() is primary  
        Table table \= updateStatement.getTable();  
        if (table \!= null) {  
             System.out.println("Target Table: " \+ table.getFullyQualifiedName());  
        } else if (updateStatement.getTables() \!= null && \!updateStatement.getTables().isEmpty()) {  
            // For multi-table update syntax if supported and parsed as such  
            System.out.println("Target Tables (multi-table update):");  
            for(Table t : updateStatement.getTables()){  
                System.out.println("  \- " \+ t.getFullyQualifiedName());  
            }  
        }

        System.out.println("SET Clause (using UpdateSet for JSqlParser 4.7+):");  
        List\<UpdateSet\> updateSets \= updateStatement.getUpdateSets();  
        if (updateSets \!= null) {  
            for (UpdateSet updateSet : updateSets) {  
                List\<Column\> cols \= updateSet.getColumns();  
                List\<Expression\> exprs \= updateSet.getExpressions();  
                for (int i \= 0; i \< cols.size(); i++) {  
                    System.out.println("  \- Column: " \+ cols.get(i).getFullyQualifiedName() \+ " \= Expression: " \+ exprs.get(i));  
                }  
            }  
        }  
        /\*  
        // For JSqlParser 4.6 and earlier:  
        List\<Column\> columns \= updateStatement.getColumns();  
        List\<Expression\> expressions \= updateStatement.getExpressions();  
        System.out.println("SET Clause (older JSqlParser):");  
        if (columns \!= null && expressions \!= null && columns.size() \== expressions.size()) {  
            for (int i \= 0; i \< columns.size(); i++) {  
                System.out.println("  \- Column: " \+ columns.get(i).getFullyQualifiedName() \+ " \= Expression: " \+ expressions.get(i));  
            }  
        }  
        \*/

        Expression whereCondition \= updateStatement.getWhere();  
        if (whereCondition \!= null) {  
            System.out.println("WHERE Clause: " \+ whereCondition.toString());  
            // Analyze whereCondition as in SELECT statements  
        } else {  
            System.out.println("WHERE Clause: None (updates all rows \- potentially dangerous\!)");  
        }

        if (updateStatement.getFromItem() \!= null) {  
            System.out.println("FROM Item (for UPDATE ... FROM syntax): " \+ updateStatement.getFromItem());  
            if (updateStatement.getJoins() \!= null && \!updateStatement.getJoins().isEmpty()) {  
                System.out.println("  Joins in FROM: " \+ updateStatement.getJoins());  
            }  
        }

        if (updateStatement.getOrderByElements() \!= null) {  
            System.out.println("ORDER BY exists: " \+ updateStatement.getOrderByElements());  
        }  
        if (updateStatement.getLimit() \!= null) {  
            System.out.println("LIMIT exists: " \+ updateStatement.getLimit().getRowCount());  
        }  
        if (updateStatement.getReturningExpressionList() \!= null && \!updateStatement.getReturningExpressionList().isEmpty()){  
            System.out.println("RETURNING clause exists: " \+ updateStatement.getReturningExpressionList());  
        }

    } else {  
        System.out.println("Not an UPDATE statement: " \+ sql);  
    }  
}  

}

3. 解析 DELETE 语句 (Delete)

DELETE 语句用于从表中删除数据行。com.github.jsqlparser.statement.delete.Delete 类代表了解析后的 DELETE 语句。

一个 Delete 对象主要包含以下部分:

  • 目标表 (Table):
    • getTable(): 返回一个 Table 对象,表示要从中删除数据的目标表。
  • WHERE 条件 (Expression):
    • getWhere(): 返回一个 Expression 对象,代表 WHERE 子句的过滤条件。如果不存在 WHERE 子句 (即删除所有行),则返回 null。分析方式与 SELECT 和 UPDATE 语句的 WHERE 条件相同。
  • USING 子句 (用于连接删除,特定方言如 PostgreSQL, MySQL):
    • getUsingList(): 返回 List<Table>,代表 DELETE ... USING table_references ... WHERE ... 语法中的 USING 部分。
    • JSqlParser 也可能通过 getTables() (返回 List<Table>) 和 getJoins() 来解析某些多表 DELETE 语法 (如 MySQL 的 DELETE t1 FROM t1 JOIN t2 ...)。
  • ORDER BY 和 LIMIT (特定方言如 MySQL):
    • getOrderByElements(): 返回 List<OrderByElement>。
    • getLimit(): 返回 Limit 对象。
  • RETURNING / OUTPUT (特定方言):
    • getReturningExpressionList(): 类似于 INSERT 和 UPDATE。

示例代码:

import com.github.jsqlparser.parser.CCJSqlParserUtil;
import com.github.jsqlparser.statement.Statement;
import com.github.jsqlparser.statement.delete.Delete;
import com.github.jsqlparser.expression.Expression;
import com.github.jsqlparser.schema.Table;
import com.github.jsqlparser.JSQLParserException;
import java.util.List;

public class ParseDeleteDemo {
public static void main(String[] args) {
String sqlSimpleDelete = "DELETE FROM orders WHERE order_date < '2022-01-01' AND status = 'CANCELLED'";
String sqlDeleteAll = "DELETE FROM temp_logs"; // Deletes all rows
String sqlDeleteUsing = "DELETE FROM customers USING customer_addresses ca WHERE customers.id = ca.customer_id AND ca.city = 'Unknown'"; // PostgreSQL syntax

    try {  
        System.out.println("--- Parsing Simple DELETE \---");  
        parseDelete(sqlSimpleDelete);

        System.out.println("\\n--- Parsing DELETE All Rows \---");  
        parseDelete(sqlDeleteAll);

        System.out.println("\\n--- Parsing DELETE USING (PostgreSQL) \---");  
        parseDelete(sqlDeleteUsing);

    } catch (JSQLParserException e) {  
        e.printStackTrace();  
    }  
}

public static void parseDelete(String sql) throws JSQLParserException {  
    Statement statement \= CCJSqlParserUtil.parse(sql);  
    if (statement instanceof Delete) {  
        Delete deleteStatement \= (Delete) statement;

        Table table \= deleteStatement.getTable(); // This is the primary table from which rows are deleted.  
        System.out.println("Target Table: " \+ table.getFullyQualifiedName());

        // For multi-table delete syntax like MySQL's \`DELETE t1, t2 FROM ...\`  
        // JSqlParser might populate \`getTables()\` with the list of tables from which to delete.  
        // \`getTable()\` would still be the first table listed after DELETE.  
        if (deleteStatement.getTables() \!= null && deleteStatement.getTables().size() \> 1\) {  
            System.out.println("Also deleting from tables (MySQL multi-table delete syntax):");  
            for(Table t : deleteStatement.getTables()){  
                if(\!t.equals(table)) { // Avoid re-listing the primary table  
                    System.out.println("  \- " \+ t.getFullyQualifiedName());  
                }  
            }  
        }

        Expression whereCondition \= deleteStatement.getWhere();  
        if (whereCondition \!= null) {  
            System.out.println("WHERE Clause: " \+ whereCondition.toString());  
            // Analyze whereCondition as in SELECT statements  
        } else {  
            System.out.println("WHERE Clause: None (deletes all rows from the target table \- potentially dangerous\!)");  
        }

        List\<Table\> usingTables \= deleteStatement.getUsingList();  
        if (usingTables \!= null && \!usingTables.isEmpty()) {  
            System.out.println("USING Tables:");  
            for (Table ut : usingTables) {  
                System.out.println("  \- " \+ ut.getFullyQualifiedName());  
            }  
        }

        if (deleteStatement.getJoins() \!= null && \!deleteStatement.getJoins().isEmpty()) {  
             System.out.println("Joins (for some multi-table delete syntaxes): " \+ deleteStatement.getJoins());  
        }

        if (deleteStatement.getOrderByElements() \!= null) {  
            System.out.println("ORDER BY exists: " \+ deleteStatement.getOrderByElements());  
        }  
        if (deleteStatement.getLimit() \!= null) {  
            System.out.println("LIMIT exists: " \+ deleteStatement.getLimit().getRowCount());  
        }  
        if (deleteStatement.getReturningExpressionList() \!= null && \!deleteStatement.getReturningExpressionList().isEmpty()){  
            System.out.println("RETURNING clause exists: " \+ deleteStatement.getReturningExpressionList());  
        }

    } else {  
        System.out.println("Not a DELETE statement: " \+ sql);  
    }  
}  

}

动手实验 3:

  1. 解析 INSERT 语句:
    • 编写 Java 代码解析以下 SQL 语句:
      INSERT INTO products (id, product_name, category_id, price, last_updated)
      VALUES (1001, 'Super Widget', 20, 99.99, CURRENT_TIMESTAMP),
      (1002, 'Mega Gadget', 20, 149.50, CURRENT_TIMESTAMP)
      ON DUPLICATE KEY UPDATE price = VALUES(price), last_updated = NOW();

    • 提取目标表名。

    • 提取插入的列名。

    • 提取每一行插入的值 (注意 CURRENT_TIMESTAMP 会被解析为 Function)。

    • 识别并打印 ON DUPLICATE KEY UPDATE 部分的更新操作。

  2. 解析 UPDATE 语句:
    • 编写 Java 代码解析以下 SQL 语句:
      UPDATE user_profiles
      SET
      email = LOWER(CONCAT(username, '@newdomain.com')),
      last_login = '2024-01-15 10:00:00',
      profile_status = CASE WHEN failed_logins > 3 THEN 'LOCKED' ELSE profile_status END
      WHERE
      account_type = 'REGULAR' AND creation_date < '2023-01-01';

    • 提取目标表名。

    • 提取所有 SET 子句的列名和对应的新值表达式 (注意函数和 CASE 表达式)。

    • 打印 WHERE 条件。

  3. 解析 DELETE 语句:
    • 编写 Java 代码解析以下 SQL 语句:
      DELETE FROM event_logs
      WHERE
      event_timestamp < (SELECT MIN(start_time) FROM active_sessions)
      AND event_type NOT IN ('CRITICAL_ERROR', 'SYSTEM_BOOT')
      LIMIT 1000;

    • 提取目标表名。

    • 打印 WHERE 条件,并尝试识别其中的子查询和 IN 表达式。

    • 提取 LIMIT 子句的值。

模块三小结:

在本模块中,我们学习了如何使用 JSqlParser 解析核心的 DML 语句:

  • INSERT: 提取目标表、插入列、插入值 (来自 VALUES 或 SELECT 子查询),以及处理如 ON DUPLICATE KEY UPDATE 和 RETURNING 等子句。
  • UPDATE: 提取目标表、更新的列和新值表达式,以及 WHERE 条件,并了解了多表更新和 RETURNING 的概念。
  • DELETE: 提取目标表和 WHERE 条件,并了解了 USING、ORDER BY、LIMIT 等在特定方言中的应用。

通过对 DML 语句的解析,我们进一步扩展了对 JSqlParser 对象模型的理解,为后续更复杂的操作(如 SQL 修改)奠定了基础。