Reutilización de un PreparedStatement varias veces

96

en el caso de usar PreparedStatement con una sola conexión común sin ningún grupo, ¿puedo recrear una instancia para cada operación dml / sql manteniendo el poder de las declaraciones preparadas?

Quiero decir:

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

en vez de:

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

mi pregunta surge por el hecho de que quiero poner este código en un entorno multiproceso, ¿puede darme algún consejo? Gracias

Penacho de acero
fuente
Entonces, sql¿ su consulta no cambia con el bucle? Si esa consulta no cambia para cada iteración del ciclo, ¿por qué está creando una nueva PreparedStatementpara cada iteración (en el primer fragmento de código)? ¿Hay alguna razón para hacerlo?
Sabir Khan
digamos que si la consulta está cambiando, entonces el segundo enfoque es mejor, ¿verdad? ¿Alguna desventaja?
Stunner

Respuestas:

144

La segunda forma es un poco más eficiente, pero una forma mucho mejor es ejecutarlos en 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();
    }
}

Sin embargo, depende de la implementación del controlador JDBC cuántos lotes puede ejecutar a la vez. Por ejemplo, puede querer ejecutarlos 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.
            }
        }
    }
}

En cuanto a los entornos multiproceso, no necesita preocuparse por esto si adquiere y cierra la conexión y la declaración en el alcance más corto posible dentro del mismo bloque de método de acuerdo con el idioma JDBC normal usando la declaración try-with-resources como se muestra en fragmentos anteriores.

Si esos lotes son transaccionales, entonces le gustaría desactivar la confirmación automática de la conexión y solo confirmar la transacción cuando todos los lotes estén terminados. De lo contrario, puede resultar en una base de datos sucia cuando el primer grupo de lotes tuvo éxito y el posterior 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;
            }
        }
    }
}
BalusC
fuente
inside the same method block- quiere decir que cada hilo tendrá su propia pila y estas conexiones y declaraciones están en la pila desde un lado y desde otra fuente de datos darán a cada nueva llamada de executeFunction (== cada hilo) una instancia separada de conexión. ¿Te entiendo bien? "
Pavel_K
Estoy haciendo el primer método, pero mientras monitoreo en el generador de perfiles de SQL, veo varias declaraciones preparadas repetidas en lugar de una. No pude entender por qué muestra múltiples declaraciones. asistencia requerida.
chico pícaro
Su respuesta es buena siempre que la consulta no esté cambiando en el ciclo ... ¿qué pasa si la consulta está cambiando, por ejemplo, en mi caso donde se cambia la consulta ... supongo que aún el segundo enfoque es mejor? por favor valide
Stunner
13

El bucle en su código es solo un ejemplo demasiado simplificado, ¿verdad?

Sería mejor crear el PreparedStatementúnico una vez y reutilizarlo una y otra vez en el ciclo.

En situaciones en las que eso no es posible (porque complicó demasiado el flujo del programa), aún es beneficioso usar un PreparedStatement, incluso si lo usa solo una vez, porque el lado del servidor del trabajo (analizar el SQL y almacenar en caché la ejecución plan), aún se reducirá.

Para abordar la situación en la que desea reutilizar el lado de Java PreparedStatement, algunos controladores JDBC (como Oracle) tienen una función de almacenamiento en caché: si crea un PreparedStatementpara el mismo SQL en la misma conexión, le dará el mismo (caché ) instancia.

Acerca de los subprocesos múltiples: No creo que las conexiones JDBC se puedan compartir entre varios subprocesos (es decir, utilizadas simultáneamente por varios subprocesos) de todos modos. Cada hilo debe obtener su propia conexión del grupo, usarlo y devolverlo al grupo nuevamente.

Thilo
fuente
1
De hecho, la conexión tiene su hilo exclusivo y cada declaración se ejecuta en ella, pero accedo a través de una pila expuesta de declaraciones preparadas a ese hilo. Entonces, otros subprocesos concurrentes inicialmente pasan solo los parámetros necesarios para construir todas las declaraciones preparadas, pero luego pueden modificar los parámetros al mismo tiempo
Steel Plume