Réutiliser plusieurs fois un PreparedStatement

96

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 en 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 quelques conseils? Merci

Plume d'acier
la source
donc votre requête sqlne change pas avec dans la boucle? si cette requête ne change pas pour chaque itération de la boucle, alors pourquoi en créez-vous une nouvelle PreparedStatementpour chaque itération (dans le premier extrait de code)? Y a-t-il une raison de le faire?
Sabir Khan
disons si la requête est en train de changer, alors la deuxième approche est toujours meilleure, non? Des inconvénients?
Stunner le

Réponses:

144

La deuxième façon est un peu plus efficace, mais une bien meilleure façon est de 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();
    }
}

Vous dépendez cependant de l'implémentation du pilote JDBC du nombre de lots que vous pouvez exécuter à la fois. 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 soucier si vous acquérez et fermez la connexion et l'instruction dans la plus courte portée possible à l'intérieur du même bloc de méthode selon l'idiome JDBC normal en utilisant l' instruction try-with-resources comme indiqué dans au-dessus des extraits.

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, cela peut entraîner une base de données sale lorsque le premier groupe de lots a réussi et le dernier non.

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
la source
inside the same method block- vous voulez dire que chaque thread aura sa propre pile et que ces connexions et instructions sont dans la pile d'un côté et d'une autre source de données donnera à chaque nouvel appel de executeFunction (== chaque thread) une instance de connexion distincte. Dois-je vous comprendre bien? "
Pavel_K
Je fais la première méthode, mais lors de la surveillance dans le profileur SQL, je vois plusieurs instructions préparées répétées plutôt qu'une. Je ne pouvais pas comprendre pourquoi il montre plusieurs déclarations. assistance requise.
rogue lad
Votre réponse est bonne à condition que la requête ne change pas dans la boucle .. que faire si la requête change par exemple dans mon cas où la requête est modifiée .. je suppose que la deuxième approche est encore meilleure. veuillez valider
Stunner le
13

La boucle dans votre code n'est qu'un exemple trop simplifié, non?

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

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

Pour résoudre la situation dans laquelle vous souhaitez réutiliser le côté Java PreparedStatement, certains pilotes JDBC (comme Oracle) ont une fonction de mise en cache: si vous créez un PreparedStatementpour le même SQL sur la même connexion, il vous donnera le même (mis en cache ) exemple.

À 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 doit obtenir sa propre connexion à partir du pool, l'utiliser et la renvoyer à nouveau au pool.

Thilo
la source
1
En fait, la connexion a son thread exclusif et chaque instruction y est exécutée, mais j'accède via une pile exposée d'instructions préparées à ce thread. Ainsi, les autres threads concurrents ne transmettent initialement que les paramètres nécessaires pour construire toutes les instructions préparées, mais ils peuvent ensuite modifier les paramètres simultanément
Steel Plume