JSQLParser 4.x 中 IN 表达式缺少括号的问题排查与解决
JSQLParser 4.x 中 IN 表达式缺少括号的问题排查与解决问题背景在使用 MyBatis Plus 拦截器动态拼接数据权限 SQL 时使用 JSQLParser 构造IN表达式运行后发现生成的 SQL 语法错误-- 期望SELECT*FROMsdk_projectWHEREdeleted0ANDsales_user_idIN(aaa,bbb,ccc)-- 实际SELECT*FROMsdk_projectWHEREdeleted0ANDsales_user_idINaaa,bbb,cccDruid 随即抛出异常com.alibaba.druid.sql.parser.ParserException: not supported. pos 116, line 1, column 116, token ,问题代码InExpressioninExpressionnewInExpression();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(){StringBuildersbnewStringBuilder();appendTo(sb);// 自行拼接不加括号returnsb.toString();// 输出aaa, bbb, ccc}ParenthesedExpressionList.toString() 带括号// ParenthesedExpressionList.toString() 等价源码OverridepublicStringtoString(){returnPlainSelect.getStringList(this,true,true);// ^^^^ ^^^^// useBracketstrue ─┘ │// useCommatrue ─────────┘// 输出(aaa, bbb, ccc)}两个true是源码内硬编码的不是外部调用者传入的。通过字节码可以直接验证ParenthesedExpressionList.toString(): 0: aload_0 ← this 1: iconst_1 ← 硬编码 trueuseBrackets 2: iconst_1 ← 硬编码 trueuseComma 3: invokestatic PlainSelect.getStringList:(List, Z, Z)三种写法实测对比ListStringidsArrays.asList(aaa,bbb,ccc);// A: Parenthesis 包裹 ExpressionListInExpressioninAnewInExpression();inA.setLeftExpression(newColumn(user_id));inA.setRightExpression(newParenthesis(newExpressionList(ids.stream().map(StringValue::new).collect(Collectors.toList()))));// 输出user_id IN (aaa, bbb, ccc) ✓// B: ParenthesedExpressionListInExpressioninBnewInExpression();inB.setLeftExpression(newColumn(user_id));inB.setRightExpression(newParenthesedExpressionList(ids.stream().map(StringValue::new).collect(Collectors.toList())));// 输出user_id IN (aaa, bbb, ccc) ✓// C: 裸 ExpressionList原始问题写法InExpressioninCnewInExpression();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: areturntoString() 的触发时机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)✓内部硬编码useBracketstrueParenthesis(ExpressionList(...))IN (a, b)✓装饰器强制加括号注意此问题仅出现在 JSQLParser 4.x。若项目升级至 5.x建议重新验证 API 兼容性。