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

考虑下面的SQL语句:

SELECT my_column FROM my_table where search_column IN (?)

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

有什么可行的解决办法?


当前回答

我只是为此制定了一个特定于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.

但它确实有效。

其他回答

PostgreSQL的解决方案:

final PreparedStatement statement = connection.prepareStatement(
        "SELECT my_column FROM my_table where search_column = ANY (?)"
);
final String[] values = getValues();
statement.setArray(1, connection.createArrayOf("text", values));

try (ResultSet rs = statement.executeQuery()) {
    while(rs.next()) {
        // do some...
    }
}

or

final PreparedStatement statement = connection.prepareStatement(
        "SELECT my_column FROM my_table " + 
        "where search_column IN (SELECT * FROM unnest(?))"
);
final String[] values = getValues();
statement.setArray(1, connection.createArrayOf("text", values));

try (ResultSet rs = statement.executeQuery()) {
    while(rs.next()) {
        // do some...
    }
}

对于PreparedStatement中的IN子句,我们可以使用不同的替代方法。

Using Single Queries - slowest performance and resource intensive Using StoredProcedure - Fastest but database specific Creating dynamic query for PreparedStatement - Good Performance but doesn't get benefit of caching and PreparedStatement is recompiled every time. Use NULL in PreparedStatement queries - Optimal performance, works great when you know the limit of IN clause arguments. If there is no limit, then you can execute queries in batch. Sample code snippet is; int i = 1; for(; i <=ids.length; i++){ ps.setInt(i, ids[i-1]); } //set null for remaining ones for(; i<=PARAM_SIZE;i++){ ps.setNull(i, java.sql.Types.INTEGER); }

你可以在这里查看更多关于这些替代方法的细节。

这不是理想的做法,但它很简单,大多数时候对我来说都很有效。

where ? like concat( "%|", TABLE_ID , "|%" ) 

然后你穿过?|1|,|2|,|3|,…|

我只是为此制定了一个特定于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.

但它确实有效。

我从来没有尝试过,但是.setArray()做什么你正在寻找?

更新:显然不是。setArray似乎只适用于来自以前查询中检索到的ARRAY列的java.sql.Array,或具有ARRAY列的子查询。