Java: insérer plusieurs lignes dans MySQL avec PreparedStatement

87

Je veux insérer plusieurs lignes dans une table MySQL à la fois en utilisant Java. Le nombre de lignes est dynamique. Dans le passé, je faisais ...

for (String element : array) {
    myStatement.setString(1, element[0]);
    myStatement.setString(2, element[1]);

    myStatement.executeUpdate();
}

J'aimerais optimiser cela pour utiliser la syntaxe prise en charge par MySQL:

INSERT INTO table (col1, col2) VALUES ('val1', 'val2'), ('val1', 'val2')[, ...]

mais avec un PreparedStatementje ne connais aucun moyen de le faire car je ne sais pas à l'avance combien d'éléments arraycontiendront. Si ce n'est pas possible avec a PreparedStatement, comment puis-je le faire autrement (et toujours échapper aux valeurs du tableau)?

Tom Marthenal
la source

Réponses:

175

Vous pouvez créer un lot par PreparedStatement#addBatch()et l'exécuter par PreparedStatement#executeBatch().

Voici un exemple de lancement:

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

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

            statement.addBatch();
            i++;

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

Il est exécuté tous les 1000 éléments car certains pilotes JDBC et / ou bases de données peuvent avoir une limitation sur la longueur des lots.

Voir aussi :

BalusC
la source
26
Vos inserts iront plus vite si vous les mettez dans des transactions ... c'est-à-dire emballer avec connection.setAutoCommit(false);et connection.commit(); download.oracle.com/javase/tutorial/jdbc/basics/…
Joshua Martell
1
Il semblerait que vous puissiez exécuter un lot vide s'il y a 999 éléments.
djechlin
2
@electricalbah il s'exécutera normalement cari == entities.size()
Yohanes AI
Voici une autre bonne ressource sur la création de travaux par lots à l'aide d'instructions préparées. viralpatel.net/blogs/batch-insert-in-java-jdbc
Danny Bullis
1
@ AndréPaulo: N'importe quel SQL INSERT adapté à une instruction préparée. Reportez-vous aux liens du didacticiel JDBC pour des exemples de base. Ceci n'est pas lié à la question concrète.
BalusC
30

Lorsque le pilote MySQL est utilisé, vous devez définir le paramètre de connexion rewriteBatchedStatementssur true ( jdbc:mysql://localhost:3306/TestDB?**rewriteBatchedStatements=true**).

Avec ce paramètre, l'instruction est réécrite en insertion en bloc lorsque la table n'est verrouillée qu'une seule fois et les index ne sont mis à jour qu'une seule fois. C'est donc beaucoup plus rapide.

Sans ce paramètre, le seul avantage est un code source plus propre.

MichalSv
la source
ceci est un commentaire de performance pour la construction: statement.addBatch (); if ((i + 1)% 1000 == 0) {instruction.executeBatch (); // Exécute tous les 1000 éléments. }
MichalSv
Apparemment, le pilote MySQL a un bogue bugs.mysql.com/bug.php?id=71528 Cela pose également des problèmes pour les frameworks ORM comme Hibernate hibernate.atlassian.net/browse/HHH-9134
Shailendra
Oui. Ceci est également correct pour le moment. Au moins pour la 5.1.45version du connecteur mysql.
v.ladynev
<artifactId> mysql-connector-java </artifactId> <version> 8.0.14 </version> Je viens de vérifier que la version 8.0.14 est correcte. Sans ajout, rewriteBatchedStatements=trueil n'y a pas de gain de performance.
vincent mathew
7

Si vous pouvez créer votre instruction SQL de manière dynamique, vous pouvez effectuer la solution de contournement suivante:

String myArray[][] = { { "1-1", "1-2" }, { "2-1", "2-2" }, { "3-1", "3-2" } };

StringBuffer mySql = new StringBuffer("insert into MyTable (col1, col2) values (?, ?)");

for (int i = 0; i < myArray.length - 1; i++) {
    mySql.append(", (?, ?)");
}

myStatement = myConnection.prepareStatement(mySql.toString());

for (int i = 0; i < myArray.length; i++) {
    myStatement.setString(i, myArray[i][1]);
    myStatement.setString(i, myArray[i][2]);
}
myStatement.executeUpdate();
Ali Shakiba
la source
Je crois que la réponse acceptée est bien meilleure !! Je ne connaissais pas les mises à jour par lots et quand j'ai commencé à écrire cette réponse, cette réponse n'était pas encore soumise !!! :)
Ali Shakiba
Cette approche est beaucoup plus rapide que celle acceptée. Je le teste, mais je ne trouve pas pourquoi. @JohnS savez-vous pourquoi?
julian0zzx
@ julian0zzx non, mais peut-être parce qu'il est exécuté en tant que sql unique au lieu de multiple. mais je ne suis pas sur.
Ali Shakiba
3

Dans le cas où vous avez l'incrémentation automatique dans la table et avez besoin d'y accéder .. vous pouvez utiliser l'approche suivante ... Faites un test avant de l'utiliser car getGeneratedKeys () dans Statement car cela dépend du pilote utilisé. Le code ci-dessous est testé sur Maria DB 10.0.12 et Maria JDBC driver 1.2

Rappelez-vous que l'augmentation de la taille du lot n'améliore les performances que dans une certaine mesure ... pour ma configuration, augmenter la taille du lot au-dessus de 500 dégradait en fait les performances.

public Connection getConnection(boolean autoCommit) throws SQLException {
    Connection conn = dataSource.getConnection();
    conn.setAutoCommit(autoCommit);
    return conn;
}

private void testBatchInsert(int count, int maxBatchSize) {
    String querySql = "insert into batch_test(keyword) values(?)";
    try {
        Connection connection = getConnection(false);
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        boolean success = true;
        int[] executeResult = null;
        try {
            pstmt = connection.prepareStatement(querySql, Statement.RETURN_GENERATED_KEYS);
            for (int i = 0; i < count; i++) {
                pstmt.setString(1, UUID.randomUUID().toString());
                pstmt.addBatch();
                if ((i + 1) % maxBatchSize == 0 || (i + 1) == count) {
                    executeResult = pstmt.executeBatch();
                }
            }
            ResultSet ids = pstmt.getGeneratedKeys();
            for (int i = 0; i < executeResult.length; i++) {
                ids.next();
                if (executeResult[i] == 1) {
                    System.out.println("Execute Result: " + i + ", Update Count: " + executeResult[i] + ", id: "
                            + ids.getLong(1));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            success = false;
        } finally {
            if (rs != null) {
                rs.close();
            }
            if (pstmt != null) {
                pstmt.close();
            }
            if (connection != null) {
                if (success) {
                    connection.commit();
                } else {
                    connection.rollback();
                }
                connection.close();
            }
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
gladiateur
la source
3

@Ali Shakiba votre code a besoin de quelques modifications. Partie d'erreur:

for (int i = 0; i < myArray.length; i++) {
     myStatement.setString(i, myArray[i][1]);
     myStatement.setString(i, myArray[i][2]);
}

Code mis à jour:

String myArray[][] = {
    {"1-1", "1-2"},
    {"2-1", "2-2"},
    {"3-1", "3-2"}
};

StringBuffer mySql = new StringBuffer("insert into MyTable (col1, col2) values (?, ?)");

for (int i = 0; i < myArray.length - 1; i++) {
    mySql.append(", (?, ?)");
}

mysql.append(";"); //also add the terminator at the end of sql statement
myStatement = myConnection.prepareStatement(mySql.toString());

for (int i = 0; i < myArray.length; i++) {
    myStatement.setString((2 * i) + 1, myArray[i][1]);
    myStatement.setString((2 * i) + 2, myArray[i][2]);
}

myStatement.executeUpdate();
vinay
la source
C'est une approche beaucoup plus rapide et meilleure dans l'ensemble de la réponse. Cela devrait être la réponse acceptée
Arun Shankar
1
Comme mentionné dans la réponse acceptée, certains pilotes / bases de données JDBC ont des limites sur le nombre de lignes que vous pouvez inclure dans une instruction INSERT. Dans le cas de l'exemple ci-dessus, si la myArraylongueur est supérieure à cette limite, vous rencontrerez une exception. Dans mon cas, j'ai une limite de 1000 lignes qui nécessite une exécution par lots, car je pourrais potentiellement mettre à jour plus de 1000 lignes sur une exécution donnée. Ce type de déclaration devrait théoriquement fonctionner correctement si vous savez que vous insérez moins que le maximum autorisé. Quelque chose à garder à l'esprit.
Danny Bullis
Pour clarifier, la réponse ci-dessus mentionne les limitations du pilote / base de données JDBC sur la longueur du lot, mais il peut également y avoir des limites sur le nombre de lignes incluses dans une instruction d'insertion, comme je l'ai vu dans mon cas.
Danny Bullis
0

nous pouvons soumettre plusieurs mises à jour ensemble dans JDBC pour soumettre des mises à jour par lots.

nous pouvons utiliser les objets Statement, PreparedStatement et CallableStatement pour la mise à jour bacth avec désactiver l'autocommit

Les fonctions addBatch () et executeBatch () sont disponibles avec tous les objets instruction pour avoir BatchUpdate

ici, la méthode addBatch () ajoute un ensemble d'instructions ou de paramètres au lot courant.

kapil das
la source