【问题标题】:PreparedStatement question in Java against OracleJava中针对Oracle的PreparedStatement问题
【发布时间】:2011-01-31 09:23:51
【问题描述】:

出于安全和性能原因,我正在修改一些代码以使用preparedStatement而不是普通Statement。

我们的应用程序目前正在将信息存储到一个嵌入式 derby 数据库中,但我们很快就会迁移到 Oracle。

我发现了两件关于 Oracle 和 Prepared Statement 的事情需要你们帮助:

1- 我发现 this document 说 Oracle 不会将绑定参数处理到 IN 子句中,因此我们无法提供如下查询:

Select pokemon from pokemonTable where capacity in (?,?,?,?)

这是真的吗?有什么解决方法吗? ... 为什么 ?

2- 我们有一些 TIMESTAMP 类型的字段。因此,使用我们的实际语句,查询看起来像这样:

Select raichu from pokemonTable where evolution = TO_TIMESTAMP('2500-12-31 00:00:00.000', 'YYYY-MM-DD HH24:MI:SS.FF')

准备好的 Statement 应该做什么?我应该放入参数数组: 2500-12-31 或 TO_TIMESTAMP('2500-12-31 00:00:00.000', 'YYYY-MM-DD HH24:MI:SS.FF') ?

感谢您的帮助,希望我的问题很清楚!

问候,

【问题讨论】:

    标签: java oracle prepared-statement


    【解决方案1】:

    使用IN 列表

    完全可以使用 JDBC 创建 IN 列表,例如 x IN (?, ?, ?)。您将不得不手动重复参数标记?,如建议的那样,例如BalusC 并记住:

    • IN 列表有 1000 个元素的限制,因此您必须在该限制之后将它们与 OR 连接:x IN (?, ?, ..., ?) OR x IN (?, ?, ..., ?)

    • 如果绑定变量太多,使用“内联值”可以更好地提高单个查询的性能,或者使用VARRAYTABLE 类型

    • 为了防止通过产生太多不同的 sql 字符串以及 SQL_ID 导致许多硬解析来防止游标缓存争用问题,我建议再次使用 VARRAYTABLE 类型,如果你有大量参数,或者至少要使用我称为IN list padding 的这个小技巧,在这里你重复? 的幂2 次,例如

    • x IN (?)

    • x IN (?, ?)

    • x IN (?, ?, ?, ?)

    • x IN (?, ?, ?, ?, ?, ?, ?, ?)

      这会将不同的SQL_ID 的数量从O(N) 减少到O(log(N)),代价是必须为大量数字重复绑定值多次,再次查看VARRAYTABLE 类型

    使用动态 SQL 构建器

    可以选择为此查询使用现成的动态 SQL 构建器,而不是滚动您自己的,它已经透明地处理了上述所有内容,例如jOOQ 或其他类似标准 API。甚至 Spring 的 JdbcTemplate 也内置了解决方法。

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

    使用VARRAYTABLE 类型

    其他人指出了VARRAYTABLE 类型的用法,但请注意,这种方法可能会对小数组数产生显着的性能损失。 In some unrepresentative benchmarks, I've found that the IN list can still outperform the array for sizes < 100(自己衡量)。此外,有可能得到非常错误的基数估计,我也在上面的文章中记录了这一点

    【讨论】:

      【解决方案2】:

      Oracle 确实在 IN 子句中处理绑定参数,但希望每个参数绑定一个与 IN 关键字之前的表达式兼容的类型的单个值。您通常想要的是一个可变长度的 IN 列表,而这并不立即得到支持。但是,IN 子句的expr IN (subquery) 变体加上数组的取消嵌套就可以解决问题。

      Oracle 不支持匿名数组类型,因此您需要在数据库中定义一个命名数组类型,例如

      create type NUM_LIST as table of number(10);
      

      确保您的连接是 OracleConnection。使用 table() 函数取消嵌套输入数组。并使用createOracleArray() 方法(自Oracle 11.2 起支持)而不是标准的createArrayOf() 方法(Oracle JDBC 驱动程序不支持):

      PreparedStatement statement = connection.prepareStatement("select pokemon from pokemonTable where capacity in (select * from table(?))");
      Array array = ((OracleConnection)statement.getConnection()).createOracleArray("NUM_LIST", new int[]{1,2,3});
      statement.setArray(1, array);
      ResultSet rs = statement.executeQuery();
      

      您也可以将NUM_LIST 定义为varray 而不是table。但是,您需要在 table() 函数中添加一个 cast(? as NUM_LIST)

      【讨论】:

      • 正如@MT0 所指出的,您可以使用member of ? 而不是in (select * from table(?))
      【解决方案3】:

      看到这份文件,我有点惊讶。确实不能像下面这样设置数组/集合(这与使用的数据库/JDBC 驱动程序无关):

      String sql = "SELECT col FROM tbl WHERE id IN (?)";
      statement = connection.prepareStatement(sql);
      statement.setArray(1, arrayOfValues); // Fail.
      

      但是文档中提到的查询应该可以工作。我可以从至少使用 Oracle 10g XE 和 ojdbc14.jar 的经验中看出这一点。我怀疑文档的作者混淆了一些东西,或者它实际上涉及不同(旧?)版本的 DB 和/或 JDBC 驱动程序。

      无论使用何种 JDBC 驱动程序,以下内容都应该有效(尽管您依赖于使用的 DB,IN 子句可以包含多少项,Oracle(是的,再次)限制为大约 1000 个项):

      private static final String SQL_FIND = "SELECT id, name, value FROM data WHERE id IN (%s)";
      
      public List<Data> find(Set<Long> ids) throws SQLException {
          Connection connection = null;
          PreparedStatement statement = null;
          ResultSet resultSet = null;
          List<Data> list = new ArrayList<Data>();
          String sql = String.format(SQL_FIND, preparePlaceHolders(ids.size()));
      
          try{
              connection = database.getConnection();
              statement = connection.prepareStatement(sql);
              setValues(statement, ids.toArray());
              resultSet = statement.executeQuery();
              while (resultSet.next()) {
                  Data data = new Data();
                  data.setId(resultSet.getLong("id"));
                  data.setName(resultSet.getString("name"));
                  data.setValue(resultSet.getInt("value"));
                  list.add(data);
              }
          } finally {
              close(connection, statement, resultSet);
          }
      
          return list;
      }
      
      public static String preparePlaceHolders(int length) {
          StringBuilder builder = new StringBuilder();
          for (int i = 0; i < length;) {
              builder.append("?");
              if (++i < length) {
                  builder.append(",");
              }
          }
          return builder.toString();
      }
      
      public static void setValues(PreparedStatement preparedStatement, Object... values) throws SQLException {
          for (int i = 0; i < values.length; i++) {
              preparedStatement.setObject(i + 1, values[i]);
          }
      }
      

      关于TIMESTAMP的问题,就用PreparedStatement#setTimestamp()吧。

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-06-12
      • 2011-04-19
      • 1970-01-01
      • 2021-02-21
      • 1970-01-01
      • 2016-12-27
      相关资源
      最近更新 更多