在java.sql实例中使用SQL IN子句的最佳变通方法是什么?由于SQL注入攻击安全问题,不支持多值的PreparedStatement:一个?占位符表示一个值,而不是一个值列表。

考虑下面的SQL语句:

SELECT my_column FROM my_table where search_column IN (?)

使用preparedStatement。setString(1, "'A', 'B', 'C'");本质上是一种无用的尝试,试图解决使用原因?首先。

有什么可行的解决办法?


当前回答

遵循亚当的想法。让你的准备语句select my_column from my_table where search_column in (#) 创建一个字符串x,并根据你的值列表用一些“?,?,?”填充它 然后只需更改查询中的#为您的新字符串x填充

其他回答

使用嵌套查询是一种令人不快的变通方法,但肯定是可行的。创建一个包含列的临时表MYVALUES。将值列表插入到MYVALUES表中。然后执行

select my_column from my_table where search_column in ( SELECT value FROM MYVALUES )

很丑,但如果你的价值列表非常大,这是一个可行的选择。

如果您的数据库没有缓存准备好的语句,这种技术还有一个额外的好处,那就是可能会从优化器获得更好的查询计划(检查一个页面是否有多个值,表只能检查一次,而不是每个值检查一次,等等),这样可以节省开销。您的“insert”将需要批处理,并且可能需要调整MYVALUES表以使锁定或其他高开销保护最小化。

似乎还没有人建议使用现成的查询构建器,比如jOOQ或QueryDSL,甚至Criteria query,它们可以开箱即用地管理动态IN列表,可能包括对可能出现的所有边缘情况的管理,例如:

运行到Oracle的每个IN列表最多1000个元素(与绑定值的数量无关) 遇到任何驱动程序的绑定值的最大数目,这是我在这个答案中记录的 遇到游标缓存争用问题,因为太多不同的SQL字符串被“硬解析”,执行计划不能再缓存了(jOOQ和最近Hibernate也通过提供IN列表填充来解决这个问题)

(免责声明:我为jOOQ背后的公司工作)

只是为了完整性:只要值集不是太大,您也可以简单地用字符串构造语句,如

... WHERE tab.col = ? OR tab.col = ? OR tab.col = ?

然后你可以把它传递给prepare(),然后在循环中使用setXXX()来设置所有的值。这看起来很讨厌,但许多“大型”商业系统通常都会这样做,直到达到特定于db的限制,例如Oracle中的语句为32 KB(我认为是)。

当然,您需要确保集合永远不会不合理地大,或者在这种情况下进行错误捕获。

我只是为此制定了一个特定于postgresql的选项。它有点像黑客,有自己的优缺点和局限性,但它似乎是有效的,而且不局限于特定的开发语言、平台或PG驱动程序。

当然,诀窍是找到一种方法,将任意长度的值集合作为单个参数传递,并让db将其识别为多个值。我的解决方案是从集合中的值构造一个带分隔符的字符串,将该字符串作为单个参数传递,并使用string_to_array()与PostgreSQL正确使用它所需的强制转换。

因此,如果你想搜索“foo”,“blah”和“abc”,你可以将它们连接成一个字符串,如:'foo,blah,abc'。下面是直接的SQL语句:

select column from table
where search_column = any (string_to_array('foo,blah,abc', ',')::text[]);

显然,您可以将显式强制转换更改为您想要的结果值数组——int、text、uuid等。因为函数接受一个字符串值(或者两个,如果你也想自定义分隔符),你可以在准备好的语句中作为参数传递它:

select column from table
where search_column = any (string_to_array($1, ',')::text[]);

这甚至足够灵活,可以支持like比较:

select column from table
where search_column like any (string_to_array('foo%,blah%,abc%', ',')::text[]);

Again, no question it's a hack, but it works and allows you to still use pre-compiled prepared statements that take *ahem* discrete parameters, with the accompanying security and (maybe) performance benefits. Is it advisable and actually performant? Naturally, it depends, as you've got string parsing and possibly casting going on before your query even runs. If you're expecting to send three, five, a few dozen values, sure, it's probably fine. A few thousand? Yeah, maybe not so much. YMMV, limitations and exclusions apply, no warranty express or implied.

但它确实有效。

在PreparedStatement中生成查询字符串,使其具有与列表中项目数量相匹配的多个?。这里有一个例子:

public void myQuery(List<String> items, int other) {
  ...
  String q4in = generateQsForIn(items.size());
  String sql = "select * from stuff where foo in ( " + q4in + " ) and bar = ?";
  PreparedStatement ps = connection.prepareStatement(sql);
  int i = 1;
  for (String item : items) {
    ps.setString(i++, item);
  }
  ps.setInt(i++, other);
  ResultSet rs = ps.executeQuery();
  ...
}

private String generateQsForIn(int numQs) {
    String items = "";
    for (int i = 0; i < numQs; i++) {
        if (i != 0) items += ", ";
        items += "?";
    }
    return items;
}