SpringBatch从入门到精通-12 配置和运行job【掘金日新计划】

254 阅读13分钟

持续创作,加速成长,6月更文活动来啦!| 掘金·日新计划

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情

SpringBatch从入门到精通-1【掘金日新计划】

SpringBatch从入门到精通-2-StepScope作用域和用法【掘金日新计划】

SpringBatch从入门到精通-3-并行处理【掘金日新计划】

SpringBatch从入门到精通-3.2-并行处理-远程分区【掘金日新计划】

SpringBatch从入门到精通-3.3-并行处理-远程分区(消息聚合)【掘金日新计划】

SpringBatch从入门到精通-4 监控和指标【掘金日新计划】

SpringBatch从入门到精通-4.2 监控和指标-原理【掘金日新计划】

SpringBatch从入门到精通-5 数据源配置相关【掘金日新计划】

SpringBatch从入门到精通-5.2 数据源配置相关-原理【掘金日新计划】

SpringBatch从入门到精通-6 读和写处理【掘金日新计划】

SpringBatch从入门到精通-6.1 读和写处理-实战【掘金日新计划】

SpringBatch从入门到精通-6.2 读和写处理-实战2【掘金日新计划】

SpringBatch从入门到精通-6.3 读和写处理-实战3【掘金日新计划】

SpringBatch从入门到精通-6.4 读和写处理-实战4【掘金日新计划】

SpringBatch从入门到精通-7 常见的批处理的一些模式【掘金日新计划】

SpringBatch从入门到精通-8 数据处理【掘金日新计划】

SpringBatch从入门到精通-9 重复处理【掘金日新计划】

SpringBatch从入门到精通-10 重试处理【掘金日新计划】

SpringBatch从入门到精通-11 同步执行但提前返回结果【掘金日新计划】

springbatch整体的领域模型如下:

图 2.1:批次定型

虽然该Job对象可能看起来像一个简单的步骤容器,但开发人员必须了解许多配置选项。Job此外,对于 a将如何运行以及在该运行期间如何存储其元数据,有许多考虑因素

配置作业

接口有多种实现Job。但是,构建器抽象出配置上的差异。

@Bean
public Job footballJob() {
    return this.jobBuilderFactory.get("footballJob")
                     .start(playerLoad())
                     .next(gameLoad())
                     .next(playerSummarization())
                     .build();
}

Job可重启性

执行批处理作业时的一个关键问题涉及Job重新启动时的行为。 如果 已经存在于特定的 ,则启动 Job被认为是“重新启动” 。理想情况下,所有作业都应该能够从中断的地方开始,但在某些情况下这是不可能的。在这种情况下创建新的完全取决于开发人员。*但是,Spring Batch 确实提供了一些帮助。如果 a jobInstance永远不应该重新启动,但应该始终作为 new 的一部分运行,那么 restartable 属性可以设置为“false”。

以下示例显示如何在 Java中将restartable字段设置为:false

Java 配置

@Bean
public Job footballJob() {
    return this.jobBuilderFactory.get("footballJob")
                     .preventRestart()
                     ...
                     .build();
}

换句话说,将 restartable 设置为 false 意味着“这 Job不支持重新启动”。重新启动不可重新启动的Job会导致抛出JobRestartException。

Job job = new SimpleJob();
job.setRestartable(false);
​
JobParameters jobParameters = new JobParameters();
​
JobExecution firstExecution = jobRepository.createJobExecution(job, jobParameters);
jobRepository.saveOrUpdate(firstExecution);
​
try {
    jobRepository.createJobExecution(job, jobParameters);
    fail();
}
catch (JobRestartException e) {
    // expected
}

这段 JUnit 代码展示了如何尝试 JobExecution为不可重新启动的作业创建第一次不会导致任何问题。但是,第二次尝试会抛出一个JobRestartException.

拦截作业执行

在 Job 的执行过程中,通知其生命周期中的各种事件可能很有用,以便可以执行自定义代码。SimpleJob允许通过在适当的时间调用 JobListener 来实现这 一点 :

public interface JobExecutionListener {
​
    void beforeJob(JobExecution jobExecution);
​
    void afterJob(JobExecution jobExecution);
​
}

JobListeners可以SimpleJob通过在作业上设置监听器来添加。

以下示例显示如何将侦听器方法添加到 Java 作业定义:

Java 配置

@Bean
public Job footballJob() {
    return this.jobBuilderFactory.get("footballJob")
                     .listener(sampleListener())
                     ...
                     .build();
}

需要注意的是,afterJob无论成功与否,都会调用该方法Job。如果需要判断成败,可以从 中获取JobExecution,如下:

public void afterJob(JobExecution jobExecution){
    if (jobExecution.getStatus() == BatchStatus.COMPLETED ) {
        //job success
    }
    else if (jobExecution.getStatus() == BatchStatus.FAILED) {
        //job failure
    }
}

该接口对应的注解为:

  • @BeforeJob
  • @AfterJob

JobParametersValidator

在 XML 命名空间中声明或使用任何子类的 AbstractJob作业可以选择在运行时为作业参数声明验证器。例如,当您需要断言作业以其所有必需参数启动时,这很有用。有一个 DefaultJobParametersValidator可以用来约束简单的强制和可选参数的组合,对于更复杂的约束你可以自己实现接口。

通过 java builder 支持验证器的配置,如以下示例所示:

@Bean
public Job job1() {
    return this.jobBuilderFactory.get("job1")
                     .validator(parametersValidator())
                     ...
                     .build();
}

Java 配置

Spring 3 带来了通过 java 而不是 XML 配置应用程序的能力。从 Spring Batch 2.2.0 开始,可以使用相同的 java 配置来配置批处理作业。基于 java 的配置有两个组件:@EnableBatchProcessing 注解和两个构建器。

其@EnableBatchProcessing工作方式类似于 Spring 家族中的其他 @Enable* 注释。在这种情况下,@EnableBatchProcessing为构建批处理作业提供基本配置。StepScope在这个基本配置中,除了一些可用于自动装配的 bean 之外,还会创建一个实例:

  • JobRepository:bean名称“jobRepository”
  • JobLauncher:bean名称“jobLauncher”
  • JobRegistry:bean名称“jobRegistry”
  • PlatformTransactionManager: bean 名称 "transactionManager"
  • JobBuilderFactory:bean名称“jobBuilders”
  • StepBuilderFactory:bean名称“stepBuilders”

此配置的核心接口是BatchConfigurer. 默认实现提供了上面提到的 bean,并且需要 DataSource作为上下文中的 bean 来提供。JobRepository 使用此数据源。您可以通过创建接口的自定义实现来自定义这些 bean BatchConfigurer。通常,扩展DefaultBatchConfigurer(如果 未找到则提供BatchConfigurer)并覆盖所需的 getter 就足够了。但是,可能需要从头开始实施您自己的。以下示例显示了如何提供自定义事务管理器:

@Bean
public BatchConfigurer batchConfigurer(DataSource dataSource) {
    return new DefaultBatchConfigurer(dataSource) {
        @Override
        public PlatformTransactionManager getTransactionManager() {
            return new MyTransactionManager();
        }
    };
}

有了基本配置,用户就可以使用提供的构建器工厂来配置作业。JobBuilderFactory以下示例显示了使用和配置的两步作业 StepBuilderFactory:

@Configuration
@EnableBatchProcessing
@Import(DataSourceConfiguration.class)
public class AppConfig {
​
    @Autowired
    private JobBuilderFactory jobs;
​
    @Autowired
    private StepBuilderFactory steps;
​
    @Bean
    public Job job(@Qualifier("step1") Step step1, @Qualifier("step2") Step step2) {
        return jobs.get("myJob").start(step1).next(step2).build();
    }
​
    @Bean
    protected Step step1(ItemReader<Person> reader,
                         ItemProcessor<Person, Person> processor,
                         ItemWriter<Person> writer) {
        return steps.get("step1")
            .<Person, Person> chunk(10)
            .reader(reader)
            .processor(processor)
            .writer(writer)
            .build();
    }
​
    @Bean
    protected Step step2(Tasklet tasklet) {
        return steps.get("step2")
            .tasklet(tasklet)
            .build();
    }
}

配置 JobRepository

使用时@EnableBatchProcessing,JobRepository为您提供开箱即用的一个。

如前所述,JobRepository用于 Spring Batch 中各种持久域对象的基本 CRUD 操作,例如 JobExecution和 StepExecution。许多主要框架功能都需要它,例如JobLauncher、 Job和Step.

使用 java 配置时,JobRepository为您提供了一个。如果提供了 datasource,则提供了一个基于 JDBC 的开箱即用,如果没有DataSource提供,则提供基于 JDBC 的Map。但是,您可以通过接口JobRepository的实现来自 定义配置BatchConfigurer。

Java 配置

...
// This would reside in your BatchConfigurer implementation
@Override
protected JobRepository createJobRepository() throws Exception {
    JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
    factory.setDataSource(dataSource);
    factory.setTransactionManager(transactionManager);
    factory.setIsolationLevelForCreate("ISOLATION_SERIALIZABLE");
    factory.setTablePrefix("BATCH_");
    factory.setMaxVarCharLength(1000);
    return factory.getObject();
}
...

除了 dataSource 和 transactionManager 之外,上面列出的配置选项都不是必需的。如果未设置,将使用上面显示的默认值。出于提高认识的目的,它们在上面显示。[最大 varchar 长度默认为 2500,这是示例架构脚本中长VARCHAR列 的最大长度

JobRepository 的事务配置

如果使用命名空间或提供的命名空间FactoryBean,则会自动围绕存储库创建事务建议。这是为了确保批处理元数据(包括故障后重新启动所需的状态)正确持久化。如果存储库方法不是事务性的,那么框架的行为就没有很好的定义。方法属性中的隔离级别create是单独指定的,以确保在启动作业时,如果两个进程尝试同时启动同一个作业,则只有一个成功。该方法的默认隔离级别是SERIALIZABLE,这是非常激进的。READ_COMMITTED也可以。READ_UNCOMMITTED如果两个进程不太可能以这种方式发生冲突,那就没问题了。然而,由于调用create方法很短,不太可能 SERIALIZED出问题,只要数据库平台支持。但是,这可以被覆盖。

以下示例显示了如何覆盖 Java 中的隔离级别:

Java 配置

// This would reside in your BatchConfigurer implementation
@Override
protected JobRepository createJobRepository() throws Exception {
    JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
    factory.setDataSource(dataSource);
    factory.setTransactionManager(transactionManager);
    factory.setIsolationLevelForCreate("ISOLATION_REPEATABLE_READ");
    return factory.getObject();
}

如果不使用名称空间或工厂 bean,那么使用 AOP 配置存储库的事务行为也是必不可少的。

以下示例显示了如何在 Java 中配置存储库的事务行为:

Java 配置

@Bean
public TransactionProxyFactoryBean baseProxy() {
    TransactionProxyFactoryBean transactionProxyFactoryBean = new TransactionProxyFactoryBean();
    Properties transactionAttributes = new Properties();
    transactionAttributes.setProperty("*", "PROPAGATION_REQUIRED");
    transactionProxyFactoryBean.setTransactionAttributes(transactionAttributes);
    transactionProxyFactoryBean.setTarget(jobRepository());
    transactionProxyFactoryBean.setTransactionManager(transactionManager());
    return transactionProxyFactoryBean;
}

更改表前缀

另一个可修改的属性JobRepository是元数据表的表前缀。默认情况下,它们都以BATCH_. BATCH_JOB_EXECUTION并且 BATCH_STEP_EXECUTION是两个例子。但是,有可能修改此前缀的原因。如果模式名称需要添加到表名称之前,或者如果同一模式中需要多组元数据表,则需要更改表前缀:

Java 配置

// This would reside in your BatchConfigurer implementation
@Override
protected JobRepository createJobRepository() throws Exception {
    JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
    factory.setDataSource(dataSource);
    factory.setTransactionManager(transactionManager);
    factory.setTablePrefix("SYSTEM.TEST_");
    return factory.getObject();
}

鉴于前面的更改,对元数据表的每个查询都带有前缀 SYSTEM.TEST_。BATCH_JOB_EXECUTION被称为系统。TEST_JOB_EXECUTION.

只有表前缀是可配置的。表名和列名不是。

内存存储库

在某些情况下,您可能不想将域对象持久化到数据库中。一个原因可能是速度。在每个提交点存储域对象需要额外的时间。另一个原因可能是您不需要为特定工作保留状态。出于这个原因,Spring 批处理提供了Map作业存储库的内存版本。

以下示例显示了MapJobRepositoryFactoryBeanJava 中的包含:

Java 配置

// This would reside in your BatchConfigurer implementation
@Override
protected JobRepository createJobRepository() throws Exception {
    MapJobRepositoryFactoryBean factory = new MapJobRepositoryFactoryBean();
    factory.setTransactionManager(transactionManager);
    return factory.getObject();
}

请注意,内存存储库是易失性的,因此不允许在 JVM 实例之间重新启动。它也不能保证两个具有相同参数的作业实例同时启动,并且不适合在多线程作业或本地分区的作业中使用Step。因此,只要您需要这些功能,就可以使用存储库的数据库版本。

然而,它确实需要定义事务管理器,因为存储库中有回滚语义,并且因为业务逻辑可能仍然是事务性的(例如 RDBMS 访问)。出于测试目的,许多人发现它 ResourcelessTransactionManager很有用。

和相关的MapJobRepositoryFactoryBean类在 v4 中已被弃用,并计划在 v5 中删除。如果要使用内存中的作业存储库,可以使用嵌入式数据库,如 H2、Apache Derby 或 HSQLDB。有几种方法可以创建嵌入式数据库并在 Spring Batch 应用程序中使用它。一种方法是使用Spring JDBC中的 API :

@Bean 
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()            
            .setType(EmbeddedDatabaseType.H2)            
            .addScript("/org/springframework/batch/core/schema-drop-h2.sql")                                             .addScript("/org/springframework/batch/core/schema-h2.sql")            
            .build(); 
            }

在应用程序上下文中将嵌入式数据源定义为 bean 后,如果使用@EnableBatchProcessing. 否则,您可以使用基于 JDBC 的手动配置它JobRepositoryFactoryBean,如配置 JobRepository 部分所示。

存储库中的非标准数据库类型

如果您使用的数据库平台不在受支持的平台列表中,则您可以使用其中一种受支持的类型,前提是 SQL 变体足够接近。为此,您可以使用 rawJobRepositoryFactoryBean而不是命名空间快捷方式,并使用它将数据库类型设置为最接近的匹配。

以下示例显示了如何使用JobRepositoryFactoryBean 将数据库类型设置为最接近的匹配:

Java 配置

配置 JobLauncher

使用时@EnableBatchProcessing,JobRegistry为您提供开箱即用的一个。本节介绍如何配置您自己的。

JobLauncher接口的最基本实现是SimpleJobLauncher. 它唯一需要的依赖项是 a JobRepository,以便获得执行。

以下示例显示了SimpleJobLauncherJava 中的 a:

Java 配置

...
// This would reside in your BatchConfigurer implementation
@Override
protected JobLauncher createJobLauncher() throws Exception {
    SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
    jobLauncher.setJobRepository(jobRepository);
    jobLauncher.afterPropertiesSet();
    return jobLauncher;
}
...

获取到JobExecution后,将其传递给Job的 execute 方法,最终返回JobExecution给调用者,如下图所示:

作业启动器序列

图 2. Job Launcher 序列

该序列很简单,并且在从调度程序启动时运行良好。但是,尝试从 HTTP 请求启动时会出现问题。在这种情况下,启动需要异步完成,以便SimpleJobLauncher立即返回给它的调用者。这是因为在长时间运行的进程(例如批处理)所需的时间内保持 HTTP 请求打开并不是一个好习惯。下图显示了一个示例序列:

异步作业启动器序列

图 3. 异步作业启动器序列

SimpleJobLauncher可以通过 配置TaskExecutor.

以下 Java 示例显示了一个SimpleJobLauncher配置为立即返回:

Java 配置

@Bean
public JobLauncher jobLauncher() {
    SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
    jobLauncher.setJobRepository(jobRepository());
    jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor());
    jobLauncher.afterPropertiesSet();
    return jobLauncher;
}

springTaskExecutor 接口的任何实现都可以用来控制作业的异步执行方式。

运行作业

启动批处理作业至少需要两件事: Job要启动的和 JobLauncher. 两者都可以包含在相同的上下文或不同的上下文中。例如,如果从命令行启动作业,将为每个作业实例化一个新的 JVM,因此每个作业都有自己的JobLauncher. 但是,如果在 范围内的 Web 容器中运行, HttpRequest通常会有一个 JobLauncher配置为异步作业启动,多个请求将调用该容器来启动它们的作业。

从命令行运行作业

对于想要从企业调度程序运行其作业的用户,命令行是主要界面。这是因为大多数调度程序(Quartz 除外,除非使用 NativeJob)直接与操作系统进程一起工作,主要由 shell 脚本启动。除了 shell 脚本之外,还有很多方法可以启动 Java 进程,例如 Perl、Ruby,甚至是 ant 或 maven 等“构建工具”。但是,由于大多数人都熟悉 shell 脚本,因此本示例将重点介绍它们。

CommandLineJobRunner

因为启动作业的脚本必须启动 Java 虚拟机,所以需要有一个带有 main 方法的类作为主要入口点。Spring Batch 提供了一个实现这个目的的实现: CommandLineJobRunner. 需要注意的是,这只是引导应用程序的一种方式,但启动 Java 进程的方式有很多种,而且绝不应将此类视为确定性的。执行CommandLineJobRunner 四项任务:

  • 加载适当的 ApplicationContext
  • 将命令行参数解析为 JobParameters
  • 根据参数找到合适的工作
  • 使用JobLauncher应用程序上下文中提供的启动作业。

所有这些任务都只使用传入的参数来完成。以下是必需的参数:

jobpath将用于创建ApplicationContext. 该文件应包含运行完整作业所需的所有内容
jobname要运行的作业的名称。

这些参数必须首先传入路径,然后传入名称。这些之后的所有参数都被认为是作业参数,转换为 JobParameters 对象,并且必须采用“name=value”的格式。

以下示例显示了作为作业参数传递给 Java 中定义的作业的日期:

<bash$ java CommandLineJobRunner io.spring.EndOfDayJobConfiguration endOfDay schedule.date(date)=2007/05/05

在大多数情况下,您会希望使用清单在 jar 中声明您的主类,但为简单起见,直接使用该类。第一个参数是“io.spring.EndOfDayJobConfiguration”,它是包含作业的配置类的完全限定类名。第二个参数“endOfDay”表示作业名称。最后一个参数 'schedule.date(date)=2007/05/05' 被转换为一个 JobParameters对象。java配置的一个例子如下:

以下示例显示了endOfDayJava 中的示例配置:

@Configuration
@EnableBatchProcessing
public class EndOfDayJobConfiguration {
​
    @Autowired
    private JobBuilderFactory jobBuilderFactory;
​
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
​
    @Bean
    public Job endOfDay() {
        return this.jobBuilderFactory.get("endOfDay")
                    .start(step1())
                    .build();
    }
​
    @Bean
    public Step step1() {
        return this.stepBuilderFactory.get("step1")
                    .tasklet((contribution, chunkContext) -> null)
                    .build();
    }
}

代码位置: github.com/jackssybin/…