PreparedStatement IN 子句替代方案?

发布于 2024-12-01 21:06:17 字数 386 浏览 4 评论 0 原文

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

考虑以下 SQL 语句:

SELECT my_column FROM my_table where search_column IN (?)

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

有哪些可用的解决方法?

What are the best workarounds for using a SQL IN clause with instances of java.sql.PreparedStatement, which is not supported for multiple values due to SQL injection attack security issues: One ? placeholder represents one value, rather than a list of values.

Consider the following SQL statement:

SELECT my_column FROM my_table where search_column IN (?)

Using preparedStatement.setString( 1, "'A', 'B', 'C'" ); is essentially a non-working attempt at a workaround of the reasons for using ? in the first place.

What workarounds are available?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(30

橙味迷妹 2024-12-08 21:06:18

SetArray 是最好的解决方案,但它不适用于许多旧驱动程序。在 java8 中可以使用以下解决方法

String baseQuery ="SELECT my_column FROM my_table where search_column IN (%s)"

String markersString = inputArray.stream().map(e -> "?").collect(joining(","));
String sqlQuery = String.format(baseSQL, markersString);

//Now create Prepared Statement and use loop to Set entries
int index=1;

for (String input : inputArray) {
     preparedStatement.setString(index++, input);
}

该解决方案比其他丑陋的 while 循环解决方案更好,其中查询字符串是通过手动迭代构建的

SetArray is the best solution but its not available for many older drivers. The following workaround can be used in java8

String baseQuery ="SELECT my_column FROM my_table where search_column IN (%s)"

String markersString = inputArray.stream().map(e -> "?").collect(joining(","));
String sqlQuery = String.format(baseSQL, markersString);

//Now create Prepared Statement and use loop to Set entries
int index=1;

for (String input : inputArray) {
     preparedStatement.setString(index++, input);
}

This solution is better than other ugly while loop solutions where the query string is built by manual iterations

澉约 2024-12-08 21:06:18

我刚刚为此制定了一个特定于 PostgreSQL 的选项。它有点像 hack,有自己的优点、缺点和限制,但它似乎有效,并且不限于特定的开发语言、平台或 PG 驱动程序。

当然,技巧是找到一种方法将任意长度的值集合作为单个参数传递,并使数据库将其识别为多个值。我正在工作的解决方案是从集合中的值构造一个分隔字符串,将该字符串作为单个参数传递,并使用 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[]);

同样,毫无疑问这是一个 hack,但它可以工作并允许您仍然使用预编译准备好的语句采用*ahem*离散参数,并具有随之而来的安全性和(可能)性能优势。它是可取的并且实际上有效吗?当然,这取决于您在查询运行之前就已经进行了字符串解析和可能的转换。如果您希望发送三个、五个或几十个值,当然,这可能没问题。几千?是的,也许没那么多。 YMMV,限制和排除适用,没有明示或暗示的保证。

但它有效。

I just worked out a PostgreSQL-specific option for this. It's a bit of a hack, and comes with its own pros and cons and limitations, but it seems to work and isn't limited to a specific development language, platform, or PG driver.

The trick of course is to find a way to pass an arbitrary length collection of values as a single parameter, and have the db recognize it as multiple values. The solution I have working is to construct a delimited string from the values in the collection, pass that string as a single parameter, and use string_to_array() with the requisite casting for PostgreSQL to properly make use of it.

So if you want to search for "foo", "blah", and "abc", you might concatenate them together into a single string as: 'foo,blah,abc'. Here's the straight SQL:

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

You would obviously change the explicit cast to whatever you wanted your resulting value array to be -- int, text, uuid, etc. And because the function is taking a single string value (or two I suppose, if you want to customize the delimiter as well), you can pass it as a parameter in a prepared statement:

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

This is even flexible enough to support things like LIKE comparisons:

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.

But it works.

庆幸我还是我 2024-12-08 21:06:18

似乎没有其他人建议使用现成的查询生成器,例如 jOOQQueryDSL 甚至 管理 ="nofollow noreferrer">开箱即用的动态 IN 列表,可能包括对可能出现的所有边缘情况的管理,例如:

  • 遇到 Oracle 的每个元素最多 1000 个的情况IN 列表(无论绑定值的数量)
  • 遇到任何驱动程序的最大绑定值数量,我遇到
  • 游标缓存争用问题,因为太多不同的 SQL 字符串被“硬解析”,并且执行计划无法再缓存(jOOQ 以及最近 Hibernate 通过提供 <代码> IN 列表填充

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

No one else seems to have suggested using an off-the-shelf query builder yet, like jOOQ or QueryDSL or even Criteria Query that manage dynamic IN lists out of the box, possibly including the management of all edge cases that may arise, such as:

  • Running into Oracle's maximum of 1000 elements per IN list (irrespective of the number of bind values)
  • Running into any driver's maximum number of bind values, which I've documented in this answer
  • Running into cursor cache contention problems because too many distinct SQL strings are "hard parsed" and execution plans cannot be cached anymore (jOOQ and since recently also Hibernate work around this by offering IN list padding)

(Disclaimer: I work for the company behind jOOQ)

梦断已成空 2024-12-08 21:06:18

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

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

然后您可以将其传递给prepare(),然后在中使用setXXX()设置所有值的循环。这看起来很恶心,但许多“大型”商业系统通常会执行此类操作,直到达到特定于数据库的限制,例如 Oracle 中的语句为 32 KB(我认为是)。

当然,您需要确保该集合永远不会过大,或者在出现过大的情况下进行错误捕获。

Just for completeness: So long as the set of values is not too large, you could also simply string-construct a statement like

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

which you could then pass to prepare(), and then use setXXX() in a loop to set all the values. This looks yucky, but many "big" commercial systems routinely do this kind of thing until they hit DB-specific limits, such as 32 KB (I think it is) for statements in Oracle.

Of course you need to ensure that the set will never be unreasonably large, or do error trapping in the event that it is.

蓦然回首 2024-12-08 21:06:18

遵循亚当的想法。让您准备好的语句类似于 select my_column from my_table where search_column in (#)
创建一个字符串x并用多个“?,?,?”填充它取决于您的值列表
然后只需更改查询中的 # 即可填充新字符串 x

Following Adam's idea. Make your prepared statement sort of select my_column from my_table where search_column in (#)
Create a String x and fill it with a number of "?,?,?" depending on your list of values
Then just change the # in the query for your new String x an populate

沙与沫 2024-12-08 21:06:18

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

  1. 使用单个查询 - 最慢的性能和资源密集型
  2. 使用 StoredProcedure - 最快,但特定于数据库
  3. 为PreparedStatement 创建动态查询 - 性能良好,但无法获得缓存的好处,并且每次都会重新编译PreparedStatement。
  4. 在PreparedStatement查询中使用NULL - 最佳性能,当您知道 IN 子句参数的限制时效果很好。如果没有限制,则可以批量执行查询。
    示例代码片段是;

     int i = 1;
        for(; i <=ids.length; i++){
            ps.setInt(i, ids[i-1]);
        }
    
        //将剩余的设置为null
        for(; i<=PARAM_SIZE;i++){
            ps.setNull(i, java.sql.Types.INTEGER);
        }
    

您可以在此处查看有关这些替代方法的更多详细信息。

There are different alternative approaches that we can use for IN clause in PreparedStatement.

  1. Using Single Queries - slowest performance and resource intensive
  2. Using StoredProcedure - Fastest but database specific
  3. Creating dynamic query for PreparedStatement - Good Performance but doesn't get benefit of caching and PreparedStatement is recompiled every time.
  4. 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);
        }
    

You can check more details about these alternative approaches here.

笑饮青盏花 2024-12-08 21:06:18

对于某些情况,正则表达式可能会有所帮助。
这是我在 Oracle 上检查过的一个示例,它有效。

select * from my_table where REGEXP_LIKE (search_column, 'value1|value2')

但它有许多缺点:

  1. 它应用的任何列都应该转换为 varchar/char,至少是隐式转换。
  2. 需要小心特殊字符。
  3. 它会降低性能 - 在我的例子中,IN 版本使用索引和范围扫描,而 REGEXP 版本则执行完整扫描。

For some situations regexp might help.
Here is an example I've checked on Oracle, and it works.

select * from my_table where REGEXP_LIKE (search_column, 'value1|value2')

But there is a number of drawbacks with it:

  1. Any column it applied should be converted to varchar/char, at least implicitly.
  2. Need to be careful with special characters.
  3. It can slow down performance - in my case IN version uses index and range scan, and REGEXP version do full scan.
情仇皆在手 2024-12-08 21:06:18

在检查了不同论坛中的各种解决方案并且没有找到好的解决方案之后,我觉得我想出的以下 hack 是最容易遵循和编码的:

示例:假设您有多个参数要在“IN”子句中传递。只需在“IN”子句中放置一个虚拟字符串,例如“PARAM”确实表示将代替该虚拟字符串的参数列表。

    select * from TABLE_A where ATTR IN (PARAM);

您可以将所有参数收集到 Java 代码中的单个字符串变量中。这可以按如下方式完成:

    String param1 = "X";
    String param2 = "Y";
    String param1 = param1.append(",").append(param2);

您可以将所有用逗号分隔的参数附加到单个字符串变量“param1”中,在我们的例子中。

将所有参数收集到单个字符串中后,您可以将查询中的虚拟文本(即本例中的“PARAM”)替换为参数字符串(即 param1)。以下是您需要执行的操作:

    String query = query.replaceFirst("PARAM",param1); where we have the value of query as 

    query = "select * from TABLE_A where ATTR IN (PARAM)";

您现在可以使用executeQuery() 方法执行查询。只需确保您的查询中任何地方都没有“PARAM”一词。您可以使用特殊字符和字母的组合来代替单词“PARAM”,以确保查询中不可能出现此类单词。希望你能找到解决方案。

注意:虽然这不是一个准备好的查询,但它完成了我希望我的代码完成的工作。

After examining various solutions in different forums and not finding a good solution, I feel the below hack I came up with, is the easiest to follow and code:

Example: Suppose you have multiple parameters to pass in the 'IN' clause. Just put a dummy String inside the 'IN' clause, say, "PARAM" do denote the list of parameters that will be coming in the place of this dummy String.

    select * from TABLE_A where ATTR IN (PARAM);

You can collect all the parameters into a single String variable in your Java code. This can be done as follows:

    String param1 = "X";
    String param2 = "Y";
    String param1 = param1.append(",").append(param2);

You can append all your parameters separated by commas into a single String variable, 'param1', in our case.

After collecting all the parameters into a single String you can just replace the dummy text in your query, i.e., "PARAM" in this case, with the parameter String, i.e., param1. Here is what you need to do:

    String query = query.replaceFirst("PARAM",param1); where we have the value of query as 

    query = "select * from TABLE_A where ATTR IN (PARAM)";

You can now execute your query using the executeQuery() method. Just make sure that you don't have the word "PARAM" in your query anywhere. You can use a combination of special characters and alphabets instead of the word "PARAM" in order to make sure that there is no possibility of such a word coming in the query. Hope you got the solution.

Note: Though this is not a prepared query, it does the work that I wanted my code to do.

短叹 2024-12-08 21:06:18

只是为了完整起见,并且因为我没有看到其他人提出这样的建议:

在实施上述任何复杂的建议之前,请考虑 SQL 注入是否确实是您的场景中的问题。

在许多情况下,提供给 IN (...) 的值是一个 id 列表,这些 ID 的生成方式可以确保不可能进行注入...(例如,之前 select some_id from some_table 的结果,其中some_condition。)

如果是这种情况,您可能只是连接此值,而不使用服务或准备好的语句,或者将它们用于此查询的其他参数。

query="select f1,f2 from t1 where f3=? and f2 in (" + sListOfIds + ");";

Just for completeness and because I did not see anyone else suggest it:

Before implementing any of the complicated suggestions above consider if SQL injection is indeed a problem in your scenario.

In many cases the value provided to IN (...) is a list of ids that have been generated in a way that you can be sure that no injection is possible... (e.g. the results of a previous select some_id from some_table where some_condition.)

If that is the case you might just concatenate this value and not use the services or the prepared statement for it or use them for other parameters of this query.

query="select f1,f2 from t1 where f3=? and f2 in (" + sListOfIds + ");";
迟到的我 2024-12-08 21:06:18

PreparedStatement没有提供任何好的方法来处理SQL IN子句。每 http://www.javaranch.com/journal/200510/Journal200510.jsp #a2 “您不能替换本应成为 SQL 语句一部分的内容。这是必要的,因为如果SQL本身可以改变,驱动程序无法预编译语句,它还有一个很好的副作用,可以防止SQL注入攻击。”我最终使用了以下方法:

String query = "SELECT my_column FROM my_table where search_column IN ($searchColumns)";
query = query.replace("$searchColumns", "'A', 'B', 'C'");
Statement stmt = connection.createStatement();
boolean hasResults = stmt.execute(query);
do {
    if (hasResults)
        return stmt.getResultSet();

    hasResults = stmt.getMoreResults();

} while (hasResults || stmt.getUpdateCount() != -1);

PreparedStatement doesn't provide any good way to deal with SQL IN clause. Per http://www.javaranch.com/journal/200510/Journal200510.jsp#a2 "You can't substitute things that are meant to become part of the SQL statement. This is necessary because if the SQL itself can change, the driver can't precompile the statement. It also has the nice side effect of preventing SQL injection attacks." I ended up using following approach:

String query = "SELECT my_column FROM my_table where search_column IN ($searchColumns)";
query = query.replace("$searchColumns", "'A', 'B', 'C'");
Statement stmt = connection.createStatement();
boolean hasResults = stmt.execute(query);
do {
    if (hasResults)
        return stmt.getResultSet();

    hasResults = stmt.getMoreResults();

} while (hasResults || stmt.getUpdateCount() != -1);
梦途 2024-12-08 21:06:18

好的,所以我记不清之前是如何(或在哪里)执行此操作的,因此我来到堆栈溢出以快速找到答案。我很惊讶我不能。

所以,很久以前我是如何解决 IN 问题的,是用这样的语句:

where myColumn in ( select regexp_substr(:myList,'[^,]+', 1, level) from Dual connect by regexp_substr (:myList, '[^,]+', 1, level) is not null)

将 myList 参数设置为逗号分隔的字符串:A,B,C,D...

注意:您必须设置参数两次!

OK, so I couldn't remember exactly how (or where) I did this before so I came to stack overflow to quickly find the answer. I was surprised I couldn't.

So, how I got around the IN problem a long time ago was with a statement like this:

where myColumn in ( select regexp_substr(:myList,'[^,]+', 1, level) from dual connect by regexp_substr(:myList, '[^,]+', 1, level) is not null)

set the myList parameter as a comma delimited string: A,B,C,D...

Note: You have to set the parameter twice!

自我难过 2024-12-08 21:06:18

这不是理想的做法,但它很简单并且在大多数情况下对我来说效果很好。

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

那你就穿越了? ID 的格式如下: |1|,|2|,|3|,...|

This is not the ideal practice, yet it's simple and works well for me most of the time.

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

Then you pass through ? the IDs in this way: |1|,|2|,|3|,...|

水溶 2024-12-08 21:06:17

Jeanne Boyarsky 的 Batching Select statements in JDBC 条目。

建议的选项是:

  • 准备 SELECT my_column FROM my_table WHERE search_column = ?,对每个值执行它并在客户端对结果进行 UNION。仅需要一份准备好的声明。缓慢而痛苦。
  • 准备 SELECT my_column FROM my_table WHERE search_column IN (?,?,?) 并执行它。每个 size-of-IN-list 需要一个准备好的语句。快速而明显。
  • 准备 SELECT my_column FROM my_table WHERE search_column = ? ;从 my_table 中选择 my_column WHERE search_column = ? ; ... 并执行它。 [或使用 UNION ALL 代替这些分号。 --ed] 每个 size-of-IN-list 需要一个准备好的语句。愚蠢地慢,严格来说比 WHERE search_column IN (?,?,?) 更糟糕,所以我不知道为什么博主甚至建议它。
  • 使用存储过程构造结果集。
  • 准备N个不同大小的IN列表查询;例如,具有 2、10 和 50 个值。要搜索具有 6 个不同值的 IN 列表,请填充 size-10 查询,使其看起来像 SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6, 6,6)。任何像样的服务器都会在运行查询之前优化重复值。

这些选项都不是理想的。

如果您使用 JDBC4 和支持 x = ANY(y) 的服务器,最好的选择是使用 PreparedStatement.setArray,如 鲍里斯的回答

不过,似乎没有任何方法可以使 setArray 与 IN 列表一起使用。


有时,SQL 语句在运行时加载(例如,从属性文件),但需要数量可变的参数。在这种情况下,首先定义查询:

query=SELECT * FROM table t WHERE t.column IN (?)

然后加载查询。然后在运行之前确定参数的数量。知道参数计数后,运行:

sql = any( sql, count );

例如:

/**
 * Converts a SQL statement containing exactly one IN clause to an IN clause
 * using multiple comma-delimited parameters.
 *
 * @param sql The SQL statement string with one IN clause.
 * @param params The number of parameters the SQL statement requires.
 * @return The SQL statement with (?) replaced with multiple parameter
 * placeholders.
 */
public static String any(String sql, final int params) {
    // Create a comma-delimited list based on the number of parameters.
    final StringBuilder sb = new StringBuilder(
        String.join(", ", Collections.nCopies(possibleValue.size(), "?")));

    // For more than 1 parameter, replace the single parameter with
    // multiple parameter placeholders.
    if (sb.length() > 1) {
        sql = sql.replace("(?)", "(" + sb + ")");
    }

    // Return the modified comma-delimited list of parameters.
    return sql;
}

对于某些不支持通过 JDBC 4 规范传递数组的数据库,此方法可以帮助将慢速 = ? 转换为更快的 IN (?) 子句条件,然后可以通过调用 any 方法来扩展。

An analysis of the various options available, and the pros and cons of each is available in Jeanne Boyarsky's Batching Select Statements in JDBC entry on JavaRanch Journal.

The suggested options are:

  • Prepare SELECT my_column FROM my_table WHERE search_column = ?, execute it for each value and UNION the results client-side. Requires only one prepared statement. Slow and painful.
  • Prepare SELECT my_column FROM my_table WHERE search_column IN (?,?,?) and execute it. Requires one prepared statement per size-of-IN-list. Fast and obvious.
  • Prepare SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ... and execute it. [Or use UNION ALL in place of those semicolons. --ed] Requires one prepared statement per size-of-IN-list. Stupidly slow, strictly worse than WHERE search_column IN (?,?,?), so I don't know why the blogger even suggested it.
  • Use a stored procedure to construct the result set.
  • Prepare N different size-of-IN-list queries; say, with 2, 10, and 50 values. To search for an IN-list with 6 different values, populate the size-10 query so that it looks like SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6). Any decent server will optimize out the duplicate values before running the query.

None of these options are ideal.

The best option if you are using JDBC4 and a server that supports x = ANY(y), is to use PreparedStatement.setArray as described in Boris's anwser.

There doesn't seem to be any way to make setArray work with IN-lists, though.


Sometimes SQL statements are loaded at runtime (e.g., from a properties file) but require a variable number of parameters. In such cases, first define the query:

query=SELECT * FROM table t WHERE t.column IN (?)

Next, load the query. Then determine the number of parameters prior to running it. Once the parameter count is known, run:

sql = any( sql, count );

For example:

/**
 * Converts a SQL statement containing exactly one IN clause to an IN clause
 * using multiple comma-delimited parameters.
 *
 * @param sql The SQL statement string with one IN clause.
 * @param params The number of parameters the SQL statement requires.
 * @return The SQL statement with (?) replaced with multiple parameter
 * placeholders.
 */
public static String any(String sql, final int params) {
    // Create a comma-delimited list based on the number of parameters.
    final StringBuilder sb = new StringBuilder(
        String.join(", ", Collections.nCopies(possibleValue.size(), "?")));

    // For more than 1 parameter, replace the single parameter with
    // multiple parameter placeholders.
    if (sb.length() > 1) {
        sql = sql.replace("(?)", "(" + sb + ")");
    }

    // Return the modified comma-delimited list of parameters.
    return sql;
}

For certain databases where passing an array via the JDBC 4 specification is unsupported, this method can facilitate transforming the slow = ? into the faster IN (?) clause condition, which can then be expanded by calling the any method.

醉南桥 2024-12-08 21:06:17

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...
    }
}

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...
    }
}

Solution for 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...
    }
}
遇见了你 2024-12-08 21:06:17

没有简单的方法据我所知。
如果目标是保持较高的语句缓存比率(即不为每个参数计数创建一个语句),您可以执行以下操作:

  1. 创建一个包含几个(例如 10 个)参数的语句:

    ... WHERE A IN (?,?,?,?,?,?,?,?,?,?) ...

  2. 绑定所有实际参数

    setString(1,“foo”);
    setString(2,"bar");

  3. 将其余部分绑定为 NULL

    setNull(3,Types.VARCHAR)
    ...
    setNull(10,Types.VARCHAR)

NULL 永远不会匹配任何内容,因此它会被 SQL 计划生成器优化。

当您将 List 传递到 DAO 函数时,逻辑很容易自动化:

while( i < param.size() ) {
  ps.setString(i+1,param.get(i));
  i++;
}

while( i < MAX_PARAMS ) {
  ps.setNull(i+1,Types.VARCHAR);
  i++;
}

No simple way AFAIK.
If the target is to keep statement cache ratio high (i.e to not create a statement per every parameter count), you may do the following:

  1. create a statement with a few (e.g. 10) parameters:

    ... WHERE A IN (?,?,?,?,?,?,?,?,?,?) ...

  2. Bind all actuall parameters

    setString(1,"foo");
    setString(2,"bar");

  3. Bind the rest as NULL

    setNull(3,Types.VARCHAR)
    ...
    setNull(10,Types.VARCHAR)

NULL never matches anything, so it gets optimized out by the SQL plan builder.

The logic is easy to automate when you pass a List into a DAO function:

while( i < param.size() ) {
  ps.setString(i+1,param.get(i));
  i++;
}

while( i < MAX_PARAMS ) {
  ps.setNull(i+1,Types.VARCHAR);
  i++;
}
半衬遮猫 2024-12-08 21:06:17

您可以使用Collections.nCopies生成占位符集合,并使用String.join将它们连接起来:

List<String> params = getParams();
String placeHolders = String.join(",", Collections.nCopies(params.size(), "?"));
String sql = "select * from your_table where some_column in (" + placeHolders + ")";
try (   Connection connection = getConnection();
        PreparedStatement ps = connection.prepareStatement(sql)) {
    int i = 1;
    for (String param : params) {
        ps.setString(i++, param);
    }
    /*
     * Execute query/do stuff
     */
}

You can use Collections.nCopies to generate a collection of placeholders and join them using String.join:

List<String> params = getParams();
String placeHolders = String.join(",", Collections.nCopies(params.size(), "?"));
String sql = "select * from your_table where some_column in (" + placeHolders + ")";
try (   Connection connection = getConnection();
        PreparedStatement ps = connection.prepareStatement(sql)) {
    int i = 1;
    for (String param : params) {
        ps.setString(i++, param);
    }
    /*
     * Execute query/do stuff
     */
}
怪我鬧 2024-12-08 21:06:17

一个令人不愉快但确实可行的解决方法是使用嵌套查询。创建一个临时表 MYVALUES,其中包含一列。将值列表插入 MYVALUES 表中。然后执行

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

Ugly,但如果您的值列表非常大,那么这是一个可行的替代方案。

此技术的另一个优点是优化器可能会提供更好的查询计划(检查页面中的多个值、仅表扫描一次而不是每个值一次等),如果您的数据库不缓存准备好的语句,则可能会节省开销。您的“插入”需要批量完成,并且可能需要调整 MYVALUES 表以具有最小的锁定或其他高开销保护。

An unpleasant work-around, but certainly feasible is to use a nested query. Create a temporary table MYVALUES with a column in it. Insert your list of values into the MYVALUES table. Then execute

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

Ugly, but a viable alternative if your list of values is very large.

This technique has the added advantage of potentially better query plans from the optimizer (check a page for multiple values, tablescan only once instead once per value, etc) may save on overhead if your database doesn't cache prepared statements. Your "INSERTS" would need to be done in batch and the MYVALUES table may need to be tweaked to have minimal locking or other high-overhead protections.

九歌凝 2024-12-08 21:06:17

in() 运算符的局限性是万恶之源。

它适用于简单的情况,您可以通过“自动生成准备好的语句”来扩展它,但它总是有其局限性。

  • 如果您要创建参数数量可变的语句,这将在
  • 许多平台上的每次调用时产生 sql 解析开销,in() 运算符的参数数量
  • 在所有平台上都受到限制,总 SQL 文本大小也受到限制,使得不可能为 in 参数发送 2000 个占位符
  • 不可能发送 1000-10k 的绑定变量,因为 JDBC 驱动程序有其局限性

in() 方法对于某些情况来说足够好,但不是火箭般的

:)防火箭的解决方案是在单独的调用中传递任意数量的参数(例如,通过传递参数的 clob),然后使用视图(或任何其他方式)在 SQL 中表示它们并在您的 where 条件中使用。

暴力变体在这里 http://tkyte.blogspot.hu/ 2006/06/varying-in-lists.html

但是,如果您可以使用 PL/SQL,这种混乱会变得非常整洁。

function getCustomers(in_customerIdList clob) return sys_refcursor is 
begin
    aux_in_list.parse(in_customerIdList);
    open res for
        select * 
        from   customer c,
               in_list v
        where  c.customer_id=v.token;
    return res;
end;

然后,您可以在参数中传递任意数量的逗号分隔的客户 ID,并且:

  • 不会得到解析延迟,因为 select 的 SQL 很稳定,
  • 没有管道函数复杂性 - 这只是一个查询,
  • SQL 使用简单的连接,而不是IN 运算符,毕竟速度相当快
  • ,不使用任何普通 select 或 DML 访问数据库是一个很好的经验法则,因为它是 Oracle,它提供的功能比 MySQL 或类似的简单数据库引擎。 PL/SQL 允许您以有效的方式从应用程序域模型中隐藏存储模型。

这里的技巧是:

  • 我们需要一个接受长字符串的调用,并将其存储在数据库会话可以访问它的地方(例如简单的包变量或dbms_session.set_context),
  • 然后我们需要一个可以将其解析为行的视图
  • ,然后您有一个包含您正在查询的 id 的视图,因此您所需要的只是对所查询的表进行简单的联接。

该视图如下所示:

create or replace view in_list
as
select
    trim( substr (txt,
          instr (txt, ',', 1, level  ) + 1,
          instr (txt, ',', 1, level+1)
             - instr (txt, ',', 1, level) -1 ) ) as token
    from (select ','||aux_in_list.getpayload||',' txt from dual)
connect by level <= length(aux_in_list.getpayload)-length(replace(aux_in_list.getpayload,',',''))+1

其中 aux_in_list.getpayload 指原始输入字符串。


一种可能的方法是传递 pl/sql 数组(仅 Oracle 支持),但是您不能在纯 SQL 中使用这些数组,因此始终需要转换步骤。这种转换无法在 SQL 中完成,所以毕竟,传递一个包含字符串中所有参数的 clob 并在视图中转换它是最有效的解决方案。

Limitations of the in() operator is the root of all evil.

It works for trivial cases, and you can extend it with "automatic generation of the prepared statement" however it is always having its limits.

  • if you're creating a statement with variable number of parameters, that will make an sql parse overhead at each call
  • on many platforms, the number of parameters of in() operator are limited
  • on all platforms, total SQL text size is limited, making impossible for sending down 2000 placeholders for the in params
  • sending down bind variables of 1000-10k is not possible, as the JDBC driver is having its limitations

The in() approach can be good enough for some cases, but not rocket proof :)

The rocket-proof solution is to pass the arbitrary number of parameters in a separate call (by passing a clob of params, for example), and then have a view (or any other way) to represent them in SQL and use in your where criteria.

A brute-force variant is here http://tkyte.blogspot.hu/2006/06/varying-in-lists.html

However if you can use PL/SQL, this mess can become pretty neat.

function getCustomers(in_customerIdList clob) return sys_refcursor is 
begin
    aux_in_list.parse(in_customerIdList);
    open res for
        select * 
        from   customer c,
               in_list v
        where  c.customer_id=v.token;
    return res;
end;

Then you can pass arbitrary number of comma separated customer ids in the parameter, and:

  • will get no parse delay, as the SQL for select is stable
  • no pipelined functions complexity - it is just one query
  • the SQL is using a simple join, instead of an IN operator, which is quite fast
  • after all, it is a good rule of thumb of not hitting the database with any plain select or DML, since it is Oracle, which offers lightyears of more than MySQL or similar simple database engines. PL/SQL allows you to hide the storage model from your application domain model in an effective way.

The trick here is:

  • we need a call which accepts the long string, and store somewhere where the db session can access to it (e.g. simple package variable, or dbms_session.set_context)
  • then we need a view which can parse this to rows
  • and then you have a view which contains the ids you're querying, so all you need is a simple join to the table queried.

The view looks like:

create or replace view in_list
as
select
    trim( substr (txt,
          instr (txt, ',', 1, level  ) + 1,
          instr (txt, ',', 1, level+1)
             - instr (txt, ',', 1, level) -1 ) ) as token
    from (select ','||aux_in_list.getpayload||',' txt from dual)
connect by level <= length(aux_in_list.getpayload)-length(replace(aux_in_list.getpayload,',',''))+1

where aux_in_list.getpayload refers to the original input string.


A possible approach would be to pass pl/sql arrays (supported by Oracle only), however you can't use those in pure SQL, therefore a conversion step is always needed. The conversion can not be done in SQL, so after all, passing a clob with all parameters in string and converting it witin a view is the most efficient solution.

葬花如无物 2024-12-08 21:06:17

这是我在自己的应用程序中解决该问题的方法。理想情况下,您应该使用 StringBuilder 而不是对字符串使用 +。

    String inParenthesis = "(?";
    for(int i = 1;i < myList.size();i++) {
      inParenthesis += ", ?";
    }
    inParenthesis += ")";

    try(PreparedStatement statement = SQLite.connection.prepareStatement(
        String.format("UPDATE table SET value='WINNER' WHERE startTime=? AND name=? AND traderIdx=? AND someValue IN %s", inParenthesis))) {
      int x = 1;
      statement.setLong(x++, race.startTime);
      statement.setString(x++, race.name);
      statement.setInt(x++, traderIdx);

      for(String str : race.betFair.winners) {
        statement.setString(x++, str);
      }

      int effected = statement.executeUpdate();
    }

如果您决定稍后更改查询,那么使用上面的 x 这样的变量而不是具体的数字会很有帮助。

Here's how I solved it in my own application. Ideally, you should use a StringBuilder instead of using + for Strings.

    String inParenthesis = "(?";
    for(int i = 1;i < myList.size();i++) {
      inParenthesis += ", ?";
    }
    inParenthesis += ")";

    try(PreparedStatement statement = SQLite.connection.prepareStatement(
        String.format("UPDATE table SET value='WINNER' WHERE startTime=? AND name=? AND traderIdx=? AND someValue IN %s", inParenthesis))) {
      int x = 1;
      statement.setLong(x++, race.startTime);
      statement.setString(x++, race.name);
      statement.setInt(x++, traderIdx);

      for(String str : race.betFair.winners) {
        statement.setString(x++, str);
      }

      int effected = statement.executeUpdate();
    }

Using a variable like x above instead of concrete numbers helps a lot if you decide to change the query at a later time.

夏九 2024-12-08 21:06:17

我从未尝试过,但是 .setArray() 会做你正在寻找的事情吗?

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

I've never tried it, but would .setArray() do what you're looking for?

Update: Evidently not. setArray only seems to work with a java.sql.Array that comes from an ARRAY column that you've retrieved from a previous query, or a subquery with an ARRAY column.

梦过后 2024-12-08 21:06:17

我的解决方法是:

create or replace type split_tbl as table of varchar(32767);
/

create or replace function split
(
  p_list varchar2,
  p_del varchar2 := ','
) return split_tbl pipelined
is
  l_idx    pls_integer;
  l_list    varchar2(32767) := p_list;
  l_value    varchar2(32767);
begin
  loop
    l_idx := instr(l_list,p_del);
    if l_idx > 0 then
      pipe row(substr(l_list,1,l_idx-1));
      l_list := substr(l_list,l_idx+length(p_del));
    else
      pipe row(l_list);
      exit;
    end if;
  end loop;
  return;
end split;
/

现在您可以使用一个变量来获取表中的一些值:

select * from table(split('one,two,three'))
  one
  two
  three

select * from TABLE1 where COL1 in (select * from table(split('value1,value2')))
  value1 AAA
  value2 BBB

因此,准备好的语句可以是:

  "select * from TABLE where COL in (select * from table(split(?)))"

问候,

哈维尔·伊巴内斯

My workaround is:

create or replace type split_tbl as table of varchar(32767);
/

create or replace function split
(
  p_list varchar2,
  p_del varchar2 := ','
) return split_tbl pipelined
is
  l_idx    pls_integer;
  l_list    varchar2(32767) := p_list;
  l_value    varchar2(32767);
begin
  loop
    l_idx := instr(l_list,p_del);
    if l_idx > 0 then
      pipe row(substr(l_list,1,l_idx-1));
      l_list := substr(l_list,l_idx+length(p_del));
    else
      pipe row(l_list);
      exit;
    end if;
  end loop;
  return;
end split;
/

Now you can use one variable to obtain some values in a table:

select * from table(split('one,two,three'))
  one
  two
  three

select * from TABLE1 where COL1 in (select * from table(split('value1,value2')))
  value1 AAA
  value2 BBB

So, the prepared statement could be:

  "select * from TABLE where COL in (select * from table(split(?)))"

Regards,

Javier Ibanez

芯好空 2024-12-08 21:06:17

我想您可以(使用基本的字符串操作)在 PreparedStatement 中生成查询字符串,以使许多 ? 与列表中的项目数量相匹配。

当然,如果您这样做,那么您距离在查询中生成巨大的链式 OR 仅有一步之遥,但查询中没有正确数量的 ?字符串,我不知道你还能如何解决这个问题。

I suppose you could (using basic string manipulation) generate the query string in the PreparedStatement to have a number of ?'s matching the number of items in your list.

Of course if you're doing that you're just a step away from generating a giant chained OR in your query, but without having the right number of ? in the query string, I don't see how else you can work around this.

千年*琉璃梦 2024-12-08 21:06:17

Spring允许 传递java.util.Lists 到 NamedParameterJdbcTemplate ,它根据参数数量自动生成 (?, ?, ?, ..., ?)。

对于 Oracle,此博文讨论 oracle.sql.ARRAY 的使用(Connection.createArrayOf 不适用于 Oracle)。为此,您必须修改 SQL 语句:

SELECT my_column FROM my_table where search_column IN (select COLUMN_VALUE from table(?))

oracle 表函数< /a> 将传递的数组转换为可在 IN 语句中使用的类似表格的值。

Spring allows passing java.util.Lists to NamedParameterJdbcTemplate , which automates the generation of (?, ?, ?, ..., ?), as appropriate for the number of arguments.

For Oracle, this blog posting discusses the use of oracle.sql.ARRAY (Connection.createArrayOf doesn't work with Oracle). For this you have to modify your SQL statement:

SELECT my_column FROM my_table where search_column IN (select COLUMN_VALUE from table(?))

The oracle table function transforms the passed array into a table like value usable in the IN statement.

银河中√捞星星 2024-12-08 21:06:17

您可以使用 此 javadoc

PreparedStatement statement = connection.prepareStatement("Select * from emp where field in (?)");
Array array = statement.getConnection().createArrayOf("VARCHAR", new Object[]{"E1", "E2","E3"});
statement.setArray(1, array);
ResultSet rs = statement.executeQuery();

You could use setArray method as mentioned in this javadoc:

PreparedStatement statement = connection.prepareStatement("Select * from emp where field in (?)");
Array array = statement.getConnection().createArrayOf("VARCHAR", new Object[]{"E1", "E2","E3"});
statement.setArray(1, array);
ResultSet rs = statement.executeQuery();
烟花肆意 2024-12-08 21:06:17

这是一个完整的 Java 解决方案,可以为您创建准备好的语句:

/*usage:

Util u = new Util(500); //500 items per bracket. 
String sqlBefore  = "select * from myTable where (";
List<Integer> values = new ArrayList<Integer>(Arrays.asList(1,2,4,5)); 
string sqlAfter = ") and foo = 'bar'"; 

PreparedStatement ps = u.prepareStatements(sqlBefore, values, sqlAfter, connection, "someId");
*/



import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class Util {

    private int numValuesInClause;

    public Util(int numValuesInClause) {
        super();
        this.numValuesInClause = numValuesInClause;
    }

    public int getNumValuesInClause() {
        return numValuesInClause;
    }

    public void setNumValuesInClause(int numValuesInClause) {
        this.numValuesInClause = numValuesInClause;
    }

    /** Split a given list into a list of lists for the given size of numValuesInClause*/
    public List<List<Integer>> splitList(
            List<Integer> values) {


        List<List<Integer>> newList = new ArrayList<List<Integer>>(); 
        while (values.size() > numValuesInClause) {
            List<Integer> sublist = values.subList(0,numValuesInClause);
            List<Integer> values2 = values.subList(numValuesInClause, values.size());   
            values = values2; 

            newList.add( sublist);
        }
        newList.add(values);

        return newList;
    }

    /**
     * Generates a series of split out in clause statements. 
     * @param sqlBefore ""select * from dual where ("
     * @param values [1,2,3,4,5,6,7,8,9,10]
     * @param "sqlAfter ) and id = 5"
     * @return "select * from dual where (id in (1,2,3) or id in (4,5,6) or id in (7,8,9) or id in (10)"
     */
    public String genInClauseSql(String sqlBefore, List<Integer> values,
            String sqlAfter, String identifier) 
    {
        List<List<Integer>> newLists = splitList(values);
        String stmt = sqlBefore;

        /* now generate the in clause for each list */
        int j = 0; /* keep track of list:newLists index */
        for (List<Integer> list : newLists) {
            stmt = stmt + identifier +" in (";
            StringBuilder innerBuilder = new StringBuilder();

            for (int i = 0; i < list.size(); i++) {
                innerBuilder.append("?,");
            }



            String inClause = innerBuilder.deleteCharAt(
                    innerBuilder.length() - 1).toString();

            stmt = stmt + inClause;
            stmt = stmt + ")";


            if (++j < newLists.size()) {
                stmt = stmt + " OR ";
            }

        }

        stmt = stmt + sqlAfter;
        return stmt;
    }

    /**
     * Method to convert your SQL and a list of ID into a safe prepared
     * statements
     * 
     * @throws SQLException
     */
    public PreparedStatement prepareStatements(String sqlBefore,
            ArrayList<Integer> values, String sqlAfter, Connection c, String identifier)
            throws SQLException {

        /* First split our potentially big list into lots of lists */
        String stmt = genInClauseSql(sqlBefore, values, sqlAfter, identifier);
        PreparedStatement ps = c.prepareStatement(stmt);

        int i = 1;
        for (int val : values)
        {

            ps.setInt(i++, val);

        }
        return ps;

    }

}

Here's a complete solution in Java to create the prepared statement for you:

/*usage:

Util u = new Util(500); //500 items per bracket. 
String sqlBefore  = "select * from myTable where (";
List<Integer> values = new ArrayList<Integer>(Arrays.asList(1,2,4,5)); 
string sqlAfter = ") and foo = 'bar'"; 

PreparedStatement ps = u.prepareStatements(sqlBefore, values, sqlAfter, connection, "someId");
*/



import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class Util {

    private int numValuesInClause;

    public Util(int numValuesInClause) {
        super();
        this.numValuesInClause = numValuesInClause;
    }

    public int getNumValuesInClause() {
        return numValuesInClause;
    }

    public void setNumValuesInClause(int numValuesInClause) {
        this.numValuesInClause = numValuesInClause;
    }

    /** Split a given list into a list of lists for the given size of numValuesInClause*/
    public List<List<Integer>> splitList(
            List<Integer> values) {


        List<List<Integer>> newList = new ArrayList<List<Integer>>(); 
        while (values.size() > numValuesInClause) {
            List<Integer> sublist = values.subList(0,numValuesInClause);
            List<Integer> values2 = values.subList(numValuesInClause, values.size());   
            values = values2; 

            newList.add( sublist);
        }
        newList.add(values);

        return newList;
    }

    /**
     * Generates a series of split out in clause statements. 
     * @param sqlBefore ""select * from dual where ("
     * @param values [1,2,3,4,5,6,7,8,9,10]
     * @param "sqlAfter ) and id = 5"
     * @return "select * from dual where (id in (1,2,3) or id in (4,5,6) or id in (7,8,9) or id in (10)"
     */
    public String genInClauseSql(String sqlBefore, List<Integer> values,
            String sqlAfter, String identifier) 
    {
        List<List<Integer>> newLists = splitList(values);
        String stmt = sqlBefore;

        /* now generate the in clause for each list */
        int j = 0; /* keep track of list:newLists index */
        for (List<Integer> list : newLists) {
            stmt = stmt + identifier +" in (";
            StringBuilder innerBuilder = new StringBuilder();

            for (int i = 0; i < list.size(); i++) {
                innerBuilder.append("?,");
            }



            String inClause = innerBuilder.deleteCharAt(
                    innerBuilder.length() - 1).toString();

            stmt = stmt + inClause;
            stmt = stmt + ")";


            if (++j < newLists.size()) {
                stmt = stmt + " OR ";
            }

        }

        stmt = stmt + sqlAfter;
        return stmt;
    }

    /**
     * Method to convert your SQL and a list of ID into a safe prepared
     * statements
     * 
     * @throws SQLException
     */
    public PreparedStatement prepareStatements(String sqlBefore,
            ArrayList<Integer> values, String sqlAfter, Connection c, String identifier)
            throws SQLException {

        /* First split our potentially big list into lots of lists */
        String stmt = genInClauseSql(sqlBefore, values, sqlAfter, identifier);
        PreparedStatement ps = c.prepareStatement(stmt);

        int i = 1;
        for (int val : values)
        {

            ps.setInt(i++, val);

        }
        return ps;

    }

}
忘年祭陌 2024-12-08 21:06:17

尝试使用 instr 函数?

select my_column from my_table where  instr(?, ','||search_column||',') > 0

那么

ps.setString(1, ",A,B,C,"); 

不可否认,这是一个有点肮脏的黑客,但它确实减少了 sql 注入的机会。无论如何,在甲骨文中工作。

try using the instr function?

select my_column from my_table where  instr(?, ','||search_column||',') > 0

then

ps.setString(1, ",A,B,C,"); 

Admittedly this is a bit of a dirty hack, but it does reduce the opportunities for sql injection. Works in oracle anyway.

没有伤那来痛 2024-12-08 21:06:17

Sormula 通过允许您提供 java.util.Collection 对象作为参数来支持 SQL IN 运算符。它创建一个带有 ? 的准备好的语句。对于集合中的每个元素。请参阅 示例 4(示例中的 SQL 是一条注释,用于澄清 Sormula 创建但不使用的内容) 。

Sormula supports SQL IN operator by allowing you to supply a java.util.Collection object as a parameter. It creates a prepared statement with a ? for each of the elements the collection. See Example 4 (SQL in example is a comment to clarify what is created but is not used by Sormula).

短暂陪伴 2024-12-08 21:06:17

在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;
}

Generate the query string in the PreparedStatement to have a number of ?'s matching the number of items in your list. Here's an example:

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;
}
忆沫 2024-12-08 21:06:17

不要使用

SELECT my_column FROM my_table where search_column IN (?)

Sql 语句作为

select id, name from users where id in (?, ?, ?)

preparedStatement.setString( 1, 'A');
preparedStatement.setString( 2,'B');
preparedStatement.setString( 3, 'C');

使用存储过程,这将是最好的解决方案,因为 sql 语句将被编译并存储在数据库服务器中

instead of using

SELECT my_column FROM my_table where search_column IN (?)

use the Sql Statement as

select id, name from users where id in (?, ?, ?)

and

preparedStatement.setString( 1, 'A');
preparedStatement.setString( 2,'B');
preparedStatement.setString( 3, 'C');

or use a stored procedure this would be the best solution, since the sql statements will be compiled and stored in DataBase server

水晶透心 2024-12-08 21:06:17

我遇到了与准备好的语句相关的许多限制:

  1. 准备好的语句仅缓存在同一会话(Postgres)内,因此它只能与连接池一起使用
  2. @BalusC 提出的许多不同的准备好的语句可能会导致缓存溢出并且先前缓存的语句将被删除
  3. 必须优化查询并使用索引。听起来很明显,但是例如 @Boris 在最佳答案之一中提出的 ANY(ARRAY...) 语句不能使用索引,并且尽管缓存查询也会很慢
  4. 准备好的语句也会缓存查询计划以及任何参数的实际值声明中指定的内容不可用。

在建议的解决方案中,我会选择不会降低查询性能且查询次数较少的解决方案。这将是来自 @Don 链接的#4(批处理几个查询)或为不需要的“?”指定 NULL 值由 @Vladimir Dyuzhev 提议的标记

I came across a number of limitations related to prepared statement:

  1. The prepared statements are cached only inside the same session (Postgres), so it will really work only with connection pooling
  2. A lot of different prepared statements as proposed by @BalusC may cause the cache to overfill and previously cached statements will be dropped
  3. The query has to be optimized and use indices. Sounds obvious, however e.g. the ANY(ARRAY...) statement proposed by @Boris in one of the top answers cannot use indices and query will be slow despite caching
  4. The prepared statement caches the query plan as well and the actual values of any parameters specified in the statement are unavailable.

Among the proposed solutions I would choose the one that doesn't decrease the query performance and makes the less number of queries. This will be the #4 (batching few queries) from the @Don link or specifying NULL values for unneeded '?' marks as proposed by @Vladimir Dyuzhev

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文