Я написал класс для динамической генерации такого многопараметрического запроса. В настоящее время он имеет некоторые ограничения (для быстроты написания) и не был тщательно протестирован, но может быть хорошим способом для начала. Ограничения:
- Обрабатывает только один параметр с несколькими аргументами (??)
- Ложно распознает вопросительные знаки в кавычках как параметры
- API не очень приятный, но альтернативой было написание полноценного декоратора PreparedStatement с большим количеством управления состоянием, и это было больше работы, чем я хотел в него вставить.
Источник:
/**
* A PreparedStatement decorator that can bind a set of arguments
*
* A specialized ?? placeholder in a string can be bound to a set of
* values instead of just single values. Currently, only one such
* specialized placeholder is supported, and you must bind it before
* obtaining the prepared statement.
*
* If you want to bind additional values to the PreparedStatement after
* producing it, you must run the parameter index through the param()
* method.
*
* Example use:
*
*
* MultiValueBinder binder = new MultiValueBinder(
* "UPDATE table SET value = ? WHERE id IN (??)", conn);
* binder.setInts(myIds);
*
* PreparedStatement stmt = binder.statement();
* stmt.setString(binder.param(1), "myValue");
*
* ResultSet rs = stmt.executeQuery();
*
* Note: this class is not robust against using question marks in literal
* strings. Don't use them :).
*/
public class MultiValueBinder {
private Connection connection;
private PreparedStatement statement;
private String sql;
private int argumentsBefore = 0;
private int setSize = 0;
public MultiValueBinder(String sql, Connection connection) {
this.sql = sql;
this.connection = connection;
}
/**
* Bind a collection of integers to the multi-valued argument
*/
public void setInts(Collection<Integer> ints) throws SQLException {
explodeQuery(ints.size());
buildStatement();
try {
int i = 0;
for (Integer integer: ints)
statement.setInt(1 + argumentsBefore + i++, integer);
} catch (Exception ex) {
cleanStatement();
throw (ex instanceof SQLException) ? (SQLException) ex : new SQLException(ex);
}
}
/**
* Bind a collection of strings to the multi-valued argument
*/
public void setStrings(Collection<String> strings) throws SQLException {
explodeQuery(strings.size());
buildStatement();
try {
int i = 0;
for (String str: strings)
statement.setString(1 + argumentsBefore + i++, str);
} catch (Exception ex) {
cleanStatement();
throw (ex instanceof SQLException) ? (SQLException) ex : new SQLException(ex);
}
}
/**
* Explode the multi-value parameter into a sequence of comma-separated
* question marks.
*/
private void explodeQuery(int size) throws SQLException {
int mix = sql.indexOf("??");
if (mix == -1) throw new SQLException("Query does not contain a multi-valued argument.");
if (size == 0) throw new SQLException("Can't bind an empty collection; generated SQL won't parse.");
// Count the number of arguments before the multi-marker
argumentsBefore = 0;
for (int i = 0; i < mix; i++) {
if (sql.charAt(i) == '?') argumentsBefore++;
}
setSize = size;
// Generate the exploded SQL query
StringBuilder sb = new StringBuilder(sql.substring(0, mix)); // Start
for (int i = 0; i < setSize; i++) { // ?, ?, ...
if (i > 0) sb.append(", ");
sb.append('?');
}
sb.append(sql.substring(mix + 2)); // Remainder
sql = sb.toString();
}
/**
* Create the statement if it hasn't been created yet
*/
private void buildStatement() throws SQLException {
if (statement != null) return;
if (sql.contains("??"))
throw new SQLException("Multi-valued argument not bound yet.");
statement = connection.prepareStatement(sql);
}
private void cleanStatement() {
if (statement != null) {
try {
statement.close();
} catch (Exception ex) {
/* Ignore */
}
statement = null;
}
}
public PreparedStatement statement() throws SQLException {
buildStatement();
return statement;
}
/**
* Transform the 1-based-index of the given argument before query expansion
* into the index after expansion.
*
* The ?? placeholder takes up one index slot.
*/
public int param(int ix) {
if (ix <= argumentsBefore) return ix;
if (ix == argumentsBefore + 1)
throw new RuntimeException(ix + " is the index of the multi-valued parameter.");
return argumentsBefore + 1 + setSize;
}
}