Пример Spring Batch

Добро пожаловать в Пример Spring Batch. Spring Batch – это модуль фреймворка Spring для выполнения пакетных задач. Мы можем использовать Spring Batch для обработки серии задач.

Пример Spring Batch

Прежде чем приступить к программе примера Spring Batch, давайте ознакомимся с некоторыми терминами Spring Batch.

  • A job can consist of ‘n’ number of steps. Each step contains Read-Process-Write task or it can have single operation, which is called tasklet.
  • Read-Process-Write в основном означает чтение из источника, такого как база данных, CSV и т. д., затем обработку данных и запись их в источник, такой как база данных, CSV, XML и т. д.
  • Задача (Tasklet) означает выполнение одной задачи или операции, например, очистку соединений, освобождение ресурсов после завершения обработки.
  • Read-Process-Write и задачи (Tasklets) могут быть объединены в цепочку для запуска задачи (job).

Пример Spring Batch

Давайте рассмотрим рабочий пример реализации Spring Batch. Мы рассмотрим следующий сценарий для целей реализации. Файл CSV, содержащий данные, должен быть преобразован в формат XML вместе с данными, и теги будут названы по имени столбца. Ниже приведены важные инструменты и библиотеки, используемые в примере Spring Batch.

  1. Апачи Мейвен 3.5.0 – для построения проекта и управления зависимостями.
  2. Эклипс Оксиген Релиз 4.7.0 – среда разработки для создания приложения на основе Spring Batch Maven.
  3. Java 1.8
  4. Spring Core 4.3.12.RELEASE
  5. Spring OXM 4.3.12.RELEASE
  6. Spring JDBC 4.3.12.RELEASE
  7. Spring Batch 3.0.8.RELEASE
  8. MySQL Java Driver 5.1.25 – использовать в соответствии с вашей установкой MySQL. Это необходимо для таблиц метаданных Spring Batch.

Структура каталогов примера Spring Batch

На изображении ниже показаны все компоненты в нашем примере проекта Spring Batch.

Зависимости Maven для Spring Batch

Ниже приведено содержимое файла pom.xml со всеми необходимыми зависимостями для нашего примера проекта Spring Batch.

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.journaldev.spring</groupId>
	<artifactId>SpringBatchExample</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>SpringBatchDemo</name>
	<url>https://maven.apache.org</url>

	<properties>
		<jdk.version>1.8</jdk.version>
		<spring.version>4.3.12.RELEASE</spring.version>
		<spring.batch.version>3.0.8.RELEASE</spring.batch.version>
		<mysql.driver.version>5.1.25</mysql.driver.version>
		<junit.version>4.11</junit.version>
	</properties>

	<dependencies>

		<!-- Spring Core -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<!-- Spring jdbc, for database -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<!-- Spring XML to/back object -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-oxm</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<!-- MySQL database driver -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>${mysql.driver.version}</version>
		</dependency>

		<!-- Spring Batch dependencies -->
		<dependency>
			<groupId>org.springframework.batch</groupId>
			<artifactId>spring-batch-core</artifactId>
			<version>${spring.batch.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.batch</groupId>
			<artifactId>spring-batch-infrastructure</artifactId>
			<version>${spring.batch.version}</version>
		</dependency>

		<!-- Spring Batch unit test -->
		<dependency>
			<groupId>org.springframework.batch</groupId>
			<artifactId>spring-batch-test</artifactId>
			<version>${spring.batch.version}</version>
		</dependency>

		<!-- Junit -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>com.thoughtworks.xstream</groupId>
			<artifactId>xstream</artifactId>
			<version>1.4.10</version>
		</dependency>

	</dependencies>
	<build>
		<finalName>spring-batch</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-eclipse-plugin</artifactId>
				<version>2.9</version>
				<configuration>
					<downloadSources>true</downloadSources>
					<downloadJavadocs>false</downloadJavadocs>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>${jdk.version}</source>
					<target>${jdk.version}</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

Обработка CSV-файла в Spring Batch

Вот содержимое нашего образцового CSV-файла для обработки в Spring Batch.

1001,Tom,Moody, 29/7/2013
1002,John,Parker, 30/7/2013
1003,Henry,Williams, 31/7/2013

Конфигурация задачи Spring Batch

Нам нужно определить spring bean и задачу Spring Batch в конфигурационном файле. Ниже приведено содержимое файла job-batch-demo.xml, это самая важная часть проекта Spring Batch.

<beans xmlns="https://www.springframework.org/schema/beans"
	xmlns:batch="https://www.springframework.org/schema/batch" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://www.springframework.org/schema/batch
		https://www.springframework.org/schema/batch/spring-batch-3.0.xsd
		https://www.springframework.org/schema/beans 
		https://www.springframework.org/schema/beans/spring-beans-4.3.xsd
	">

	<import resource="../config/context.xml" />
	<import resource="../config/database.xml" />

	<bean id="report" class="com.journaldev.spring.model.Report"
		scope="prototype" />
	<bean id="itemProcessor" class="com.journaldev.spring.CustomItemProcessor" />

	<batch:job id="DemoJobXMLWriter">
		<batch:step id="step1">
			<batch:tasklet>
				<batch:chunk reader="csvFileItemReader" writer="xmlItemWriter"
					processor="itemProcessor" commit-interval="10">
				</batch:chunk>
			</batch:tasklet>
		</batch:step>
	</batch:job>

	<bean id="csvFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader">

		<property name="resource" value="classpath:csv/input/report.csv" />

		<property name="lineMapper">
			<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
				<property name="lineTokenizer">
					<bean
						class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
						<property name="names" value="id,firstname,lastname,dob" />
					</bean>
				</property>
				<property name="fieldSetMapper">
					<bean class="com.journaldev.spring.ReportFieldSetMapper" />

					<!-- if no data type conversion, use BeanWrapperFieldSetMapper to map 
						by name <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"> 
						<property name="prototypeBeanName" value="report" /> </bean> -->
				</property>
			</bean>
		</property>

	</bean>

	<bean id="xmlItemWriter" class="org.springframework.batch.item.xml.StaxEventItemWriter">
		<property name="resource" value="file:xml/outputs/report.xml" />
		<property name="marshaller" ref="reportMarshaller" />
		<property name="rootTagName" value="report" />
	</bean>

	<bean id="reportMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
		<property name="classesToBeBound">
			<list>
				<value>com.journaldev.spring.model.Report</value>
			</list>
		</property>
	</bean>

</beans>
  1. Мы используем FlatFileItemReader для чтения CSV-файла, CustomItemProcessor для обработки данных и записи их в XML-файл с использованием StaxEventItemWriter.
  2. batch:job – Этот тег определяет создаваемую задачу. Свойство Id указывает идентификатор задачи. Мы можем определить несколько задач в одном XML-файле.
  3. batch:step – Этот тег используется для определения различных шагов задачи Spring Batch.
  4. Две различных стиля обработки предлагает фреймворк Spring Batch, которые называются “Ориентированный на задачи” и “Ориентированный на чанки”. Стиль “Ориентированный на чанки” используется в этом примере для пошагового чтения данных и создания “чанков”, которые будут записаны в рамках транзакции.
  5. reader: бин Spring, используемый для чтения данных. В этом примере мы использовали бин csvFileItemReader, который является экземпляром FlatFileItemReader.
  6. processor: это класс, используемый для обработки данных. В этом примере мы использовали CustomItemProcessor.
  7. writer: бин, используемый для записи данных в файл XML.
  8. commit-interval: Это свойство определяет размер чанка, который будет подтвержден после завершения обработки. В основном это означает, что ItemReader будет читать данные по одному, а ItemProcessor также будет обрабатывать их таким же образом, но ItemWriter будет записывать данные только тогда, когда их размер будет равен значению commit-interval.
  9. Три важных интерфейса, используемых в этом проекте, это ItemReader, ItemProcessor и ItemWriter из пакета org.springframework.batch.item.

Модельный класс Spring Batch

Сначала мы считываем файл CSV в объект Java, а затем используем JAXB для записи его в файл XML. Ниже приведен наш класс модели с необходимыми аннотациями JAXB.

package com.journaldev.spring.model;

import java.util.Date;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "record")
public class Report {

	private int id;
	private String firstName;
	private String lastName;
	private Date dob;

	@XmlAttribute(name = "id")
	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	@XmlElement(name = "firstname")
	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	@XmlElement(name = "lastname")
	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	@XmlElement(name = "dob")
	public Date getDob() {
		return dob;
	}

	public void setDob(Date dob) {
		this.dob = dob;
	}

	@Override
	public String toString() {
		return "Report [id=" + id + ", firstname=" + firstName + ", lastName=" + lastName + ", DateOfBirth=" + dob
				+ "]";
	}

}

Обратите внимание, что поля класса модели должны совпадать с определенными в конфигурации маппера Spring Batch, т.е. property name="names" value="id,firstname,lastname,dob" в нашем случае.

Spring Batch FieldSetMapper

A custom FieldSetMapper is needed to convert a Date. If no data type conversion is required, then only BeanWrapperFieldSetMapper should be used to map the values by name automatically. The java class which extends FieldSetMapper is ReportFieldSetMapper.

package com.journaldev.spring;

import java.text.ParseException;
import java.text.SimpleDateFormat;

import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.validation.BindException;

import com.journaldev.spring.model.Report;

public class ReportFieldSetMapper implements FieldSetMapper {

	private SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");

	public Report mapFieldSet(FieldSet fieldSet) throws BindException {

		Report report = new Report();
		report.setId(fieldSet.readInt(0));
		report.setFirstName(fieldSet.readString(1));
		report.setLastName(fieldSet.readString(2));

		// формат по умолчанию yyyy-MM-dd
		// fieldSet.readDate(4);
		String date = fieldSet.readString(3);
		try {
			report.setDob(dateFormat.parse(date));
		} catch (ParseException e) {
			e.printStackTrace();
		}

		return report;

	}

}

Spring Batch Item Processor

Теперь, как определено в конфигурации задания, ItemProcessor будет выполнен перед ItemWriter. Мы создали класс CustomItemProcessor.java для этого.

package com.journaldev.spring;

import org.springframework.batch.item.ItemProcessor;

import com.journaldev.spring.model.Report;

public class CustomItemProcessor implements ItemProcessor<Report, Report> {

	public Report process(Report item) throws Exception {
		
		System.out.println("Processing..." + item);
		String fname = item.getFirstName();
		String lname = item.getLastName();
		
		item.setFirstName(fname.toUpperCase());
		item.setLastName(lname.toUpperCase());
		return item;
	}

}

Мы можем манипулировать данными в реализации ItemProcessor. Как видите, я преобразую значения имени и фамилии в верхний регистр.

Spring Configuration Files

В нашем файле конфигурации весенней партии мы импортировали два дополнительных файла конфигурации – context.xml и database.xml.

<beans xmlns="https://www.springframework.org/schema/beans"
	xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
		https://www.springframework.org/schema/beans 
		https://www.springframework.org/schema/beans/spring-beans-4.3.xsd">

	<!-- stored job-meta in memory -->
	<!--  
	<bean id="jobRepository"
		class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
		<property name="transactionManager" ref="transactionManager" />
	</bean>
 	 -->
 	 
 	 <!-- stored job-meta in database -->
	<bean id="jobRepository"
		class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="transactionManager" ref="transactionManager" />
		<property name="databaseType" value="mysql" />
	</bean>
	
	<bean id="transactionManager"
		class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
	 
	<bean id="jobLauncher"
		class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
		<property name="jobRepository" ref="jobRepository" />
	</bean>

</beans>
  • jobRepository – JobRepository отвечает за сохранение каждого объекта Java в соответствующую таблицу метаданных для весенней партии.
  • transactionManager – это отвечает за фиксацию транзакции, когда размер интервала фиксации и обработанные данные равны.
  • jobLauncher – Это ядро весенней партии. Этот интерфейс содержит метод run, который используется для запуска задания.
<beans xmlns="https://www.springframework.org/schema/beans"
	xmlns:jdbc="https://www.springframework.org/schema/jdbc" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://www.springframework.org/schema/beans 
		https://www.springframework.org/schema/beans/spring-beans-4.3.xsd
		https://www.springframework.org/schema/jdbc 
		https://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd">

	<!-- connect to database -->
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/Test" />
		<property name="username" value="test" />
		<property name="password" value="test123" />
	</bean>

	<bean id="transactionManager"
		class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />

	<!-- create job-meta tables automatically -->
	<!-- <jdbc:initialize-database data-source="dataSource"> <jdbc:script location="org/springframework/batch/core/schema-drop-mysql.sql" 
		/> <jdbc:script location="org/springframework/batch/core/schema-mysql.sql" 
		/> </jdbc:initialize-database> -->
</beans>

Spring Batch использует некоторые таблицы метаданных для хранения информации о пакетных заданиях. Мы можем их создать из конфигураций весенней партии, но рекомендуется делать это вручную, выполняя SQL-файлы, как показано в закомментированном коде выше. С точки зрения безопасности лучше не предоставлять пользователю базы данных весенней партии доступ к выполнению DDL-запросов.

Таблицы весенней партии

Spring Batch таблицы очень тесно соответствуют объектам домена, представляющим их в Java. Например – JobInstance, JobExecution, JobParameters и StepExecution соответствуют BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION, BATCH_JOB_EXECUTION_PARAMS и BATCH_STEP_EXECUTION соответственно. ExecutionContext соответствует как BATCH_JOB_EXECUTION_CONTEXT, так и BATCH_STEP_EXECUTION_CONTEXT. JobRepository отвечает за сохранение и хранение каждого объекта Java в соответствующей таблице. Ниже приведены подробности каждой метаданные таблицы.

  1. Batch_job_instance: Таблица BATCH_JOB_INSTANCE содержит всю информацию, относящуюся к JobInstance.
  2. Batch_job_execution_params: Таблица BATCH_JOB_EXECUTION_PARAMS содержит всю информацию, относящуюся к объекту JobParameters.
  3. Batch_job_execution: Таблица BATCH_JOB_EXECUTION содержит данные, относящиеся к объекту JobExecution. Новая запись добавляется при каждом запуске задачи.
  4. Batch_step_execution: Таблица BATCH_STEP_EXECUTION содержит всю информацию, относящуюся к объекту StepExecution.
  5. Batch_job_execution_context: Таблица BATCH_JOB_EXECUTION_CONTEXT содержит данные, относящиеся к ExecutionContext работы. Существует ровно один ExecutionContext для каждого JobExecution, и он содержит все данные уровня задачи, необходимые для выполнения этой конкретной задачи. Эти данные обычно представляют собой состояние, которое необходимо восстановить после сбоя, чтобы JobInstance мог возобновить выполнение с того места, где произошел сбой.
  6. Batch_step_execution_context: Таблица BATCH_STEP_EXECUTION_CONTEXT содержит данные, относящиеся к контексту ExecutionContext шага. Для каждого StepExecution существует ровно один ExecutionContext, который содержит все данные, необходимые для сохранения состояния выполнения конкретного шага. Эти данные обычно представляют состояние, которое необходимо восстановить после сбоя, чтобы JobInstance мог продолжить выполнение с того момента, где произошел сбой.
  7. Batch_job_execution_seq: Эта таблица содержит последовательность выполнения данных задачи.
  8. Batch_step_execution_seq: Эта таблица содержит данные о последовательности выполнения шагов.
  9. Batch_job_seq: Эта таблица содержит данные о последовательности выполнения задачи. В случае наличия нескольких задач мы получим несколько строк.

Тестовая программа Spring Batch

Наш пример проекта Spring Batch готов, последний шаг – написать тестовый класс для его выполнения как java-программы.

package com.journaldev.spring;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
	public static void main(String[] args) {

		String[] springConfig = { "spring/batch/jobs/job-batch-demo.xml" };

		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(springConfig);

		JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher");
		Job job = (Job) context.getBean("DemoJobXMLWriter");

		JobParameters jobParameters = new JobParametersBuilder().addLong("time", System.currentTimeMillis())
				.toJobParameters();

		try {

			JobExecution execution = jobLauncher.run(job, jobParameters);
			System.out.println("Exit Status : " + execution.getStatus());

		} catch (Exception e) {
			e.printStackTrace();
		}

		System.out.println("Done");
		context.close();
	}
}

Просто запустите вышеуказанную программу, и вы получите выходной XML, подобный приведенному ниже.

<?xml version="1.0" encoding="UTF-8"?><report><record id="1001"><dob>2013-07-29T00:00:00+05:30</dob><firstname>TOM</firstname><lastname>MOODY</lastname></record><record id="1002"><dob>2013-07-30T00:00:00+05:30</dob><firstname>JOHN</firstname><lastname>PARKER</lastname></record><record id="1003"><dob>2013-07-31T00:00:00+05:30</dob><firstname>HENRY</firstname><lastname>WILLIAMS</lastname></record></report>

Вот и все, что касается примера Spring Batch. Вы можете скачать окончательный проект по следующей ссылке.

Скачать пример проекта Spring Batch

Ссылка: Официальное руководство

Source:
https://www.digitalocean.com/community/tutorials/spring-batch-example