Avvio a molla con lotto di primavera

1. Panoramica

Spring Batch è un potente framework per lo sviluppo di robuste applicazioni batch. Nel nostro tutorial precedente, abbiamo introdotto Spring Batch.

In questo tutorial, svilupperemo il precedente e impareremo come impostare e creare un'applicazione di base basata su batch utilizzando Spring Boot.

2. Dipendenze di Maven

Per prima cosa, aggiungiamo lo spring-boot-starter-batch al nostro pom.xml :

 org.springframework.boot spring-boot-starter-batch 2.4.0.RELEASE 

Aggiungeremo anche la dipendenza org.hsqldb , disponibile anche da Maven Central:

 org.hsqldb hsqldb 2.5.1 runtime 

3. Definizione di un lavoro batch primaverile semplice

Costruiremo un lavoro che importa una lista di caffè da un file CSV, la trasforma utilizzando un processore personalizzato e memorizza i risultati finali in un database in memoria .

3.1. Iniziare

Cominciamo definendo il nostro punto di ingresso dell'applicazione:

@SpringBootApplication public class SpringBootBatchProcessingApplication { public static void main(String[] args) { SpringApplication.run(SpringBootBatchProcessingApplication.class, args); } }

Come possiamo vedere, questa è un'applicazione Spring Boot standard. Poiché desideriamo utilizzare i valori di configurazione predefiniti ove possibile, utilizzeremo un insieme molto leggero di proprietà di configurazione dell'applicazione.

Definiremo queste proprietà nel nostro file src / main / resources / application.properties :

file.input=coffee-list.csv

Questa proprietà contiene la posizione della nostra lista dei caffè di input. Ogni linea contiene il marchio, l'origine e alcune caratteristiche del nostro caffè:

Blue Mountain,Jamaica,Fruity Lavazza,Colombia,Strong Folgers,America,Smokey

Come vedremo, questo è un file CSV semplice, il che significa che Spring può gestirlo senza alcuna personalizzazione speciale.

Successivamente, si aggiungerà uno script SQL schema-all.sql per creare il nostro caffè tabella per memorizzare i dati:

DROP TABLE coffee IF EXISTS; CREATE TABLE coffee ( coffee_id BIGINT IDENTITY NOT NULL PRIMARY KEY, brand VARCHAR(20), origin VARCHAR(20), characteristics VARCHAR(30) );

Convenientemente Spring Boot eseguirà questo script automaticamente durante l'avvio .

3.2. Classe di dominio del caffè

Successivamente, avremo bisogno di una semplice classe di dominio per contenere i nostri articoli da caffè:

public class Coffee { private String brand; private String origin; private String characteristics; public Coffee(String brand, String origin, String characteristics) { this.brand = brand; this.origin = origin; this.characteristics = characteristics; } // getters and setters }

Come accennato in precedenza, il nostro oggetto Coffee contiene tre proprietà:

  • Una marca
  • Un'origine
  • Alcune caratteristiche aggiuntive

4. Configurazione del lavoro

Ora, passiamo al componente chiave, la nostra configurazione del lavoro. Andremo passo dopo passo, costruendo la nostra configurazione e spiegando ogni parte lungo il percorso:

@Configuration @EnableBatchProcessing public class BatchConfiguration { @Autowired public JobBuilderFactory jobBuilderFactory; @Autowired public StepBuilderFactory stepBuilderFactory; @Value("${file.input}") private String fileInput; // ... }

In primo luogo, iniziamo con una classe Spring @Configuration standard . Successivamente, aggiungiamo un'annotazione @EnableBatchProcessing alla nostra classe. In particolare, questo ci dà accesso a molti bean utili che supportano i lavori e ci farà risparmiare molto lavoro.

Inoltre, l'utilizzo di questa annotazione ci fornisce anche l'accesso a due utili factory che utilizzeremo in seguito durante la creazione della nostra configurazione del lavoro e dei passaggi dei lavori.

Per l'ultima parte della nostra configurazione iniziale, includiamo un riferimento alla proprietà file.input che abbiamo dichiarato in precedenza.

4.1. Un lettore e scrittore per il nostro lavoro

Ora possiamo andare avanti e definire un bean di lettura nella nostra configurazione:

@Bean public FlatFileItemReader reader() { return new FlatFileItemReaderBuilder().name("coffeeItemReader") .resource(new ClassPathResource(fileInput)) .delimited() .names(new String[] { "brand", "origin", "characteristics" }) .fieldSetMapper(new BeanWrapperFieldSetMapper() {{ setTargetType(Coffee.class); }}) .build(); }

In breve, il nostro bean di lettura definito sopra cerca un file chiamato coffee-list.csv e analizza ogni voce in un oggetto Coffee .

Allo stesso modo, definiamo un bean writer:

@Bean public JdbcBatchItemWriter writer(DataSource dataSource) { return new JdbcBatchItemWriterBuilder() .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider()) .sql("INSERT INTO coffee (brand, origin, characteristics) VALUES (:brand, :origin, :characteristics)") .dataSource(dataSource) .build(); }

Questa volta includiamo l'istruzione SQL necessaria per inserire un singolo elemento di caffè nel nostro database, guidata dalle proprietà Java del nostro oggetto Coffee . Comoda la dataSource viene automaticamente creato da @EnableBatchProcessing annotazioni .

4.2. Mettere insieme il nostro lavoro

Infine, dobbiamo aggiungere i passaggi e la configurazione effettivi del lavoro:

@Bean public Job importUserJob(JobCompletionNotificationListener listener, Step step1) { return jobBuilderFactory.get("importUserJob") .incrementer(new RunIdIncrementer()) .listener(listener) .flow(step1) .end() .build(); } @Bean public Step step1(JdbcBatchItemWriter writer) { return stepBuilderFactory.get("step1") . chunk(10) .reader(reader()) .processor(processor()) .writer(writer) .build(); } @Bean public CoffeeItemProcessor processor() { return new CoffeeItemProcessor(); }

Come possiamo vedere, il nostro lavoro è relativamente semplice e consiste in un passaggio definito nel metodo step1 .

Diamo un'occhiata a cosa sta facendo questo passaggio:

  • Innanzitutto, configuriamo il nostro passaggio in modo che scriva fino a dieci record alla volta utilizzando la dichiarazione chunk (10)
  • Then, we read in the coffee data using our reader bean, which we set using the reader method
  • Next, we pass each of our coffee items to a custom processor where we apply some custom business logic
  • Finally, we write each coffee item to the database using the writer we saw previously

On the other hand, our importUserJob contains our job definition, which contains an id using the build-in RunIdIncrementer class. We also set a JobCompletionNotificationListener, which we use to get notified when the job completes.

To complete our job configuration, we list each step (though this job has only one step). We now have a perfectly configured job!

5. A Custom Coffee Processor

Let's take a look in detail at the custom processor we defined previously in our job configuration:

public class CoffeeItemProcessor implements ItemProcessor { private static final Logger LOGGER = LoggerFactory.getLogger(CoffeeItemProcessor.class); @Override public Coffee process(final Coffee coffee) throws Exception { String brand = coffee.getBrand().toUpperCase(); String origin = coffee.getOrigin().toUpperCase(); String chracteristics = coffee.getCharacteristics().toUpperCase(); Coffee transformedCoffee = new Coffee(brand, origin, chracteristics); LOGGER.info("Converting ( {} ) into ( {} )", coffee, transformedCoffee); return transformedCoffee; } }

Of particular interest, the ItemProcessor interface provides us with a mechanism to apply some specific business logic during our job execution.

To keep things simple, we define our CoffeeItemProcessor, which takes an input Coffee object and transforms each of the properties to uppercase.

6. Job Completion

Additionally, we're also going to write a JobCompletionNotificationListener to provide some feedback when our job finishes:

@Override public void afterJob(JobExecution jobExecution) { if (jobExecution.getStatus() == BatchStatus.COMPLETED) { LOGGER.info("!!! JOB FINISHED! Time to verify the results"); String query = "SELECT brand, origin, characteristics FROM coffee"; jdbcTemplate.query(query, (rs, row) -> new Coffee(rs.getString(1), rs.getString(2), rs.getString(3))) .forEach(coffee -> LOGGER.info("Found  in the database.", coffee)); } }

In the above example, we override the afterJob method and check the job completed successfully. Moreover, we run a trivial query to check that each coffee item was stored in the database successfully.

7. Running Our Job

Now that we have everything in place to run our job, here comes the fun part. Let's go ahead and run our job:

... 17:41:16.336 [main] INFO c.b.b.JobCompletionNotificationListener - !!! JOB FINISHED! Time to verify the results 17:41:16.336 [main] INFO c.b.b.JobCompletionNotificationListener - Found  in the database. 17:41:16.337 [main] INFO c.b.b.JobCompletionNotificationListener - Found  in the database. 17:41:16.337 [main] INFO c.b.b.JobCompletionNotificationListener - Found  in the database. ... 

As we can see, our job ran successfully, and each coffee item was stored in the database as expected.

8. Conclusion

In this article, we've learned how to create a simple Spring Batch job using Spring Boot. First, we started by defining some basic configuration.

Then, we saw how to add a file reader and database writer. Finally, we took a look at how to apply some custom processing and check our job was executed successfully.

Come sempre, il codice sorgente completo dell'articolo è disponibile su GitHub.