web-dev-qa-db-fra.com

Réutiliser plusieurs fois un PreparedStatement

dans le cas de l'utilisation de PreparedStatement avec une seule connexion commune sans pool, puis-je recréer une instance pour chaque opération dml/sql conservant la puissance des instructions préparées?

Je veux dire:

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

au lieu de:

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

ma question vient du fait que je veux mettre ce code dans un environnement multithread, pouvez-vous me donner un conseil? Merci

95
Steel Plume

La deuxième façon est un peu plus efficace, mais une bien meilleure méthode consiste à les exécuter par lots:

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

Cependant, vous dépendez de la mise en œuvre du pilote JDBC du nombre de lots que vous pouvez exécuter simultanément. Vous pouvez par exemple vouloir les exécuter tous les 1000 lots:

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 ce qui concerne les environnements multithread, vous n'avez pas à vous en préoccuper si vous acquérez et fermez la connexion et l'instruction dans la portée la plus courte possible à l'intérieur du même bloc de méthodes selon l'idiome JDBC normal en utilisant l'instruction try-with-resources comme indiqué dans les extraits ci-dessus.

Si ces lots sont transactionnels, vous souhaitez désactiver la validation automatique de la connexion et ne valider la transaction que lorsque tous les lots sont terminés. Sinon, la base de données peut être corrompue lorsque le premier groupe de lots a réussi et que le dernier échoue.

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

La boucle dans votre code n'est qu'un exemple trop simpliste, n'est-ce pas?

Il serait préférable de créer le PreparedStatement une seule fois et de le réutiliser encore et encore dans la boucle.

Dans les cas où cela n’est pas possible (parce que cela complique trop le déroulement du programme), il est toujours avantageux d’utiliser un PreparedStatement, même si vous ne l’utilisez qu’une seule fois, car le côté serveur du travail (analyse du code SQL et mise en cache du plan d’exécution), sera toujours réduite.

Pour répondre à la situation voulant que vous souhaitiez réutiliser PreparedStatement côté Java, certains pilotes JDBC (tels que Oracle) possèdent une fonctionnalité de mise en cache: Si vous créez un PreparedStatement pour le même code SQL sur la même connexion, il vous en sera donné le même. (mise en cache) instance.

À propos du multi-threading: Je ne pense pas que les connexions JDBC puissent être partagées entre plusieurs threads (c'est-à-dire utilisées simultanément par plusieurs threads) de toute façon. Chaque thread devrait obtenir sa propre connexion du pool, l'utiliser et le retourner à nouveau au pool.

13
Thilo