JSqlParser 语法解析异常深度解析:如何解决 ‘Encountered unexpected token: “.”‘ 错误

1次阅读
没有评论

共计 2264 个字符,预计需要花费 6 分钟才能阅读完成。

image.webp

问题场景复现

上周在开发数据报表模块时,我遇到了一个令人头疼的问题:系统在解析类似这样的 SQL 时突然报错:

文章配图

SELECT user.id, order.value FROM user, order WHERE user.id = order.user_id

错误信息显示:

caused by: net.sf.jsqlparser.parser.ParseException: 
Encountered unexpected token: "." "." at line 1, column 8.
Was expecting one of: ";", "CONNECT", "FOR", "GROUP", "HAVING"

这个场景非常典型——当我们在多表查询中直接使用 表名. 列名 的格式时,JSqlParser 的默认配置可能会抛出这个异常。

技术原理解析

JSqlParser 工作流程

  1. 词法分析阶段:将 SQL 字符串拆分为 Token 序列
  2. 语法分析阶段:根据 SQL 语法规则构建 AST
  3. 语义分析阶段:验证表 / 列是否存在等语义信息
flowchart TD
    A[SQL 字符串] --> B[词法分析器]
    B --> C[Token 流]
    C --> D[语法分析器]
    D --> E[AST]
    E --> F[访问者模式处理]

点号的合法使用场景

根据 SQL 标准,点号通常出现在:

  • 表名. 列名引用(table.column
  • 别名引用(alias.column
  • 函数调用(schema.function()
  • 带命名空间的类型(type.subtype

解决方案大全

方案一:SQL 语句重构

原始问题语句

SELECT a.id, b.value FROM table_a a, table_b b

改进写法 1(使用 AS 关键字)

SELECT a.id AS a_id, b.value AS b_value 
FROM table_a AS a, table_b AS b

改进写法 2(省略点号)

SELECT id, value FROM table_a, table_b 
WHERE table_a.key = table_b.key

改进写法 3(使用 JOIN 语法)

SELECT a.id, b.value 
FROM table_a a JOIN table_b b ON a.key = b.key

方案二:配置解析器

CCJSqlParserManager parserManager = new CCJSqlParserManager();
parserManager.setErrorHandler(new JSqlParserErrorHandler() {
    @Override
    public void handleParseError(ParseException ex) {// 自定义错误处理逻辑}
});

// 启用宽松模式
System.setProperty("JSqlParser.parser.strict", "false");

方案三:自定义解析器(完整示例)

public class DotNotationVisitor extends SelectVisitorAdapter {
    @Override
    public void visit(PlainSelect plainSelect) {
        // 处理点号表达式
        for (SelectItem item : plainSelect.getSelectItems()) {if (item instanceof SelectExpressionItem) {Expression expr = ((SelectExpressionItem) item).getExpression();
                if (expr instanceof Column) {Column column = (Column) expr;
                    // 自动添加 AS 子句
                    if (column.getColumnName().contains(".")) {
                        item.setAlias(new Alias(column.getColumnName().replace('.', '_')));
                    }
                }
            }
        }
        super.visit(plainSelect);
    }
}

// 使用示例
Statement stmt = CCJSqlParserUtil.parse(sql);
stmt.accept(new DotNotationVisitor());

避坑指南

版本兼容性

  • JSqlParser 1.4+:默认严格模式
  • JSqlParser 4.5+:支持更多 SQL 方言
  • 建议使用最新稳定版(当前 4.7)

危险写法模式

-- 反模式 1:未限定的点号
SELECT .id FROM table

-- 反模式 2:连续点号
SELECT a..b FROM table

-- 反模式 3:带空格的点号
SELECT a . b FROM table

监控建议

  1. 日志记录配置

    Logger.getLogger("net.sf.jsqlparser").setLevel(Level.WARNING);

  2. 异常捕获模板

    try {Statement stmt = CCJSqlParserUtil.parse(sql);
    } catch (JSQLParserException e) {log.error("SQL 解析失败: {}", sql, e);
        // 添加指标监控
        metrics.counter("sql.parse.errors").increment();}

延伸思考

要实现多 SQL 方言兼容,可以考虑:

  1. 使用 SqlDialect 枚举区分语法规则
  2. 为每种方言实现特定的 Visitor 适配器
  3. 构建 SQL 语法校验前置层

通过本文的解决方案,我们不仅能快速修复眼前的问题,更能深入理解 SQL 解析器的工作原理。下次遇到类似异常时,不妨从词法分析的角度思考,或许会有新的发现。

正文完
 0
评论(没有评论)