共计 1768 个字符,预计需要花费 5 分钟才能阅读完成。
从业务场景看问题
最近在开发报表导出功能时,遇到一个头疼的问题:系统使用 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 工作流程
- 词法分析阶段:将 SQL 字符串拆分为 Token 流
- 语法分析阶段:根据 SQL 语法规则构建 AST
- 语义分析阶段:验证 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);
}
}
避坑指南
- 版本差异:
- JSqlParser 3.2 以下版本对字符串常量处理不完善
-
4.0+ 版本增加了
withSquareBracketQuotation配置项 -
生产环境禁忌:
- 避免动态拼接含点号的字符串条件
-
不要在 ORM 框架中直接使用原生 SQL
-
监控实践:
try {CCJSqlParserUtil.parse(sql); } catch (JSQLParserException e) {log.error("SQL 解析失败: {}", sql, e); metrics.counter("sql.parse.errors").increment();}
延伸思考
处理这类问题时,我们实际上在解决 SQL 方言的兼容性问题。可以考虑:
- 实现 SQL 预处理层,统一标准化输入
- 采用多解析器策略,根据 SQL 特征自动选择
- 建立 SQL 语法白名单机制
希望这些经验能帮你少走弯路。你在 SQL 解析中还遇到过哪些棘手问题?欢迎一起探讨更优雅的解决方案。
正文完
