ברוכים הבאים לדוגמה של Spring Batch. Spring Batch הוא מודול בתוך פריימוורק Spring לביצוע עבודה עם תהליכי Batch. ניתן להשתמש ב-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 יחד כדי להפעיל עבודה.
דוגמת Spring Batch
בואו נשקול דוגמה פועלת ליישום של Spring Batch. נשקול את התרחיש הבא למטרת היישום. קובץ CSV שמכיל נתונים צריך להיות ממורה כ-XML יחד עם הנתונים והתגיות יקראו על שם שם העמודה. למטה מצוים הכלים והספריות החשובים המשמשים לדוגמת Spring Batch.
- אפאצ'י מאוון 3.5.0 – לבניית פרויקט וניהול תלות.
- אקליפסה אוקסיגן גרסה 4.7.0 – סביבת פיתוח ליצירת אפליקציות Spring Batch באמצעות Maven.
- ג'אווה 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 – השתמש בה בהתאם להתקנת MySQL שלך. דרוש לטבלאות Spring Batch metadata.
מבנה ספריית דוגמה ל-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 בבאטץ' של הקראה מסווגת
זהו תוכן הקובץ CSV של הדוגמה שלנו לעיבוד באט' של הקראה מסווגת.
1001,Tom,Moody, 29/7/2013
1002,John,Parker, 30/7/2013
1003,Henry,Williams, 31/7/2013
הגדרת עבודת Batch של Spring
עלינו להגדיר סוג של פולנית ועבודת batch של Spring בקובץ התצורה. להלן התוכן של קובץ job-batch-demo.xml, הוא החלק החשוב ביותר של פרוייקט batch של Spring.
<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>
- אנו משתמשים ב-FlatFileItemReader כדי לקרוא את קובץ ה-CSV, CustomItemProcessor כדי לעבד את הנתונים ולכתוב אותם לקובץ XML באמצעות StaxEventItemWriter.
batch:job
– תג זה מגדיר את העבודה שברצוננו ליצור. המאפיין Id מציין את הזהות של העבודה. אנו יכולים להגדיר מספר עבודות בקובץ XML אחד.- batch:step – תג זה משמש להגדרת שלבים שונים של עבודה בבאט' של Spring.
- שני סוגים שונים של סגנונות עיבוד מצויינים על ידי ספרינג באץ' פריימוורק, שהם "ממוקד TaskletStep" ו"ממוקד Chunk". סגנון ממוקד Chunk משמש בדוגמה זו, והתייחס לקריאת הנתונים באופן יחידי ויצירת 'חתיכות' שיתווספו, בתוך גבול עסקאות.
- קורא: מחלקת ה- spring bean המשמשת לקריאת הנתונים. בדוגמה זו השתמשנו ב-
csvFileItemReader
שהוא מופע שלFlatFileItemReader
. - מעבד: זוהי המחלקה שמשמשת לעיבוד הנתונים. בדוגמה זו השתמשנו ב-
CustomItemProcessor
. - כותב: bean המשמש לכתיבת הנתונים לקובץ XML.
- commit-interval: מאפיין זה מגדיר את גודל החתיכה שתיכנס לאישור לאחר שהעיבוד נגמר. בגדר פשוטה, זה אומר ש-ItemReader יקרא את הנתונים אחד-לאחד, ItemProcessor יעבד אותם באותו האופן, אך ItemWriter יכתוב את הנתונים רק כאשר הם שווים לגודל של commit-interval.
- שלוש ממשקים חשובים המשמשים כחלק מהפרויקט הם
ItemReader
,ItemProcessor
ו-ItemWriter
מחבילתorg.springframework.batch.item
.
מודל מחלקת Spring Batch
ראשית כל, אנחנו קוראים קובץ CSV לתוך אובייקט ג'אווה ואז משתמשים ב-JAXB כדי לכתוב אותו לקובץ XML. להלן הקובץ המודל עם ה-annotations שנדרשים:
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
+ "]";
}
}
. שימו לב ששדות המחלקה המודל צריכים להיות זהים לשדות שמוגדרים בתצורת מפרש ספרינג, כמו "property name="names" value="id,firstname,lastname,dob"" במקרה שלנו.
מפרש שדה ספרינג
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;
}
}
מעבד פריט של ספרינג באטצ'
כעת, כפי שמוגדר בתצורת המשימה, יתבצע מעבד פריט לפני כותב הפריטים. יצרנו מחלקה בשם 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, כפי שאתה רואה שאני משנה את ערכי שם הפרטי ושם המשפחה לאותיות ראשונות גדולות.
קבצי תצורה של ספרינג
בקובץ התצורה שלנו לסדר הפעולות באביב, יש יבוא של שני קבצי תצורה נוספים – `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 – זהו לב ה-Spring Batch. ממשק זה מכיל את השיטה 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
טבלאות 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 בטבלה הנכונה שלו. למטה מוצגות פרטי כל טבלת מטא-דאטה.
- Batch_job_instance: טבלת BATCH_JOB_INSTANCE מחזיקה את כל המידע הרלוונטי ל-JobInstance.
- Batch_job_execution_params: טבלת BATCH_JOB_EXECUTION_PARAMS מחזיקה את כל המידע הרלוונטי לאובייקט JobParameters.
- Batch_job_execution: טבלת BATCH_JOB_EXECUTION מחזיקה נתונים רלוונטיים לאובייקט JobExecution. שורה חדשה מתווספת בכל פעם שהמשימה מופעלת.
- Batch_step_execution: טבלת BATCH_STEP_EXECUTION מחזיקה את כל המידע הרלוונטי לאובייקט StepExecution.
- Batch_job_execution_context: טבלת BATCH_JOB_EXECUTION_CONTEXT מחזיקה נתונים רלוונטיים ל-ExecutionContext של משימת Job. קיים בדיוק אחד ExecutionContext של Job לכל JobExecution, והוא מכיל את כל נתוני הרמה שנדרשים להפעלת המשימה הספציפית הזו. נתונים אלו מייצגים בדרך כלל את המצב שחייב להיות מחוזר אחרי כשל כדי ש-JobInstance יוכל להתחיל מהמקום בו כשל.
- Batch_step_execution_context: הטבלה BATCH_STEP_EXECUTION_CONTEXT מחזיקה נתונים הרלוונטיים להקשר בין הצעד. יש בדיוק תקשורת אחת ExecutionContext לכל StepExecution, והיא מכילה את כל הנתונים שיש צורך לשמור עבור ביצוע צעד מסוים. הנתונים האלה מייצגים בדרך כלל את המצב שחייב להישאר לאחר כישלון כך ש-JobInstance יכול להתחיל מהמקום שנכשל.
- Batch_job_execution_seq: הטבלה הזו מחזיקה את רצף ביצוע הנתונים של המשימה.
- Batch_step_execution_seq: הטבלה הזו מחזיקה את הנתונים עבור רצף ביצוע הצעד.
- 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