【问题标题】:What is the best approach using JDBC for parameterizing an IN clause? [duplicate]使用 JDBC 参数化 IN 子句的最佳方法是什么? [复制]
【发布时间】:2011-02-21 02:51:21
【问题描述】:

说我有一个表格查询

SELECT * FROM MYTABLE WHERE MYCOL in (?)

我想将参数参数化为 in。

有没有一种直接的方法可以在 Java 中使用 JDBC 执行此操作,并且可以在不修改 SQL 本身的情况下在多个数据库上工作?

最接近的question I've found had to do with C#,我想知道Java/JDBC 是否有不同之处。

【问题讨论】:

    标签: java sql jdbc in-clause


    【解决方案1】:

    我从docs.spring(19.7.3)得到了答案

    SQL 标准允许基于包含变量值列表的表达式来选择行。一个典型的例子是 select * from T_ACTOR where id in (1, 2, 3)。 JDBC 标准不直接支持准备好的语句使用此变量列表;您不能声明可变数量的占位符。您需要准备好所需数量的占位符的多种变体,或者您需要在知道需要多少占位符后动态生成 SQL 字符串。 NamedParameterJdbcTemplate 和 JdbcTemplate 中提供的命名参数支持采用后一种方法。将值作为原始对象的 java.util.List 传递。此列表将用于在语句执行期间插入所需的占位符并传入值。

    希望对你有帮助。

    【讨论】:

      【解决方案2】:

      在 JDBC 中确实没有直接的方法可以做到这一点。 一些 JDBC 驱动程序似乎支持IN 子句上的PreparedStatement#setArray()。我只是不确定哪些是。

      您可以只使用带有String#join()Collections#nCopies() 的辅助方法来生成IN 子句的占位符,并使用另一个辅助方法来使用PreparedStatement#setObject() 在循环中设置所有值。

      public static String preparePlaceHolders(int length) {
          return String.join(",", Collections.nCopies(length, "?"));
      }
      
      public static void setValues(PreparedStatement preparedStatement, Object... values) throws SQLException {
          for (int i = 0; i < values.length; i++) {
              preparedStatement.setObject(i + 1, values[i]);
          }
      }
      

      你可以这样使用它:

      private static final String SQL_FIND = "SELECT id, name, value FROM entity WHERE id IN (%s)";
      
      public List<Entity> find(Set<Long> ids) throws SQLException {
          List<Entity> entities = new ArrayList<Entity>();
          String sql = String.format(SQL_FIND, preparePlaceHolders(ids.size()));
      
          try (
              Connection connection = dataSource.getConnection();
              PreparedStatement statement = connection.prepareStatement(sql);
          ) {
              setValues(statement, ids.toArray());
      
              try (ResultSet resultSet = statement.executeQuery()) {
                  while (resultSet.next()) {
                      entities.add(map(resultSet));
                  }
              }
          }
      
          return entities;
      }
      
      private static Entity map(ResultSet resultSet) throws SQLException {
          Enitity entity = new Entity();
          entity.setId(resultSet.getLong("id"));
          entity.setName(resultSet.getString("name"));
          entity.setValue(resultSet.getInt("value"));
          return entity;
      }
      

      请注意,某些数据库在IN 子句中对允许的值数量有限制。例如,Oracle 对 1000 个项目有此限制。

      【讨论】:

      • 这种方式会不会导致SQL注入??
      • @Kaylan:没有单个代码行将用户控制的原始输入添加到 SQL 查询字符串中。所以绝对没有 SQL 注入风险。
      • 很好。感谢您的澄清
      • jtds 驱动程序的最大参数变量列表为 2,000。
      • 在 MySQL(驱动程序 5.1.37)和 PostgreSQL(驱动程序 9.1-901)之间,只有 PostgreSQL 对 PreparedStatement#setArray() 有一些支持
      【解决方案3】:

      sormula 让这一切变得简单(参见Example 4):

      ArrayList<Integer> partNumbers = new ArrayList<Integer>();
      partNumbers.add(999);
      partNumbers.add(777);
      partNumbers.add(1234);
      
      // set up
      Database database = new Database(getConnection());
      Table<Inventory> inventoryTable = database.getTable(Inventory.class);
      
      // select operation for list "...WHERE PARTNUMBER IN (?, ?, ?)..."
      for (Inventory inventory: inventoryTable.
          selectAllWhere("partNumberIn", partNumbers))    
      {
          System.out.println(inventory.getPartNumber());
      }
      

      【讨论】:

        【解决方案4】:

        我们可以使用不同的替代方法。

        1. 执行单个查询 - 速度慢且不推荐
        2. 使用存储过程 - 特定于数据库
        3. 动态创建 PreparedStatement 查询 - 性能良好,但缓存的好处松散,需要重新编译
        4. 在 PreparedStatement 查询中使用 NULL - 我认为这是一种具有最佳性能的好方法。

        查看有关这些here 的更多详细信息。

        【讨论】:

          【解决方案5】:

          执行此操作的标准方法是(如果您使用 Spring JDBC)是使用 org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate 类。

          使用这个类,可以定义一个 List 作为你的 SQL 参数,并使用 NamedParameterJdbcTemplate 来替换一个命名参数。例如:

          public List<MyObject> getDatabaseObjects(List<String> params) {
              NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
              String sql = "select * from my_table where my_col in (:params)";
              List<MyObject> result = jdbcTemplate.query(sql, Collections.singletonMap("params", params), myRowMapper);
              return result;
          }
          

          【讨论】:

            【解决方案6】:

            由于没有人回答 大型 IN 子句(超过 100 个),我将提出我的解决方案来解决这个问题,该解决方案非常适用于 JDBC。简而言之,我将 tmp 表上的 IN 替换为 INNER JOIN

            我所做的是创建我称之为批处理 ids 的表,根据 RDBMS,我可能会创建一个 tmp 表或内存表。

            表格有两列。一列带有来自 IN 子句的 ID,另一列带有我即时生成的批处理 ID。

            SELECT * FROM MYTABLE M INNER JOIN IDTABLE T ON T.MYCOL = M.MYCOL WHERE T.BATCH = ?
            

            在您选择之前,将您的 id 放入具有给定批次 id 的表中。 然后,您只需将原始查询 IN 子句替换为与您的 ids 表匹配的 INNER JOIN WHERE batch_id 等于您当前的批次。完成后,您可以批量删除条目。

            【讨论】:

            • +1 这对于大型数据集非常有效,并且不会破坏您的数据库
            • 嗯,半连接(INEXISTS 谓词)不会比内连接更好吗?
            • @LukasEder YMMV 和我的 SQL 伪代码。一如既往的测试/基准测试。这是一个有趣的想法。说到这里,当我们这样做时,我应该去看看我们的实际 SQL 是做什么的。
            • 确实如此。在 MySQL 上,半连接仍然有变慢的风险。但是内部连接有不正确的风险;)
            • @Male,当您需要从包含所有结果的 JDBC 函数(例如,使用商定/固定的 API)输出完整的 ResultSet 时,这将是一个不错的选择:填写 temp-表并进行连接,而不是批量输入,而无法合并 ResultSet。 (注意:当您只有 SELECT 权限时,您将无法创建临时表)
            【解决方案7】:

            看到我的试用成功了,据说列表大小有潜在的限制。 列表 l = Arrays.asList(new Integer[]{12496,12497,12498,12499}); 地图参数 = Collections.singletonMap("goodsid",l);

                NamedParameterJdbcTemplate  namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(getJdbcTemplate().getDataSource());
                String sql = "SELECT bg.goodsid FROM beiker_goods bg WHERE bg.goodsid in(:goodsid)";
                List<Long> list = namedParameterJdbcTemplate.queryForList(sql, param2, Long.class);
            

            【讨论】:

              【解决方案8】:

              AFAIK,JDBC 中没有标准支持将集合作为参数处理。如果您可以只传入一个 List 并将其扩展,那就太好了。

              Spring 的 JDBC 访问支持将集合作为参数传递。您可以看看这是如何完成的,以获取安全编码的灵感。

              Auto-expanding collections as JDBC parameters

              (本文先讨论 Hibernate,然后再讨论 JDBC。)

              【讨论】:

                【解决方案9】:

                我能想到的一种方法是使用 java.sql.PreparedStatement 和一些陪审团操纵

                PreparedStatement PreparedStmt = conn.prepareStatement("SELECT * FROM MYTABLE WHERE MYCOL in (?)");

                ...然后...

                preparedStmt.setString(1, [你的字符串参数]);

                http://java.sun.com/docs/books/tutorial/jdbc/basics/prepared.html

                【讨论】:

                • 这将不起作用,因为它可能会创建像 ... WHERE MYCOL IN ('2,3,5,6') 这样的查询,而这不是您想要做的。
                【解决方案10】:

                我通过使用尽可能多的 ? 构造 SQL 字符串来解决这个问题。

                SELECT * FROM MYTABLE WHERE MYCOL in (?,?,?,?)
                

                首先,我搜索了可以传递到语句中的数组类型,但所有 JDBC 数组类型都是特定于供应商的。所以我留在了多个?

                【讨论】:

                • 这就是我们现在正在做的事情,但我希望有一种统一的方法可以在没有自定义 SQL 的情况下做到这一点......
                • 另外,如果它类似于 Oracle,它必须重新解析大多数语句。
                猜你喜欢
                • 2019-02-14
                • 2021-10-05
                • 2010-09-05
                • 2013-08-12
                • 1970-01-01
                相关资源
                最近更新 更多