Reutilizar um PreparedStatement várias vezes

96

no caso de usar PreparedStatement com uma única conexão comum sem nenhum pool, posso recriar uma instância para cada operação dml / sql mantendo o poder das instruções preparadas?

Quero dizer:

for (int i=0; i<1000; i++) {
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    preparedStatement.setObject(1, someValue);
    preparedStatement.executeQuery();
    preparedStatement.close();
}

ao invés de:

PreparedStatement preparedStatement = connection.prepareStatement(sql);
for (int i=0; i<1000; i++) {
    preparedStatement.clearParameters();
    preparedStatement.setObject(1, someValue);
    preparedStatement.executeQuery();
}
preparedStatement.close();

minha pergunta surge pelo fato de que eu quero colocar este código em um ambiente multithread, você pode me dar alguns conselhos? obrigado

Pluma de aço
fonte
então sua consulta, sqlnão muda com no loop? se essa consulta não muda para cada iteração do loop, por que você está criando uma nova PreparedStatementpara cada iteração (no primeiro trecho de código)? Existe algum motivo para isso?
Sabir Khan
vamos dizer que se a consulta está mudando, então a segunda abordagem é melhor, certo? Quaisquer desvantagens?
Stunner

Respostas:

144

A segunda maneira é um pouco mais eficiente, mas uma maneira muito melhor é executá-los em lotes:

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(SQL);
    ) {
        for (Entity entity : entities) {
            statement.setObject(1, entity.getSomeProperty());
            // ...

            statement.addBatch();
        }

        statement.executeBatch();
    }
}

No entanto, você depende da implementação do driver JDBC de quantos lotes pode executar de uma vez. Você pode, por exemplo, querer executá-los a cada 1000 lotes:

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(SQL);
    ) {
        int i = 0;

        for (Entity entity : entities) {
            statement.setObject(1, entity.getSomeProperty());
            // ...

            statement.addBatch();
            i++;

            if (i % 1000 == 0 || i == entities.size()) {
                statement.executeBatch(); // Execute every 1000 items.
            }
        }
    }
}

Quanto aos ambientes multithread, você não precisa se preocupar com isso se adquirir e fechar a conexão e a instrução no menor escopo possível dentro do mesmo bloco de método de acordo com o idioma JDBC normal usando a instrução try-with-resources conforme mostrado em trechos acima.

Se esses lotes forem transacionais, você gostaria de desligar o autocommit da conexão e apenas confirmar a transação quando todos os lotes forem concluídos. Caso contrário, pode resultar em um banco de dados sujo quando o primeiro grupo de lotes for bem-sucedido e o posterior não.

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (Connection connection = dataSource.getConnection()) {
        connection.setAutoCommit(false);

        try (PreparedStatement statement = connection.prepareStatement(SQL)) {
            // ...

            try {
                connection.commit();
            } catch (SQLException e) {
                connection.rollback();
                throw e;
            }
        }
    }
}
BalusC
fonte
inside the same method block- você quer dizer que cada thread terá sua própria pilha e essas conexões e instruções estão empilhadas de um lado e de outra fonte de dados para cada nova chamada de executeFunction (== cada thread) instância separada de conexão. Eu entendi bem? "
Pavel_K 05 de
Estou fazendo o primeiro método, mas durante o monitoramento no SQL Profiler, vejo várias instruções preparadas repetidas em vez de uma. Não consegui descobrir por que ele está mostrando várias declarações. assistência necessária.
ladino rapaz
Sua resposta é boa, desde que a consulta não esteja mudando no loop ... e se a consulta estiver mudando, por exemplo, no meu caso em que a consulta foi alterada ... Presumo que a segunda abordagem seja melhor. valide
Stunner
13

O loop em seu código é apenas um exemplo simplificado demais, certo?

Seria melhor criar PreparedStatementapenas uma vez e reutilizá-lo continuamente no loop.

Em situações onde isso não é possível (porque complicou muito o fluxo do programa), ainda é benéfico usar um PreparedStatement, mesmo se você usá-lo apenas uma vez, porque o lado do servidor do trabalho (analisar o SQL e armazenar em cache a execução plano), ainda será reduzido.

Para resolver a situação em que você deseja reutilizar o lado Java PreparedStatement, alguns drivers JDBC (como o Oracle) têm um recurso de cache: Se você criar um PreparedStatementpara o mesmo SQL na mesma conexão, ele fornecerá o mesmo (em cache ) instância.

Sobre multi-threading: Eu não acho que as conexões JDBC podem ser compartilhadas entre vários threads (ou seja, usadas simultaneamente por vários threads) de qualquer maneira. Cada thread deve obter sua própria conexão do pool, usá-la e devolvê-la ao pool novamente.

Thilo
fonte
1
Na verdade, a conexão tem seu encadeamento exclusivo e todas as instruções são executadas nele, mas eu acesso por meio de uma pilha exposta de instruções preparadas a esse encadeamento. Portanto, outros encadeamentos simultâneos inicialmente passam apenas os parâmetros necessários para construir todas as instruções preparadas, mas então eles podem modificar os parâmetros simultaneamente
Steel Plume