Sql语句解析工具类

需求:

项目 web-sql模块,需要根据 sql 解析获取数据库表,然后对(金库)表权限进行校验。

金库表:用户查询该表前需要审批。

一、Druid (推荐)

添加依赖:

xml 复制代码
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.2.8</version>
</dependency>

工具类:

java 复制代码
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.statement.*;

import java.util.ArrayList;
import java.util.List;

/**
 * @author NanNan Wang
 */
public class SqlUtil {

    public static List<String> getTableNamesFromSQL(String sql, String dbType) {
        List<String> tableNames = new ArrayList<>();

        // 解析 SQL 语句
        List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);

        for (SQLStatement stmt : stmtList) {
            if (stmt instanceof SQLSelectStatement) {
                SQLSelectStatement selectStatement = (SQLSelectStatement) stmt;
                SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock) selectStatement.getSelect().getQuery();

                extractTableNames(queryBlock.getFrom(), tableNames);
            } else if (stmt instanceof SQLInsertStatement) {
                SQLInsertStatement insertStatement = (SQLInsertStatement) stmt;
                tableNames.add(insertStatement.getTableName().getSimpleName());
            } else if (stmt instanceof SQLUpdateStatement) {
                SQLUpdateStatement updateStatement = (SQLUpdateStatement) stmt;
                tableNames.add(updateStatement.getTableName().getSimpleName());
            } else if (stmt instanceof SQLDeleteStatement) {
                SQLDeleteStatement deleteStatement = (SQLDeleteStatement) stmt;
                tableNames.add(deleteStatement.getTableName().getSimpleName());
            }
        }
        return tableNames;
    }

    private static void extractTableNames(SQLTableSource tableSource, List<String> tableNames) {
        if (tableSource instanceof SQLExprTableSource) {
            // 如果是简单表名,直接添加
            SQLExprTableSource exprTableSource = (SQLExprTableSource) tableSource;
            tableNames.add(exprTableSource.getTableName());
        } else if (tableSource instanceof SQLJoinTableSource) {
            // 如果是 JOIN,递归获取左表和右表
            SQLJoinTableSource joinTableSource = (SQLJoinTableSource) tableSource;
            extractTableNames(joinTableSource.getLeft(), tableNames);
            extractTableNames(joinTableSource.getRight(), tableNames);
        } else if (tableSource instanceof SQLSubqueryTableSource) {
            // 如果是子查询,递归处理子查询中的表
            SQLSelect subSelect = ((SQLSubqueryTableSource) tableSource).getSelect();
            extractTableNames(subSelect.getQueryBlock().getFrom(), tableNames);
        }
    }
}

测试样例

java 复制代码
 @Test
 void getTableNamesFromPGSQL() {
     String sql = "SELECT e.id, e.name, d.name AS dept_name FROM schema.employee e " +
             "LEFT JOIN schema.department d ON e.dept_id = d.id WHERE e.salary > 1000";
     List<String> tableNames = SqlUtil.getTableNamesFromSQL(sql, JdbcConstants.POSTGRESQL.name());

     System.out.println("Tables: " + tableNames); //Tables: [employee, department]
 }

二、JSqlParser

添加依赖:

xml 复制代码
<dependency>
  <groupId>com.github.jsqlparser</groupId>
  <artifactId>jsqlparser</artifactId>
  <version>4.6</version>
</dependency>

工具类:

java 复制代码
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.util.TablesNamesFinder;

import java.util.ArrayList;
import java.util.List;

public class SqlUtil {

     /**
     * 使用 JSqlParser 根据 SQL 和数据库类型解析 SQL 中的所有表名
     * @param sql SQL 语句
     * @return 表名列表
     * @throws Exception 解析异常
     */
    public static List<String> extractTableNames(String sql) {
        // 使用 JSqlParser 解析 SQL 语句
        Statement statement = null;
        try {
            statement = CCJSqlParserUtil.parse(sql);
        } catch (JSQLParserException e) {
            throw new RuntimeException(e);
        }

        // 使用 TablesNamesFinder 提取表名
        TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
        return tablesNamesFinder.getTableList(statement);
    }
}

重要:要注意如果是 PostgreSQL 这里支持 模式(schema) 的需要进一步处理!

例如:

java 复制代码
@Test
void extractPgTableNames () {
    String sql = "SELECT e.id, e.name, d.name AS dept_name FROM schema.employee e " +
            "LEFT JOIN schema.department d ON e.dept_id = d.id WHERE e.salary > 1000";
    List<String> tableNames = SqlUtil.extractTableNames(sql);
    System.out.println("Tables: " + tableNames); //Tables: [schema.employee, schema.department]
}
相关推荐
杨云龙UP3 小时前
SQL Server2022部署:Windows Server 2016下安装、SSMS配置、备份还原与1433端口放通全流程_20260508
运维·服务器·数据库·sql·sqlserver·2022
墨染天姬5 小时前
【AI】cursor提示词小技巧
前端·数据库·人工智能
古月-一个C++方向的小白5 小时前
MySQL数据库——数据类型
android·数据库·mysql
qq_413502026 小时前
如何创建CDB公共用户_C##前缀强制规则与CONTAINER=ALL
jvm·数据库·python
逸Y 仙X6 小时前
文章二十七:ElasticSearch ES查询模板(Search Template)高效复用实战
java·大数据·数据库·elasticsearch·搜索引擎·全文检索
m0_738120726 小时前
应急响应(重点)——记一次某公司流量应急溯源分析(附带下载链接)
服务器·前端·数据库·安全·web安全·网络安全
yexuhgu6 小时前
CSS如何利用-checked实现纯CSS手风琴折叠_通过状态选择器控制区域高度
jvm·数据库·python
2301_779622417 小时前
mysql如何通过主从备份实现读写分离_配置mysql架构模式
jvm·数据库·python
m0_741173337 小时前
HTML5中WebSocket在弱网环境下的延迟抖动算法补偿
jvm·数据库·python