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

1次阅读
没有评论

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

image.webp

从业务场景看问题

最近在开发报表导出功能时,遇到一个头疼的问题:系统使用 JSqlParser 动态解析用户输入的 SQL 语句,但当 SQL 包含类似 SELECT a.* FROM tableA a, tableB b WHERE a.id=b.id AND a.name='test.value' 的查询时,总会抛出 net.sf.jsqlparser.parser.ParseException: Encountered unexpected token: "." 异常。

文章配图

经过排查发现,问题出在字符串常量中的点号(如 ’test.value’)。JSqlParser 默认将点号解析为表名 / 列名分隔符,但当点号出现在字符串常量中时,就会导致语法解析失败。

技术原理解析

JSqlParser 工作流程

  1. 词法分析阶段:将 SQL 字符串拆分为 Token 流
  2. 语法分析阶段:根据 SQL 语法规则构建 AST
  3. 语义分析阶段:验证 AST 的合理性

点号在 SQL 标准中有三种合法用途:

  • 表名. 列名分隔(table.column)
  • 浮点数表示(123.45)
  • 函数调用(function.param)

但在字符串常量中出现的点号(如'test.value')应该被识别为普通字符。

解决方案实战

方案一:SQL 语句重构

-- 错误写法
SELECT * FROM users WHERE email='user.name@domain.com';

-- 正确写法 1:使用参数化查询
SELECT * FROM users WHERE email=?;

-- 正确写法 2:转义处理
SELECT * FROM users WHERE email='user.name@domain.com' ESCAPE '\\';

方案二:配置 CCJSqlParserManager

CCJSqlParserManager parserManager = new CCJSqlParserManager();
// 启用字符串常量识别
parserManager.setErrorHandler(new JSqlParserErrorHandler() {
    @Override
    public void handleParseError(ParseException e) {// 自定义错误处理逻辑}
});
String sql = "SELECT * FROM table WHERE col='value.with.dots';";
Select select = (Select) parserManager.parse(new StringReader(sql));

方案三:自定义 StatementVisitor

public class CustomVisitor extends StatementVisitorAdapter {
    @Override
    public void visit(PlainSelect plainSelect) {
        // 处理带点号的字符串常量
        if(plainSelect.getWhere() != null) {plainSelect.getWhere().accept(new ExpressionVisitorAdapter() {
                @Override
                public void visit(StringValue stringValue) {
                    // 对字符串值进行特殊处理
                    super.visit(stringValue);
                }
            });
        }
        super.visit(plainSelect);
    }
}

避坑指南

  1. 版本差异:
  2. JSqlParser 3.2 以下版本对字符串常量处理不完善
  3. 4.0+ 版本增加了 withSquareBracketQuotation 配置项

  4. 生产环境禁忌:

  5. 避免动态拼接含点号的字符串条件
  6. 不要在 ORM 框架中直接使用原生 SQL

  7. 监控实践:

    try {CCJSqlParserUtil.parse(sql);
    } catch (JSQLParserException e) {log.error("SQL 解析失败: {}", sql, e);
        metrics.counter("sql.parse.errors").increment();}

延伸思考

处理这类问题时,我们实际上在解决 SQL 方言的兼容性问题。可以考虑:

  1. 实现 SQL 预处理层,统一标准化输入
  2. 采用多解析器策略,根据 SQL 特征自动选择
  3. 建立 SQL 语法白名单机制

希望这些经验能帮你少走弯路。你在 SQL 解析中还遇到过哪些棘手问题?欢迎一起探讨更优雅的解决方案。

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