Riutilizzo di un PreparedStatement più volte


97

nel caso di utilizzo di PreparedStatement con una singola connessione comune senza alcun pool, posso ricreare un'istanza per ogni operazione dml / sql mantenendo la potenza delle istruzioni preparate?

Intendo:

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

invece di:

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

la mia domanda nasce dal fatto che voglio mettere questo codice in un ambiente multithread, puoi darmi qualche consiglio? Grazie


quindi la tua query, sqlnon cambia con nel ciclo? se quella query non cambia per ogni iterazione del ciclo, allora perché ne crei una nuova PreparedStatementper ogni iterazione (nel primo frammento di codice)? C'è qualche motivo per farlo?
Sabir Khan

diciamo che se la query sta cambiando, allora il secondo approccio è ancora migliore, giusto? Qualche svantaggio?
Storditore

Risposte:


144

Il secondo modo è un po 'più efficiente, ma un modo molto migliore è eseguirli in batch:

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();
    }
}

Tuttavia, dipendi dall'implementazione del driver JDBC quanti batch potresti eseguire contemporaneamente. Ad esempio, potresti voler eseguirli ogni 1000 batch:

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.
            }
        }
    }
}

Per quanto riguarda gli ambienti multithread, non devi preoccuparti di questo se acquisisci e chiudi la connessione e l'istruzione nel più breve ambito possibile all'interno dello stesso blocco del metodo secondo il normale idioma JDBC usando l' istruzione try-with-resources come mostrato in sopra i frammenti.

Se questi batch sono transazionali, disattivare il commit automatico della connessione e eseguire il commit della transazione solo quando tutti i batch sono terminati. Altrimenti potrebbe risultare in un database sporco quando il primo gruppo di batch è riuscito e il secondo no.

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

inside the same method block- vuoi dire che ogni thread avrà il proprio stack e queste connessioni e istruzioni sono in stack da un lato e da un'altra origine dati daranno a ogni nuova chiamata di executeFunction (== ogni thread) istanza di connessione separata. Ti capisco, vero? "
Pavel_K

Sto eseguendo il primo metodo ma durante il monitoraggio nel profiler SQL vedo ripetute istruzioni preparate multiple anziché una. Non sono riuscito a capire perché mostra più affermazioni. assistenza richiesta.
ladro

La tua risposta è buona a condizione che la query non stia cambiando nel ciclo .. cosa succede se la query sta cambiando, ad esempio nel mio caso in cui la query è cambiata .. presumo ancora che il secondo approccio sia migliore. si prega di convalidare
Stunner

13

Il loop nel tuo codice è solo un esempio estremamente semplificato, giusto?

Sarebbe meglio creare l' PreparedStatementunica volta e riutilizzarla più e più volte nel ciclo.

In situazioni in cui ciò non è possibile (perché ha complicato troppo il flusso del programma), è comunque vantaggioso utilizzare un PreparedStatement, anche se lo si utilizza solo una volta, perché il lato server del lavoro (analizzare l'SQL e memorizzare nella cache l'esecuzione piano), sarà comunque ridotto.

Per risolvere la situazione in cui desideri riutilizzare il lato Java PreparedStatement, alcuni driver JDBC (come Oracle) hanno una funzione di memorizzazione nella cache: se crei un PreparedStatementper lo stesso SQL sulla stessa connessione, ti darà lo stesso (memorizzato nella cache ) istanza.

Informazioni sul multi-threading: non credo che le connessioni JDBC possano essere condivise tra più thread (cioè utilizzate contemporaneamente da più thread) in ogni caso. Ogni thread dovrebbe ottenere la propria connessione dal pool, usarlo e restituirlo nuovamente al pool.


1
In effetti la connessione ha il suo thread esclusivo e ogni istruzione viene eseguita in essa, ma accedo a quel thread tramite uno stack esposto di istruzioni preparate. Quindi altri thread simultanei passano inizialmente solo i parametri necessari per costruire tutte le istruzioni preparate, ma poi possono modificare i parametri contemporaneamente
Steel Plume
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.