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

考虑下面的SQL语句:

SELECT my_column FROM my_table where search_column IN (?)

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

有什么可行的解决办法?


当前回答

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 table函数将传递的数组转换为在in语句中可用的类似表的值。

其他回答

您可以使用集合。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
     */
}

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

而不是使用

SELECT my_column FROM my_table where search_column IN (?)

使用Sql语句作为

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

and

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

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

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

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

示例:假设您有多个参数要传递到' 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”,以确保查询中不可能出现这样的单词。希望你得到了答案。

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