https://spring.io/guides/gs/batch-processing/
通常,您的客户或业务分析师会提供电子表格。在这种情况下,你弥补了。
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是所有平台的默认值。
现在您已经看到了数据输入和输出的格式,您可以编写代码来表示一行数据。
src/main/java/hello/Person.java
package hello;
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通过构造函数通过名字和姓氏实例化该类,也可以通过设置属性来实例化该类。
批处理中的一个常见范例是摄取数据,对其进行转换,然后将其传输到其他地方。在这里编写一个简单的转换器,将名称转换为大写。
src/main/java/hello/PersonItemProcessor.java
package hello;
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。
不要求输入和输出类型相同。实际上,在读取一个数据源之后,有时应用程序的数据流需要不同的数据类型。
现在您将实际的批处理作业放在一起。Spring Batch提供了许多实用程序类,可以减少编写自定义代码的需要。相反,您可以专注于业务逻辑。
src/main/java/hello/BatchConfiguration.java
package hello;
import javax.sql.DataSource;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider;
import org.springframework.batch.item.database.JdbcBatchItemWriter;
import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.core.JdbcTemplate;
@Configuration
@EnableBatchProcessing
public class BatchConfiguration {
@Autowired
public JobBuilderFactory jobBuilderFactory;
@Autowired
public StepBuilderFactory stepBuilderFactory;
// tag::readerwriterprocessor[]
@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();
}
// end::readerwriterprocessor[]
// tag::jobstep[]
@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();
}
// end::jobstep[]
}
对于初学者来说,@EnableBatchProcessing注释添加了许多支持工作的关键bean,并为您节省了大量的工作量。此示例使用基于内存的数据库(由提供@EnableBatchProcessing),这意味着在完成后,数据将消失。
分解:
src/main/java/hello/BatchConfiguration.java
@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之前定义的实例,用于大写数据。- write(DataSource)创造一个ItemWriter。这个目标是一个JDBC目标,并自动获取由其创建的dataSource的副本@EnableBatchProcessing。它包括插入Person由Java bean属性驱动的单个语句所需的SQL语句。
下一个块重点关注实际的作业配置。
src/main/java/hello/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生成完美配置的作业。
在步骤定义中,您可以定义一次写入的数据量。在这种情况下,它一次最多可写入十条记录。接下来,使用之前注入的位配置读取器,处理器和写入器。
chunk()是前缀的,
src/main/java/hello/JobCompletionNotificationListener.java
package hello;
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."));
}
}
}
此代码侦听作业的时间BatchStatus.COMPLETED,然后用于JdbcTemplate检查结果。
虽然批处理可以嵌入到Web应用程序和WAR文件中,但下面演示的更简单的方法创建了一个独立的应用程序。您将所有内容打包在一个可执行的JAR文件中,由一个好的旧Java main()方法驱动。
src/main/java/hello/Application.java
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
@SpringBootApplication 是一个便利注释,添加了以下所有内容:
@Configuration 标记该类作为应用程序上下文的bean定义的来源。
@EnableAutoConfiguration 告诉Spring Boot开始根据类路径设置,其他bean和各种属性设置添加bean。
通常你会添加@EnableWebMvc一个Spring MVC应用程序,但Spring Boot会在类路径上看到spring-webmvc时自动添加它。这会将应用程序标记为Web应用程序并激活关键行为,例如设置a DispatcherServlet。
@ComponentScan告诉Spring在包中寻找其他组件,配置和服务hello,允许它找到控制器。
该main()方法使用Spring Boot的SpringApplication.run()方法启动应用程序。您是否注意到没有一行XML?也没有web.xml文件。此Web应用程序是100%纯Java,您无需处理配置任何管道或基础结构。
出于演示目的,可以使用代码创建JdbcTemplate,查询数据库,并打印批处理作业插入的人员的名称。
您可以使用Gradle或Maven从命令行运行该应用程序。或者,您可以构建一个包含所有必需依赖项,类和资源的可执行JAR文件,并运行该文件。这使得在整个开发生命周期中,跨不同环境等将服务作为应用程序发布,版本和部署变得容易。
如果您使用的是Gradle,则可以使用./gradlew bootRun。或者您可以使用构建JAR文件./gradlew build。然后你可以运行JAR文件:
java -jar build / libs / gs-batch-processing-0.1.0.jar
如果您使用的是Maven,则可以使用该应用程序运行该应用程序./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.
恭喜!您构建了一个批处理作业,从电子表格中提取数据,对其进行处理并将其写入数据库。