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
Job
có thể bao gồmn
số lượngStep
. MỗiStep
chứa một tác vụRead-Process-Write
hoặ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-Write
là 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-Write
vàTasklet
lại với nhau để chạy mộtJob
hoà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ĩajob
mà chúng ta muốn tạo. Thuộc tínhid
chỉ định ID củajob
. Bạn có thể định nghĩa nhiềujob
trong 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ộtjob
Spring 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ụngcsvFileItemReader
trong 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ụngCustomItemProcessor
trong 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ủachunk
sẽ đượccommit
sau khi quá trình xử lý hoàn tất. Về cơ bản, nó có nghĩa làItemReader
sẽ đọc dữ liệu từng dòng một vàItemProcessor
cũng sẽ xử lý theo cách tương tự, nhưngItemWriter
chỉ 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
,ItemProcessor
vàItemWriter
từ 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
:JobRepository
chịu trách nhiệm lưu trữ từng đối tượng Java vào các bảngmeta-data
tương ứng của Spring Batch.transactionManager
: Cái này chịu trách nhiệmcommit
giao dịch khi kích thướccommit-interval
và 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 đếnExecutionContext
của mộtJob
. Có chính xác mộtJob ExecutionContext
cho mỗiJobExecution
, và nó chứa tất cả dữ liệu cấpjob
cần thiết cho việc thực thijob
cụ 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ộtJobInstance
có 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 đếnExecutionContext
của mộtStep
. Có chính xác mộtExecutionContext
cho 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 thistep
cụ 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ộtJobInstance
có 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ỗijob
trong 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.