Business Data
商业数据
通常,你的客户或者商业分析员提供一个电子表格。对于这个简单的例子,你可以在src/main/resources/sample-data.csv中找到一些虚构的数据。
Jill,Doe
Joe,Doe
Justin,Doe
Jane,Doe
John,Doe
这个表格的每一行包含一个名字和一个姓氏,用逗号隔开。这是一种spring不用定制就可处理的相当常见的模式。
接下来,你需要写入一个SQL脚本创建一个表格来储存数据。你可以在src/main/resources/schema-all.sql里找到这样的脚本
DROP TABLE people IF EXISTS;
CREATE TABLE people (
person_id BIGINT IDENTITY NOT NULL PRIMARY KEY,
first_name VARCHAR(20),
last_name VARCHAR(20)
);
spring boot 在启动期间会自动运行schema-@@platform@@.sql。-all是所有平台的默认值。
Create a Business Class
创建一个商业类
现在你可以看到数据输入和输出的格式,你可以编写一个代码来表现一行数据,就像下面这个例子(从src/main/java/com/example/batchprocessing/Person.java)可以看到:
package com.example.batchprocessing;
public class Person {
private String lastName;
private String firstName;
public Person() {
}
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public String toString() {
return "firstName: " + firstName + ", lastName: " + lastName;
}
}
你可以通过构造函数或通过设置属性使用名字和姓氏实例化Person类
Create an Intermediate Processor
创建一个中间处理器
在批量处理中的一个常见范例是摄取数据,将其转换,然后输出到其他地方。在这里,你需要编写一个简单的转换器将名字转变成大写。(从src/main/java/com/example/batchprocessing/PersonItemProcessor.java中)可以看到怎么是做的:
package com.example.batchprocessing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.ItemProcessor;
public class PersonItemProcessor implements ItemProcessor {
private static final Logger log = LoggerFactory.getLogger(PersonItemProcessor.class);
@Override
public Person process(final Person person) throws Exception {
final String firstName = person.getFirstName().toUpperCase();
final String lastName = person.getLastName().toUpperCase();
final Person transformedPerson = new Person(firstName, lastName);
log.info("Converting (" + person + ") into (" + transformedPerson + ")");
return transformedPerson;
}
}
PersonItemProcessor 实现了Spring Batch的ItemProcessor接口。这使得将代码连接到您将在本指南后面定义的批处理作业变得容易。根据这个接口,你能就收到一个传入的Person对象,然后将其转换成一个大写的Person。
输入输出的类型不需要相同,事实上,一个数据源被读写之后,有时应用程序数据流需要不一样的数据类型。
Put Together a Batch Job
把批量任务放在一起
现在你需要把实际的批量作业放在一起,Spring Batch提供许多实用的类以减少编写自定义代码的需要。相反的,你可以专注于商业逻辑。
配置你的作业,你必须首相创建一个Spring @Configuration类如下面这个例子所示
src/main/java/com/exampe/batchprocessing/BatchConfiguration.java
:
@Configuration
@EnableBatchProcessing
public class BatchConfiguration {
@Autowired
public JobBuilderFactory jobBuilderFactory;
@Autowired
public StepBuilderFactory stepBuilderFactory;
...
}
对于初学者,@EnableBatchProcessing的注释增加了许多支持作业和节省你大量工作的关键bean。这个例子使用了一个记忆基于内存(由 @EnableBatchProcessing提供)的
数据库。意味着,当它完成后,数据就消失了。它也自动配置了虾米那需要的几个factory。现在添加以下bean在BatchConfiguration类用来定义一个读取器,一个处理器,一个写入器:
@Bean
public FlatFileItemReader reader() {
return new FlatFileItemReaderBuilder()
.name("personItemReader")
.resource(new ClassPathResource("sample-data.csv"))
.delimited()
.names(new String[]{"firstName", "lastName"})
.fieldSetMapper(new BeanWrapperFieldSetMapper() {{
setTargetType(Person.class);
}})
.build();
}
@Bean
public PersonItemProcessor processor() {
return new PersonItemProcessor();
}
@Bean
public JdbcBatchItemWriter writer(DataSource dataSource) {
return new JdbcBatchItemWriterBuilder()
.itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>())
.sql("INSERT INTO people (first_name, last_name) VALUES (:firstName, :lastName)")
.dataSource(dataSource)
.build();
}
第一段代码定义了输入,处理器和输出。
reader()创建一个ItemReader,它查找一个叫做sample-data.csv的文件和用足够信息解析每一行的项目使其转换成Person。
processor()创建一个你之前定义的PersonItemProcessor的实例,用于将数据转换为大写的。
writer(DataSource)创建一个ItemWriter。这是一个针对JDBC的目标,目的是自动复制得到@EnableBatchProcessing创建的数据源。它包括嵌入的单个Person需要的SQL语句,由java bean配置驱动。
最后一块(如src/main/java/com/example/batchprocessing/BatchConfiguration.java)所示的实际的作业配置:
@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")
. chunk(10)
.reader(reader())
.processor(processor())
.writer(writer)
.build();
}
第一种方法定义任务,第二种方法定义单个步骤,作业是由步骤构建的,其中每一个步骤都可能涉及到一个读取器一个处理器和一个写入器。
在此作业定义中,您需要一个增量器,因为作业使用数据库来维护执行状态。 然后列出每一步(尽管这项工作只有一步)。 作业结束,Java API 生成完美配置的作业。
在步骤定义中,你定义了一次写入多少数据。在这种情况下,它一次最多写入十条记录。接下来,你将用之前注入的bean配置读取器,处理器和写入器。
chunk() 以 为前缀,因为它是一个通用方法。 这表示处理的每个“块”的输入和输出类型,并与 ItemReader 和 ItemWriter 对齐。
批处理配置的最后一点是在作业完成时获得通知的一种方式。 以下示例(来自 src/main/java/com/example/batchprocessing/JobCompletionNotificationListener.java)显示了这样一个类:
package com.example.batchprocessing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.listener.JobExecutionListenerSupport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
@Component
public class JobCompletionNotificationListener extends JobExecutionListenerSupport {
private static final Logger log = LoggerFactory.getLogger(JobCompletionNotificationListener.class);
private final JdbcTemplate jdbcTemplate;
@Autowired
public JobCompletionNotificationListener(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void afterJob(JobExecution jobExecution) {
if(jobExecution.getStatus() == BatchStatus.COMPLETED) {
log.info("!!! JOB FINISHED! Time to verify the results");
jdbcTemplate.query("SELECT first_name, last_name FROM people",
(rs, row) -> new Person(
rs.getString(1),
rs.getString(2))
).forEach(person -> log.info("Found <" + person + "> in the database."));
}
}
}
JobCompletionNotificationListener 侦听作业何时为 BatchStatus.COMPLETED,然后使用 JdbcTemplate 检查结果。
Make the Application Executable
使应用可执行
尽管批处理可以嵌入到 Web 应用程序和 WAR 文件中,但下面演示的更简单的方法可以创建一个独立的应用程序。 您将所有内容打包在一个单一的、可执行的 JAR 文件中,由一个很好的旧 Java main() 方法驱动。
Spring Initializr 为您创建了一个应用程序类。 对于这个简单的示例,它无需进一步修改即可工作。 以下清单(来自 src/main/java/com/example/batchprocessing/BatchProcessingApplication.java)显示了应用程序类:
package com.example.batchprocessing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BatchProcessingApplication {
public static void main(String[] args) throws Exception {
System.exit(SpringApplication.exit(SpringApplication.run(BatchProcessingApplication.class, args)));
}
}
@SpringBootApplication 是一个方便的注解,它添加了以下所有内容:
@Configuration:将类标记为应用程序上下文的 bean 定义源。
@EnableAutoConfiguration:告诉 Spring Boot 根据类路径设置、其他 bean 和各种属性设置开始添加 bean。例如,如果 spring-webmvc 在类路径上,则此注释将应用程序标记为 Web 应用程序并激活关键行为,例如设置 DispatcherServlet。
@ComponentScan:告诉 Spring 在 com/example 包中查找其他组件、配置和服务,让它找到控制器。
main() 方法使用 Spring Boot 的 SpringApplication.run() 方法来启动应用程序。您是否注意到没有一行 XML?也没有 web.xml 文件。该 Web 应用程序是 100% 纯 Java 的,您无需处理任何管道或基础设施的配置。
请注意 SpringApplication.exit() 和 System.exit() 确保 JVM 在作业完成时退出。有关更多详细信息,请参阅 Spring Boot 参考文档中的应用程序退出部分。
出于演示目的,有一些代码可以创建 JdbcTemplate、查询数据库并打印出批处理作业插入的人员姓名。
Build an executable JAR
创建一个可执行的JAR
如果你使用Mavan,你可以通过/mvnw spring-boot:run
.运行应用。你也可以创建一个JAR文用./mvnw clean package再运行JAR文件,如下所示
java -jar target/gs-batch-processing-0.1.0.jar
这儿的骤描述了创建一个可运行的JAR。你也可以创建一个经典的WAR文件。
该作业为每个被转换的人打印出一行。 作业运行后,您还可以看到查询数据库的输出。 它应该类似于以下输出:
Converting (firstName: Jill, lastName: Doe) into (firstName: JILL, lastName: DOE)
Converting (firstName: Joe, lastName: Doe) into (firstName: JOE, lastName: DOE)
Converting (firstName: Justin, lastName: Doe) into (firstName: JUSTIN, lastName: DOE)
Converting (firstName: Jane, lastName: Doe) into (firstName: JANE, lastName: DOE)
Converting (firstName: John, lastName: Doe) into (firstName: JOHN, lastName: DOE)
Found in the database.
Found in the database.
Found in the database.
Found in the database.
Found in the database.