«1. Обзор
Spring Batch — мощная платформа для разработки надежных пакетных приложений. В нашем предыдущем уроке мы представили Spring Batch.
В этом руководстве мы будем основываться на предыдущем и узнаем, как настроить и создать базовое пакетное приложение с использованием Spring Boot.
2. Зависимости Maven
Во-первых, давайте добавим spring-boot-starter-batch в наш pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
<version>2.4.0</version>
</dependency>
Мы также добавим зависимость org.hsqldb, которая также доступно в Maven Central:
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.5.1</version>
<scope>runtime</scope>
</dependency>
3. Определение простого пакетного задания Spring
Мы собираемся создать задание, которое импортирует список кофе из CSV-файла, преобразует его с помощью специального процессора и сохраняет окончательные результаты в базе данных в памяти.
3.1. Начало работы
Давайте начнем с определения точки входа нашего приложения:
@SpringBootApplication
public class SpringBootBatchProcessingApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootBatchProcessingApplication.class, args);
}
}
Как мы видим, это стандартное приложение Spring Boot. Поскольку мы хотим использовать значения конфигурации по умолчанию, где это возможно, мы собираемся использовать очень легкий набор свойств конфигурации приложения.
Мы определим эти свойства в нашем файле src/main/resources/application.properties:
file.input=coffee-list.csv
Это свойство содержит расположение входного списка кофе. Каждая строка содержит марку, происхождение и некоторые характеристики нашего кофе:
Blue Mountain,Jamaica,Fruity
Lavazza,Colombia,Strong
Folgers,America,Smokey
Как мы увидим, это плоский файл CSV, что означает, что Spring может обрабатывать его без какой-либо специальной настройки.
Далее мы добавим SQL-скрипт schema-all.sql, чтобы создать журнальный столик для хранения данных:
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)
);
Для удобства Spring Boot будет запускать этот скрипт автоматически во время запуска.
3.2. Класс домена кофе
Далее нам понадобится простой класс домена для хранения наших позиций кофе:
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
}
Как упоминалось ранее, наш объект Coffee содержит три свойства:
-
Торговая марка Происхождение Некоторые дополнительные характеристики ~ ~~ 4. Конфигурация задания
Теперь перейдем к ключевому компоненту, нашей конфигурации задания. Мы пойдем шаг за шагом, создавая нашу конфигурацию и объясняя каждую часть по пути:
Во-первых, мы начнем со стандартного класса Spring @Configuration. Затем мы добавляем аннотацию @EnableBatchProcessing в наш класс. Примечательно, что это дает нам доступ ко многим полезным bean-компонентам, которые поддерживают рабочие места, и сэкономит нам много работы.
@Configuration
@EnableBatchProcessing
public class BatchConfiguration {
@Autowired
public JobBuilderFactory jobBuilderFactory;
@Autowired
public StepBuilderFactory stepBuilderFactory;
@Value("${file.input}")
private String fileInput;
// ...
}
Кроме того, использование этой аннотации также предоставляет нам доступ к двум полезным фабрикам, которые мы будем использовать позже при построении нашей конфигурации задания и шагов заданий.
В последней части нашей начальной конфигурации мы включаем ссылку на свойство file.input, которое мы объявили ранее.
4.1. Читатель и писатель для нашей работы
Теперь мы можем продолжить и определить bean-компонент чтения в нашей конфигурации:
Короче говоря, наш bean-компонент чтения, определенный выше, ищет файл с именем coffee-list.csv и анализирует каждый элемент строки в объект Coffee.
@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();
}
Точно так же мы определяем компонент записи:
На этот раз мы включаем оператор SQL, необходимый для вставки одного элемента кофе в нашу базу данных, управляемый свойствами компонента Java нашего объекта Coffee. Удобно, что источник данных автоматически создается аннотацией @EnableBatchProcessing.
@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();
}
4.2. Собираем нашу работу
Наконец, нам нужно добавить фактические шаги работы и конфигурацию:
Как мы видим, наша работа относительно проста и состоит из одного шага, определенного в методе step1.
@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")
.<Coffee, Coffee> chunk(10)
.reader(reader())
.processor(processor())
.writer(writer)
.build();
}
@Bean
public CoffeeItemProcessor processor() {
return new CoffeeItemProcessor();
}
Давайте посмотрим, что делает этот шаг:
Во-первых, мы настраиваем наш шаг так, чтобы он мог записывать до десяти записей за раз с помощью объявления chunk(10). Затем мы читаем данные о кофе. с помощью нашего компонента чтения, который мы установили с помощью метода чтения. Затем мы передаем каждый из наших элементов кофе в пользовательский процессор, где мы применяем некоторую пользовательскую бизнес-логику. Наконец, мы записываем каждый элемент кофе в базу данных, используя модуль записи, который мы видели ранее ~~ ~ С другой стороны, наш importUserJob содержит определение нашей работы, которое содержит идентификатор с использованием встроенного класса RunIdIncrementer. Мы также устанавливаем JobCompletionNotificationListener, который мы используем, чтобы получать уведомления о завершении задания.
-
«Чтобы завершить настройку нашего задания, мы перечислим каждый шаг (хотя в этом задании только один шаг). Теперь у нас есть идеально настроенная работа!
5. Пользовательский обработчик кофе
Давайте подробно рассмотрим пользовательский процессор, который мы определили ранее в нашей конфигурации задания:
Особый интерес представляет интерфейс ItemProcessor, предоставляющий нам механизм для применения некоторая конкретная бизнес-логика во время выполнения нашей работы.
Для простоты мы определяем наш CoffeeItemProcessor, который принимает входной объект Coffee и преобразует каждое свойство в верхний регистр.
public class CoffeeItemProcessor implements ItemProcessor<Coffee, Coffee> {
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;
}
}
6. Завершение задания
Кроме того, мы также напишем JobCompletionNotificationListener, чтобы обеспечить некоторую обратную связь, когда наше задание завершится:
В приведенном выше примере мы переопределяем метод afterJob и проверяем задание завершено успешно. Кроме того, мы запускаем тривиальный запрос, чтобы убедиться, что каждый кофейный элемент успешно сохранен в базе данных.
7. Выполнение нашей работы
@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));
}
}
Теперь, когда у нас есть все необходимое для выполнения нашей работы, наступает самое интересное. Давайте продолжим и запустим наше задание:
Как мы видим, наше задание выполнено успешно, и каждый элемент кофе был сохранен в базе данных, как и ожидалось.
8. Заключение
...
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 < Coffee [brand=BLUE MOUNTAIN, origin=JAMAICA, characteristics=FRUITY] > in the database.
17:41:16.337 [main] INFO c.b.b.JobCompletionNotificationListener -
Found < Coffee [brand=LAVAZZA, origin=COLOMBIA, characteristics=STRONG] > in the database.
17:41:16.337 [main] INFO c.b.b.JobCompletionNotificationListener -
Found < Coffee [brand=FOLGERS, origin=AMERICA, characteristics=SMOKEY] > in the database.
...
В этой статье мы узнали, как создать простое задание Spring Batch с помощью Spring Boot. Во-первых, мы начали с определения некоторой базовой конфигурации.
Затем мы увидели, как добавить модуль чтения файлов и модуль записи базы данных. Наконец, мы рассмотрели, как применить некоторую пользовательскую обработку и убедиться, что наша работа выполнена успешно.
Как всегда, полный исходный код статьи доступен на GitHub.
«
As always, the full source code of the article is available over on GitHub.