web-dev-qa-db-fra.com

Spring Batch: Un lecteur, plusieurs processeurs et écrivains

Dans Spring batch, je dois transmettre les éléments lus par un ItemReader à deux processeurs et rédacteurs différents. Ce que j'essaie de réaliser, c'est que ...

 + ---> ItemProcessor # 1 ---> ItemWriter # 1 
 | 
 ItemReader ---> item --- + 
 | 
 + ---> ItemProcessor # 2 ---> ItemWriter # 2 

Cela est nécessaire car les éléments écrits par ItemWriter # 1 doivent être traités de manière complètement différente de ceux écrits par ItemWriter # 2. De plus, ItemReader lit l'élément dans une base de données et les requêtes qu'il exécute sont donc aussi calculatoires. Il est coûteux d’exécuter deux fois la même requête.

Un indice sur la façon de réaliser une telle configuration? Ou, au moins, une configuration logiquement équivalente?

15
danidemi

Cette solution est valable si votre article doit être traité par le processeur n ° 1 et le processeur n ° 2

Vous devez créer un processeur n ° 0 avec cette signature:

class Processor0<Item, CompositeResultBean>

CompositeResultBean est un haricot défini comme

class CompositeResultBean {
  Processor1ResultBean result1;
  Processor2ResultBean result2;
}

Dans votre processeur n ° 0, déléguez simplement le travail aux processeurs n ° 1 et n ° 2 et mettez le résultat dans CompositeResultBean

CompositeResultBean Processor0.process(Item item) {
  final CompositeResultBean r = new CompositeResultBean();
  r.setResult1(processor1.process(item));
  r.setResult2(processor2.process(item));
  return r;
}

Votre propre rédacteur est une CompositeItemWriter qui délègue au rédacteur CompositeResultBean.result1 ou CompositeResultBean.result2 (regardez PropertyExtractingDelegatingItemWriter , peut-être utile)

9
Luca Basso Ricci

J'ai suivi la proposition de Luca d'utiliser PropertyExtractingDelegatingItemWriter comme rédacteur et j'ai pu travailler avec deux entités différentes en une seule étape. 

Tout d’abord, j’ai défini un DTO qui stocke les deux entités/résultats du processeur.

public class DatabaseEntry {
    private AccessLogEntry accessLogEntry;
    private BlockedIp blockedIp;

    public AccessLogEntry getAccessLogEntry() {
        return accessLogEntry;
    }

    public void setAccessLogEntry(AccessLogEntry accessLogEntry) {
        this.accessLogEntry = accessLogEntry;
    }

    public BlockedIp getBlockedIp() {
        return blockedIp;
    }

    public void setBlockedIp(BlockedIp blockedIp) {
        this.blockedIp = blockedIp;
    }
}

Ensuite, j'ai passé ce DTO à l'écrivain, une classe PropertyExtractingDelegatingItemWriter dans laquelle je définis deux méthodes personnalisées pour écrire les entités dans la base de données, voir mon code d'écrivain ci-dessous:

@Configuration
public class LogWriter extends LogAbstract {
    @Autowired
    private DataSource dataSource;

    @Bean()
    public PropertyExtractingDelegatingItemWriter<DatabaseEntry> itemWriterAccessLogEntry() {
        PropertyExtractingDelegatingItemWriter<DatabaseEntry> propertyExtractingDelegatingItemWriter = new PropertyExtractingDelegatingItemWriter<DatabaseEntry>();
        propertyExtractingDelegatingItemWriter.setFieldsUsedAsTargetMethodArguments(new String[]{"accessLogEntry", "blockedIp"});
        propertyExtractingDelegatingItemWriter.setTargetObject(this);
        propertyExtractingDelegatingItemWriter.setTargetMethod("saveTransaction");
        return propertyExtractingDelegatingItemWriter;
    }

    public void saveTransaction(AccessLogEntry accessLogEntry, BlockedIp blockedIp) throws SQLException {
        writeAccessLogTable(accessLogEntry);
        if (blockedIp != null) {
            writeBlockedIp(blockedIp);
        }

    }

    private void writeBlockedIp(BlockedIp entry) throws SQLException {
        PreparedStatement statement = dataSource.getConnection().prepareStatement("INSERT INTO blocked_ips (ip,threshold,startDate,endDate,comment) VALUES (?,?,?,?,?)");
        statement.setString(1, entry.getIp());
        statement.setInt(2, threshold);
        statement.setTimestamp(3, Timestamp.valueOf(startDate));
        statement.setTimestamp(4, Timestamp.valueOf(endDate));
        statement.setString(5, entry.getComment());
        statement.execute();
    }

    private void writeAccessLogTable(AccessLogEntry entry) throws SQLException {
        PreparedStatement statement = dataSource.getConnection().prepareStatement("INSERT INTO log_entries (date,ip,request,status,userAgent) VALUES (?,?,?,?,?)");
        statement.setTimestamp(1, Timestamp.valueOf(entry.getDate()));
        statement.setString(2, entry.getIp());
        statement.setString(3, entry.getRequest());
        statement.setString(4, entry.getStatus());
        statement.setString(5, entry.getUserAgent());
        statement.execute();
    }
}

Avec cette approche, vous pouvez obtenir le comportement initial souhaité à partir d'un seul lecteur pour traiter plusieurs entités et les enregistrer en une seule étape.

2
Juan Pablo G

Vous pouvez utiliser CompositeItemProcessor et CompositeItemWriter

Cela ne ressemblera pas exactement à votre schéma, ce sera séquentiel, mais cela fera l'affaire.

2
Sebastien Lorber

Il existe une autre solution si vous avez une quantité raisonnable d'éléments (par exemple, moins de 1 Go): vous pouvez mettre en cache le résultat de votre sélection dans une collection encapsulée dans un bean Spring.

Ensuite, vous pouvez simplement lire la collection deux fois sans frais.

0
Tristan