Exception PostgreSQL lors de l'écriture en lot avec erreur d'E/S

Lors de l'insertion de données en lot dans une base de données PostgreSQL, une exception de type I/O peut survenir, comme illustrée ci-dessous :

Cause : org.postgresql.util.PSQLException : Une erreur d'E/S s'est produite lors de l'envoi au backend.

Détails de l'erreur

Premier message d'erreur

Caused by : org.postgresql.util.PSQLException : Une erreur d'E/S s'est produite lors de l'envoi au backend.
    at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:336)
    at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:446)
    at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:370)
    at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:149)
    at org.postgresql.jdbc.PgPreparedStatement.execute(PgPreparedStatement.java:138)

Second message d'erreur

Caused by : java.io.IOException : Tentative d'envoi d'un entier hors plage comme valeur sur 2 octets : 1847252
    at org.postgresql.core.PGStream.sendInteger2(PGStream.java:252)
    at org.postgresql.core.v3.QueryExecutorImpl.sendParse(QueryExecutorImpl.java:1470)
    at org.postgresql.core.v3.QueryExecutorImpl.sendOneQuery(QueryExecutorImpl.java:1793)
    at org.postgresql.core.v3.QueryExecutorImpl.sendQuery(QueryExecutorImpl.java:1356)
    at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:301)
    ... 117 trames supplémentaires

Analyse du problème

Le premier message indique une exception d'entrée/sortie lors de l'exécution de la requête SQL, suggérant soit un problème avec la requête elle-même, soit une défaillance potentielle du système de fichiers de la base de données.

Le second message révèle que PostgreSQL a tenté de transmettre une valeur entière trop grande pour être encodée sur deux octets. Cela signifie que la requête SQL génère des résultats volumineux, dépassant les limites de transmission par défaut, souvent dus à un nombre excessif de lignes ou à des données de grande taille.

Examen du code source

  /**
   * Envoie un entier sur 2 octets (short) au backend.
   *
   * @param val l'entier à envoyer
   * @throws IOException si une erreur E/S survient ou si {@code val} ne peut pas être encodé sur 2 octets
   */
  public void sendInteger2(int val) throws IOException {
    if (val < Short.MIN_VALUE || val > Short.MAX_VALUE) {
      throw new IOException("Tentative d'envoi d'un entier hors plage comme valeur sur 2 octets : " + val);
    }

    int2Buf[0] = (byte) (val >>> 8);
    int2Buf[1] = (byte) val;
    pgOutput.write(int2Buf);
  }

La méthode sendInteger2 convertit un entier en binaire sur deux octets et l'envoie via un flux de sortie. Elle effectue une vérification de plage pour s'assurer que l'enteir est dans les limites d'un short. L'erreur observée corrsepond à cette exception.

Découverte de la cause

Après inspection, la requête SQL en cause est une instruction d'insertion en lot avec une longueur excessive. L'exécution directe dans un outil comme Navicat provoque une erreur, tandis qu'une réduction du nombre d'entrées permet une exécution réussie.

Exemple de code initial :

// Récupérer une liste de données depuis une réponse
List<customentity> listeEntites = ExtraireListeDepuisReponse(reponse, "liste");

// Insérer les données dans la table cible via un composant d'accès aux données
AccesDonnees.persisterDonnees(listeEntites, nomTableCible);</customentity>

Solution appliquée

Partitionnement de la collection

// Utiliser ListUtils de Apache Commons Collections pour diviser la collection en lots de 1000 éléments
List<list>> partitions = ListUtils.partition(listeEntites, 1000);

// Itérer sur chaque lot pour une insertion incrémentale
for (List<customentity> lot : partitions) {
    AccesDonnees.persisterDonnees(lot, nomTableCible);
}</customentity></list>

Utilisation de l'insertion en lot avec MybatisPlus

Assurez-vous que MybatisPlus est intégré à votre projet.

// Récupérer la liste des entités
List<customentity> listeEntites = ExtraireListeDepuisReponse(reponse, "liste");

// Effectuer une insertion en lot avec MybatisPlus, en spécifiant la taille du lot
serviceCustom.insertionEnLot(listeEntites, 1000);</customentity>

Étiquettes: PostgreSQL Java SQL BatchInsert MybatisPlus

Publié le 3 juin à 19h09