JSqlParser:SQL 解析利器的使用与最佳实践

在日常开发中,我们或多或少都遇到过这样的场景:

  • 动态审计:需要记录所有 UPDATEDELETE 操作影响了哪些表、哪些字段。
  • 数据脱敏:在查询特定敏感字段时,自动在 SQL 中套上脱敏函数,如 SELECT DES_PHONE(phone) FROM user
  • 多租户改造:需要在所有查询语句中,无侵入地自动加上租户 ID 的过滤条件,如 WHERE tenant_id = 'xxx'
  • SQL 性能分析:提取 SQL 中的表名、字段、JOIN 关系,进行性能瓶颈分析。
  • 自定义 ORM:想写一个自己的轻量级 ORM 框架,SQL 解析是第一步。

面对这些需求,如果只用字符串的 replace 或者正则表达式,代码会变得极其脆弱和难以维护。一个引号、一个换行都可能让整个逻辑崩溃。

这时,我们就需要一个专业的 SQL 解析器。而在 Java 世界里,JSqlParser 无疑是其中最闪亮的明星之一。

什么是 JSqlParser?

JSqlParser 是一个用 Java 编写的 SQL 语句解析器。它可以将一条 SQL 文本字符串,转换成一个可遍历的 Java 类层次结构(即抽象语法树 AST)。

简单来说,你给它一条 SQL:

SELECT id, name FROM users WHERE id > 10;

它还你一个对象世界:

Select
└── PlainSelect
    ├── SelectItems ([id, name])
    ├── FromItem (Table: users)
    └── Where (GreaterThan)
        ├── LeftExpression (Column: id)
        └── RightExpression (LongValue: 10)

有了这个结构化的对象树,我们就可以随心所欲地读取、分析、修改甚至重构 SQL,而无需关心复杂的字符串匹配。

它的核心优势:

  • 轻量级:几乎没有第三方依赖,集成成本极低。
  • 功能强大:支持 SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER 等绝大多数 SQL 语句。
  • 多方言支持:对 Oracle, SQL Server, MySQL, PostgreSQL 等主流数据库的语法有很好的兼容性。
  • 纯 Java 实现:与 Java 生态无缝集成。

快速上手:三步搞定 SQL 解析

1. 添加依赖

在你的 pom.xml 中加入 JSqlParser 的依赖:

<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>4.6</version> <!-- 建议使用最新版本 -->
</dependency>

2. 解析你的第一条 SQL

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 JSqlParserDemo {
    public static void main(String[] args) {
        String sql = "SELECT id, name FROM users WHERE id > 10 ORDER BY name DESC";
        printParser(sql);

        System.out.println("------------------");
        sql = "SELECT u.name, p.title, COUNT(c.id) as comment_count " +
                "FROM users u " +
                "LEFT JOIN posts p ON u.id = p.user_id " +
                "LEFT JOIN comments c ON p.id = c.post_id " +
                "WHERE u.status = 'active' AND p.published_at > '2024-01-01' " +
                "GROUP BY u.name, p.title " +
                "ORDER BY comment_count DESC";
        printParser(sql);
    }

    private static void printParser(String sql) {
        try {
            // 1. 使用 CCJSqlParserUtil 解析 SQL 字符串
            Statement statement = CCJSqlParserUtil.parse(sql);

            // 2. 判断语句类型并强转
            if (statement instanceof Select) {
                Select select = (Select) statement;
                // 3. 获取查询主体 (通常是 PlainSelect)
                PlainSelect plainSelect = (PlainSelect) select.getSelectBody();

                // --- 开始你的“解剖”表演 ---

                // 获取 FROM 的表名
                System.out.println("表名: " + ((net.sf.jsqlparser.schema.Table) plainSelect.getFromItem()).getName());

                // 获取 SELECT 的字段
                System.out.println("查询字段:");
                for (SelectItem selectItem : plainSelect.getSelectItems()) {
                    SelectExpressionItem selectItem1 = (SelectExpressionItem) selectItem;
                    Alias alias = selectItem1.getAlias();
                    if (alias != null) {
                        System.out.println("  - " + alias.getName());
                    } else {
                        System.out.println("  - " + selectItem1.getExpression());
                    }

                }

                // 获取 WHERE 条件
                System.out.println("WHERE 条件: " + plainSelect.getWhere());

                // 获取 ORDER BY
                System.out.println("排序: " + plainSelect.getOrderByElements());
            }

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

输出结果:

表名: users
查询字段:
  - id
  - name
WHERE 条件: id > 10
排序: [name DESC]
------------------
表名: users
查询字段:
  - u.name
  - p.title
  - comment_count
WHERE 条件: u.status = 'active' AND p.published_at > '2024-01-01'
排序: [comment_count DESC]

看,是不是很简单?我们不再需要关心字符串的空格、大小写,而是直接通过对象的方法获取了我们需要的信息。

核心概念与 API 探秘

JSqlParser 的 API 设计非常清晰,主要围绕以下几个核心接口/类:

  • Statement:所有 SQL 语句的根接口,如 Select, Insert, Update 等。
  • Expression:所有表达式的根接口,如 Column (字段), LongValue (数字), StringValue (字符串), Function (函数), 以及各种运算表达式 EqualsTo, GreaterThan, AndExpression 等。
  • SelectBodySELECT 语句的主体,通常是 PlainSelect(简单查询)或 SetOperationListUNION, INTERSECT 等集合操作)。
  • Visitor 模式:这是 JSqlParser 的精髓所在,也是我们接下来要讲的最佳实践的核心。

JSqlParser 最佳实践 🛠️

1. 拥抱 Visitor 模式,告别 if-else 地狱

当我们需要遍历整个语法树时,如果用大量的 instanceof 判断,代码会变得臃肿不堪。

反例(不推荐):

// 伪代码,展示 if-else 的复杂度
public void processExpression(Expression expr) {
    if (expr instanceof AndExpression) {
        // 处理 AND 逻辑
        AndExpression and = (AndExpression) expr;
        processExpression(and.getLeftExpression());
        processExpression(and.getRightExpression());
    } else if (expr instanceof OrExpression) {
        // 处理 OR 逻辑
        // ...
    } else if (expr instanceof EqualsTo) {
        // 处理等于
        // ...
    } else if (expr instanceof Function) {
        // 处理函数
        // ...
    }
    // ... 还有几十个 else if
}

正例(强烈推荐):使用 ExpressionVisitorStatementVisitor

JSqlParser 提供了 ExpressionVisitorStatementVisitor 接口(以及它们的 Adapter 适配器类),让我们可以优雅地遍历语法树。

场景:我们想找出 WHERE 条件中所有的 = 比较,并打印出它们。

import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.util.TablesNamesFinder;

// 自定义一个 Visitor 来查找等于表达式
public class EqualsToFinder implements ExpressionVisitor {
    @Override
    public void visit(EqualsTo equalsTo) {
        // 当遍历到一个 EqualsTo 节点时,这个方法会被调用
        Expression left = equalsTo.getLeftExpression();
        Expression right = equalsTo.getRightExpression();
        System.out.println("找到等于条件: " + left + " = " + right);
    }

    // 为了能遍历到 AND/OR 的子节点,需要实现这些方法
    @Override
    public void visit(AndExpression andExpression) {
        andExpression.getLeftExpression().accept(this);
        andExpression.getRightExpression().accept(this);
    }
    
    @Override
    public void visit(OrExpression orExpression) {
        orExpression.getLeftExpression().accept(this);
        orExpression.getRightExpression().accept(this);
    }

    // 其他需要实现的方法可以暂时留空,或者继承 ExpressionVisitorAdapter
    @Override public void visit(NullValue nullValue) {}
    @Override public void visit(Function function) {}
    // ... 省略一堆空实现
}

public class VisitorDemo {
    public static void main(String[] args) throws Exception {
        String sql = "SELECT * FROM orders WHERE user_id = 1 AND (status = 'PAID' OR amount > 100)";
        Statement statement = CCJSqlParserUtil.parse(sql);
        Select select = (Select) statement;
        PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
        
        System.out.println("开始分析 SQL: " + sql);
        EqualsToFinder finder = new EqualsToFinder();
        // 让 WHERE 条件接受我们的访问者
        plainSelect.getWhere().accept(finder);
    }
}

输出结果:

开始分析 SQL: SELECT * FROM orders WHERE user_id = 1 AND (status = 'PAID' OR amount > 100)
找到等于条件: user_id = 1
找到等于条件: status = 'PAID'

使用 Visitor 模式,代码逻辑清晰,扩展性极强。当需要处理新的表达式类型时,只需在 Visitor 中添加对应的 visit 方法即可,完全符合开闭原则。

2. 修改 SQL:不只是“读”,更是“写”

JSqlParser 的 AST 是可变的。这意味着我们可以修改它,然后再重新生成 SQL 字符串。

场景:为所有 SELECT 语句自动添加一个 LIMIT 0, 1000 的限制,防止查询过大。

import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.expression.LongValue;

public class ModifySqlDemo {
    public static void main(String[] args) throws Exception {
        String originalSql = "SELECT id, product_name FROM products WHERE category = 'electronics'";
        
        // 1. 解析
        Statement statement = CCJSqlParserUtil.parse(originalSql);
        Select selectStatement = (Select) statement;
        PlainSelect plainSelect = (PlainSelect) selectStatement.getSelectBody();

        // 2. 修改:创建一个 LIMIT 对象
        Limit limit = new Limit();
        limit.setRowCount(new LongValue(1000)); // 设置限制行数
        limit.setOffset(new LongValue(0));      // 设置偏移量
        
        // 3. 将 LIMIT 对象设置到 PlainSelect 中
        plainSelect.setLimit(limit);

        // 4. 重新生成 SQL
        String modifiedSql = selectStatement.toString();
        
        System.out.println("原始 SQL: " + originalSql);
        System.out.println("修改后 SQL: " + modifiedSql);
    }
}

输出结果:

原始 SQL: SELECT id, product_name FROM products WHERE category = 'electronics'
修改后 SQL: SELECT id, product_name FROM products WHERE category = 'electronics' LIMIT 0, 1000

这个特性在实现多租户、数据权限隔离等功能时,简直是无价之宝。

3. 善用工具类

JSqlParser 提供了一些好用的工具类,如 TablesNamesFinder,可以快速提取 SQL 中的所有表名。

// ... 接上文
TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
List<String> tableList = tablesNamesFinder.getTableList(statement);
System.out.println("SQL 涉及的表: " + tableList); // 输出: [products]

// 将堆在一起的多条复杂sql解析为一条条 sql 去执行
 Statements statements = CCJSqlParserUtil.parseStatements(sqls);
List<Statement> statementList = statements.getStatements();

4. 缓存提高解析效率

@Component
public class SqlParseCache {
    private final LoadingCache<String, Statement> cache;
    
    public SqlParseCache() {
        this.cache = CacheBuilder.newBuilder()
                .maximumSize(1000)
                .expireAfterWrite(Duration.ofMinutes(30))
                .build(new CacheLoader<String, Statement>() {
                    @Override
                    public Statement load(String sql) throws Exception {
                        return CCJSqlParserUtil.parse(sql);
                    }
                });
    }
    
    public Statement getParsedStatement(String sql) {
        try {
            return cache.get(sql);
        } catch (ExecutionException e) {
            throw new SqlParseException("缓存解析失败", e);
        }
    }
    
    public void invalidate(String sql) {
        cache.invalidate(sql);
    }
    
    public void invalidateAll() {
        cache.invalidateAll();
    }
}

5. 为任意SQL添加或修改分页

/**
     * 为SQL添加或修改分页。这是一个总入口。
     *
     * @param sql      原始SQL
     * @param pageNum  页码 (从1开始)
     * @param pageSize 每页大小
     * @param dbType   数据库类型
     * @return 修改后的分页SQL
     * @throws JSQLParserException SQL解析失败
     */
    public static String addOrModifyPagination(String sql, DbType dbType, int pageNum, int pageSize)
            throws JSQLParserException {
        Statement statement = CCJSqlParserUtil.parse(sql);
        if (!(statement instanceof Select)) {
            log.warn("非SELECT语句,将返回原始SQL,sql:{}", sql);
            return sql; // 非SELECT语句直接返回
        }

        Select select = (Select) statement;
        SelectBody selectBody = select.getSelectBody();

        long offset = (long) (pageNum - 1) * pageSize;

        switch (dbType) {
            case mysql:
            case gbase:
            case mariadb:
            case oceanbase:
            case hive:
            case argo:
            case sqlite:
                handleLimit(selectBody, offset, pageSize);
                break;
            case postgresql:
            case gaussdb:
            case oscar:
            case XCloud:
                handleLimitOffset(selectBody, offset, pageSize);
                break;
            case sqlserver:
//            case oracle:
                // Oracle 12c+ and SQL Server 2012+ use the same syntax
                handleSqlServerPagination(selectBody, offset, pageSize);
                break;
            case oracle:
            case dm:
            case oceanbase_oracle:
                // 老版本Oracle和达梦Oracle模式使用ROWNUM
                // 这种方式需要重写整个查询,所以我们返回一个新的Select对象
                select = handleOracleRowNum(select, offset, pageSize);
                break;
//            case GBASE8S:
//                handleSkipFirst(selectBody, offset, pageSize);
//                break;
            default:
                throw new UnsupportedOperationException("Unsupported database type: " + dbType);
        }

        return select.toString();
    }

    /**
     * 获取 sql 中分页大小,如果没有分页或者获取失败,则返回 -1,如果查询语句中包含临时表或不包含表名时,则返回 -2
     *
     * @param sql      原始SQL
     * @param dbType   数据库类型
     * @return 修改后的分页SQL
     * @throws JSQLParserException SQL解析失败
     */
    public static long getLimitByJSqlParser(String sql, DbType dbType) throws JSQLParserException {
        Statement statement = CCJSqlParserUtil.parse(sql);
        if (!(statement instanceof Select)) {
            log.warn("非SELECT语句,将返回原始SQL,sql:{}", sql);
            return -1; // 非SELECT语句直接返回
        }

        Select select = (Select) statement;
        List<String> tables = getTables(select);
        // 如果是临时表,则返回 -2
        if (isTempTable(tables)) {
            return -2;
        }
        SelectBody selectBody = select.getSelectBody();

        switch (dbType) {
            case mysql:
            case gbase:
            case mariadb:
            case oceanbase:
            case hive:
            case argo:
            case sqlite:
            case postgresql:
            case gaussdb:
            case oscar:
            case XCloud:
                return getLimit(selectBody);
            case sqlserver:
//            case oracle:
                // Oracle 12c+ and SQL Server 2012+ use the same syntax
                return getSqlServerPagination(selectBody);
            case oracle:
            case dm:
            case oceanbase_oracle:
                // 老版本Oracle和达梦Oracle模式使用ROWNUM
                // 这种方式需要重写整个查询,所以我们返回一个新的Select对象
                // todo
                return getOraclePagination(selectBody);
//            case GBASE8S:
//                handleSkipFirst(selectBody, offset, pageSize);
//                break;
            default:
                throw new UnsupportedOperationException("Unsupported database type: " + dbType);
        }
    }

    private static long getLimit(SelectBody selectBody) {
        if (selectBody instanceof PlainSelect) {
            PlainSelect plainSelect = (PlainSelect) selectBody;

            Limit limit = plainSelect.getLimit();
            return getLimitFromLimit(limit);
        } else if (selectBody instanceof SetOperationList) {
            SetOperationList setOpList = (SetOperationList) selectBody;
            Limit limit = setOpList.getLimit();
            return getLimitFromLimit(limit);
        } else {
            log.warn("不支持的SELECT语句类型,将返回原始SQL");
        }
        return -1;
    }

    private static long getLimitFromLimit(Limit limit) {
        if (limit == null) {
            return -1;
        }

        Expression rowCount = limit.getRowCount();
        if (rowCount == null) {
            return -1;
        }
        if (rowCount instanceof LongValue) {
            return ((LongValue) rowCount).getValue();
        }
        // 其他占位符等情况,则返回 -1
        return -1;
    }

    // 处理器1: LIMIT ...  ...
    private static void handleLimit(SelectBody selectBody, long offset, long pageSize) {
        if (selectBody instanceof PlainSelect) {
            PlainSelect plainSelect = (PlainSelect) selectBody;
            Limit limit = plainSelect.getLimit();
            if (limit == null) {
                limit = new Limit();
                plainSelect.setLimit(limit);
            }
            limit.setRowCount(new LongValue(pageSize));
            limit.setOffset(new LongValue(offset));
        } else if (selectBody instanceof SetOperationList) {
            SetOperationList setOpList = (SetOperationList) selectBody;
            Limit limit = setOpList.getLimit();
            if (limit == null) {
                limit = new Limit();
                setOpList.setLimit(limit);
            }
            limit.setRowCount(new LongValue(pageSize));
            limit.setOffset(new LongValue(offset));
        }
    }

    // 处理器2: LIMIT ... OFFSET ...
    private static void handleLimitOffset(SelectBody selectBody, long offset, long pageSize) {
        if (selectBody instanceof PlainSelect) {
            PlainSelect plainSelect = (PlainSelect) selectBody;
            Limit limit = plainSelect.getLimit();
            if (limit == null) {
                limit = new Limit();
                plainSelect.setLimit(limit);
            }
            limit.setRowCount(new LongValue(pageSize));
            Offset offsetObject = plainSelect.getOffset();
            if (offsetObject == null) {
                offsetObject = new Offset();
                plainSelect.setOffset(offsetObject);
            }
            setOffsetValue(offset, offsetObject);
        } else if (selectBody instanceof SetOperationList) {
            SetOperationList setOpList = (SetOperationList) selectBody;
            Limit limit = setOpList.getLimit();
            if (limit == null) {
                limit = new Limit();
                setOpList.setLimit(limit);
            }
            limit.setRowCount(new LongValue(pageSize));
            Offset offsetObject = setOpList.getOffset();
            if (offsetObject == null) {
                offsetObject = new Offset();
                setOpList.setOffset(offsetObject);
            }
            setOffsetValue(offset, offsetObject);
        }
    }

    // 处理器2: OFFSET ... FETCH ...
    private static void handleFetchOffset(SelectBody selectBody, long offset, long pageSize) {
        if (selectBody instanceof PlainSelect) {
            PlainSelect plainSelect = (PlainSelect) selectBody;
            // JSqlParser支持在PlainSelect和SetOperationList上直接设置
            Offset offsetObj = plainSelect.getOffset();
            if (offsetObj == null) {
                offsetObj = new Offset();
                plainSelect.setOffset(offsetObj);
            }
            offsetObj.setOffsetParam("ROWS");
            setOffsetValue(offset, offsetObj);

            Fetch fetch = plainSelect.getFetch();
            if (fetch == null) {
                fetch = new Fetch();
                plainSelect.setFetch(fetch);
            }
            fetch.setFetchParam("ROWS");
            fetch.setRowCount(pageSize);
        } else if (selectBody instanceof SetOperationList) {
            SetOperationList setOpList = (SetOperationList) selectBody;
            // JSqlParser支持在PlainSelect和SetOperationList上直接设置
            Offset offsetObj = setOpList.getOffset();
            if (offsetObj == null) {
                offsetObj = new Offset();
                offsetObj.setOffsetParam("ROWS");
                setOpList.setOffset(offsetObj);
            }
            setOffsetValue(offset, offsetObj);

            Fetch fetch = setOpList.getFetch();
            if (fetch == null) {
                fetch = new Fetch();
                setOpList.setFetch(fetch);
            }
            fetch.setFetchParam("ROWS");
            fetch.setRowCount(pageSize);
//            JdbcParameter jdbcParameter = new JdbcParameter();
//            jdbcParameter.setUseFixedIndex(false);
//            fetch.setFetchJdbcParameter(jdbcParameter); // 使用 "ROWS" 而不是 "?"
        }

    }

    /**
     * 设置偏移值 , 对 JSqlParser 不同版本的适配
     *
     * @param offset       抵消
     * @param offsetObject 补偿对象
     */
    public static void setOffsetValue(long offset, Offset offsetObject) {
        // 4.0 版本
//        offsetObject.setOffset(offset);
        // 4.6 版本
        offsetObject.setOffset(new LongValue(offset));
    }

    // 处理器2: OFFSET ... FETCH ...
    private static void handleSqlServerPagination(SelectBody selectBody, long offset, long pageSize) {
        if (selectBody instanceof PlainSelect) {
            PlainSelect plainSelect = (PlainSelect) selectBody;
            if (offset == 0) {
                Top top = plainSelect.getTop();
                if (top == null) {
                    top = new Top();
                    plainSelect.setTop(top);
                }
                top.setExpression(new LongValue(pageSize));
                return;
            }
            Top top = plainSelect.getTop();
            if (top != null) {
                plainSelect.setTop(null);
            }

            if (plainSelect.getOrderByElements() == null || plainSelect.getOrderByElements().isEmpty()) {
                OrderByElement orderByElement = getOrderByNullElement();
                plainSelect.setOrderByElements(Collections.singletonList(orderByElement));
                log.warn("SQL Server 分页查询未添加ORDER BY子句,已添加'ORDER BY (SELECT NULL)'");
            }
        } else if (selectBody instanceof SetOperationList) {
            SetOperationList setOpList = (SetOperationList) selectBody;
            if (setOpList.getOrderByElements() == null || setOpList.getOrderByElements().isEmpty()) {
                OrderByElement orderByElement = getOrderByNullElement();
                setOpList.setOrderByElements(Collections.singletonList(orderByElement));
                log.warn("SQL Server 分页查询未添加ORDER BY子句,已添加'ORDER BY (SELECT NULL)'");
            }
        }
        handleFetchOffset(selectBody, offset, pageSize);

    }

    /**
     * 获取 null元素排序 sql
     *
     * @return {@link OrderByElement }
     */
    private static OrderByElement getOrderByNullElement() {
        OrderByElement orderByElement = new OrderByElement();
        SubSelect subSelect = new SubSelect();
        PlainSelect select = new PlainSelect();
        select.addSelectItems(new SelectExpressionItem(new NullValue()));
        subSelect.setSelectBody(select);
        orderByElement.setExpression(subSelect);
        return orderByElement;
    }

    private static long getSqlServerPagination(SelectBody selectBody) {
        if (selectBody instanceof PlainSelect) {
            PlainSelect plainSelect = (PlainSelect) selectBody;
            Top top = plainSelect.getTop();
            if (top != null) {
                Expression expression = top.getExpression();
                if (expression instanceof LongValue) {
                    return ((LongValue) expression).getValue();
                }
                log.warn("不支持TOP表达式:{}", expression);
                return -1;
            }
            Fetch fetch = plainSelect.getFetch();
            if (fetch != null) {
                return fetch.getRowCount();
            }
        } else if (selectBody instanceof SetOperationList) {
            SetOperationList setOpList = (SetOperationList) selectBody;
            Fetch fetch = setOpList.getFetch();
            if (fetch != null) {
                return fetch.getRowCount();
            }
        }

        return -1;
    }

    /**
     * 获取oracle分页大小
     *
     * @param selectBody 选择身体
     * @return long
     */
    private static long getOraclePagination(SelectBody selectBody) {
        if (selectBody instanceof PlainSelect) {
            PlainSelect plainSelect = (PlainSelect) selectBody;

            Fetch fetch = plainSelect.getFetch();
            if (fetch != null) {
                return fetch.getRowCount();
            }

            // 主要用来兼容达梦数据库的 limit 语法
            Limit limit = plainSelect.getLimit();
            if (limit != null) {
                Expression rowCount = limit.getRowCount();
                if (rowCount instanceof LongValue) {
                    return ((LongValue) rowCount).getValue();
                }
            }
        } else if (selectBody instanceof SetOperationList) {
            SetOperationList setOpList = (SetOperationList) selectBody;
            Fetch fetch = setOpList.getFetch();
            if (fetch != null) {
                return fetch.getRowCount();
            }
        }

        Optional<Pager> optional = parseRowNum(selectBody);
        if (optional.isPresent()) {
            return optional.get().getPageSize();
        }
        return -1;
    }

    /**
     * 解析传统的 ROWNUM 嵌套子查询语法
     * 识别模式: SELECT * FROM (SELECT ..., ROWNUM rnum FROM (...) WHERE ROWNUM <= end) WHERE rnum > start
     */
    private static Optional<Pager> parseRowNum(SelectBody selectBody) {
        // ROWNUM 分页模式一定是一个 PlainSelect
        if (!(selectBody instanceof PlainSelect)) {
            return Optional.empty();
        }
        PlainSelect outerSelect = (PlainSelect) selectBody;

        // 1. 检查外部查询结构:FROM子句必须是子查询,且有WHERE条件
        if (!(outerSelect.getFromItem() instanceof SubSelect) || outerSelect.getWhere() == null) {
            return Optional.empty();
        }

        // 2. 解析外部WHERE条件,获取 offset (e.g., WHERE rnum > 20)
        long offset = -1;
        String rowNumAlias;

        if (outerSelect.getWhere() instanceof ComparisonOperator) {
            ComparisonOperator where = (ComparisonOperator) outerSelect.getWhere();
            // 支持 "rnum > 20" 或 "rnum >= 21"
            if (where.getLeftExpression() instanceof Column && where.getRightExpression() instanceof LongValue) {
                rowNumAlias = ((Column) where.getLeftExpression()).getColumnName();
                long value = ((LongValue) where.getRightExpression()).getValue();

                String op = where.getStringExpression();
                if (">".equals(op)) {
                    offset = value;
                } else if (">=".equals(op)) {
                    offset = value - 1;
                }
            } else {
                rowNumAlias = null;
            }
        } else {
            rowNumAlias = null;
        }
        if (offset == -1 || rowNumAlias == null) {
            return Optional.empty();
        }

        // 3. 进入中间层子查询
        SubSelect middleSubSelect = (SubSelect) outerSelect.getFromItem();
        if (!(middleSubSelect.getSelectBody() instanceof PlainSelect)) {
            return Optional.empty();
        }
        PlainSelect middleSelect = (PlainSelect) middleSubSelect.getSelectBody();

        // 4. 检查中间层查询结构:必须有WHERE条件
        if (middleSelect.getWhere() == null) {
            return Optional.empty();
        }

        // 5. 解析中间层WHERE,获取 endRow (e.g., WHERE ROWNUM <= 30)
        long endRow = -1;
        if (middleSelect.getWhere() instanceof ComparisonOperator) {
            ComparisonOperator where = (ComparisonOperator) middleSelect.getWhere();
            // 支持 "ROWNUM <= 30" 或 "ROWNUM < 31"
            if (where.getLeftExpression() instanceof Column &&
                    "ROWNUM".equalsIgnoreCase(((Column) where.getLeftExpression()).getColumnName())
                    && where.getRightExpression() instanceof LongValue) {
                long value = ((LongValue) where.getRightExpression()).getValue();
                String op = where.getStringExpression();
                if ("<=".equals(op)) {
                    endRow = value;
                } else if ("<".equals(op)) {
                    endRow = value - 1;
                }
            }
        }
        if (endRow == -1) {
            return Optional.empty();
        }

        // 6. 验证 ROWNUM 别名在中间层 SELECT 列表中
        boolean aliasFound = middleSelect.getSelectItems().stream()
                .filter(item -> item instanceof SelectExpressionItem)
                .map(item -> (SelectExpressionItem) item)
                .anyMatch(item -> item.getAlias() != null
                        && rowNumAlias.equalsIgnoreCase(item.getAlias().getName())
                        && item.getExpression() instanceof Column
                        && "ROWNUM".equalsIgnoreCase(((Column) item.getExpression()).getColumnName()));

        if (!aliasFound) {
            return Optional.empty();
        }

        // 7. 计算 pageSize 并返回
        long pageSize = endRow - offset;
        if (pageSize > 0) {
            int pageNo = pageSize <= 0 ? 1 : (int) (offset / pageSize + 1);
            return Optional.of(Pager.of(pageNo, (int) pageSize));
        }

        return Optional.empty();
    }

    // 处理器3: SKIP ... FIRST ...
    private static void handleSkipFirst(SelectBody selectBody, long offset, long pageSize) {
        if (selectBody instanceof PlainSelect) {
            PlainSelect plainSelect = (PlainSelect) selectBody;

            Skip skip = plainSelect.getSkip();
            if (skip == null) {
                skip = new Skip();
                plainSelect.setSkip(skip);
            }
            skip.setRowCount(offset);

            First first = plainSelect.getFirst();
            if (first == null) {
                first = new First();
                plainSelect.setFirst(first);
            }
            first.setRowCount(pageSize);
        }
        // 注意:UNION查询与SKIP/FIRST的组合在某些数据库中语法可能有限制,
        // 这里只处理最常见的PlainSelect场景。
    }

    // 处理器4: Oracle ROWNUM (最复杂)
    private static Select handleOracleRowNum(Select originalSelect, long offset, long pageSize) {
        long endRow = offset + pageSize;

        // 确保原始查询有ORDER BY,否则ROWNUM分页是无意义的
        SelectBody selectBody = originalSelect.getSelectBody();
        if (selectBody instanceof PlainSelect) {
            if (((PlainSelect) selectBody).getOrderByElements() == null) {
                log.warn("ROWNUM pagination without ORDER BY is not recommended.");
            }
        }

        // 外部查询: SELECT * FROM ( ... ) WHERE rnum >= ?
        PlainSelect outerSelect = new PlainSelect();
        outerSelect.addSelectItems(new AllColumns()); // SELECT *

        // 中间查询: SELECT t.*, ROWNUM AS rnum FROM ( original_sql ) t WHERE ROWNUM <= ?
        PlainSelect middleSelect = new PlainSelect();
        // SELECT t.*, ROWNUM as rnum
        middleSelect.addSelectItems(new SelectExpressionItem(new Column("t.*")));
        middleSelect.addSelectItems(new SelectExpressionItem(new Column("ROWNUM")).withAlias(new Alias("rnum")));

        // FROM ( original_sql ) t
        SubSelect originalSubSelect = new SubSelect();
        originalSubSelect.setSelectBody(selectBody);
        originalSubSelect.setAlias(new Alias("t"));
        middleSelect.setFromItem(originalSubSelect);

        // WHERE ROWNUM <= endRow
        middleSelect.setWhere(new MinorThanEquals().withLeftExpression(new Column("ROWNUM"))
                .withRightExpression(new LongValue(endRow)));

        // 将中间查询包装成子查询,用于外部查询
        SubSelect middleSubSelect = new SubSelect();
        middleSubSelect.setSelectBody(middleSelect);
        middleSubSelect.setAlias(new Alias("")); // Oracle子查询可以没有别名
        outerSelect.setFromItem(middleSubSelect);

        // WHERE rnum >= startRow
        outerSelect.setWhere(new GreaterThan().withLeftExpression(new Column("rnum"))
                .withRightExpression(new LongValue(offset)));

        // 创建一个新的Select对象并返回
        Select newSelect = new Select();
        newSelect.setSelectBody(outerSelect);
        return newSelect;
    }

    /**
     * 是否是临时表
     *
     * @param tables 表
     * @return boolean
     */
    private static boolean isTempTable(List<String> tables) {
        if (tables.isEmpty() || (tables.size() == 1 && "dual".equalsIgnoreCase(tables.get(0)))) {
            return true;
        }
        return false;
    }

JSqlParser vs. Alibaba Druid:强强对话,如何选择?💡

谈到 Java 世界的 SQL 解析,另一个无法绕开的名字就是 Alibaba Druid。Druid 不仅是一个强大的数据库连接池,其内置的 SQL Parser 也同样功能强大。那么,我们该如何选择呢?

特性维度JSqlParserAlibaba Druid SQL Parser
核心定位纯粹的 SQL 解析器。专注、轻量,只做一件事并把它做好。数据库连接池 的一个核心模块。为监控、防火墙、SQL 格式化等服务。
功能范围解析、遍历、修改 SQL。解析、遍历、修改 SQL,并提供 SQL 格式化、指纹计算、语法兼容性检查等高级功能。
依赖与体积极简。几乎无外部依赖,JAR 包体积小(约 500KB)。庞大。作为 Druid 的一部分,引入它意味着引入整个连接池生态,体积较大(约 3MB)。
API 设计AST 导向。直接操作语法树对象,非常直观。AST + Visitor。同样支持 Visitor 模式,API 设计更偏向于其内部生态。
方言支持良好。支持主流数据库,但对一些非常规或新特性的支持可能稍慢。非常强大。阿里巴巴内部海量业务驱动,对 MySQL、Oracle 等的方言支持非常深入和及时。
性能非常快。因为目标单一,优化做得很好。同样很快。但作为连接池的一部分,其解析器设计需兼顾多种场景。

如何抉择?

一张图帮你决定:

总结一下:

  • 选择 JSqlParser 的场景:

    • 你的项目是一个工具、库或微服务,需要保持轻量。
    • 需要 SQL 解析和修改功能,不想引入连接池等“重”组件。
    • 你希望有一个专注、纯粹的解析器,API 更直接。
  • 选择 Druid SQL Parser 的场景:

    • 你的项目已经在使用 Druid 作为连接池。那么“就地取材”是最高效的选择。
    • 你需要 Druid 提供的附加功能,如 SQL 监控、WallFilter(SQL 防火墙)、SQL 格式化等。
    • 你需要处理极其复杂或特定的数据库方言,Druid 的实践可能更丰富。

两者都是顶级的开源项目,没有绝对的优劣,只有是否适合你的业务场景。

结语

JSqlParser 就像一把精巧的瑞士军刀,它赋予了 Java 应用前所未有的 SQL 处理能力。从简单的信息提取,到复杂的动态改写,它都能游刃有余。

掌握 JSqlParser,意味着你拥有了在数据层进行“元编程”的能力,能够构建出更加智能、灵活和强大的数据应用。希望今天的分享,能为你打开一扇通往 SQL 解析新世界的大门。

注意: duid 和 JSqlParser 并不是互斥的,druid 对特定数据库做了解析优化,可以在 druid 解析异常时,再使用 JSqlPaser去解析,这样既保证了效率又保证了sql解析的兼容性

Q.E.D.


寻门而入,破门而出