【练习】6.Creating a Batch Service

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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值