Spring Batch là một công cụ mạnh mẽ, được xây dựng dựa trên Spring Framework, chuyên biệt cho việc phát triển các ứng dụng batch với khả năng mở rộng và khả năng quản lý lỗi ưu việt. Nó cung cấp một kiến trúc rõ ràng và các thành phần có thể tái sử dụng, giúp bạn dễ dàng thiết kế và triển khai các tác vụ phức tạp một cách hiệu quả.
Giới thiệu về Spring Batch
Trước khi chúng ta bắt tay vào ví dụ chương trình Spring Batch, hãy cùng nắm bắt một vài thuật ngữ quan trọng của Spring Batch:
- Job: Một
Jobcó thể bao gồmnsố lượngStep. MỗiStepchứa một tác vụRead-Process-Writehoặc nó có thể có một hoạt động duy nhất, được gọi làTasklet. - Read-Process-Write: Về cơ bản,
Read-Process-Writelà quá trình đọc dữ liệu từ một nguồn (như Database, CSV, v.v.), sau đó xử lý dữ liệu và ghi nó ra một nguồn đích (như Database, CSV, XML, v.v.). - Tasklet:
Taskletđơn giản là việc thực hiện một tác vụ hoặc thao tác duy nhất, chẳng hạn như dọn dẹp các kết nối, giải phóng tài nguyên sau khi quá trình xử lý hoàn tất. Read-Process-WritevàTaskletlại với nhau để chạy mộtJobhoàn chỉnh.
Ví dụ Spring Batch
Chúng ta sẽ xem xét một ví dụ thực tế để triển khai Spring Batch. Kịch bản của chúng ta là: một tệp CSV chứa dữ liệu cần được chuyển đổi sang định dạng XML. Các thẻ (tags) trong XML sẽ được đặt tên theo tên cột của dữ liệu.
Dưới đây là các công cụ và thư viện quan trọng chúng ta sử dụng cho ví dụ Spring Batch này:
- Apache Maven 3.5.0: Để quản lý build dự án và các thư viện phụ thuộc.
- Eclipse Oxygen Release 4.7.0: Môi trường phát triển tích hợp (IDE) để tạo ứng dụng Spring Batch Maven.
- Java 1.8
- Spring Core 4.3.12.RELEASE
- Spring OXM 4.3.12.RELEASE
- Spring JDBC 4.3.12.RELEASE
- Spring Batch 3.0.8.RELEASE
- MySQL Java Driver 5.1.25: Sử dụng dựa trên cài đặt MySQL của bạn. Cái này cần thiết cho các bảng metadata của Spring Batch.
Cấu trúc thư mục dự án Spring Batch
Dưới đây là một mô tả về các thành phần trong dự án Spring Batch ví dụ của chúng ta.

Maven Dependencies cho Spring Batch
Dưới đây là nội dung tệp pom.xml với tất cả các thư viện phụ thuộc cần thiết cho dự án Spring Batch ví dụ của chúng ta. Bạn cần thêm các dependency này để Maven có thể tải và quản lý các thư viện cần thiết cho ứng dụng.
<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>
</build>
</project>
Tệp Input CSV cho Spring Batch Processing
Đây là nội dung tệp CSV mẫu của chúng ta cho quá trình xử lý Spring Batch. Bạn hãy tạo một tệp report.csv trong thư mục src/main/resources/csv/input/.
1001,Tom,Moody, 29/7/2013
1002,John,Parker, 30/7/2013
1003,Henry,Williams, 31/7/2013
Cấu hình Spring Batch Job
Chúng ta phải định nghĩa các spring bean và spring batch job trong một tệp cấu hình. Dưới đây là nội dung của tệp job-batch-demo.xml, đây là phần quan trọng nhất của dự án Spring Batch. Bạn sẽ đặt tệp này trong thư mục src/main/resources/spring/batch/jobs/.
job-batch-demo.xml
<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>
Chúng ta đang sử dụng FlatFileItemReader để đọc tệp CSV, CustomItemProcessor để xử lý dữ liệu và ghi vào tệp XML bằng StaxEventItemWriter.
Hãy phân tích các thành phần chính:
batch:job: Thẻ này định nghĩajobmà chúng ta muốn tạo. Thuộc tínhidchỉ định ID củajob. Bạn có thể định nghĩa nhiềujobtrong một tệp XML duy nhất.batch:step: Thẻ này dùng để định nghĩa các bước khác nhau của mộtjobSpring Batch.batch:chunk: Spring Batch Framework cung cấp hai kiểu xử lý chính: “TaskletStep Oriented” và “Chunk Oriented”. Trong ví dụ này, chúng ta sử dụng kiểu “Chunk Oriented”, nghĩa là bạn đọc dữ liệu từng mục một, xử lý nó và sau đó ghi các “chunk” (khối) dữ liệu ra trong phạm vi một giao dịch.reader:spring beanđược sử dụng để đọc dữ liệu. Chúng ta sử dụngcsvFileItemReadertrong ví dụ này, là một thể hiện củaFlatFileItemReader.processor: Đây là lớp dùng để xử lý dữ liệu. Chúng ta sử dụngCustomItemProcessortrong ví dụ này.writer:beanđược sử dụng để ghi dữ liệu vào tệp XML.commit-interval: Thuộc tính này định nghĩa kích thước củachunksẽ đượccommitsau khi quá trình xử lý hoàn tất. Về cơ bản, nó có nghĩa làItemReadersẽ đọc dữ liệu từng dòng một vàItemProcessorcũng sẽ xử lý theo cách tương tự, nhưngItemWriterchỉ ghi dữ liệu khi số lượng mục đạt đến kích thước củacommit-interval.- Ba giao diện quan trọng được sử dụng trong dự án này là
ItemReader,ItemProcessorvàItemWritertừ góiorg.springframework.batch.item.
Lớp Model Spring Batch
Đầu tiên, chúng ta đọc tệp CSV vào đối tượng Java và sau đó sử dụng JAXB để ghi nó ra tệp XML. Dưới đây là lớp model của chúng ta với các annotation JAXB cần thiết. Bạn hãy tạo lớp Report.java trong gói com.journaldev.spring.model.
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
+ "]";
}
}
Lưu ý rằng các trường của lớp model phải giống với các trường được định nghĩa trong cấu hình spring batch mapper của chúng ta, tức là property name="names" value="id,firstname,lastname,dob" trong tệp XML cấu hình FlatFileItemReader.
Spring Batch FieldSetMapper
Chúng ta cần một FieldSetMapper tùy chỉnh để chuyển đổi định dạng Date từ String trong CSV sang đối tượng Date trong Java. Nếu không yêu cầu chuyển đổi kiểu dữ liệu, chúng ta có thể sử dụng BeanWrapperFieldSetMapper để tự động ánh xạ các giá trị theo tên. Lớp Java của chúng ta mở rộng FieldSetMapper là ReportFieldSetMapper. Bạn hãy tạo lớp này trong gói com.journaldev.spring.
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<Report> {
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));
// default format 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
Như đã định nghĩa trong cấu hình job, một itemProcessor sẽ được kích hoạt trước itemWriter. Chúng ta đã tạo lớp CustomItemProcessor.java cho mục đích này. Lớp này sẽ xử lý dữ liệu trước khi nó được ghi ra. Bạn hãy tạo lớp này trong gói com.journaldev.spring.
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;
}
}
Bạn có thể thao tác dữ liệu trong triển khai ItemProcessor. Như bạn thấy, tôi đang chuyển đổi giá trị của firstName và lastName thành chữ in hoa. Đây là nơi bạn đặt logic nghiệp vụ để biến đổi dữ liệu.
Các tệp cấu hình Spring
Trong tệp cấu hình Spring Batch của chúng ta (job-batch-demo.xml), chúng ta đã import hai tệp cấu hình bổ sung: context.xml và database.xml. Bạn hãy tạo các tệp này trong thư mục src/main/resources/config/.
context.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>
Chúng ta cùng nhau giải thích các bean quan trọng trong context.xml:
jobRepository:JobRepositorychịu trách nhiệm lưu trữ từng đối tượng Java vào các bảngmeta-datatương ứng của Spring Batch.transactionManager: Cái này chịu trách nhiệmcommitgiao dịch khi kích thướccommit-intervalvà dữ liệu đã xử lý bằng nhau.jobLauncher: Đây là trái tim của Spring Batch. Giao diện này chứa phương thứcrunđược sử dụng để kích hoạt mộtjob.
database.xml
<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 sử dụng một số bảng metadata để lưu trữ thông tin job batch. Bạn có thể để chúng được tạo tự động từ cấu hình Spring Batch, nhưng tốt hơn hết là tạo chúng thủ công bằng cách thực thi các tệp SQL. Về mặt bảo mật, tốt hơn là không cấp quyền thực thi DDL (Data Definition Language) cho người dùng cơ sở dữ liệu của Spring Batch.
Các bảng Spring Batch
Các bảng Spring Batch khớp rất chặt chẽ với các đối tượng Domain đại diện cho chúng trong Java. Ví dụ: JobInstance, JobExecution, JobParameters và StepExecution lần lượt ánh xạ tới BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION, BATCH_JOB_EXECUTION_PARAMS và BATCH_STEP_EXECUTION. ExecutionContext ánh xạ tới cả BATCH_JOB_EXECUTION_CONTEXT và BATCH_STEP_EXECUTION_CONTEXT. JobRepository chịu trách nhiệm lưu và lưu trữ từng đối tượng Java vào bảng chính xác của nó.

Dưới đây là chi tiết về từng bảng meta-data:
BATCH_JOB_INSTANCE: Bảng này lưu trữ tất cả thông tin liên quan đến mộtJobInstance.BATCH_JOB_EXECUTION_PARAMS: Bảng này lưu trữ tất cả thông tin liên quan đến đối tượngJobParameters.BATCH_JOB_EXECUTION: Bảng này lưu trữ dữ liệu liên quan đến đối tượngJobExecution. Một hàng mới được thêm vào mỗi khi mộtJobđược chạy.BATCH_STEP_EXECUTION: Bảng này lưu trữ tất cả thông tin liên quan đến đối tượngStepExecution.BATCH_JOB_EXECUTION_CONTEXT: Bảng này lưu trữ dữ liệu liên quan đếnExecutionContextcủa mộtJob. Có chính xác mộtJob ExecutionContextcho mỗiJobExecution, và nó chứa tất cả dữ liệu cấpjobcần thiết cho việc thực thijobcụ thể đó. Dữ liệu này thường đại diện cho trạng thái phải được truy xuất sau khi một lỗi xảy ra để mộtJobInstancecó thể khởi động lại từ nơi nó đã thất bại.BATCH_STEP_EXECUTION_CONTEXT: Bảng này lưu trữ dữ liệu liên quan đếnExecutionContextcủa mộtStep. Có chính xác mộtExecutionContextcho mỗiStepExecution, và nó chứa tất cả dữ liệu cần được lưu trữ cho một lần thực thistepcụ thể. Dữ liệu này thường đại diện cho trạng thái phải được truy xuất sau khi một lỗi xảy ra để mộtJobInstancecó thể khởi động lại từ nơi nó đã thất bại.BATCH_JOB_EXECUTION_SEQ: Bảng này lưu trữ dữ liệu về chuỗi thực thi củajob.BATCH_STEP_EXECUTION_SEQ: Bảng này lưu trữ dữ liệu về chuỗi thực thistep.BATCH_JOB_SEQ: Bảng này lưu trữ dữ liệu về chuỗijobtrong trường hợp chúng ta có nhiềujob, chúng ta sẽ nhận được nhiều hàng.
Chương trình Test Spring Batch
Dự án Spring Batch ví dụ của chúng ta đã sẵn sàng. Bước cuối cùng là viết một lớp kiểm thử để thực thi nó như một chương trình Java độc lập. Bạn hãy tạo lớp App.java trong gói com.journaldev.spring.
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();
}
}
Bạn chỉ cần chạy chương trình trên và bạn sẽ nhận được tệp XML đầu ra như dưới đây trong thư mục xml/outputs/ của dự án bạn.
<?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>
Kết luận
Qua bài viết này, chúng ta đã khám phá Spring Batch, một module của Spring Framework giúp bạn thực thi các batch job một cách hiệu quả và tin cậy. Với kiến thức này, bạn có thể tự tin áp dụng Spring Batch vào các tác vụ xử lý dữ liệu thực tế trong công việc của mình.