Pergunta

Eu tenho algum código que produz um conjunto de valores de chave primária que desejo excluir de uma tabela de banco de dados.

long[] keysToDelete = { 0, 1, 2, 3 };

e eu gostaria de usar uma estatização preparada para executar o equivalente a

DELETE FROM MyTable WHERE myPrimaryKey IN (0, 1, 2, 3);

Alguma ideia de como?

Foi útil?

Solução

Dois passos:

  1. Construa a sequência SQL preparada de estatamento preparado com o # de parâmetros apropriado.
  2. Loop sobre a matriz de valores e vincule cada um ao seu parâmetro.

Infelizmente, não há uma boa maneira de vincular uma matriz de uma só vez.

Outras dicas

Escrevi uma classe para gerar dinamicamente uma consulta de vários parâmetros. Atualmente, possui algumas limitações (para a rapidez da escrita) e não foi bem testado, mas pode ser uma boa maneira de iniciar você. Limitações:

  • Lida apenas com um parâmetro multi-argumento (??)
  • Reconhece falsamente pontos de interrogação em cotações como parâmetros
  • A API não é bonita, mas a alternativa estava escrevendo um decorador de estatuto preparado com muita gestão do estado e isso era mais trabalho do que eu estava disposto a colocar nela.

Fonte:

/**
 * 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;
    }
}

Não tenho certeza, mas isso pode ajudar:

PreparedStatement pstmt = Connection.prepareStatement("DELETE FROM MyTable WHERE myPrimaryKey IN (?)");
pstmt.setArray(1, idArray);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top