Spring Batch - Tasklets vs Chunks

Spring Batch - Tasklets gegen Chunks

1. Einführung

Spring Batch provides two different ways for implementing a job: using tasklets and chunks.

In diesem Artikel erfahren Sie anhand eines einfachen Beispiels aus der Praxis, wie Sie beide Methoden konfigurieren und implementieren.

2. Abhängigkeiten

Beginnen wir mitadding the required dependencies:


    org.springframework.batch
    spring-batch-core
    4.0.0.RELEASE


    org.springframework.batch
    spring-batch-test
    4.0.0.RELEASE
    test

Informationen zum Abrufen der neuesten Version vonspring-batch-core undspring-batch-test finden Sie unter Maven Central.

3. Unser Anwendungsfall

Betrachten wir eine CSV-Datei mit folgendem Inhalt:

Mae Hodges,10/22/1972
Gary Potter,02/22/1953
Betty Wise,02/17/1968
Wayne Rose,04/06/1977
Adam Caldwell,09/27/1995
Lucille Phillips,05/14/1992

Diefirst position of each line represents a person’s name and the second position represents his/her date of birth.

Unser Anwendungsfall istgenerate another CSV file that contains each person’s name and age:

Mae Hodges,45
Gary Potter,64
Betty Wise,49
Wayne Rose,40
Adam Caldwell,22
Lucille Phillips,25

Nachdem unsere Domäne klar ist, können wir mit beiden Ansätzen eine Lösung entwickeln. Wir beginnen mit Tasklets.

4. Tasklets-Ansatz

4.1. Einführung und Design

Tasklets sollen eine einzelne Aufgabe innerhalb eines Schritts ausführen. Unser Job besteht aus mehreren Schritten, die nacheinander ausgeführt werden. Each step should perform only one defined task.

Unsere Aufgabe besteht aus drei Schritten:

  1. Liest Zeilen aus der CSV-Eingabedatei.

  2. Berechnen Sie das Alter für jede Person in der CSV-Eingabedatei.

  3. Schreiben Sie den Namen und das Alter jeder Person in eine neue CSV-Ausgabedatei.

Nachdem das Gesamtbild fertig ist, erstellen wir eine Klasse pro Schritt.

LinesReader ist für das Lesen von Daten aus der Eingabedatei verantwortlich:

public class LinesReader implements Tasklet {
    // ...
}

LinesProcessor berechnet das Alter für jede Person in der Datei:

public class LinesProcessor implements Tasklet {
    // ...
}

Schließlich hatLinesWriter die Verantwortung, Namen und Alter in eine Ausgabedatei zu schreiben:

public class LinesWriter implements Tasklet {
    // ...
}

Zu diesem Zeitpunkt istall our steps implement Tasklet interface. Das wird uns zwingen, dieexecute-Methode zu implementieren:

@Override
public RepeatStatus execute(StepContribution stepContribution,
  ChunkContext chunkContext) throws Exception {
    // ...
}

Bei dieser Methode fügen wir die Logik für jeden Schritt hinzu. Bevor wir mit diesem Code beginnen, konfigurieren wir unseren Job.

4.2. Aufbau

Wir müssenadd some configuration to Spring’s application context. Nachdem Sie die Standard-Bean-Deklaration für die im vorherigen Abschnitt erstellten Klassen hinzugefügt haben, können Sie unsere Jobdefinition erstellen:

@Configuration
@EnableBatchProcessing
public class TaskletsConfig {

    @Autowired
    private JobBuilderFactory jobs;

    @Autowired
    private StepBuilderFactory steps;

    @Bean
    protected Step readLines() {
        return steps
          .get("readLines")
          .tasklet(linesReader())
          .build();
    }

    @Bean
    protected Step processLines() {
        return steps
          .get("processLines")
          .tasklet(linesProcessor())
          .build();
    }

    @Bean
    protected Step writeLines() {
        return steps
          .get("writeLines")
          .tasklet(linesWriter())
          .build();
    }

    @Bean
    public Job job() {
        return jobs
          .get("taskletsJob")
          .start(readLines())
          .next(processLines())
          .next(writeLines())
          .build();
    }

    // ...

}

Dies bedeutet, dass unsere“taskletsJob” aus drei Schritten bestehen. Der erste (readLines) führt das in der BeanlinesReader definierte Tasklet aus und fährt mit dem nächsten Schritt fort:processLines. ProcessLines führt das in der BeanlinesProcessor definierte Tasklet aus und geht zum letzten Schritt:writeLines.

Unser Jobfluss ist definiert und wir sind bereit, eine Logik hinzuzufügen!

4.3. Modell und Utils

Da wir Zeilen in einer CSV-Datei bearbeiten, erstellen wir eine KlasseLine:

public class Line implements Serializable {

    private String name;
    private LocalDate dob;
    private Long age;

    // standard constructor, getters, setters and toString implementation

}

Bitte beachten Sie, dassLineSerializable. implementiert. Dies liegt daran, dassLine als DTO zum Übertragen von Daten zwischen Schritten fungiert. Nach Spring Batchobjects that are transferred between steps must be serializable.

Andererseits können wir anfangen, über das Lesen und Schreiben von Zeilen nachzudenken.

Dafür verwenden wir OpenCSV:


    com.opencsv
    opencsv
    4.1

Suchen Sie in Maven Central nach der neuestenOpenCSV-Version.

Sobald OpenCSV enthalten ist,we’re also going to create a FileUtils class. Es werden Methoden zum Lesen und Schreiben von CSV-Zeilen bereitgestellt:

public class FileUtils {

    public Line readLine() throws Exception {
        if (CSVReader == null)
          initReader();
        String[] line = CSVReader.readNext();
        if (line == null)
          return null;
        return new Line(
          line[0],
          LocalDate.parse(
            line[1],
            DateTimeFormatter.ofPattern("MM/dd/yyyy")));
    }

    public void writeLine(Line line) throws Exception {
        if (CSVWriter == null)
          initWriter();
        String[] lineStr = new String[2];
        lineStr[0] = line.getName();
        lineStr[1] = line
          .getAge()
          .toString();
        CSVWriter.writeNext(lineStr);
    }

    // ...
}

Beachten Sie, dassreadLine als Wrapper über diereadNext-Methode von OpenCSV fungiert und einLine-Objekt zurückgibt.

Auf die gleiche Weise umschließtwriteLinewriteNextvon OpenCSV, die einLine-Objekt empfangen. Die vollständige Implementierung dieser Klasse finden Sie inthe GitHub Project.

An diesem Punkt können wir mit jedem Schritt der Implementierung beginnen.

4.4. LinesReader

Lassen Sie uns fortfahren und unsereLinesReader-Klasse abschließen:

public class LinesReader implements Tasklet, StepExecutionListener {

    private final Logger logger = LoggerFactory
      .getLogger(LinesReader.class);

    private List lines;
    private FileUtils fu;

    @Override
    public void beforeStep(StepExecution stepExecution) {
        lines = new ArrayList<>();
        fu = new FileUtils(
          "taskletsvschunks/input/tasklets-vs-chunks.csv");
        logger.debug("Lines Reader initialized.");
    }

    @Override
    public RepeatStatus execute(StepContribution stepContribution,
      ChunkContext chunkContext) throws Exception {
        Line line = fu.readLine();
        while (line != null) {
            lines.add(line);
            logger.debug("Read line: " + line.toString());
            line = fu.readLine();
        }
        return RepeatStatus.FINISHED;
    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        fu.closeReader();
        stepExecution
          .getJobExecution()
          .getExecutionContext()
          .put("lines", this.lines);
        logger.debug("Lines Reader ended.");
        return ExitStatus.COMPLETED;
    }
}

Die MethodeLinesReader’s executeerstellt eineFileUtils-Instanz über dem Pfad der Eingabedatei. Dannadds lines to a list until there’re no more lines to read.

Unsere Klassealso implements StepExecutionListener bietet zwei zusätzliche Methoden:beforeStep undafterStep. Wir werden diese Methoden verwenden, um Dinge vor und nachexecute-Läufen zu initialisieren und zu schließen.

Wenn wir uns den Code vonafterStepansehen, werden wir die Zeile bemerken, in der die Ergebnisliste (lines) in den Kontext des Jobs gestellt wird, um sie für den nächsten Schritt verfügbar zu machen:

stepExecution
  .getJobExecution()
  .getExecutionContext()
  .put("lines", this.lines);

Zu diesem Zeitpunkt hat unser erster Schritt bereits seine Verantwortung erfüllt: Laden Sie CSV-Zeilen inList im Speicher. Fahren wir mit dem zweiten Schritt fort und verarbeiten sie.

4.5. LinesProcessor

LinesProcessor will also implement StepExecutionListener and of course, Tasklet. Das bedeutet, dass auch die MethodenbeforeStep,execute undafterStep implementiert werden:

public class LinesProcessor implements Tasklet, StepExecutionListener {

    private Logger logger = LoggerFactory.getLogger(
      LinesProcessor.class);

    private List lines;

    @Override
    public void beforeStep(StepExecution stepExecution) {
        ExecutionContext executionContext = stepExecution
          .getJobExecution()
          .getExecutionContext();
        this.lines = (List) executionContext.get("lines");
        logger.debug("Lines Processor initialized.");
    }

    @Override
    public RepeatStatus execute(StepContribution stepContribution,
      ChunkContext chunkContext) throws Exception {
        for (Line line : lines) {
            long age = ChronoUnit.YEARS.between(
              line.getDob(),
              LocalDate.now());
            logger.debug("Calculated age " + age + " for line " + line.toString());
            line.setAge(age);
        }
        return RepeatStatus.FINISHED;
    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        logger.debug("Lines Processor ended.");
        return ExitStatus.COMPLETED;
    }
}

Es ist mühelos zu verstehen, dassit loads lines list from the job’s context and calculates the age of each person.

Es ist nicht erforderlich, eine weitere Ergebnisliste in den Kontext einzufügen, da Änderungen an demselben Objekt vorgenommen werden, das aus dem vorherigen Schritt stammt.

Und wir sind bereit für unseren letzten Schritt.

4.6. LinesWriter

LinesWriter‘s task is to go over lines list and write name and age to the output file:

public class LinesWriter implements Tasklet, StepExecutionListener {

    private final Logger logger = LoggerFactory
      .getLogger(LinesWriter.class);

    private List lines;
    private FileUtils fu;

    @Override
    public void beforeStep(StepExecution stepExecution) {
        ExecutionContext executionContext = stepExecution
          .getJobExecution()
          .getExecutionContext();
        this.lines = (List) executionContext.get("lines");
        fu = new FileUtils("output.csv");
        logger.debug("Lines Writer initialized.");
    }

    @Override
    public RepeatStatus execute(StepContribution stepContribution,
      ChunkContext chunkContext) throws Exception {
        for (Line line : lines) {
            fu.writeLine(line);
            logger.debug("Wrote line " + line.toString());
        }
        return RepeatStatus.FINISHED;
    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        fu.closeWriter();
        logger.debug("Lines Writer ended.");
        return ExitStatus.COMPLETED;
    }
}

Wir sind mit der Umsetzung unserer Arbeit fertig! Erstellen wir einen Test, um ihn auszuführen und die Ergebnisse anzuzeigen.

4.7. Ausführen des Jobs

Um den Job auszuführen, erstellen wir einen Test:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TaskletsConfig.class)
public class TaskletsTest {

    @Autowired
    private JobLauncherTestUtils jobLauncherTestUtils;

    @Test
    public void givenTaskletsJob_whenJobEnds_thenStatusCompleted()
      throws Exception {

        JobExecution jobExecution = jobLauncherTestUtils.launchJob();
        assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
    }
}

Die Annotation vonContextConfigurationverweist auf die Spring-Kontextkonfigurationsklasse mit unserer Jobdefinition.

Wir müssen ein paar zusätzliche Bohnen hinzufügen, bevor wir den Test ausführen können:

@Bean
public JobLauncherTestUtils jobLauncherTestUtils() {
    return new JobLauncherTestUtils();
}

@Bean
public JobRepository jobRepository() throws Exception {
    MapJobRepositoryFactoryBean factory
      = new MapJobRepositoryFactoryBean();
    factory.setTransactionManager(transactionManager());
    return (JobRepository) factory.getObject();
}

@Bean
public PlatformTransactionManager transactionManager() {
    return new ResourcelessTransactionManager();
}

@Bean
public JobLauncher jobLauncher() throws Exception {
    SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
    jobLauncher.setJobRepository(jobRepository());
    return jobLauncher;
}

Alles ist bereit! Mach weiter und führe den Test durch!

Nach Abschluss des Jobs hatoutput.csv den erwarteten Inhalt und die Protokolle zeigen den Ausführungsablauf:

[main] DEBUG o.b.t.tasklets.LinesReader - Lines Reader initialized.
[main] DEBUG o.b.t.tasklets.LinesReader - Read line: [Mae Hodges,10/22/1972]
[main] DEBUG o.b.t.tasklets.LinesReader - Read line: [Gary Potter,02/22/1953]
[main] DEBUG o.b.t.tasklets.LinesReader - Read line: [Betty Wise,02/17/1968]
[main] DEBUG o.b.t.tasklets.LinesReader - Read line: [Wayne Rose,04/06/1977]
[main] DEBUG o.b.t.tasklets.LinesReader - Read line: [Adam Caldwell,09/27/1995]
[main] DEBUG o.b.t.tasklets.LinesReader - Read line: [Lucille Phillips,05/14/1992]
[main] DEBUG o.b.t.tasklets.LinesReader - Lines Reader ended.
[main] DEBUG o.b.t.tasklets.LinesProcessor - Lines Processor initialized.
[main] DEBUG o.b.t.tasklets.LinesProcessor - Calculated age 45 for line [Mae Hodges,10/22/1972]
[main] DEBUG o.b.t.tasklets.LinesProcessor - Calculated age 64 for line [Gary Potter,02/22/1953]
[main] DEBUG o.b.t.tasklets.LinesProcessor - Calculated age 49 for line [Betty Wise,02/17/1968]
[main] DEBUG o.b.t.tasklets.LinesProcessor - Calculated age 40 for line [Wayne Rose,04/06/1977]
[main] DEBUG o.b.t.tasklets.LinesProcessor - Calculated age 22 for line [Adam Caldwell,09/27/1995]
[main] DEBUG o.b.t.tasklets.LinesProcessor - Calculated age 25 for line [Lucille Phillips,05/14/1992]
[main] DEBUG o.b.t.tasklets.LinesProcessor - Lines Processor ended.
[main] DEBUG o.b.t.tasklets.LinesWriter - Lines Writer initialized.
[main] DEBUG o.b.t.tasklets.LinesWriter - Wrote line [Mae Hodges,10/22/1972,45]
[main] DEBUG o.b.t.tasklets.LinesWriter - Wrote line [Gary Potter,02/22/1953,64]
[main] DEBUG o.b.t.tasklets.LinesWriter - Wrote line [Betty Wise,02/17/1968,49]
[main] DEBUG o.b.t.tasklets.LinesWriter - Wrote line [Wayne Rose,04/06/1977,40]
[main] DEBUG o.b.t.tasklets.LinesWriter - Wrote line [Adam Caldwell,09/27/1995,22]
[main] DEBUG o.b.t.tasklets.LinesWriter - Wrote line [Lucille Phillips,05/14/1992,25]
[main] DEBUG o.b.t.tasklets.LinesWriter - Lines Writer ended.

Das war's für Tasklets. Jetzt können wir zum Chunks-Ansatz übergehen.

5. Chunks Ansatz

5.1. Einführung und Design

Wie der Name schon sagt, ist dieser Ansatzperforms actions over chunks of data. Das heißt, anstatt alle Zeilen gleichzeitig zu lesen, zu verarbeiten und zu schreiben, wird eine feste Anzahl von Datensätzen (Chunk) gleichzeitig gelesen, verarbeitet und geschrieben.

Anschließend wird der Zyklus wiederholt, bis die Datei keine Daten mehr enthält.

Infolgedessen ist der Durchfluss geringfügig anders:

  1. Während es Zeilen gibt:

    • Tun Sie für X Anzahl der Zeilen:

      • Lesen Sie eine Zeile

      • Eine Zeile verarbeiten

    • Schreiben Sie X Zeilenanzahl.

Also müssen wir auchthree beans for chunk oriented approach erstellen:

public class LineReader {
     // ...
}
public class LineProcessor {
    // ...
}
public class LinesWriter {
    // ...
}

Bevor wir zur Implementierung übergehen, konfigurieren wir unseren Job.

5.2. Aufbau

Die Jobdefinition sieht auch anders aus:

@Configuration
@EnableBatchProcessing
public class ChunksConfig {

    @Autowired
    private JobBuilderFactory jobs;

    @Autowired
    private StepBuilderFactory steps;

    @Bean
    public ItemReader itemReader() {
        return new LineReader();
    }

    @Bean
    public ItemProcessor itemProcessor() {
        return new LineProcessor();
    }

    @Bean
    public ItemWriter itemWriter() {
        return new LinesWriter();
    }

    @Bean
    protected Step processLines(ItemReader reader,
      ItemProcessor processor, ItemWriter writer) {
        return steps.get("processLines"). chunk(2)
          .reader(reader)
          .processor(processor)
          .writer(writer)
          .build();
    }

    @Bean
    public Job job() {
        return jobs
          .get("chunksJob")
          .start(processLines(itemReader(), itemProcessor(), itemWriter()))
          .build();
    }

}

In diesem Fall gibt es nur einen Schritt, in dem nur ein Tasklet ausgeführt wird.

Dieses Taskletdefines a reader, a writer and a processor that will act over chunks of data.

Beachten Sie, dasscommit interval indicates the amount of data to be processed in one chunk. Unser Job liest, verarbeitet und schreibt zwei Zeilen gleichzeitig.

Jetzt können wir unsere Chunk-Logik hinzufügen!

5.3. LineReader

LineReader ist dafür verantwortlich, einen Datensatz zu lesen und eineLine-Instanz mit ihrem Inhalt zurückzugeben.

Um ein Leser zu werden,our class has to implement ItemReader interface:

public class LineReader implements ItemReader {
     @Override
     public Line read() throws Exception {
         Line line = fu.readLine();
         if (line != null)
           logger.debug("Read line: " + line.toString());
         return line;
     }
}

Der Code ist unkompliziert, liest nur eine Zeile und gibt sie zurück. Wir werden auchStepExecutionListener für die endgültige Version dieser Klasse implementieren:

public class LineReader implements
  ItemReader, StepExecutionListener {

    private final Logger logger = LoggerFactory
      .getLogger(LineReader.class);

    private FileUtils fu;

    @Override
    public void beforeStep(StepExecution stepExecution) {
        fu = new FileUtils("taskletsvschunks/input/tasklets-vs-chunks.csv");
        logger.debug("Line Reader initialized.");
    }

    @Override
    public Line read() throws Exception {
        Line line = fu.readLine();
        if (line != null) logger.debug("Read line: " + line.toString());
        return line;
    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        fu.closeReader();
        logger.debug("Line Reader ended.");
        return ExitStatus.COMPLETED;
    }
}

Es ist zu beachten, dassbeforeStep undafterStep vor bzw. nach dem gesamten Schritt ausgeführt werden.

5.4. LineProcessor

LineProcessor folgt fast der gleichen Logik wieLineReader.

In diesem Fall sind jedochwe’ll implement ItemProcessor and its method process():

public class LineProcessor implements ItemProcessor {

    private Logger logger = LoggerFactory.getLogger(LineProcessor.class);

    @Override
    public Line process(Line line) throws Exception {
        long age = ChronoUnit.YEARS
          .between(line.getDob(), LocalDate.now());
        logger.debug("Calculated age " + age + " for line " + line.toString());
        line.setAge(age);
        return line;
    }

}

The process() method takes an input line, processes it and returns an output line. Auch hier implementieren wirStepExecutionListener:

public class LineProcessor implements
  ItemProcessor, StepExecutionListener {

    private Logger logger = LoggerFactory.getLogger(LineProcessor.class);

    @Override
    public void beforeStep(StepExecution stepExecution) {
        logger.debug("Line Processor initialized.");
    }

    @Override
    public Line process(Line line) throws Exception {
        long age = ChronoUnit.YEARS
          .between(line.getDob(), LocalDate.now());
        logger.debug(
          "Calculated age " + age + " for line " + line.toString());
        line.setAge(age);
        return line;
    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        logger.debug("Line Processor ended.");
        return ExitStatus.COMPLETED;
    }
}

5.5. LinesWriter

Im Gegensatz zu Reader und ProzessorLinesWriter will write an entire chunk of lines, so dassList vonLines: empfangen werden

public class LinesWriter implements
  ItemWriter, StepExecutionListener {

    private final Logger logger = LoggerFactory
      .getLogger(LinesWriter.class);

    private FileUtils fu;

    @Override
    public void beforeStep(StepExecution stepExecution) {
        fu = new FileUtils("output.csv");
        logger.debug("Line Writer initialized.");
    }

    @Override
    public void write(List lines) throws Exception {
        for (Line line : lines) {
            fu.writeLine(line);
            logger.debug("Wrote line " + line.toString());
        }
    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        fu.closeWriter();
        logger.debug("Line Writer ended.");
        return ExitStatus.COMPLETED;
    }
}

Der Code vonLinesWriterpricht für sich. Und wieder sind wir bereit, unseren Job zu testen.

5.6. Ausführen des Jobs

Wir erstellen einen neuen Test, genau wie den, den wir für den Tasklets-Ansatz erstellt haben:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ChunksConfig.class)
public class ChunksTest {

    @Autowired
    private JobLauncherTestUtils jobLauncherTestUtils;

    @Test
    public void givenChunksJob_whenJobEnds_thenStatusCompleted()
      throws Exception {

        JobExecution jobExecution = jobLauncherTestUtils.launchJob();

        assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
    }
}

Nachdem SieChunksConfig wie oben fürTaskletsConfig beschrieben konfiguriert haben, können Sie den Test ausführen!

Sobald der Job erledigt ist, können wir sehen, dassoutput.csv wieder das erwartete Ergebnis enthält, und die Protokolle beschreiben den Ablauf:

[main] DEBUG o.b.t.chunks.LineReader - Line Reader initialized.
[main] DEBUG o.b.t.chunks.LinesWriter - Line Writer initialized.
[main] DEBUG o.b.t.chunks.LineProcessor - Line Processor initialized.
[main] DEBUG o.b.t.chunks.LineReader - Read line: [Mae Hodges,10/22/1972]
[main] DEBUG o.b.t.chunks.LineReader - Read line: [Gary Potter,02/22/1953]
[main] DEBUG o.b.t.chunks.LineProcessor - Calculated age 45 for line [Mae Hodges,10/22/1972]
[main] DEBUG o.b.t.chunks.LineProcessor - Calculated age 64 for line [Gary Potter,02/22/1953]
[main] DEBUG o.b.t.chunks.LinesWriter - Wrote line [Mae Hodges,10/22/1972,45]
[main] DEBUG o.b.t.chunks.LinesWriter - Wrote line [Gary Potter,02/22/1953,64]
[main] DEBUG o.b.t.chunks.LineReader - Read line: [Betty Wise,02/17/1968]
[main] DEBUG o.b.t.chunks.LineReader - Read line: [Wayne Rose,04/06/1977]
[main] DEBUG o.b.t.chunks.LineProcessor - Calculated age 49 for line [Betty Wise,02/17/1968]
[main] DEBUG o.b.t.chunks.LineProcessor - Calculated age 40 for line [Wayne Rose,04/06/1977]
[main] DEBUG o.b.t.chunks.LinesWriter - Wrote line [Betty Wise,02/17/1968,49]
[main] DEBUG o.b.t.chunks.LinesWriter - Wrote line [Wayne Rose,04/06/1977,40]
[main] DEBUG o.b.t.chunks.LineReader - Read line: [Adam Caldwell,09/27/1995]
[main] DEBUG o.b.t.chunks.LineReader - Read line: [Lucille Phillips,05/14/1992]
[main] DEBUG o.b.t.chunks.LineProcessor - Calculated age 22 for line [Adam Caldwell,09/27/1995]
[main] DEBUG o.b.t.chunks.LineProcessor - Calculated age 25 for line [Lucille Phillips,05/14/1992]
[main] DEBUG o.b.t.chunks.LinesWriter - Wrote line [Adam Caldwell,09/27/1995,22]
[main] DEBUG o.b.t.chunks.LinesWriter - Wrote line [Lucille Phillips,05/14/1992,25]
[main] DEBUG o.b.t.chunks.LineProcessor - Line Processor ended.
[main] DEBUG o.b.t.chunks.LinesWriter - Line Writer ended.
[main] DEBUG o.b.t.chunks.LineReader - Line Reader ended.

We have the same result and a different flow. Protokolle zeigen, wie der Job nach diesem Ansatz ausgeführt wird.

6. Fazit

Unterschiedliche Kontexte zeigen die Notwendigkeit des einen oder anderen Ansatzes. While Tasklets feel more natural for ‘one task after the other' scenarios, chunks provide a simple solution to deal with paginated reads or situations where we don’t want to keep a significant amount of data in memory.

Die vollständige Implementierung dieses Beispiels finden Sie inthe GitHub project.