PreparedStatement IN子句替代方案?

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

考虑下面的SQL语句:

SELECT my_column FROM my_table where search_column IN (?)

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

有什么可行的解决办法?

364796 次浏览

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

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

我假设你可以(使用基本的字符串操作)在PreparedStatement中生成查询字符串,以拥有与列表中项目数量匹配的?的数量。

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

尝试使用instr函数?

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

然后

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

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

只是为了完整:只要值的集合不是太大,你可以也只是简单的字符串构造语句,如

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

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

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

遵循亚当的想法。让你的准备语句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表以使锁定或其他高开销保护最小化。

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

  1. 创建一个带有一些参数(例如10)的语句:

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

  2. 绑定所有实际参数

    < p > setString (" foo "); setString(2,“酒吧”);< / p > < /李>
  3. 其余绑定为NULL

    Types.VARCHAR < p > setNull (3) ... Types.VARCHAR setNull(10日)< / p > < /李>

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

对各种可用选项的分析,以及每个选项的优缺点,可以在Jeanne Boyarsky的 batch Select Statements in JDBC . bb0 batch Select语句条目中找到。

建议的方案是:

  • 准备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 = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ...并执行它。或者使用UNION ALL来代替这些分号。每个size-of-IN-list需要一个准备好的语句。太慢了,严格来说比WHERE search_column IN (?,?,?)还差,所以我不知道为什么这个博主建议这样做。
  • 使用存储过程来构造结果集。
  • 准备N个不同大小的in列表查询;比如说,有2 10和50个值。要搜索具有6个不同值的IN-list,填充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-lists一起工作。


有时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方法进行扩展。

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

我的解决方案是:

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(?)))"

问候,

哈维尔Ibanez说

Sormula支持SQL IN操作符,允许你提供一个java.util.Collection对象作为参数。它使用?对于集合中的每个元素。参见示例4(示例中的SQL是一个注释,用于澄清Sormula创建但不使用的内容)。

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

而不是使用

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语句将被编译并存储在数据库服务器中

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

  1. 准备好的语句只缓存在同一个会话中(Postgres),所以它只在连接池中工作
  2. @BalusC提出的许多不同的准备语句可能会导致缓存过满,先前缓存的语句将被丢弃
  3. 必须优化查询并使用索引。听起来很明显,然而,例如,@Boris在一个顶级答案中提出的ANY(ARRAY…)语句不能使用索引,尽管有缓存,查询还是会很慢
  4. 准备好的语句还缓存查询计划,并且语句中指定的任何参数的实际值都不可用。

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

对于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]);
    }
    
    
    //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.

对于某些情况,regexp可能会有帮助。 下面是我在Oracle上查看的一个例子,它是有效的。< / p >
select * from my_table where REGEXP_LIKE (search_column, 'value1|value2')

但它也有一些缺点:

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

在研究了不同论坛上的各种解决方案后,没有找到一个好的解决方案,我觉得下面的方法是最容易遵循和编码的:

示例:假设您有多个参数要传递到' in '子句中。只要在'IN'子句中放入一个虚拟字符串,例如,“PARAM”确实表示将在这个虚拟字符串中出现的参数列表。

    select * from TABLE_A where ATTR IN (PARAM);

您可以在Java代码中将所有参数收集到一个String变量中。可以这样做:

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

你可以将所有用逗号分隔的参数附加到一个String变量中,在我们的例子中是'param1'。

在将所有参数收集到单个字符串中之后,您可以将查询中的虚拟文本替换为参数String,即param1,在本例中为“PARAM”。以下是你需要做的:

    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”,以确保查询中不可能出现这样的单词。希望你得到了答案。

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

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

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

在许多情况下,提供给In(…)的值是一个id列表,这些id以某种方式生成,您可以确保不可能进行注入…(例如,之前select some_id from some_table where some_condition.)

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

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

下面是一个完整的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;


}


}

Spring允许将java.util.Lists传递给NamedParameterJdbcTemplate,它自动生成(?, ?, ?,…, ?),作为适当的参数数量。

对于Oracle, 这篇博文讨论了Oracle .sql. array (Connection. array)的使用。createArrayOf不支持Oracle)。为此你必须修改你的SQL语句:

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

Oracle表函数将传递的数组转换为IN语句中可用的类似表的值。

in()操作符的局限性是万恶之源。

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

  • 如果您正在创建具有可变数量参数的语句,那么每次调用都会产生SQL解析开销
  • 在许多平台上,in()操作符的参数数量是有限的
  • 在所有平台上,总SQL文本大小是有限的,因此不可能为in参数发送2000个占位符
  • 向下发送1000-10k的绑定变量是不可能的,因为JDBC驱动程序有其局限性

在某些情况下,in()方法已经足够好了,但还不能防火箭:)

最可靠的解决方案是在一个单独的调用中传递任意数量的参数(例如,通过传递一组参数),然后用一个视图(或任何其他方式)在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,并且:

  • 将得到没有解析延迟,因为SQL选择是稳定的
  • 没有流水线函数的复杂性——它只是一个查询
  • SQL使用一个简单的连接,而不是一个IN操作符,这是相当快的
  • 毕竟,使用任何纯select或DML访问数据库是一个很好的经验法则,因为它是Oracle,它提供了比MySQL或类似的简单数据库引擎多得多的东西。PL/SQL允许您以一种有效的方式从应用程序域模型中隐藏存储模型。

这里的技巧是:

  • 我们需要一个接受长字符串的调用,并存储在db会话可以访问它的地方(例如简单的包变量,或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并在视图中进行转换是最有效的解决方案。

下面是我在自己的应用程序中解决这个问题的方法。理想情况下,您应该使用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这样的变量而不是具体的数字会有很大帮助。

你可以使用这javadoc中提到的setArray方法:

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();

我的解决方案(JavaScript)

    var s1 = " SELECT "


+ "FROM   table t "


+ "  where t.field in ";


var s3 = '(';


for(var i =0;i<searchTerms.length;i++)
{
if(i+1 == searchTerms.length)
{
s3  = s3+'?)';
}
else
{
s3  = s3+'?, ' ;
}
}
var query = s1+s3;


var pstmt = connection.prepareStatement(query);


for(var i =0;i<searchTerms.length;i++)
{
pstmt.setString(i+1, searchTerms[i]);
}

SearchTerms是包含你的输入/键/字段等的数组

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);

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循环解决方案好,其中查询字符串是通过手动迭代构建的

你可以使用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
*/
}

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

再一次,毫无疑问这是一个黑客,但它可以工作,并允许你仍然使用预先编译的准备语句,接受*嗯哼*离散参数,伴随着安全性和(可能)性能的好处。它是否可取,是否切实可行?当然,这要视情况而定,因为在查询运行之前就已经进行了字符串解析和强制转换。如果你希望发送三个,五个,几十个值,当然,这可能没问题。几千?是啊,也许没那么多。YMMV,限制和排除适用,没有明示或暗示的保证。

但它确实有效。

这适用于我(伪docode):

public class SqlHelper
{
public static final ArrayList<String>platformList = new ArrayList<>(Arrays.asList("iOS","Android","Windows","Mac"));


public static final String testQuery = "select * from devices where platform_nm in (:PLATFORM_NAME)";
}

指定绑定:

public class Test extends NamedParameterJdbcDaoSupport
public List<SampleModelClass> runQuery()
{
//define rowMapper to insert in object of SampleClass
final Map<String,Object> map = new HashMap<>();
map.put("PLATFORM_LIST",DeviceDataSyncQueryConstants.platformList);
return getNamedParameterJdbcTemplate().query(SqlHelper.testQuery, map, rowMapper)
}

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

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

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

好吧,所以我不记得我之前是如何(或在哪里)这样做的,所以我来堆栈溢出来快速找到答案。我很惊讶我不能。

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

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

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

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