You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用Java定位SQL中含{变量}的条件并替换为1=1

解决SQL条件中带变量的精准替换问题

我之前刚好踩过类似的坑——直接用字符串拆分或者简单正则处理带括号的SQL条件,很容易把逻辑搞乱,比如你遇到的括号内OR条件拆分错误的情况。下面分享两种靠谱的解决方案:

方案一:用专业SQL解析库(推荐)

Java生态里有专门的SQL解析库可以处理这种场景,比如JSqlParser,它能把SQL解析成抽象语法树(AST),让你精准遍历、修改每个条件节点,完全不用担心括号嵌套或者逻辑运算符优先级的问题。

示例代码(JSqlParser)

首先引入Maven依赖(用最新稳定版即可):

<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>4.7</version>
</dependency>

然后编写处理逻辑:

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import net.sf.jsqlparser.util.deparser.ExpressionDeParser;
import net.sf.jsqlparser.util.deparser.SelectDeParser;

import java.util.HashMap;
import java.util.Map;

public class SqlVariableReplacer {
    public static void main(String[] args) throws JSQLParserException {
        String originalSql = "Select * From customer c inner join address a on c.id = a.customer_id where c.id > {var1} AND (c.name LIKE {var2} OR a.city = {var3})";
        Map<String, Object> variables = new HashMap<>();
        variables.put("var1", 2);
        variables.put("var2", "*ALL");
        variables.put("var3", "'aa'");

        // 解析SQL为语法树
        Select select = (Select) CCJSqlParserUtil.parse(originalSql);
        SelectBody selectBody = select.getSelectBody();

        // 自定义表达式处理器,遍历并替换目标条件
        ExpressionDeParser expressionDeParser = new ExpressionDeParser() {
            @Override
            public void visit(Expression expr) {
                String exprStr = expr.toString();
                for (Map.Entry<String, Object> entry : variables.entrySet()) {
                    String varKey = "{" + entry.getKey() + "}";
                    if (exprStr.contains(varKey)) {
                        Object value = entry.getValue();
                        if ("*ALL".equals(value)) {
                            // 替换成1=1
                            super.visit(new EqualsTo(new LongValue(1), new LongValue(1)));
                            return;
                        } else {
                            // 替换为变量实际值
                            String replacedExpr = exprStr.replace(varKey, value.toString());
                            try {
                                super.visit(CCJSqlParserUtil.parseExpression(replacedExpr));
                                return;
                            } catch (JSQLParserException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
                // 非目标条件,继续遍历子节点
                super.visit(expr);
            }
        };

        // 生成处理后的SQL
        StringBuffer buffer = new StringBuffer();
        SelectDeParser selectDeParser = new SelectDeParser(expressionDeParser, buffer);
        expressionDeParser.setSelectVisitor(selectDeParser);
        expressionDeParser.setBuffer(buffer);
        selectBody.accept(selectDeParser);

        System.out.println(buffer.toString());
        // 输出结果:SELECT * FROM customer c INNER JOIN address a ON c.id = a.customer_id WHERE c.id > 2 AND (1=1 OR a.city = 'aa')
    }
}

这个方案的核心是通过AST遍历每个独立的条件表达式,精准判断是否包含目标变量,再根据变量值决定替换逻辑,完全不会破坏括号内的逻辑结构。

方案二:手动递归解析条件表达式(不依赖第三方库)

如果不想引入外部库,可以自己实现一个简单的递归解析器,核心思路是把条件表达式拆解成树形结构:

  • AND/OR为节点,递归处理左右子表达式
  • 遇到括号时,优先解析括号内的子表达式
  • 对每个叶子节点(单个条件,比如c.id > {var1})进行变量检查和替换

举个简化的逻辑框架:

public class SimpleSqlProcessor {
    // 递归处理条件字符串
    public static String processCondition(String condition, Map<String, Object> variables) {
        // 先处理最内层括号的子条件
        int start = condition.indexOf('(');
        if (start != -1) {
            int end = findMatchingBracket(condition, start);
            String inner = condition.substring(start + 1, end);
            String processedInner = processCondition(inner, variables);
            condition = condition.substring(0, start) + processedInner + condition.substring(end + 1);
            return processCondition(condition, variables);
        }

        // 拆分OR条件(优先处理低优先级运算符)
        String[] orParts = splitOnOperator(condition, "OR");
        for (int i = 0; i < orParts.length; i++) {
            // 拆分AND条件
            String[] andParts = splitOnOperator(orParts[i], "AND");
            for (int j = 0; j < andParts.length; j++) {
                String part = andParts[j].trim();
                // 检查并替换变量
                for (Map.Entry<String, Object> entry : variables.entrySet()) {
                    String varKey = "{" + entry.getKey() + "}";
                    if (part.contains(varKey)) {
                        Object value = entry.getValue();
                        andParts[j] = "*ALL".equals(value) ? "1=1" : part.replace(varKey, value.toString());
                    }
                }
            }
            orParts[i] = String.join(" AND ", andParts);
        }
        return String.join(" OR ", orParts);
    }

    // 找到匹配的右括号
    private static int findMatchingBracket(String str, int start) {
        int count = 1;
        for (int i = start + 1; i < str.length(); i++) {
            if (str.charAt(i) == '(') count++;
            else if (str.charAt(i) == ')') count--;
            if (count == 0) return i;
        }
        return -1; // 实际场景需添加格式异常处理
    }

    // 按逻辑运算符拆分,忽略括号内的运算符
    private static String[] splitOnOperator(String str, String operator) {
        // 这里需要实现完整的括号层级判断逻辑,避免拆分括号内的运算符
        // 以下是简化示例,实际需完善
        return str.split("\\s+" + operator + "\\s+");
    }
}

这个方法需要自己处理很多细节(比如括号匹配、运算符优先级、空格处理),适合简单场景,复杂SQL容易出问题,所以还是推荐用专业库。

为什么不推荐正则表达式?

简单正则无法处理嵌套括号的场景,比如你示例中的(c.name LIKE {var2} OR a.city = {var3}),如果用AND|OR拆分,会把括号内的OR也拆出来,导致替换逻辑混乱。虽然可以写复杂的正则来匹配括号,但维护成本极高,而且很容易漏处理边缘情况(比如多层嵌套括号、带注释的SQL)。


内容的提问来源于stack exchange,提问作者Lusi

火山引擎 最新活动