针对Oracle的Java中的PreparedStatement问题

时间:2010-03-24 17:42:41

标签: java oracle prepared-statement

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

我们的应用程序目前正在将信息存储到嵌入式德比数据库中,但我们很快就会转向Oracle。

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

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

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

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

2-我们有一些TIMESTAMP类型的字段。因此,使用我们的实际Statement,查询如下所示:

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

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

感谢您的帮助,我希望我的问题很明确!

此致

3 个答案:

答案 0 :(得分:1)

看到这份文件我有点意外。确实,你不能像下面那样设置一个数组/集合(这与使用的数据库/ 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驱动程序,以下情况都应该起作用(尽管您依赖于数据库使用了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()

答案 1 :(得分:0)

Oracle确实在IN子句中处理绑定参数,但是希望每个参数都绑定一个与IN关键字之前的表达式兼容的类型的单个值。您通常想要的是可变长度的IN列表,并且不立即支持该列表。但是,IN子句的nonlocal变体与取消嵌套Array即可解决问题。

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

MyTreeView.Items.IndexOf(MyTreeView.SelectedItem);

请确保您的连接是OracleConnection。使用expr IN (subquery)函数取消嵌套输入数组。并使用create type NUM_LIST as table of number(10); 方法(自Oracle 11.2开始受支持)而不是标准的table()方法(Oracle JDBC驱动程序不支持):

createOracleArray()

您也可以将createArrayOf()定义为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

答案 2 :(得分:0)

使用 IN 列表

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

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

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

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

  • 2

  • x IN (?)

  • x IN (?, ?)

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

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

使用动态 SQL 构建器

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

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

使用 TABLEVARRAY 类型

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