1
votes

Spring Batch: écrire une liste dans une table de base de données à l'aide d'une taille de lot personnalisée

<½Background

J'ai un poste Spring Batch où:

  1. FlatFileItemReader - Lit une ligne à la fois dans le fichier
  2. ItemProcesor - Transforme la ligne du fichier en List et renvoie la List . Autrement dit, chaque ligne du fichier est décomposée en une List (1 ligne du fichier transformée en plusieurs lignes de sortie).
  3. ItemWriter - Écrit la List dans une table de base de données. (J'ai utilisé ceci mise en œuvre pour décompresser la liste reçue du processeur et déléguer à un JdbcBatchItemWriter )

< gagnantQuestion

  • Au point 2) Le processeur peut renvoyer une List de 100 000 instances MyObject .
  • Au point 3), le délégué JdbcBatchItemWriter finira par écrire l'intégralité de la List avec 100 000 objets dans la base de données.

Ma question est la suivante: JdbcBatchItemWriter ne permet pas une taille de lot personnalisée. À toutes fins pratiques, le batch-size = commit-interval pour l'étape. Dans cet esprit, existe-t-il une autre implémentation d'un ItemWriter disponible dans Spring Batch qui permet d'écrire dans la base de données et autorise une taille de lot configurable? Sinon, comment rédiger moi-même un rédacteur personnalisé pour y parvenir?


0 commentaires

3 Réponses :


1
votes

Je ne vois aucun moyen évident de définir la taille du lot sur le JdbcBatchItemWriter . Cependant, vous pouvez étendre l'enregistreur et utiliser un BatchPreparedStatementSetter personnalisé pour spécifier la taille du lot. Voici un exemple rapide:

public class MyCustomWriter<T> extends JdbcBatchItemWriter<T> {

    @Override
    public void write(List<? extends T> items) throws Exception {
        namedParameterJdbcTemplate.getJdbcOperations().batchUpdate("your sql", new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                // set values on your sql
            }

            @Override
            public int getBatchSize() {
                return items.size(); // or any other value you want
            }
        });
    }

}

Les StagingItemWriter dans les exemples est un exemple d'utilisation d'un BatchPreparedStatementSetter code personnalisé > ainsi.


7 commentaires

@MohmoudBenHassine Cela semble prometteur. Concernant l'exemple StagingItemWriter , au lieu d'utiliser un BatchPreparedStatementSetter , je pourrais simplement appeler getJdbcTemplate (). batchUpdate dans la méthode write en lui passant la taille de lot requise?


Oui, c'est une autre option.


J'ai la direction nécessaire pour aller de l'avant. Merci.


heureux que cela ait aidé! Cependant, c'est délicat. Il y aura une seule transaction pour l'ensemble du bloc, quel que soit le nombre d'articles résultant de la logique de traitement. Par exemple, si commit-size = 10 et pour chaque élément, le processeur renvoie 100 éléments, alors le rédacteur recevra 1000 éléments par bloc. Ces 1000 éléments seront validés en une seule transaction, même si jdbc batch_size = 500. La taille du lot détermine le nombre de requêtes qui iront à la base de données (2 requêtes batchUpdate dans ce cas, 500 éléments dans chaque lot) et non le nombre de transactions (une seule transaction pilotée par Spring Batch dans ce cas).


@MahmoudeBenHassine Quels types de problèmes dois-je connaître qui peuvent résulter d'une seule transaction utilisée pour chaque appel à write , par opposition à une transaction utilisée par taille de lot? En outre, l'exemple StageItemWriter de Spring batch est également sujet au même problème ?. Un problème auquel je peux penser est que si la deuxième validation par lots échoue (500 enregistrements), toute l'écriture dans la base de données échouera (tous les 1000 enregistrements). Existe-t-il un moyen d'utiliser une nouvelle transaction par taille de lot?


Il n'y a aucun problème. L'idée du traitement de bloc est de traiter le bloc entier comme une unité (sémantique tout ou rien). Je voulais juste préciser que la taille du lot que vous essayez de personnaliser n'influence pas ce modèle (elle n'influence que le nombre de requêtes qui seront envoyées à la base de données).


Merci encore une fois pour la clarification.



0
votes

La réponse de Mahmoud Ben Hassine et les commentaires couvrent à peu près tous les aspects de la solution et sont la réponse acceptée.

Voici l'implémentation que j'ai utilisée si quelqu'un est intéressé:

public class JdbcCustomBatchSizeItemWriter<W> extends JdbcDaoSupport implements ItemWriter<W> {

    private int batchSize;
    private ParameterizedPreparedStatementSetter<W> preparedStatementSetter;
    private String sqlFileLocation;
    private String sql;

    public void initReader() {
        this.setSql(FileUtilties.getFileContent(sqlFileLocation));
    }

    public void write(List<? extends W> arg0) throws Exception {
        getJdbcTemplate().batchUpdate(sql, Collections.unmodifiableList(arg0), batchSize, preparedStatementSetter);
    }

    public void setBatchSize(int batchSize) {
        this.batchSize = batchSize;
    }

    public void setPreparedStatementSetter(ParameterizedPreparedStatementSetter<W> preparedStatementSetter) {
        this.preparedStatementSetter = preparedStatementSetter;
    }

    public void setSqlFileLocation(String sqlFileLocation) {
        this.sqlFileLocation = sqlFileLocation;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }
}

Remarque:

  1. L'utilisation de Collections.unmodifiableList évite la nécessité de toute diffusion explicite.
  2. J'utilise sqlFileLocation pour spécifier un fichier externe qui contient le sql et FileUtilities.getfileContents renvoie simplement le contenu de ce fichier sql. Cela peut être ignoré et on peut également passer directement le sql à la classe lors de la création du bean.


0 commentaires

0
votes

Je ne ferais pas ça. Il présente des problèmes de redémarrage. Au lieu de cela, modifiez votre lecteur pour produire des éléments individuels plutôt que de laisser votre processeur prendre un objet et renvoyer une liste.


1 commentaires

Le traitement requis pour convertir 1 ligne du fichier en plusieurs lignes est relativement complexe à mettre dans un ItemReader . Si je n'ai pas besoin de redémarrer des travaux étant donné que mes travaux n'ont jamais besoin d'être redémarrés, y a-t-il un autre problème avec cette approche que vous voyez?