JSQLParser 4.x 中 IN 表达式缺少括号的问题排查与解决
2026/7/28 2:04:15 网站建设 项目流程

JSQLParser 4.x 中 IN 表达式缺少括号的问题排查与解决

问题背景

在使用 MyBatis Plus 拦截器动态拼接数据权限 SQL 时,使用 JSQLParser 构造IN表达式,运行后发现生成的 SQL 语法错误:

-- 期望SELECT*FROMsdk_projectWHEREdeleted=0ANDsales_user_idIN('aaa','bbb','ccc')-- 实际SELECT*FROMsdk_projectWHEREdeleted=0ANDsales_user_idIN'aaa','bbb','ccc'

Druid 随即抛出异常:

com.alibaba.druid.sql.parser.ParserException: not supported. pos 116, line 1, column 116, token ,

问题代码

InExpressioninExpression=newInExpression();inExpression.setLeftExpression(newColumn("sales_user_id"));inExpression.setRightExpression(newExpressionList<>(ids.stream().map(StringValue::new).collect(Collectors.toList())));

根因分析

环境

依赖版本
JSQLParser4.9(通过 MyBatis-Plus 3.5.7 传递引入)

关键结论

InExpression在调用toString()时,括号完全由rightExpression.toString()自身决定InExpression本身不负责添加括号。

ExpressionList.toString() 不带括号

通过反编译jsqlparser-4.9.jar验证:

// ExpressionList.toString() 等价源码@OverridepublicStringtoString(){StringBuildersb=newStringBuilder();appendTo(sb);// 自行拼接,不加括号returnsb.toString();// 输出:'aaa', 'bbb', 'ccc'}

ParenthesedExpressionList.toString() 带括号

// ParenthesedExpressionList.toString() 等价源码@OverridepublicStringtoString(){returnPlainSelect.getStringList(this,true,true);// ^^^^ ^^^^// useBrackets=true ─┘ │// useComma=true ─────────┘// 输出:('aaa', 'bbb', 'ccc')}

两个true源码内硬编码的,不是外部调用者传入的。通过字节码可以直接验证:

ParenthesedExpressionList.toString(): 0: aload_0 ← this 1: iconst_1 ← 硬编码 true(useBrackets) 2: iconst_1 ← 硬编码 true(useComma) 3: invokestatic PlainSelect.getStringList:(List, Z, Z)

三种写法实测对比

List<String>ids=Arrays.asList("aaa","bbb","ccc");// A: Parenthesis 包裹 ExpressionListInExpressioninA=newInExpression();inA.setLeftExpression(newColumn("user_id"));inA.setRightExpression(newParenthesis(newExpressionList<>(ids.stream().map(StringValue::new).collect(Collectors.toList()))));// 输出:user_id IN ('aaa', 'bbb', 'ccc') ✓// B: ParenthesedExpressionListInExpressioninB=newInExpression();inB.setLeftExpression(newColumn("user_id"));inB.setRightExpression(newParenthesedExpressionList<>(ids.stream().map(StringValue::new).collect(Collectors.toList())));// 输出:user_id IN ('aaa', 'bbb', 'ccc') ✓// C: 裸 ExpressionList(原始问题写法)InExpressioninC=newInExpression();inC.setLeftExpression(newColumn("user_id"));inC.setRightExpression(newExpressionList<>(ids.stream().map(StringValue::new).collect(Collectors.toList())));// 输出:user_id IN 'aaa', 'bbb', 'ccc' ✗ SQL 语法错误

A 和 B 的 SQL 输出完全相同,两种写法均可解决问题。

Parenthesis 的原理

Parenthesis是一个装饰器,toString()直接硬拼括号:

// Parenthesis.toString() 等价源码@OverridepublicStringtoString(){return"("+expression+")";}

字节码验证:

Parenthesis.toString(): 7: ldc "(" ← 硬拼左括号 13: append(expression) 31: ldc ")" ← 硬拼右括号 39: areturn

toString() 的触发时机

setRightExpression()只存引用,不触发toString()。整个序列化发生在最后调用statement.toString()时:

// 构造阶段:只存引用,无任何输出newParenthesedExpressionList<>(list)// 构造对象inExpression.setRightExpression(...)// 存引用// 序列化阶段:statement.toString() 触发整棵 AST 递归序列化returnstatement.toString();// └─ InExpression.toString()// └─ StringBuilder.append("IN ")// └─ StringBuilder.append(rightExpression) ← 此处才调用 rightExpression.toString()// └─ ParenthesedExpressionList.toString()// └─ PlainSelect.getStringList(this, true, true)// └─ 输出 ('aaa', 'bbb', 'ccc')

解决方案

ExpressionList替换为ParenthesedExpressionList

// 修复前inExpression.setRightExpression(newExpressionList<>(ids.stream().map(StringValue::new).collect(Collectors.toList())));// 修复后inExpression.setRightExpression(newParenthesedExpressionList<>(ids.stream().map(StringValue::new).collect(Collectors.toList())));

或者用Parenthesis包裹也可以(两者 SQL 输出等价):

inExpression.setRightExpression(newParenthesis(newExpressionList<>(ids.stream().map(StringValue::new).collect(Collectors.toList()))));

总结

写法SQL 输出说明
ExpressionList<>(...)IN 'a', 'b'不带括号,SQL 语法错误
ParenthesedExpressionList<>(...)IN ('a', 'b')内部硬编码useBrackets=true
Parenthesis(ExpressionList<>(...))IN ('a', 'b')装饰器强制加括号

注意:此问题仅出现在 JSQLParser 4.x。若项目升级至 5.x,建议重新验证 API 兼容性。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询