【Mybatis Plus】JSqlParser解析sql语句
【Mybatis Plus】JSqlParser解析sql语句
- 【一】JSqlParser 是什么
- 【二】JSqlParser 的安装步骤
- 【三】使用场景
- 【1】sql语句解析
- 【2】SQL 语句转换
- 【3】SQL 语句生成
- 【4】SQL 语句验证
- 【四】在使用 JSqlParser 时,如何处理 SQL 注入攻击?
- 【1】使用预编译语句(Prepared Statements)
- 【2】使用 JSqlParser 对 SQL 语句进行验证和规范化
- 【3】白名单机制
- 【4】使用参数化查询对象
- 【五】使用 JSqlParser 解析复杂的 SQL 语句
- 【1】思路
- 【2】示例代码
- 【六】解析嵌套sql的案例
- 【1】解析 SQL 并遍历嵌套结构
- 【2】解析逻辑
- (1)解析子查询
- (2)处理表达式中的子查询
- (3)处理 UNION/INTERSECT
【一】JSqlParser 是什么
JSqlParser 是一个用于解析 SQL 语句的 Java 库。它可以将 SQL 语句解析为一个 Java 对象树,允许你以编程的方式对 SQL 语句进行分析、修改和操作。它支持多种 SQL 语句类型,包括但不限于 SELECT、INSERT、UPDATE、DELETE、CREATE、ALTER 等。
例如,对于 SQL 语句 “SELECT column1, column2 FROM table1 WHERE column1 = ‘value’”,JSqlParser 可以将其解析为一个 Java 对象,你可以方便地访问该对象的各个部分,如 SELECT 子句中的列名(column1 和 column2)、表名(table1)以及 WHERE 子句中的条件(column1 = ‘value’)等。
【二】JSqlParser 的安装步骤
使用 Maven 进行安装
(1)在 标签内添加以下依赖:
<dependency><groupId>com.github.jsqlparser</groupId><artifactId>jsqlparser</artifactId><version>4.4</version>
</dependency>
(2)测试案例
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;public class JSqlParserExample {public static void main(String[] args) throws Exception {String sql = "SELECT * FROM users WHERE id = 1";Statement statement = CCJSqlParserUtil.parse(sql);System.out.println(statement);}
}
首先,我们导入了 CCJSqlParserUtil 和 Statement 类,它们是 JSqlParser 的一部分。
在 main 方法中,我们定义了一个 SQL 语句字符串 sql。
然后,我们使用 CCJSqlParserUtil.parse(sql) 方法将 SQL 语句解析为一个 Statement 对象。
最后,我们将解析后的 Statement 对象打印出来。
【三】使用场景
【1】sql语句解析
你可以使用 JSqlParser 来解析 SQL 语句,以提取其中的关键信息。例如,如果你想知道一个 SELECT 语句选择了哪些列、查询了哪个表、使用了哪些条件等,可以通过 JSqlParser 进行解析。以下是一个简单的示例:
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import net.sf.jsqlparser.statement.select.SelectItem;public class JSqlParserExample {public static void main(String[] args) {String sql = "SELECT column1, column2 FROM table1 WHERE column1 = 'value'";try {Statement statement = CCJSqlParserUtil.parse(sql);if (statement instanceof Select) {Select selectStatement = (Select) statement;SelectBody selectBody = selectStatement.getSelectBody();if (selectBody instanceof net.sf.jsqlparser.statement.select.PlainSelect) {net.sf.jsqlparser.statement.select.PlainSelect plainSelect = (net.sf.jsqlparser.statement.select.PlainSelect) selectBody;List<SelectItem> selectItems = plainSelect.getSelectItems();for (SelectItem item : selectItems) {System.out.println("Selected column: " + item);}System.out.println("Table: " + plainSelect.getTable());System.out.println("Where clause: " + plainSelect.getWhere());}}} catch (JSQLParserException e) {e.printStackTrace();}}
}
(1)首先,我们使用 CCJSqlParserUtil.parse(sql) 将 SQL 语句解析为一个 Statement 对象。
(2)然后,我们将 Statement 对象转换为 Select 类型,因为我们知道这是一个 SELECT 语句。
(3)接着,我们通过 getSelectBody() 获取 SelectBody,并将其转换为 PlainSelect 类型,因为大多数简单的 SELECT 语句是 PlainSelect 类型。
(4)最后,我们可以使用 getSelectItems() 获取选择的列,getTable() 获取表名,getWhere() 获取 WHERE 子句。
【2】SQL 语句转换
你可以修改 SQL 语句的某些部分。例如,你可能想要将一个 SELECT 语句中的某些列替换为其他列,或者修改 WHERE 条件。以下是一个示例:
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import net.sf.jsqlparser.statement.select.SelectItem;public class JSqlParserModifyExample {public static void main(String[] args) {String sql = "SELECT column1, column2 FROM table1 WHERE column1 = 'value'";try {Statement statement = CCJSqlParserUtil.parse(sql);if (statement instanceof Select) {Select selectStatement = (Select) statement;SelectBody selectBody = selectStatement.getSelectBody();if (selectBody instanceof net.sf.jsqlparser.statement.select.PlainSelect) {net.sf.jsqlparser.statement.select.PlainSelect plainSelect = (net.sf.jsqlparser.statement.select.PlainSelect) selectBody;// 修改列名plainSelect.getSelectItems().clear();plainSelect.addSelectItems(CCJSqlParserUtil.parseSelectItem("column3, column4"));// 修改 WHERE 条件plainSelect.setWhere(CCJSqlParserUtil.parseCondExpression("column3 > 10"));}System.out.println("Modified SQL: " + statement);}} catch (JSQLParserException e) {e.printStackTrace();}}
}
(1)首先,我们按照上述的解析步骤将 SQL 语句解析为 PlainSelect 类型。
(2)然后,我们使用 getSelectItems().clear() 清除原有的选择项,并使用 addSelectItems() 添加新的选择项。
(3)最后,我们使用 setWhere() 修改 WHERE 条件。
【3】SQL 语句生成
你可以使用 JSqlParser 来构建新的 SQL 语句。例如,你可以使用其 API 来创建一个 SELECT 语句,而不是手动编写 SQL 字符串。以下是一个简单的示例:
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.GreaterThan;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;public class JSqlParserCreateExample {public static void main(String[] args) {// 创建表对象Table table = new Table("table1");// 创建列对象Column column1 = new Column("column1");Column column2 = new Column("column2");// 创建表达式 column1 = 'value'Expression equalsTo = new EqualsTo(column1, CCJSqlParserUtil.parseExpression("'value'"));// 创建表达式 column2 > 10Expression greaterThan = new GreaterThan(column2, CCJSqlParserUtil.parseExpression("10"));// 创建 AND 表达式 column1 = 'value' AND column2 > 10Expression where = new AndExpression(equalsTo, greaterThan);// 创建 SELECT 语句SelectExpressionItem selectItem1 = new SelectExpressionItem(column1);SelectExpressionItem selectItem2 = new SelectExpressionItem(column2);PlainSelect plainSelect = new PlainSelect();plainSelect.setSelectItems(List.of(selectItem1, selectItem2));plainSelect.setTable(table);plainSelect.setWhere(where);Select select = new Select();select.setSelectBody(plainSelect);System.out.println("Generated SQL: " + select);}
}
(1)首先,我们创建表对象和列对象。
(2)然后,我们创建各种表达式,如 EqualsTo 表示等于条件,GreaterThan 表示大于条件,并使用 AndExpression 将它们组合成 WHERE 条件。
(3)接着,我们创建 SelectExpressionItem 作为选择项。
(4)最后,我们将这些元素组合成 PlainSelect 对象,再将其作为 Select 语句的 SelectBody。
【4】SQL 语句验证
你可以使用 JSqlParser 来验证 SQL 语句的语法和结构。例如,在一个 SQL 编辑工具中,你可以使用 JSqlParser 来检查用户输入的 SQL 是否合法。以下是一个简单的示例:
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;public class JSqlParserValidationExample {public static void main(String[] args) {String sql = "SELECT column1, column2 FROM table1 WHERE column1 = 'value'";try {CCJSqlParserUtil.parse(sql);System.out.println("SQL is valid");} catch (JSQLParserException e) {System.out.println("SQL is invalid: " + e.getMessage());}}
}
我们使用 CCJSqlParserUtil.parse(sql) 尝试解析 SQL 语句,如果解析成功,说明 SQL 语句是合法的,否则会抛出 JSQLParserException,表明 SQL 语句存在问题。
【四】在使用 JSqlParser 时,如何处理 SQL 注入攻击?
以下是在使用 JSqlParser 时处理 SQL 注入攻击的一些方法:
【1】使用预编译语句(Prepared Statements)
在 Java 中,使用 JDBC 的预编译语句是防止 SQL 注入的重要手段,JSqlParser 可以与预编译语句结合使用。以下是一个简单的示例:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class JSqlParserWithPreparedStatement {public static void main(String[] args) {String url = "jdbc:mysql://localhost:3306/your_database";String user = "username";String password = "password";try (Connection connection = DriverManager.getConnection(url, user, password)) {// 假设解析后的 SQL 语句是一个 SELECT 语句String parsedSql = "SELECT * FROM users WHERE username =?";try (PreparedStatement preparedStatement = connection.prepareStatement(parsedSql)) {// 设置参数,这里假设用户输入来自于用户界面或其他来源String userInput = "admin"; preparedStatement.setString(1, userInput);try (ResultSet resultSet = preparedStatement.executeQuery()) {while (resultSet.next()) {// 处理结果集System.out.println(resultSet.getString("username"));}}}} catch (SQLException e) {e.printStackTrace();}}
}
(1)首先,我们使用 DriverManager.getConnection() 建立数据库连接。
(2)然后,我们定义一个包含占位符 ? 的 SQL 语句,这里的 ? 是预编译语句的占位符。
(3)使用 connection.prepareStatement() 创建预编译语句对象。
(4)通过 preparedStatement.setString() 等方法设置参数,这里的参数会被正确转义,避免了 SQL 注入的风险。
【2】使用 JSqlParser 对 SQL 语句进行验证和规范化
JSqlParser 可以用来检查 SQL 语句是否符合预期,例如,可以检查 SQL 语句是否只包含允许的关键字和结构。以下是一个简单的示例:
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;public class JSqlParserValidation {public static void main(String[] args) {String sql = "SELECT * FROM users WHERE username = 'admin' AND 1=1; DROP TABLE users;";try {Statement statement = CCJSqlParserUtil.parse(sql);// 这里可以添加更多的验证逻辑// 例如,检查是否包含不允许的关键字,如 DROP、TRUNCATE 等System.out.println("Parsed SQL: " + statement);} catch (JSQLParserException e) {e.printStackTrace();}}
}
我们使用 CCJSqlParserUtil.parse() 对 SQL 语句进行解析。
在解析后,可以添加额外的验证逻辑,例如检查 SQL 语句中是否包含 DROP、TRUNCATE 等危险的关键字,以防止恶意用户删除或修改数据库结构。
【3】白名单机制
使用白名单来限制 SQL 语句中的表名、列名和操作。以下是一个简单的示例:
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.Select;public class JSqlParserWhiteList {public static final String[] ALLOWED_TABLES = {"users", "products"};public static void main(String[] args) {String sql = "SELECT * FROM users WHERE username = 'admin'";try {Statement statement = CCJSqlParserUtil.parse(sql);if (statement instanceof Select) {Select select = (Select) statement;// 假设我们只允许查询 users 或 products 表String tableName = select.getSelectBody().toString().split("FROM")[1].trim().split(" ")[0];if (!isAllowedTable(tableName)) {throw new RuntimeException("Table not allowed");}System.out.println("Parsed SQL: " + statement);}} catch (JSQLParserException e) {e.printStackTrace();}}private static boolean isAllowedTable(String tableName) {for (String allowedTable : ALLOWED_TABLES) {if (allowedTable.equalsIgnoreCase(tableName)) {return true;}}return false;}
}
我们定义了一个允许的表名数组 ALLOWED_TABLES。
解析 SQL 语句后,对于 SELECT 语句,我们提取出表名,并检查它是否在白名单中。
【4】使用参数化查询对象
JSqlParser 可以帮助你将 SQL 语句转换为参数化查询对象,然后可以与预编译语句结合使用。以下是一个简单的示例:
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.Select;public class JSqlParserParameterized {public static void main(String[] args) {String sql = "SELECT * FROM users WHERE username = 'admin' AND age > 20";try {Statement statement = CCJSqlParserUtil.parse(sql);if (statement instanceof Select) {Select select = (Select) statement;// 假设这里可以提取表达式,如 username = 'admin' 和 age > 20Expression whereExpression = ((Select) statement).getSelectBody().toString().split("WHERE")[1].trim();// 这里可以进一步处理表达式,将其转换为参数化查询对象System.out.println("Parsed Expression: " + whereExpression);}} catch (JSQLParserException e) {e.printStackTrace();}}
}
我们使用 CCJSqlParserUtil.parse() 解析 SQL 语句。
对于 SELECT 语句,我们可以提取 WHERE 子句的表达式,将其作为参数化查询对象,然后与预编译语句结合使用,进一步避免 SQL 注入风险。
【五】使用 JSqlParser 解析复杂的 SQL 语句
【1】思路
(1)导入 JSqlParser 的相关类。
(2)创建一个 SQL 语句的字符串。
(3)使用 CCJSqlParserUtil.parse() 方法将 SQL 语句解析为 Statement 对象。
(4)根据 SQL 语句的不同类型(例如 Select、Insert、Update、Delete),将 Statement 对象进行类型转换。
(5)对转换后的对象进行进一步的操作,提取所需的信息。
【2】示例代码
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import net.sf.jsqlparser.statement.select.SelectItem;import java.util.List;public class JSqlParserComplexExample {public static void main(String[] args) {String complexSql = "SELECT column1, column2, SUM(column3) AS total FROM table1 WHERE column1 > 10 GROUP BY column1, column2 HAVING SUM(column3) > 100 ORDER BY column1 ASC, column2 DESC";try {// 将 SQL 语句解析为 Statement 对象Statement statement = CCJSqlParserUtil.parse(complexSql);// 判断 Statement 对象是否为 Select 语句if (statement instanceof Select) {Select selectStatement = (Select) statement;SelectBody selectBody = selectStatement.getSelectBody();// 提取 Select 语句中的 SelectItemsif (selectBody instanceof net.sf.jsqlparser.statement.select.PlainSelect) {net.sf.jsqlparser.statement.select.PlainSelect plainSelect = (net.sf.jsqlparser.statement.select.PlainSelect) selectBody;List<SelectItem> selectItems = plainSelect.getSelectItems();for (SelectItem item : selectItems) {System.out.println("Select Item: " + item);}// 提取 Where 条件if (plainSelect.getWhere()!= null) {System.out.println("Where Clause: " + plainSelect.getWhere());}// 提取 Group By 子句if (plainSelect.getGroupBy()!= null) {System.out.println("Group By Clause: " + plainSelect.getGroupBy());}// 提取 Having 子句if (plainSelect.getHaving()!= null) {System.out.println("Having Clause: " + plainSelect.getHaving());}// 提取 Order By 子句if (plainSelect.getOrderByElements()!= null) {System.out.println("Order By Clause: " + plainSelect.getOrderByElements());}}}} catch (JSQLParserException e) {e.printStackTrace();}}
}
(1)首先,我们导入了 JSqlParser 所需的类,包括异常处理类 JSQLParserException,解析工具类 CCJSqlParserUtil,以及用于表示 SQL 语句的各种类,如 Statement、Select、SelectBody 和 SelectItem 等。
(2)在 main 方法中,我们定义了一个复杂的 SQL 语句字符串 complexSql。
(3)然后,我们使用 CCJSqlParserUtil.parse(complexSql) 方法将这个复杂的 SQL 语句解析为一个 Statement 对象。
(4)接下来,我们检查这个 Statement 对象是否是 Select 语句(因为我们的示例是一个 SELECT 语句),如果是,我们将其转换为 Select 类型。
(5)对于 Select 语句,我们进一步提取 SelectBody,并判断它是否是 PlainSelect 类型,因为大多数简单的 SELECT 语句会使用 PlainSelect 结构。
(6)我们可以使用 getSelectItems() 方法获取 SELECT 子句中的所有选择项,并遍历打印它们。
(7)对于 WHERE 子句,我们可以使用 getWhere() 方法获取条件表达式,如果存在的话。
(8)对于 GROUP BY 子句,我们可以使用 getGroupBy() 方法获取分组信息,如果存在的话。
(9)对于 HAVING 子句,我们可以使用 getHaving() 方法获取过滤条件,如果存在的话。
(10)对于 ORDER BY 子句,我们可以使用 getOrderByElements() 方法获取排序信息,如果存在的话。
如果你要解析的 SQL 语句是 INSERT、UPDATE 或 DELETE 类型,你可以类似地将 Statement 对象转换为相应的类型,然后使用相应类型的方法提取所需的信息。例如:
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.update.Update;public class JSqlParserOtherExamples {public static void main(String[] args) {String insertSql = "INSERT INTO table1 (column1, column2) VALUES (1, 'value')";String updateSql = "UPDATE table1 SET column1 = 2 WHERE column2 = 'value'";String deleteSql = "DELETE FROM table1 WHERE column1 = 3";try {// 解析 INSERT 语句Statement insertStatement = CCJSqlParserUtil.parse(insertSql);if (insertStatement instanceof Insert) {Insert insert = (Insert) insertStatement;System.out.println("Insert Table: " + insert.getTable());System.out.println("Insert Columns: " + insert.getColumns());System.out.println("Insert Values: " + insert.getItemsList());}// 解析 UPDATE 语句Statement updateStatement = CCJSqlParserUtil.parse(updateSql);if (updateStatement instanceof Update) {Update update = (Update) updateStatement;System.out.println("Update Table: " + update.getTable());System.out.println("Update Set Items: " + update.getSets());System.out.println("Update Where Clause: " + update.getWhere());}// 解析 DELETE 语句Statement deleteStatement = CCJSqlParserUtil.parse(deleteSql);if (deleteStatement instanceof Delete) {Delete delete = (Delete) deleteStatement;System.out.println("Delete Table: " + delete.getTable());System.out.println("Delete Where Clause: " + delete.getWhere());}} catch (JSQLParserException e) {e.printStackTrace();}}
}
(1)对于 INSERT 语句,我们将 Statement 转换为 Insert 类型,然后可以使用 getTable() 方法获取插入的表名,getColumns() 方法获取插入的列名列表,getItemsList() 方法获取插入的值列表。
(2)对于 UPDATE 语句,我们将 Statement 转换为 Update 类型,然后可以使用 getTable() 方法获取更新的表名,getSets() 方法获取更新的列和值的映射,getWhere() 方法获取更新的条件。
(3)对于 DELETE 语句,我们将 Statement 转换为 Delete 类型,然后可以使用 getTable() 方法获取删除的表名,getWhere() 方法获取删除的条件。
【六】解析嵌套sql的案例
【1】解析 SQL 并遍历嵌套结构
解析嵌套的 SQL 语句(如包含子查询、多层 JOIN 或 WITH 子句)通常需要借助 SQL 解析器工具,将 SQL 转换为结构化数据(如抽象语法树,AST),然后递归遍历其节点。以下是具体步骤和示例代码:
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;public class NestedSqlParser {public static void main(String[] args) throws JSQLParserException {String sql = "SELECT u.name, (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS order_count "+ "FROM users u "+ "WHERE u.id IN (SELECT user_id FROM active_users WHERE status = 'ACTIVE')";Statement statement = CCJSqlParserUtil.parse(sql);if (statement instanceof Select) {Select select = (Select) statement;SelectBody selectBody = select.getSelectBody();processSelectBody(selectBody, 0); // 从嵌套层级 0 开始}}/*** 递归处理 SELECT 语句的嵌套结构* @param selectBody 当前层级的 SELECT 主体* @param level 嵌套层级(用于缩进输出)*/private static void processSelectBody(SelectBody selectBody, int level) {if (selectBody instanceof PlainSelect) {PlainSelect plainSelect = (PlainSelect) selectBody;// 输出当前层级的 SELECTSystem.out.println(indent(level) + "SELECT层级: " + level);// 处理子查询(嵌套 SELECT)for (SelectItem item : plainSelect.getSelectItems()) {if (item instanceof SelectExpressionItem) {SelectExpressionItem exprItem = (SelectExpressionItem) item;if (exprItem.getExpression() instanceof SubSelect) {System.out.println(indent(level) + "发现子查询:");SubSelect subSelect = (SubSelect) exprItem.getExpression();processSelectBody(subSelect.getSelectBody(), level + 1); // 递归处理子查询}}}// 处理 WHERE 子句中的子查询if (plainSelect.getWhere() != null) {plainSelect.getWhere().accept(new ExpressionVisitorAdapter() {@Overridepublic void visit(SubSelect subSelect) {System.out.println(indent(level) + "WHERE子句中的子查询:");processSelectBody(subSelect.getSelectBody(), level + 1);}});}} else if (selectBody instanceof SetOperationList) {// 处理 UNION/INTERSECT 等集合操作SetOperationList setOpList = (SetOperationList) selectBody;for (SelectBody body : setOpList.getSelects()) {processSelectBody(body, level + 1);}}}/** 生成缩进字符串 */private static String indent(int level) {return " ".repeat(level);}
}
运行上述代码,输出如下:
SELECT层级: 0发现子查询:SELECT层级: 1
WHERE子句中的子查询:SELECT层级: 1
【2】解析逻辑
(1)解析子查询
(1)子查询位置
SELECT 列表中的列(如 (SELECT …) AS order_count)。
WHERE 条件中的 IN、EXISTS 等操作符。
FROM 子句中的派生表(如 FROM (SELECT …) AS sub)。
(2)处理方法:通过递归遍历 SelectBody,逐层解析嵌套结构。
(2)处理表达式中的子查询
使用 ExpressionVisitorAdapter 访问 WHERE 条件中的子查询:
plainSelect.getWhere().accept(new ExpressionVisitorAdapter() {@Overridepublic void visit(SubSelect subSelect) {// 处理子查询}
});
(3)处理 UNION/INTERSECT
对于包含 UNION 的复杂查询,需处理 SetOperationList:
if (selectBody instanceof SetOperationList) {SetOperationList setOpList = (SetOperationList) selectBody;for (SelectBody body : setOpList.getSelects()) {processSelectBody(body, level + 1);}
}
相关文章:
【Mybatis Plus】JSqlParser解析sql语句
【Mybatis Plus】JSqlParser解析sql语句 【一】JSqlParser 是什么【二】JSqlParser 的安装步骤【三】使用场景【1】sql语句解析【2】SQL 语句转换【3】SQL 语句生成【4】SQL 语句验证 【四】在使用 JSqlParser 时,如何处理 SQL 注入攻击?【1】使用预编译…...
vue3 + ElementPlus 封装列表表格组件包含分页
在前端开发中,封装组件是必不可少的。今天就来封装一个通用的列表表格组件,包含分页功能,可以提高代码的复用性和可维护性。 1. 组件设计 Props: tableData:表格数据。columns:表格列配置。totalÿ…...
springboot3整合knife4j详细版,包会!(不带swagger2玩)
1. 引入依赖 <dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>4.4.0</version> </dependency>2. 配置文件 简短必要版 # 配置springd…...
Vue2自定义指令实现优雅的前端埋点方案
背景介绍 在前端开发中,埋点是一个非常常见的需求。通常我们需要记录用户的操作行为,以便于后续的数据分析和问题排查。传统的埋点方式往往是在每个需要埋点的地方都写一段上报代码,这样不仅代码重复度高,而且维护起来也比较麻烦…...
1.攻防世界 题目名称-文件包含
进入题目页面如下 直接给出了源码进行代码审计 题目给出提示是文件包含的题 代码审计 <?php // 高亮显示当前 PHP 文件的源代码,方便查看和调试 highlight_file(__FILE__);// 包含名为 "check.php" 的文件,通常这个文件中可能包含一些用…...
Ruby Dir 类和方法详解
Ruby Dir 类和方法详解 引言 在Ruby编程语言中,Dir类是一个非常有用的工具,它允许我们与文件系统进行交互,如列出目录内容、检查文件是否存在等。Dir类提供了多种方法,使得文件系统的操作变得简单且高效。本文将详细介绍Ruby中的…...
axios 发起 post请求 json 需要传入数据格式
• 1. axios 发起 post请求 json 传入数据格式 • 2. axios get请求 1. axios 发起 post请求 json 传入数据格式 使用 axios 发起 POST 请求并以 JSON 格式传递数据是前端开发中常见的操作。 下面是一个简单的示例,展示如何使用 axios 向服务器发送包含 JSON 数…...
Windows编程:下载与安装 Visual Studio 2010
本节前言 在写作本节的时候,本来呢,我正在写的专栏,是 MFC 专栏。而 VS2010 和 VS2019,正是 MFC 学习与开发中,可以使用的两款软件。然而呢,如果你去学习 Windows API 知识的话,那么࿰…...
python学opencv|读取图像(五十七)使用cv2.bilateralFilter()函数实现图像像素双边滤波处理
【1】引言 前序学习过程中,已经掌握了对图像的基本滤波操作技巧,具体的图像滤波方式包括均值滤波、中值滤波和高斯滤波,相关文章链接有: python学opencv|读取图像(五十四)使用cv2.blur()函数实现图像像素…...
基于Typescript,使用Vite构建融合Vue.js的Babylon.js开发环境
一、创建Vite项目 使用Vite初始化一个VueTypeScript项目: npm create vitelatest my-babylon-app -- --template vue-ts cd my-babylon-app npm create vitelatest my-babylon-app -- --template vue-ts 命令用于快速创建一个基于 Vite 的 Vue TypeScript 项目。…...
DockerFile详细学习
目录 1.DockerFile介绍 2.DockerFile常用指令 3.指令详细讲解 4.实例 构建Node-Exporter 构建Alertmanager 构建Mariadb 1.DockerFile介绍 什么是 Dockerfile? Dockerfile 是一个文本文件,包含了构建 Docker 镜像的所有指令。 Dockerfile 是一…...
C++11详解(三) -- 可变参数模版和lambda
文章目录 1.可变模版参数1.1 基本语法及其原理1.2 包扩展1.3 empalce系列接口1.3.1 push_back和emplace_back1.3.2 emplace_back在list中的使用(模拟实现) 2. lambda2.1 lambda表达式语法2.2 lambda的捕捉列表2.3 lambda的原理 1.可变模版参数 1.1 基本…...
IDEA 中集成 Maven,配置环境、创建以及导入项目
目录 在 IntelliJ IDEA 中集成 Maven 并配置环境 1. 打开 IDEA 设置 2. 定位 Maven 配置选项 3. 配置 Maven 路径 4. 应用配置 创建 Maven 项目 1. 新建项目 2. 选择项目类型 3. 配置项目信息 4. 确认 Maven 设置 5. 完成项目创建 导入 Maven 项目 1. 打开导入窗口…...
讯飞智作 AI 配音技术浅析(三):自然语言处理
自然语言处理(NLP)是讯飞智作 AI 配音技术的重要组成部分,负责将输入的文本转换为机器可理解的格式,并提取出文本的语义和情感信息,以便生成自然、富有表现力的语音。 一、基本原理 讯飞智作 AI 配音的 NLP 技术主要包…...
html转PDF文件最完美的方案(wkhtmltopdf)
目录 需求 一、方案调研 二、wkhtmltopdf使用 如何使用 文档简要说明 三、后端服务 四、前端服务 往期回顾 需求 最近在做报表类的统计项目,其中有很多指标需要汇总,网页内容有大量的echart图表,做成一个网页去浏览,同时…...
漏洞挖掘 | 基于mssql数据库的sql注入
视频教程在我主页简介或专栏里 目录: 前记 0x1 判断网站数据库类型 0x2 了解mssql数据库的主要三大系统表 0x3 了解mssql的主要函数 0x4 判断注入点及其注入类型 0x5 联合查询之判断列数 0x6 联合查询之获取数据库相关信息 0x7 mssql之时间盲注 0x8 mssql之报错注…...
.Net Core笔记知识点(跨域、缓存)
设置前端跨域配置示例: builder.Services.AddCors(option > {option.AddDefaultPolicy(policy > {policy.WithOrigins(originUrls).AllowAnyMethod().AllowAnyHeader().AllowCredentials();});});var app builder.Build();app.UseCors(); 【客户端缓存】接…...
JS实现一个通用的循环填充数组的方法
function createFilledArray(length, pattern) {return Array.from({ length }, (_, i) > pattern[i % pattern.length]); }// 示例 const result createFilledArray(8, [1, 2, 3]);console.log(result); // [1, 2, 3, 1, 2, 3, 1, 2]解析: createFilledArray(…...
Java项目: 基于SpringBoot+mybatis+maven+mysql实现的智能学习平台管理系(含源码+数据库+毕业论文)
一、项目简介 本项目是一套基于SpringBootmybatismavenmysql实现的智能学习平台管理系统 包含:项目源码、数据库脚本等,该项目附带全部源码可作为毕设使用。 项目都经过严格调试,eclipse或者idea 确保可以运行! 该系统功能完善、…...
Rust HashMap :当储物袋遇上物品清单
开场白:哈希映射的魔法本质 在Rust的奇幻世界里,HashMap就像魔法师的储物袋: 键值对存储 → 每个物品都有专属咒语(键)和实体(值)快速查找 → 念咒瞬间召唤物品动态扩容 → 自动伸展的魔法空间…...
力扣-哈希表-18 四数之和
思路 和《三数之和》类似,也使用类似双指针的操作,重点在去重,细节是多个数目相加需要小心超出范围,强转一下。 代码 class Solution { public:vector<vector<int>> fourSum(vector<int>& nums, int tar…...
DeepSeek-VL2论文解读:用于高级多模态理解的专家混合视觉语言模型
github:https://github.com/deepseek-ai/DeepSeek-VL2 paper: https://github.com/deepseek-ai/DeepSeek-VL2/blob/main/DeepSeek_VL2_paper.pdf 大型视觉语言模型(VLMs)已经成为人工智能领域的变革性力量,将大型语言模型(LLMs&…...
PHP JSON操作指南
PHP JSON操作指南 概述 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。PHP作为一门流行的服务器端脚本语言,支持对JSON数据进行读取、编写和解析。本文将…...
使用ES5和ES6求函数参数的和、解析URL Params为对象
文章目录 1 使用ES5和ES6求函数参数的和1.1 ES51.2 ES6 2 解析URL Params为对象 1 使用ES5和ES6求函数参数的和 1.1 ES5 function sum() {let sum 0;Array.prototype.forEach.call(arguments, function(item) {sum item * 1;})return sum; }1.2 ES6 function sum(...nums)…...
Python 数据挖掘与机器学习
模块一:Python编程 Python编程入门 1、Python环境搭建 2、如何选择Python编辑器? 3、Python基础 4、常见的错误与程序调试 5、第三方模块的安装与使用 6、文件读写(I/O) Python进阶与提高 1、Numpy模块库 2、Pandas模块…...
【华为OD-E卷 - 108 最大矩阵和 100分(python、java、c++、js、c)】
【华为OD-E卷 - 最大矩阵和 100分(python、java、c、js、c)】 题目 给定一个二维整数矩阵,要在这个矩阵中选出一个子矩阵,使得这个子矩阵内所有的数字和尽量大,我们把这个子矩阵称为和最大子矩阵,子矩阵的…...
Mysql系列之--重新认识Mysql
1、Mysql是什么 Mysql是一个被广泛使用的开源的关系型数据库管理系统(RDBMS),使用结构化查询语句SQL进行管理和操作数据。Mysql有客户端和服务端,客户端通过ip地址、端口、用户名、密码连接到服务端,然后使用SQL语句进…...
利用UNIAPP实现短视频上下滑动播放功能
在 UniApp 中实现一个短视频上下滑动播放的功能,可以使用 swiper 组件来实现滑动效果,并结合 video 组件来播放短视频。以下是一个完整的示例,展示如何在 UniApp 中实现这一功能。 1. 创建 UniApp 项目 如果你还没有创建 UniApp 项目,可以使用 HBuilderX 创建一个新的项目…...
计算机毕业设计hadoop+spark+hive民宿推荐系统 酒店推荐系统 民宿价格预测 酒店价预测 机器学习 深度学习 Python爬虫 HDFS集群
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
Shell 中的 Globbing:原理、使用方法与实现解析(中英双语)
Shell 中的 Globbing:原理、使用方法与实现解析 在 Unix Shell(如 Bash、Zsh)中,globbing 是指 文件名模式匹配(filename pattern matching),它允许用户使用特殊的通配符(wildcards…...
解决 ssh connect to host github.com port 22 Connection timed out
一、问题描述 本地 pull/push 推送代码到 github 项目报 22 端口连接超时,测试连接也是 22 端口连接超时 ssh 密钥没问题、也开了 Watt Toolkit 网络是通的,因此可以强制将端口切换为 443 二、解决方案 1、测试连接 ssh -T gitgithub.com意味着无法通…...
CSS的媒体查询语法
CSS的媒体查询语法 常见的媒体类型常见的特性示例 CSS的媒体查询语法可以根据不同的设备特性(如屏幕尺寸、分辨率等)应用不同的样式。基本语法如下: media 媒体类型 and (特性: 值) {/* 样式规则 */ }常见的媒体类型 screen:用于…...
生产环境超实用shell脚本一
生产环境超实用shell脚本一 Shell脚本作为一种强大的自动化工具,能够帮助运维人员轻松应对各种复杂的任务。 本文将为您介绍服务器健康检查、日志清理、备份以及监控等多个方面,并详细阐述每个脚本的功能和应用场景,助力您提升运维效率&…...
【1】高并发导出场景下,服务器性能瓶颈优化
高并发导出场景下,服务器性能瓶颈通常出现在 CPU、内存、磁盘 I/O 或网络带宽等方面。为了解决这些问题,可以从以下几个方面进行优化: 1. 优化导出逻辑 减少计算复杂度:检查导出逻辑中是否存在不必要的计算或重复操作,…...
go的sync包学习
包含了sync.Mutex,sync.RWMutex,sync.Cond,sync.Map,sync.Once等demo sync.Mutex //讲解mutex import ("fmt""math/rand""sync""time" )type Toilet struct {m sync.Mutex } type Person struct {Name string }var DateTime "2…...
一文读懂:TCP网络拥塞的应对策略与方案
TCP(传输控制协议)是互联网中广泛使用的可靠传输协议,它通过序列号、确认应答、重发控制、连接管理以及窗口控制等机制确保数据的可靠传输。然而,在网络环境中,由于多个主机共享网络资源,网络拥塞成为了一个…...
用DeepSeek分析总结一下DeepSeek发表过的8篇论文
1. 《深度求索大语言模型:以长期主义拓展开源语言模型》(2024年1月5日) 2. 《深度求索代码模型:当大语言模型遇上编程——代码智能的崛起》(2024年1月26日) 3. 《深度求索视觉语言模型:迈向真…...
node.js使用mysql2对接数据库
一、引言 在现代Web开发中,Node.js作为一种高效、轻量级的JavaScript运行时环境,已经广泛应用于后端服务的开发中。而MySQL,作为一个广泛使用的关系型数据库管理系统(RDBMS),提供了强大的数据存储和查询功能…...
华为支付-免密支付接入免密代扣说明
免密代扣包括支付并签约以及签约代扣场景。 开发者接入免密支付前需先申请开通签约代扣产品(即申请配置免密代扣模板及协议模板ID)。 华为支付以模板维度管理每一个代扣扣费服务,主要组成要素如下: 接入免密支付需注意&#x…...
Java 面试真题解析与技巧分享
Java 面试对于每一位 Java 开发者来说都是至关重要的,它不仅是对我们技术能力的检验,更是我们迈向理想工作岗位的关键一步。在面试中,掌握常见真题的解题思路和回答技巧,能够让我们更加自信地展示自己的实力。本文将结合具体的面试…...
使用 Python 编程语言来实现机器学习小项目教程案例
以下是一个简单的机器学习小项目教程案例,使用 Python 编程语言和 Scikit-learn 库来实现一个分类任务。我们将使用经典的鸢尾花(Iris)数据集来训练一个分类器,预测鸢尾花的种类。 项目目标 使用机器学习算法对鸢尾花数据集进行分类,预测鸢尾花的类别(Setosa、Versicolor…...
网络安全 | 零信任架构:重构安全防线的未来趋势
网络安全 | 零信任架构:重构安全防线的未来趋势 一、前言二、零信任架构的核心概念与原理2.1 核心概念2.2 原理 三、零信任架构的关键技术组件3.1 身份管理与认证系统3.2 授权与访问控制系统3.3 网络与安全监测系统3.4 加密与数据保护技术 四、零信任架构与传统安全…...
react关于手搓antd pro面包屑的经验(写的不好请见谅)
我们先上代码,代码里面都有注释,我是单独写了一个组件,方便使用,在其他页面引入就行了 还使用了官方的Breadcrumb组件 import React, { useEffect, useState } from react; import { Breadcrumb, Button } from antd; import { …...
PHP-运算符
[题目信息]: 题目名称题目难度PHP-运算符2 [题目考点]: PHP运算符优先级[Flag格式]: SangFor{zEk4r_djfgy0CibPz6LdXpwBrXRckhIT}[环境部署]: docker-compose.yml文件或者docker tar原始文件。 http://分配ip:2067[题目writeup]&#x…...
chrome浏览器chromedriver下载
chromedriver 下载地址 https://googlechromelabs.github.io/chrome-for-testing/ 上面的链接有和当前发布的chrome浏览器版本相近的chromedriver 实际使用感受 chrome浏览器会自动更新,可以去下载最新的chromedriver使用,自动化中使用新的chromedr…...
BurpSuite抓包与HTTP基础
文章目录 前言一、BurpSuite1.BurpSuite简介2.BurpSuite安装教程(1)BurpSuite安装与激活(2)安装 https 证书 3.BurpSuite使用4.BurpSuite资料 二、图解HTTP1.HTTP基础知识2.HTTP客户端请求消息3.HTTP服务端响应消息4.HTTP部分请求方法理解5.HTTPS与HTTP 总结 前言 在网络安全和…...
SQLAlchemy 2.0的简单使用教程
SQLAlchemy 2.0相比1.x进行了很大的更新,目前网上的教程不多,以下以链接mysql为例介绍一下基本的使用方法 环境及依赖 Python:3.8 mysql:8.3 Flask:3.0.3 SQLAlchemy:2.0.37 PyMySQL:1.1.1使用步骤 1、创建引擎,链接到mysql engine crea…...
Android原生开发问题汇总
Fragment顶部出现一个白条怎么办?父类布局搞事情。 layer-list被拉伸问题 Android之 ImageView android:src和tools:src的区别是什么? Android运行时权限的总结,以及EasyPermissions框架的使用 Android Studio添加EasyPemissions Android中module怎…...
springboot中使用注解实现分布式锁
下面将详细介绍如何在 Spring Boot 里借助注解实现分布式锁,以login_lock:作为锁的 key 前缀,使用请求参数里的phone值作为 key,等待时间设为 0 秒,锁的持续时间为 10 秒。我们会使用 Redis 来实现分布式锁,同时借助 S…...
Android TabLayout 使用进阶(含源码)
android:layout_height“match_parent” android:orientation“vertical” tools:context“.mode2.ClassificationActivity”> <com.google.android.material.tabs.TabLayout android:id“id/tab_layout” android:layout_width“match_parent” android:layout_he…...