出于安全性和性能原因,我正在修改一些代码以使用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')?
感谢您的帮助,我希望我的问题很明确!
问候,
看到这份文件我有点意外。确实,您不能像下面那样设置数组/集合(这与使用的数据库/ 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()
。
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
。但是你需要在cast(? as NUM_LIST)
函数中使用table()
。