共计 2264 个字符,预计需要花费 6 分钟才能阅读完成。
问题场景复现
上周在开发数据报表模块时,我遇到了一个令人头疼的问题:系统在解析类似这样的 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 工作流程
- 词法分析阶段:将 SQL 字符串拆分为 Token 序列
- 语法分析阶段:根据 SQL 语法规则构建 AST
- 语义分析阶段:验证表 / 列是否存在等语义信息
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
监控建议
-
日志记录配置
Logger.getLogger("net.sf.jsqlparser").setLevel(Level.WARNING); -
异常捕获模板
try {Statement stmt = CCJSqlParserUtil.parse(sql); } catch (JSQLParserException e) {log.error("SQL 解析失败: {}", sql, e); // 添加指标监控 metrics.counter("sql.parse.errors").increment();}
延伸思考
要实现多 SQL 方言兼容,可以考虑:
- 使用
SqlDialect枚举区分语法规则 - 为每种方言实现特定的
Visitor适配器 - 构建 SQL 语法校验前置层
通过本文的解决方案,我们不仅能快速修复眼前的问题,更能深入理解 SQL 解析器的工作原理。下次遇到类似异常时,不妨从词法分析的角度思考,或许会有新的发现。
正文完
发表至: 技术分享
近一天内
