Appearance
第二部分: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。
- ExpressionList: 代表 VALUES (expr1, expr2, ...), (exprA, exprB, ...) 这种形式,其中包含一个或多个值元组。
- getItemsList(): 返回一个 com.github.jsqlparser.statement.select.ItemsList 接口的实例,表示要插入的数据来源。ItemsList 有两个主要的实现:
- 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>,代表赋给对应列的新值。这两个列表的元素一一对应。
- getUpdateSets(): (JSqlParser 4.7+) 返回一个 List<UpdateSet>。每个 UpdateSet 对象包含:
- 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:
- 解析 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 部分的更新操作。
- 解析 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 条件。
- 解析 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 修改)奠定了基础。