(window.webpackJsonp=window.webpackJsonp||[]).push([[379],{813:function(e,a,t){"use strict";t.r(a);var o=t(56),r=Object(o.a)({},(function(){var e=this,a=e.$createElement,t=e._self._c||a;return t("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[t("h1",{attrs:{id:"配置和运行作业"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#配置和运行作业"}},[e._v("#")]),e._v(" 配置和运行作业")]),e._v(" "),t("h2",{attrs:{id:"配置和运行作业-2"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#配置和运行作业-2"}},[e._v("#")]),e._v(" 配置和运行作业")]),e._v(" "),t("p",[e._v("XMLJavaBoth")]),e._v(" "),t("p",[e._v("在"),t("RouterLink",{attrs:{to:"/spring-batch/domain.html#domainLanguageOfBatch"}},[e._v("领域部分")]),e._v("中,使用以下图表作为指导,讨论了总体架构设计:")],1),e._v(" "),t("p",[t("img",{attrs:{src:"https://docs.spring.io/spring-batch/docs/current/reference/html/images/spring-batch-reference-model.png",alt:"图 2.1:批处理原型"}})]),e._v(" "),t("p",[e._v("图 1。批处理模式")]),e._v(" "),t("p",[e._v("虽然"),t("code",[e._v("Job")]),e._v("对象看起来像是一个用于步骤的简单容器,但开发人员必须了解许多配置选项。此外,对于如何运行"),t("code",[e._v("Job")]),e._v("以及在运行期间如何存储其元数据,有许多考虑因素。本章将解释"),t("code",[e._v("Job")]),e._v("的各种配置选项和运行时关注点。")]),e._v(" "),t("h3",{attrs:{id:"配置作业"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#配置作业"}},[e._v("#")]),e._v(" 配置作业")]),e._v(" "),t("p",[e._v("["),t("code",[e._v("Job")]),e._v("](#configurejob)接口有多个实现方式。然而,构建者会抽象出配置上的差异。")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('@Bean\npublic Job footballJob() {\n return this.jobBuilderFactory.get("footballJob")\n .start(playerLoad())\n .next(gameLoad())\n .next(playerSummarization())\n .build();\n}\n')])])]),t("p",[e._v("一个"),t("code",[e._v("Job")]),e._v("(以及其中的任何"),t("code",[e._v("Step")]),e._v(")需要一个"),t("code",[e._v("JobRepository")]),e._v("。"),t("code",[e._v("JobRepository")]),e._v("的配置是通过["),t("code",[e._v("BatchConfigurer")]),e._v("]来处理的。")]),e._v(" "),t("p",[e._v("上面的示例演示了由三个"),t("code",[e._v("Step")]),e._v("实例组成的"),t("code",[e._v("Job")]),e._v("实例。与作业相关的构建器还可以包含有助于并行("),t("code",[e._v("Split")]),e._v(")、声明性流控制("),t("code",[e._v("Decision")]),e._v(")和流定义外部化("),t("code",[e._v("Flow")]),e._v(")的其他元素。")]),e._v(" "),t("p",[e._v("无论你使用 Java 还是 XML,["),t("code",[e._v("Job")]),e._v("](#configurejob)接口都有多个实现。然而,名称空间抽象出了配置上的差异。它只有三个必需的依赖项:一个名称,"),t("code",[e._v("JobRepository")]),e._v(",和一个"),t("code",[e._v("Step")]),e._v("实例的列表。")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n \n \n \n\n')])])]),t("p",[e._v("这里的示例使用父 Bean 定义来创建步骤。有关内联声明特定步骤详细信息的更多选项,请参见"),t("RouterLink",{attrs:{to:"/spring-batch/step.html#configureStep"}},[e._v("阶跃配置")]),e._v("一节。XML 名称空间默认情况下引用一个 ID 为“JobRepository”的存储库,这是一个合理的默认值。但是,这可以显式地重写:")],1),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n \n \n \n\n')])])]),t("p",[e._v("除了步骤之外,作业配置还可以包含有助于并行("),t("code",[e._v("")]),e._v(")、声明性流控制("),t("code",[e._v("")]),e._v(")和流定义外部化("),t("code",[e._v("")]),e._v(")的其他元素。")]),e._v(" "),t("h4",{attrs:{id:"可重启性"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#可重启性"}},[e._v("#")]),e._v(" 可重启性")]),e._v(" "),t("p",[e._v("执行批处理作业时的一个关键问题与"),t("code",[e._v("Job")]),e._v("重新启动时的行为有关。如果对于特定的"),t("code",[e._v("Job")]),e._v("已经存在"),t("code",[e._v("JobExecution")]),e._v(",则将"),t("code",[e._v("Job")]),e._v("的启动视为“重新启动”。理想情况下,所有的工作都应该能够在它们停止的地方启动,但是在某些情况下这是不可能的。* 完全由开发人员来确保在此场景中创建一个新的"),t("code",[e._v("JobInstance")]),e._v("。* 但是, Spring 批处理确实提供了一些帮助。如果"),t("code",[e._v("Job")]),e._v("永远不应该重新启动,而应该始终作为新的"),t("code",[e._v("JobInstance")]),e._v("的一部分运行,那么可重启属性可以设置为“false”。")]),e._v(" "),t("p",[e._v("下面的示例展示了如何在 XML 中将"),t("code",[e._v("restartable")]),e._v("字段设置为"),t("code",[e._v("false")]),e._v(":")]),e._v(" "),t("p",[e._v("XML 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n ...\n\n')])])]),t("p",[e._v("下面的示例展示了如何在 Java 中将"),t("code",[e._v("restartable")]),e._v("字段设置为"),t("code",[e._v("false")]),e._v(":")]),e._v(" "),t("p",[e._v("Java 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('@Bean\npublic Job footballJob() {\n return this.jobBuilderFactory.get("footballJob")\n .preventRestart()\n ...\n .build();\n}\n')])])]),t("p",[e._v("换一种说法,将 Restartable 设置为 false 意味着“this"),t("code",[e._v("Job")]),e._v("不支持重新启动”。重新启动不可重启的"),t("code",[e._v("Job")]),e._v("会导致抛出"),t("code",[e._v("JobRestartException")]),e._v("。")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("Job job = new SimpleJob();\njob.setRestartable(false);\n\nJobParameters jobParameters = new JobParameters();\n\nJobExecution firstExecution = jobRepository.createJobExecution(job, jobParameters);\njobRepository.saveOrUpdate(firstExecution);\n\ntry {\n jobRepository.createJobExecution(job, jobParameters);\n fail();\n}\ncatch (JobRestartException e) {\n // expected\n}\n")])])]),t("p",[e._v("这段 JUnit 代码展示了如何在第一次为不可重启作业创建"),t("code",[e._v("JobExecution")]),e._v("时尝试创建"),t("code",[e._v("JobExecution")]),e._v("不会导致任何问题。但是,第二次尝试将抛出"),t("code",[e._v("JobRestartException")]),e._v("。")]),e._v(" "),t("h4",{attrs:{id:"拦截作业执行"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#拦截作业执行"}},[e._v("#")]),e._v(" 拦截作业执行")]),e._v(" "),t("p",[e._v("在作业的执行过程中,通知其生命周期中的各种事件可能是有用的,以便可以执行自定义代码。通过在适当的时间调用"),t("code",[e._v("JobListener")]),e._v(","),t("code",[e._v("SimpleJob")]),e._v("允许这样做:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("public interface JobExecutionListener {\n\n void beforeJob(JobExecution jobExecution);\n\n void afterJob(JobExecution jobExecution);\n\n}\n")])])]),t("p",[t("code",[e._v("JobListeners")]),e._v("可以通过在作业上设置侦听器来添加到"),t("code",[e._v("SimpleJob")]),e._v("。")]),e._v(" "),t("p",[e._v("下面的示例展示了如何将 Listener 元素添加到 XML 作业定义中:")]),e._v(" "),t("p",[e._v("XML 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n \n \n \n \n \n \n\n')])])]),t("p",[e._v("下面的示例展示了如何将侦听器方法添加到 Java 作业定义中:")]),e._v(" "),t("p",[e._v("Java 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('@Bean\npublic Job footballJob() {\n return this.jobBuilderFactory.get("footballJob")\n .listener(sampleListener())\n ...\n .build();\n}\n')])])]),t("p",[e._v("应该注意的是,无论"),t("code",[e._v("afterJob")]),e._v("方法的成功或失败,都调用"),t("code",[e._v("Job")]),e._v("方法。如果需要确定成功或失败,则可以从"),t("code",[e._v("JobExecution")]),e._v("中获得如下:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("public void afterJob(JobExecution jobExecution){\n if (jobExecution.getStatus() == BatchStatus.COMPLETED ) {\n //job success\n }\n else if (jobExecution.getStatus() == BatchStatus.FAILED) {\n //job failure\n }\n}\n")])])]),t("p",[e._v("与此接口对应的注释是:")]),e._v(" "),t("ul",[t("li",[t("p",[t("code",[e._v("@BeforeJob")])])]),e._v(" "),t("li",[t("p",[t("code",[e._v("@AfterJob")])])])]),e._v(" "),t("h4",{attrs:{id:"继承父作业"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#继承父作业"}},[e._v("#")]),e._v(" 继承父作业")]),e._v(" "),t("p",[e._v("如果一组作业共享相似但不相同的配置,那么定义一个“父”"),t("code",[e._v("Job")]),e._v("可能会有所帮助,具体的作业可以从该“父”中继承属性。与 Java 中的类继承类似,“child”"),t("code",[e._v("Job")]),e._v("将把它的元素和属性与父元素和属性结合在一起。")]),e._v(" "),t("p",[e._v("在下面的示例中,“basejob”是一个抽象的"),t("code",[e._v("Job")]),e._v("定义,它只定义了一个侦听器列表。"),t("code",[e._v("Job")]),e._v("“job1”是一个具体的定义,它继承了“Basejob”的侦听器列表,并将其与自己的侦听器列表合并,以生成一个"),t("code",[e._v("Job")]),e._v(",其中包含两个侦听器和一个"),t("code",[e._v("Step")]),e._v(",即“步骤 1”。")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n \n \n \n\n\n\n \n\n \n \n \n\n')])])]),t("p",[e._v("有关更多详细信息,请参见"),t("RouterLink",{attrs:{to:"/spring-batch/step.html#inheritingFromParentStep"}},[e._v("从父步骤继承")]),e._v("一节。")],1),e._v(" "),t("h4",{attrs:{id:"jobparametersvalidator"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#jobparametersvalidator"}},[e._v("#")]),e._v(" JobParametersValidator")]),e._v(" "),t("p",[e._v("在 XML 命名空间中声明的作业或使用"),t("code",[e._v("AbstractJob")]),e._v("的任意子类可以在运行时为作业参数声明验证器。例如,当你需要断言一个作业是以其所有的强制参数启动时,这是有用的。有一个"),t("code",[e._v("DefaultJobParametersValidator")]),e._v("可以用来约束简单的强制参数和可选参数的组合,对于更复杂的约束,你可以自己实现接口。")]),e._v(" "),t("p",[e._v("验证程序的配置通过 XML 命名空间通过作业的一个子元素得到支持,如下面的示例所示:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n \n \n\n')])])]),t("p",[e._v("验证器可以指定为引用(如前面所示),也可以指定为 bean 名称空间中的嵌套 Bean 定义。")]),e._v(" "),t("p",[e._v("通过 Java Builders 支持验证器的配置,如以下示例所示:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('@Bean\npublic Job job1() {\n return this.jobBuilderFactory.get("job1")\n .validator(parametersValidator())\n ...\n .build();\n}\n')])])]),t("h3",{attrs:{id:"java-配置"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#java-配置"}},[e._v("#")]),e._v(" Java 配置")]),e._v(" "),t("p",[e._v("Spring 3 带来了通过 Java 而不是 XML 配置应用程序的能力。从 Spring Batch2.2.0 开始,可以使用相同的 Java 配置配置来配置批处理作业。基于 Java 的配置有两个组件:"),t("code",[e._v("@EnableBatchProcessing")]),e._v("注释和两个构建器。")]),e._v(" "),t("p",[t("code",[e._v("@EnableBatchProcessing")]),e._v("的工作原理与 Spring 家族中的其他 @enable* 注释类似。在这种情况下,"),t("code",[e._v("@EnableBatchProcessing")]),e._v("提供了用于构建批处理作业的基本配置。在这个基本配置中,除了许多可用于自动连线的 bean 之外,还创建了"),t("code",[e._v("StepScope")]),e._v("实例:")]),e._v(" "),t("ul",[t("li",[t("p",[t("code",[e._v("JobRepository")]),e._v(": Bean 名称“jobrepository”")])]),e._v(" "),t("li",[t("p",[t("code",[e._v("JobLauncher")]),e._v(": Bean 名称“joblauncher”")])]),e._v(" "),t("li",[t("p",[t("code",[e._v("JobRegistry")]),e._v(": Bean 名称“jobregistry”")])]),e._v(" "),t("li",[t("p",[t("code",[e._v("PlatformTransactionManager")]),e._v(": Bean 名称“TransactionManager”")])]),e._v(" "),t("li",[t("p",[t("code",[e._v("JobBuilderFactory")]),e._v(": Bean name“jobbuilders”")])]),e._v(" "),t("li",[t("p",[t("code",[e._v("StepBuilderFactory")]),e._v(": Bean 名称“StepBuilders”")])])]),e._v(" "),t("p",[e._v("此配置的核心接口是"),t("code",[e._v("BatchConfigurer")]),e._v("。默认的实现提供了上面提到的 bean,并且需要在要提供的上下文中提供一个"),t("code",[e._v("DataSource")]),e._v("作为 Bean。这个数据源由 JobRepository 使用。你可以通过创建"),t("code",[e._v("BatchConfigurer")]),e._v("接口的自定义实现来定制这些 bean 中的任何一个。通常,扩展"),t("code",[e._v("DefaultBatchConfigurer")]),e._v("(如果没有找到"),t("code",[e._v("BatchConfigurer")]),e._v(",则提供该扩展)并重写所需的吸气器就足够了。然而,可能需要从头开始实现自己的功能。下面的示例展示了如何提供自定义事务管理器:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("@Bean\npublic BatchConfigurer batchConfigurer(DataSource dataSource) {\n\treturn new DefaultBatchConfigurer(dataSource) {\n\t\t@Override\n\t\tpublic PlatformTransactionManager getTransactionManager() {\n\t\t\treturn new MyTransactionManager();\n\t\t}\n\t};\n}\n")])])]),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("只有一个配置类需要"),t("code",[e._v("@EnableBatchProcessing")]),e._v("注释。一旦"),t("br"),e._v("对一个类进行了注释,就可以使用上面的所有内容了。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("p",[e._v("有了基本配置,用户就可以使用提供的生成器工厂来配置作业。下面的示例显示了配置了"),t("code",[e._v("JobBuilderFactory")]),e._v("和"),t("code",[e._v("StepBuilderFactory")]),e._v("的两步作业:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('@Configuration\n@EnableBatchProcessing\n@Import(DataSourceConfiguration.class)\npublic class AppConfig {\n\n @Autowired\n private JobBuilderFactory jobs;\n\n @Autowired\n private StepBuilderFactory steps;\n\n @Bean\n public Job job(@Qualifier("step1") Step step1, @Qualifier("step2") Step step2) {\n return jobs.get("myJob").start(step1).next(step2).build();\n }\n\n @Bean\n protected Step step1(ItemReader reader,\n ItemProcessor processor,\n ItemWriter writer) {\n return steps.get("step1")\n . chunk(10)\n .reader(reader)\n .processor(processor)\n .writer(writer)\n .build();\n }\n\n @Bean\n protected Step step2(Tasklet tasklet) {\n return steps.get("step2")\n .tasklet(tasklet)\n .build();\n }\n}\n')])])]),t("h3",{attrs:{id:"配置-jobrepository"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#配置-jobrepository"}},[e._v("#")]),e._v(" 配置 JobRepository")]),e._v(" "),t("p",[e._v("当使用"),t("code",[e._v("@EnableBatchProcessing")]),e._v("时,将为你提供一个"),t("code",[e._v("JobRepository")]),e._v("。本节讨论如何配置自己的配置。")]),e._v(" "),t("p",[e._v("如前面所述,["),t("code",[e._v("JobRepository")]),e._v("](#configurejob)用于 Spring 批处理中各种持久化域对象的基本增删改查操作,例如"),t("code",[e._v("JobExecution")]),e._v("和"),t("code",[e._v("StepExecution")]),e._v("。它是许多主要框架特性所要求的,例如"),t("code",[e._v("JobLauncher")]),e._v("、"),t("code",[e._v("Job")]),e._v("和"),t("code",[e._v("Step")]),e._v("。")]),e._v(" "),t("p",[e._v("批处理名称空间抽象出了"),t("code",[e._v("JobRepository")]),e._v("实现及其协作者的许多实现细节。然而,仍然有一些可用的配置选项,如以下示例所示:")]),e._v(" "),t("p",[e._v("XML 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n')])])]),t("p",[e._v("除了"),t("code",[e._v("id")]),e._v("之外,上面列出的配置选项都不是必需的。如果没有设置,将使用上面显示的默认值。以上所示是为了提高认识。"),t("code",[e._v("max-varchar-length")]),e._v("默认为 2500,这是"),t("RouterLink",{attrs:{to:"/spring-batch/schema-appendix.html#metaDataSchemaOverview"}},[e._v("示例模式脚本")]),e._v("中的长"),t("code",[e._v("VARCHAR")]),e._v("列的长度。")],1),e._v(" "),t("p",[e._v("当使用 Java 配置时,将为你提供"),t("code",[e._v("JobRepository")]),e._v("。如果提供了"),t("code",[e._v("DataSource")]),e._v(",则提供了基于 JDBC 的一个,如果没有,则提供基于"),t("code",[e._v("Map")]),e._v("的一个。但是,你可以通过"),t("code",[e._v("BatchConfigurer")]),e._v("接口的实现来定制"),t("code",[e._v("JobRepository")]),e._v("的配置。")]),e._v(" "),t("p",[e._v("Java 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('...\n// This would reside in your BatchConfigurer implementation\n@Override\nprotected JobRepository createJobRepository() throws Exception {\n JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();\n factory.setDataSource(dataSource);\n factory.setTransactionManager(transactionManager);\n factory.setIsolationLevelForCreate("ISOLATION_SERIALIZABLE");\n factory.setTablePrefix("BATCH_");\n factory.setMaxVarCharLength(1000);\n return factory.getObject();\n}\n...\n')])])]),t("p",[e._v("除了数据源和 TransactionManager 之外,上面列出的配置选项都不是必需的。如果没有设置,将使用上面显示的默认值。以上所示是为了提高认识。最大 VARCHAR 长度默认为 2500,这是"),t("code",[e._v("VARCHAR")]),e._v("中的长"),t("RouterLink",{attrs:{to:"/spring-batch/schema-appendix.html#metaDataSchemaOverview"}},[e._v("示例模式脚本")]),e._v("列的长度")],1),e._v(" "),t("h4",{attrs:{id:"jobrepository-的事务配置"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#jobrepository-的事务配置"}},[e._v("#")]),e._v(" JobRepository 的事务配置")]),e._v(" "),t("p",[e._v("如果使用了名称空间或提供的"),t("code",[e._v("FactoryBean")]),e._v(",则会在存储库周围自动创建事务建议。这是为了确保批处理元数据(包括在发生故障后重新启动所必需的状态)被正确地持久化。如果存储库方法不是事务性的,那么框架的行为就没有得到很好的定义。"),t("code",[e._v("create*")]),e._v("方法属性中的隔离级别是单独指定的,以确保在启动作业时,如果两个进程试图同时启动相同的作业,则只有一个进程成功。该方法的默认隔离级别是"),t("code",[e._v("SERIALIZABLE")]),e._v(",这是非常激进的。"),t("code",[e._v("READ_COMMITTED")]),e._v("同样有效。如果两个过程不太可能以这种方式碰撞,"),t("code",[e._v("READ_UNCOMMITTED")]),e._v("就可以了。然而,由于对"),t("code",[e._v("create*")]),e._v("方法的调用相当短,所以只要数据库平台支持它,"),t("code",[e._v("SERIALIZED")]),e._v("不太可能导致问题。然而,这一点可以被重写。")]),e._v(" "),t("p",[e._v("下面的示例展示了如何覆盖 XML 中的隔离级别:")]),e._v(" "),t("p",[e._v("XML 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n')])])]),t("p",[e._v("下面的示例展示了如何在 Java 中重写隔离级别:")]),e._v(" "),t("p",[e._v("Java 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('// This would reside in your BatchConfigurer implementation\n@Override\nprotected JobRepository createJobRepository() throws Exception {\n JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();\n factory.setDataSource(dataSource);\n factory.setTransactionManager(transactionManager);\n factory.setIsolationLevelForCreate("ISOLATION_REPEATABLE_READ");\n return factory.getObject();\n}\n')])])]),t("p",[e._v("如果没有使用名称空间或工厂 bean,那么使用 AOP 配置存储库的事务行为也是必不可少的。")]),e._v(" "),t("p",[e._v("下面的示例展示了如何在 XML 中配置存储库的事务行为:")]),e._v(" "),t("p",[e._v("XML 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n \n \n\n\n\n \n \n \n\n')])])]),t("p",[e._v("前面的片段可以按原样使用,几乎没有变化。还请记住包括适当的名称空间声明,并确保 Spring-tx 和 Spring- AOP(或整个 Spring)都在 Classpath 上。")]),e._v(" "),t("p",[e._v("下面的示例展示了如何在 Java 中配置存储库的事务行为:")]),e._v(" "),t("p",[e._v("Java 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('@Bean\npublic TransactionProxyFactoryBean baseProxy() {\n\tTransactionProxyFactoryBean transactionProxyFactoryBean = new TransactionProxyFactoryBean();\n\tProperties transactionAttributes = new Properties();\n\ttransactionAttributes.setProperty("*", "PROPAGATION_REQUIRED");\n\ttransactionProxyFactoryBean.setTransactionAttributes(transactionAttributes);\n\ttransactionProxyFactoryBean.setTarget(jobRepository());\n\ttransactionProxyFactoryBean.setTransactionManager(transactionManager());\n\treturn transactionProxyFactoryBean;\n}\n')])])]),t("h4",{attrs:{id:"更改表格前缀"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#更改表格前缀"}},[e._v("#")]),e._v(" 更改表格前缀")]),e._v(" "),t("p",[t("code",[e._v("JobRepository")]),e._v("的另一个可修改的属性是元数据表的表前缀。默认情况下,它们都以"),t("code",[e._v("BATCH_")]),e._v("开头。"),t("code",[e._v("BATCH_JOB_EXECUTION")]),e._v("和"),t("code",[e._v("BATCH_STEP_EXECUTION")]),e._v("是两个例子。然而,有潜在的理由修改这个前缀。如果需要将模式名称前置到表名,或者如果同一模式中需要多个元数据表集合,则需要更改表前缀:")]),e._v(" "),t("p",[e._v("下面的示例展示了如何更改 XML 中的表前缀:")]),e._v(" "),t("p",[e._v("XML 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n')])])]),t("p",[e._v("下面的示例展示了如何在 Java 中更改表前缀:")]),e._v(" "),t("p",[e._v("Java 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('// This would reside in your BatchConfigurer implementation\n@Override\nprotected JobRepository createJobRepository() throws Exception {\n JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();\n factory.setDataSource(dataSource);\n factory.setTransactionManager(transactionManager);\n factory.setTablePrefix("SYSTEM.TEST_");\n return factory.getObject();\n}\n')])])]),t("p",[e._v("给定上述更改,对元数据表的每个查询都以"),t("code",[e._v("SYSTEM.TEST_")]),e._v("作为前缀。"),t("code",[e._v("BATCH_JOB_EXECUTION")]),e._v("被称为系统。"),t("code",[e._v("TEST_JOB_EXECUTION")]),e._v("。")]),e._v(" "),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("只有表前缀是可配置的。表和列名不是。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("h4",{attrs:{id:"内存存储库"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#内存存储库"}},[e._v("#")]),e._v(" 内存存储库")]),e._v(" "),t("p",[e._v("在某些情况下,你可能不希望将域对象持久化到数据库。原因之一可能是速度;在每个提交点存储域对象需要额外的时间。另一个原因可能是,你不需要为一份特定的工作坚持现状。出于这个原因, Spring 批处理提供了作业存储库的内存"),t("code",[e._v("Map")]),e._v("版本。")]),e._v(" "),t("p",[e._v("下面的示例显示了"),t("code",[e._v("MapJobRepositoryFactoryBean")]),e._v("在 XML 中的包含:")]),e._v(" "),t("p",[e._v("XML 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n \n\n')])])]),t("p",[e._v("下面的示例显示了在 Java 中包含"),t("code",[e._v("MapJobRepositoryFactoryBean")]),e._v(":")]),e._v(" "),t("p",[e._v("Java 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("// This would reside in your BatchConfigurer implementation\n@Override\nprotected JobRepository createJobRepository() throws Exception {\n MapJobRepositoryFactoryBean factory = new MapJobRepositoryFactoryBean();\n factory.setTransactionManager(transactionManager);\n return factory.getObject();\n}\n")])])]),t("p",[e._v("请注意,内存中的存储库是不稳定的,因此不允许在 JVM 实例之间重新启动。它也不能保证具有相同参数的两个作业实例同时启动,并且不适合在多线程作业或本地分区"),t("code",[e._v("Step")]),e._v("中使用。因此,只要你需要这些特性,就可以使用存储库的数据库版本。")]),e._v(" "),t("p",[e._v("但是,它确实需要定义事务管理器,因为存储库中存在回滚语义,并且业务逻辑可能仍然是事务性的(例如 RDBMS 访问)。对于测试目的,许多人发现"),t("code",[e._v("ResourcelessTransactionManager")]),e._v("很有用。")]),e._v(" "),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("在 V4 中,"),t("code",[e._v("MapJobRepositoryFactoryBean")]),e._v("和相关的类已被弃用,并计划在 V5 中删除"),t("br"),e._v("。如果希望使用内存中的作业存储库,可以使用嵌入式数据库"),t("br"),e._v(",比如 H2、 Apache Derby 或 HSQLDB。有几种方法可以创建嵌入式数据库并在"),t("br"),e._v("你的 Spring 批处理应用程序中使用它。一种方法是使用"),t("a",{attrs:{href:"https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#jdbc-embedded-database-support",target:"_blank",rel:"noopener noreferrer"}},[e._v("Spring JDBC"),t("OutboundLink")],1),e._v("中的 API:"),t("br"),t("br"),t("code",[e._v('
@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();
}
')]),t("br"),t("br"),e._v("一旦你在应用程序上下文中将嵌入式数据源定义为 Bean,如果你使用"),t("code",[e._v("@EnableBatchProcessing")]),e._v(",就应该自动选择"),t("br"),e._v("。否则,你可以使用"),t("br"),e._v("基于"),t("code",[e._v("JobRepositoryFactoryBean")]),e._v("的 JDBC 手动配置它,如"),t("a",{attrs:{href:"#configuringJobRepository"}},[e._v("配置 JobRepository 部分")]),e._v("所示。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("h4",{attrs:{id:"存储库中的非标准数据库类型"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#存储库中的非标准数据库类型"}},[e._v("#")]),e._v(" 存储库中的非标准数据库类型")]),e._v(" "),t("p",[e._v("如果你使用的数据库平台不在受支持的平台列表中,那么如果 SQL 变量足够接近,则可以使用受支持的类型之一。要做到这一点,你可以使用 RAW"),t("code",[e._v("JobRepositoryFactoryBean")]),e._v("而不是名称空间快捷方式,并使用它将数据库类型设置为最接近的匹配。")]),e._v(" "),t("p",[e._v("下面的示例展示了如何使用"),t("code",[e._v("JobRepositoryFactoryBean")]),e._v("将数据库类型设置为 XML 中最接近的匹配:")]),e._v(" "),t("p",[e._v("XML 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n \n \n\n')])])]),t("p",[e._v("下面的示例展示了如何使用"),t("code",[e._v("JobRepositoryFactoryBean")]),e._v("将数据库类型设置为 Java 中最接近的匹配:")]),e._v(" "),t("p",[e._v("Java 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('// This would reside in your BatchConfigurer implementation\n@Override\nprotected JobRepository createJobRepository() throws Exception {\n JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();\n factory.setDataSource(dataSource);\n factory.setDatabaseType("db2");\n factory.setTransactionManager(transactionManager);\n return factory.getObject();\n}\n')])])]),t("p",[e._v("(如果没有指定,"),t("code",[e._v("JobRepositoryFactoryBean")]),e._v("会尝试从"),t("code",[e._v("DataSource")]),e._v("中自动检测数据库类型,)平台之间的主要差异主要是由主键递增策略造成的,因此,通常可能还需要覆盖"),t("code",[e._v("incrementerFactory")]),e._v("(使用 Spring 框架中的一个标准实现)。")]),e._v(" "),t("p",[e._v("如果连这都不起作用,或者你没有使用 RDBMS,那么唯一的选择可能是实现"),t("code",[e._v("Dao")]),e._v("所依赖的各种"),t("code",[e._v("SimpleJobRepository")]),e._v("接口,并以正常的方式手动连接。")]),e._v(" "),t("h3",{attrs:{id:"配置一个-joblauncher"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#配置一个-joblauncher"}},[e._v("#")]),e._v(" 配置一个 joblauncher")]),e._v(" "),t("p",[e._v("当使用"),t("code",[e._v("@EnableBatchProcessing")]),e._v("时,将为你提供一个"),t("code",[e._v("JobRegistry")]),e._v("。本节讨论如何配置自己的配置。")]),e._v(" "),t("p",[t("code",[e._v("JobLauncher")]),e._v("接口的最基本实现是"),t("code",[e._v("SimpleJobLauncher")]),e._v("。它唯一需要的依赖关系是"),t("code",[e._v("JobRepository")]),e._v(",以便获得执行。")]),e._v(" "),t("p",[e._v("下面的示例显示了 XML 中的"),t("code",[e._v("SimpleJobLauncher")]),e._v(":")]),e._v(" "),t("p",[e._v("XML 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n \n\n')])])]),t("p",[e._v("下面的示例显示了 Java 中的"),t("code",[e._v("SimpleJobLauncher")]),e._v(":")]),e._v(" "),t("p",[e._v("Java 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("...\n// This would reside in your BatchConfigurer implementation\n@Override\nprotected JobLauncher createJobLauncher() throws Exception {\n\tSimpleJobLauncher jobLauncher = new SimpleJobLauncher();\n\tjobLauncher.setJobRepository(jobRepository);\n\tjobLauncher.afterPropertiesSet();\n\treturn jobLauncher;\n}\n...\n")])])]),t("p",[e._v("一旦获得了"),t("RouterLink",{attrs:{to:"/spring-batch/domain.html#domainLanguageOfBatch"}},[e._v("工作执行")]),e._v(",它就被传递给"),t("code",[e._v("Job")]),e._v("的执行方法,最终将"),t("code",[e._v("JobExecution")]),e._v("返回给调用者,如下图所示:")],1),e._v(" "),t("p",[t("img",{attrs:{src:"https://docs.spring.io/spring-batch/docs/current/reference/html/images/job-launcher-sequence-sync.png",alt:"作业启动器序列"}})]),e._v(" "),t("p",[e._v("图 2。作业启动器序列")]),e._v(" "),t("p",[e._v("这个序列很简单,从调度程序启动时效果很好。然而,在尝试从 HTTP 请求启动时会出现问题。在这种情况下,启动需要异步完成,以便"),t("code",[e._v("SimpleJobLauncher")]),e._v("立即返回其调用方。这是因为,在长时间运行的进程(如批处理)所需的时间内保持 HTTP 请求的开放状态是不好的做法。下图显示了一个示例序列:")]),e._v(" "),t("p",[t("img",{attrs:{src:"https://docs.spring.io/spring-batch/docs/current/reference/html/images/job-launcher-sequence-async.png",alt:"异步作业启动器序列"}})]),e._v(" "),t("p",[e._v("图 3。异步作业启动器序列")]),e._v(" "),t("p",[e._v("可以通过配置"),t("code",[e._v("TaskExecutor")]),e._v("将"),t("code",[e._v("SimpleJobLauncher")]),e._v("配置为允许这种情况。")]),e._v(" "),t("p",[e._v("下面的 XML 示例显示了配置为立即返回的"),t("code",[e._v("SimpleJobLauncher")]),e._v(":")]),e._v(" "),t("p",[e._v("XML 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n \n \n \n \n\n')])])]),t("p",[e._v("下面的 Java 示例显示了配置为立即返回的"),t("code",[e._v("SimpleJobLauncher")]),e._v(":")]),e._v(" "),t("p",[e._v("Java 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("@Bean\npublic JobLauncher jobLauncher() {\n\tSimpleJobLauncher jobLauncher = new SimpleJobLauncher();\n\tjobLauncher.setJobRepository(jobRepository());\n\tjobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor());\n\tjobLauncher.afterPropertiesSet();\n\treturn jobLauncher;\n}\n")])])]),t("p",[e._v("Spring "),t("code",[e._v("TaskExecutor")]),e._v("接口的任何实现都可以用来控制如何异步执行作业。")]),e._v(" "),t("h3",{attrs:{id:"运行作业"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#运行作业"}},[e._v("#")]),e._v(" 运行作业")]),e._v(" "),t("p",[e._v("至少,启动批处理作业需要两个条件:启动"),t("code",[e._v("Job")]),e._v("和"),t("code",[e._v("JobLauncher")]),e._v("。两者都可以包含在相同的上下文中,也可以包含在不同的上下文中。例如,如果从命令行启动一个作业,将为每个作业实例化一个新的 JVM,因此每个作业都有自己的"),t("code",[e._v("JobLauncher")]),e._v("。但是,如果在"),t("code",[e._v("HttpRequest")]),e._v("范围内的 Web 容器中运行,通常会有一个"),t("code",[e._v("JobLauncher")]),e._v(",该配置用于异步作业启动,多个请求将调用以启动其作业。")]),e._v(" "),t("h4",{attrs:{id:"从命令行运行作业"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#从命令行运行作业"}},[e._v("#")]),e._v(" 从命令行运行作业")]),e._v(" "),t("p",[e._v("对于希望从 Enterprise 调度器运行作业的用户,命令行是主要的接口。这是因为大多数调度程序(Quartz 除外,除非使用 nativeJob)直接与操作系统进程一起工作,主要是通过 shell 脚本开始的。除了 shell 脚本之外,还有许多启动 Java 进程的方法,例如 Perl、Ruby,甚至是 Ant 或 Maven 之类的“构建工具”。但是,由于大多数人都熟悉 shell 脚本,因此本例将重点讨论它们。")]),e._v(" "),t("h5",{attrs:{id:"the-commandlinejobrunner"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#the-commandlinejobrunner"}},[e._v("#")]),e._v(" The CommandlineJobrunner")]),e._v(" "),t("p",[e._v("因为启动作业的脚本必须启动一个 Java 虚拟机,所以需要有一个具有 main 方法的类来充当主要入口点。 Spring 批处理提供了一种实现,它仅服务于此目的:"),t("code",[e._v("CommandLineJobRunner")]),e._v("。需要注意的是,这只是引导应用程序的一种方法,但是启动 Java 进程的方法有很多,并且这个类绝不应该被视为确定的。"),t("code",[e._v("CommandLineJobRunner")]),e._v("执行四项任务:")]),e._v(" "),t("ul",[t("li",[t("p",[e._v("装入适当的"),t("code",[e._v("ApplicationContext")])])]),e._v(" "),t("li",[t("p",[e._v("将命令行参数解析为"),t("code",[e._v("JobParameters")])])]),e._v(" "),t("li",[t("p",[e._v("根据参数定位适当的作业")])]),e._v(" "),t("li",[t("p",[e._v("使用应用程序上下文中提供的"),t("code",[e._v("JobLauncher")]),e._v("来启动作业。")])])]),e._v(" "),t("p",[e._v("所有这些任务都是仅使用传入的参数来完成的。以下是必要的论据:")]),e._v(" "),t("table",[t("thead",[t("tr",[t("th",[e._v("jobPath")]),e._v(" "),t("th",[e._v("将用于"),t("br"),e._v("的 XML 文件的位置创建一个"),t("code",[e._v("ApplicationContext")]),e._v("。此文件"),t("br"),e._v("应该包含运行完整"),t("br"),e._v("作业所需的所有内容")])])]),e._v(" "),t("tbody",[t("tr",[t("td",[e._v("jobName")]),e._v(" "),t("td",[e._v("要运行的作业的名称。")])])])]),e._v(" "),t("p",[e._v("这些参数必须首先传递路径,然后传递名称。在这些参数之后的所有参数都被认为是作业参数,被转换为一个 JobParameters 对象,并且必须是“name=value”的格式。")]),e._v(" "),t("p",[e._v("下面的示例显示了作为作业参数传递给 XML 中未定义的作业的日期:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v(" +schedule.date(date)=2007/05/05 -vendor.id=123
")]),t("br"),t("code",[e._v("
+schedule.date(date)=2007/05/05 -vendor.id=123
")]),t("br"),e._v("可以通过使用自定义"),t("code",[e._v("JobParametersConverter")]),e._v("来重写此行为。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("p",[e._v("在大多数情况下,你可能希望使用清单在 JAR 中声明主类,但为了简单起见,直接使用了该类。这个示例使用的是来自"),t("RouterLink",{attrs:{to:"/spring-batch/domain.html#domainLanguageOfBatch"}},[e._v("DomainLanguageofBatch")]),e._v("的相同的“Endofday”示例。第一个参数是“endofdayjob.xml”,这是 Spring 包含"),t("code",[e._v("Job")]),e._v("的应用上下文。第二个参数“Endofday”表示工作名称。最后一个参数“schedule.date=2007/05/05”被转换为一个 JobParameters 对象。")],1),e._v(" "),t("p",[e._v("下面的示例显示了在 XML 中"),t("code",[e._v("endOfDay")]),e._v("的示例配置:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n \n\n\n\x3c!-- Launcher details removed for clarity --\x3e\n\n')])])]),t("p",[e._v("在大多数情况下,你希望使用清单在 JAR 中声明主类,但为了简单起见,直接使用了该类。这个示例使用的是来自"),t("RouterLink",{attrs:{to:"/spring-batch/domain.html#domainLanguageOfBatch"}},[e._v("DomainLanguageofBatch")]),e._v("的相同的“Endofday”示例。第一个参数是“IO. Spring.EndofdayJobConfiguration”,它是包含该作业的配置类的完全限定类名称。第二个参数“Endofday”表示工作名称。最后一个参数’schedule.date=2007/05/05’被转换为"),t("code",[e._v("JobParameters")]),e._v("对象。下面是 Java 配置的一个示例:")],1),e._v(" "),t("p",[e._v("下面的示例显示了在 Java 中"),t("code",[e._v("endOfDay")]),e._v("的示例配置:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('@Configuration\n@EnableBatchProcessing\npublic class EndOfDayJobConfiguration {\n\n @Autowired\n private JobBuilderFactory jobBuilderFactory;\n\n @Autowired\n private StepBuilderFactory stepBuilderFactory;\n\n @Bean\n public Job endOfDay() {\n return this.jobBuilderFactory.get("endOfDay")\n \t\t\t\t.start(step1())\n \t\t\t\t.build();\n }\n\n @Bean\n public Step step1() {\n return this.stepBuilderFactory.get("step1")\n \t\t\t\t.tasklet((contribution, chunkContext) -> null)\n \t\t\t\t.build();\n }\n}\n')])])]),t("p",[e._v("前面的示例过于简单,因为在 Spring 批处理中运行一个批处理作业通常有更多的需求,但是它用于显示"),t("code",[e._v("CommandLineJobRunner")]),e._v("的两个主要需求:"),t("code",[e._v("Job")]),e._v("和"),t("code",[e._v("JobLauncher")]),e._v("。")]),e._v(" "),t("h5",{attrs:{id:"exitcodes"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#exitcodes"}},[e._v("#")]),e._v(" exitcodes")]),e._v(" "),t("p",[e._v("当从命令行启动批处理作业时,通常使用 Enterprise 调度器。大多数调度器都相当笨拙,只能在流程级别工作。这意味着他们只知道一些操作系统进程,比如他们正在调用的 shell 脚本。在这种情况下,将工作的成功或失败反馈给调度程序的唯一方法是通过返回代码。返回代码是进程返回给调度程序的一个数字,它指示运行的结果。在最简单的情况下:0 是成功,1 是失败。然而,可能有更复杂的情况:如果作业 A 返回 4,则启动作业 B,如果它返回 5,则启动作业 C。这种类型的行为是在计划程序级别上配置的,但是重要的是, Spring 批处理框架提供了一种方法来返回用于特定批处理作业的“退出代码”的数字表示。在 Spring 批处理中,这被封装在"),t("code",[e._v("ExitStatus")]),e._v("中,这在第 5 章中有更详细的介绍。为了讨论退出代码,唯一需要知道的是"),t("code",[e._v("ExitStatus")]),e._v("具有一个退出代码属性,该属性由框架(或开发人员)设置,并作为从"),t("code",[e._v("JobLauncher")]),e._v("返回的"),t("code",[e._v("JobExecution")]),e._v("的一部分返回。"),t("code",[e._v("CommandLineJobRunner")]),e._v("使用"),t("code",[e._v("ExitCodeMapper")]),e._v("接口将这个字符串值转换为一个数字:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("public interface ExitCodeMapper {\n\n public int intValue(String exitCode);\n\n}\n")])])]),t("p",[t("code",[e._v("ExitCodeMapper")]),e._v("的基本契约是,给定一个字符串退出代码,将返回一个数字表示。Job Runner 使用的默认实现是"),t("code",[e._v("SimpleJvmExitCodeMapper")]),e._v(",它返回 0 表示完成,1 表示泛型错误,2 表示任何 Job Runner 错误,例如无法在提供的上下文中找到"),t("code",[e._v("Job")]),e._v("。如果需要比上述 3 个值更复杂的值,则必须提供"),t("code",[e._v("ExitCodeMapper")]),e._v("接口的自定义实现。因为"),t("code",[e._v("CommandLineJobRunner")]),e._v("是创建"),t("code",[e._v("ApplicationContext")]),e._v("的类,因此不能“连线在一起”,所以需要重写的任何值都必须是自动连线的。这意味着,如果在"),t("code",[e._v("BeanFactory")]),e._v("中找到了"),t("code",[e._v("ExitCodeMapper")]),e._v("的实现,则将在创建上下文后将其注入到运行器中。要提供你自己的"),t("code",[e._v("ExitCodeMapper")]),e._v(",需要做的就是将实现声明为根级别 Bean,并确保它是由运行器加载的"),t("code",[e._v("ApplicationContext")]),e._v("的一部分。")]),e._v(" "),t("h4",{attrs:{id:"在-web-容器中运行作业"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#在-web-容器中运行作业"}},[e._v("#")]),e._v(" 在 Web 容器中运行作业")]),e._v(" "),t("p",[e._v("从历史上看,离线处理(如批处理作业)是从命令行启动的,如上文所述。然而,在许多情况下,从"),t("code",[e._v("HttpRequest")]),e._v("发射是更好的选择。许多这样的用例包括报告、临时作业运行和 Web 应用程序支持。因为按定义,批处理作业是长时间运行的,所以最重要的问题是确保异步启动该作业:")]),e._v(" "),t("p",[t("img",{attrs:{src:"https://docs.spring.io/spring-batch/docs/current/reference/html/images/launch-from-request.png",alt:"基于 Web 容器的异步作业启动器序列"}})]),e._v(" "),t("p",[e._v("图 4。来自 Web 容器的异步作业启动器序列")]),e._v(" "),t("p",[e._v("在这种情况下,控制器是 Spring MVC 控制器。关于 Spring MVC 的更多信息可以在这里找到:的"),t("code",[e._v("Job")]),e._v("启动"),t("code",[e._v("Job")]),e._v(",该控制器立即返回"),t("code",[e._v("JobExecution")]),e._v("。"),t("code",[e._v("Job")]),e._v("可能仍在运行,但是,这种非阻塞行为允许控制器立即返回,这是处理"),t("code",[e._v("HttpRequest")]),e._v("时所需的。以下是一个例子:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('@Controller\npublic class JobLauncherController {\n\n @Autowired\n JobLauncher jobLauncher;\n\n @Autowired\n Job job;\n\n @RequestMapping("/jobLauncher.html")\n public void handle() throws Exception{\n jobLauncher.run(job, new JobParameters());\n }\n}\n')])])]),t("h3",{attrs:{id:"高级元数据使用"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#高级元数据使用"}},[e._v("#")]),e._v(" 高级元数据使用")]),e._v(" "),t("p",[e._v("到目前为止,"),t("code",[e._v("JobLauncher")]),e._v("和"),t("code",[e._v("JobRepository")]),e._v("接口都已经讨论过了。它们一起表示作业的简单启动,以及批处理域对象的基本操作:")]),e._v(" "),t("p",[t("img",{attrs:{src:"https://docs.spring.io/spring-batch/docs/current/reference/html/images/job-repository.png",alt:"作业存储库"}})]),e._v(" "),t("p",[e._v("图 5。作业存储库")]),e._v(" "),t("p",[e._v("a"),t("code",[e._v("JobLauncher")]),e._v("使用"),t("code",[e._v("JobRepository")]),e._v("来创建新的"),t("code",[e._v("JobExecution")]),e._v("对象并运行它们。"),t("code",[e._v("Job")]),e._v("和"),t("code",[e._v("Step")]),e._v("实现稍后将使用相同的"),t("code",[e._v("JobRepository")]),e._v("用于运行作业期间相同执行的基本更新。对于简单的场景,基本的操作就足够了,但是在具有数百个批处理任务和复杂的调度需求的大批处理环境中,需要对元数据进行更高级的访问:")]),e._v(" "),t("p",[t("img",{attrs:{src:"https://docs.spring.io/spring-batch/docs/current/reference/html/images/job-repository-advanced.png",alt:"作业存储库高级版"}})]),e._v(" "),t("p",[e._v("图 6。高级作业存储库访问")]),e._v(" "),t("p",[e._v("下面将讨论"),t("code",[e._v("JobExplorer")]),e._v("和"),t("code",[e._v("JobOperator")]),e._v("接口,它们添加了用于查询和控制元数据的附加功能。")]),e._v(" "),t("h4",{attrs:{id:"查询存储库"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#查询存储库"}},[e._v("#")]),e._v(" 查询存储库")]),e._v(" "),t("p",[e._v("在任何高级特性之前,最基本的需求是查询存储库中现有执行的能力。此功能由"),t("code",[e._v("JobExplorer")]),e._v("接口提供:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("public interface JobExplorer {\n\n List getJobInstances(String jobName, int start, int count);\n\n JobExecution getJobExecution(Long executionId);\n\n StepExecution getStepExecution(Long jobExecutionId, Long stepExecutionId);\n\n JobInstance getJobInstance(Long instanceId);\n\n List getJobExecutions(JobInstance jobInstance);\n\n Set findRunningJobExecutions(String jobName);\n}\n")])])]),t("p",[e._v("从上面的方法签名中可以明显看出,"),t("code",[e._v("JobExplorer")]),e._v("是"),t("code",[e._v("JobRepository")]),e._v("的只读版本,并且,像"),t("code",[e._v("JobRepository")]),e._v("一样,可以通过使用工厂 Bean 轻松地对其进行配置:")]),e._v(" "),t("p",[e._v("下面的示例展示了如何在 XML 中配置"),t("code",[e._v("JobExplorer")]),e._v(":")]),e._v(" "),t("p",[e._v("XML 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n')])])]),t("p",[e._v("下面的示例展示了如何在 Java 中配置"),t("code",[e._v("JobExplorer")]),e._v(":")]),e._v(" "),t("p",[e._v("Java 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("...\n// This would reside in your BatchConfigurer implementation\n@Override\npublic JobExplorer getJobExplorer() throws Exception {\n\tJobExplorerFactoryBean factoryBean = new JobExplorerFactoryBean();\n\tfactoryBean.setDataSource(this.dataSource);\n\treturn factoryBean.getObject();\n}\n...\n")])])]),t("p",[t("a",{attrs:{href:"#repositoryTablePrefix"}},[e._v("在本章的前面")]),e._v(",我们注意到"),t("code",[e._v("JobRepository")]),e._v("的表前缀可以进行修改以允许不同的版本或模式。因为"),t("code",[e._v("JobExplorer")]),e._v("与相同的表一起工作,所以它也需要设置前缀的能力。")]),e._v(" "),t("p",[e._v("下面的示例展示了如何在 XML 中设置"),t("code",[e._v("JobExplorer")]),e._v("的表前缀:")]),e._v(" "),t("p",[e._v("XML 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n')])])]),t("p",[e._v("下面的示例展示了如何在 Java 中设置"),t("code",[e._v("JobExplorer")]),e._v("的表前缀:")]),e._v(" "),t("p",[e._v("Java 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('...\n// This would reside in your BatchConfigurer implementation\n@Override\npublic JobExplorer getJobExplorer() throws Exception {\n\tJobExplorerFactoryBean factoryBean = new JobExplorerFactoryBean();\n\tfactoryBean.setDataSource(this.dataSource);\n\tfactoryBean.setTablePrefix("SYSTEM.");\n\treturn factoryBean.getObject();\n}\n...\n')])])]),t("h4",{attrs:{id:"jobregistry"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#jobregistry"}},[e._v("#")]),e._v(" JobRegistry")]),e._v(" "),t("p",[e._v("a"),t("code",[e._v("JobRegistry")]),e._v("(及其父接口"),t("code",[e._v("JobLocator")]),e._v(")不是强制性的,但如果你想跟踪上下文中哪些作业可用,它可能会很有用。当工作在其他地方创建时(例如,在子上下文中),它对于在应用程序上下文中集中收集工作也很有用。还可以使用自定义"),t("code",[e._v("JobRegistry")]),e._v("实现来操作已注册作业的名称和其他属性。该框架只提供了一个实现,它基于从作业名称到作业实例的简单映射。")]),e._v(" "),t("p",[e._v("下面的示例展示了如何为 XML 中定义的作业包含"),t("code",[e._v("JobRegistry")]),e._v(":")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n')])])]),t("p",[e._v("下面的示例展示了如何为 Java 中定义的作业包含"),t("code",[e._v("JobRegistry")]),e._v(":")]),e._v(" "),t("p",[e._v("当使用"),t("code",[e._v("@EnableBatchProcessing")]),e._v("时,将为你提供一个"),t("code",[e._v("JobRegistry")]),e._v("。如果你想配置自己的:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("...\n// This is already provided via the @EnableBatchProcessing but can be customized via\n// overriding the getter in the SimpleBatchConfiguration\n@Override\n@Bean\npublic JobRegistry jobRegistry() throws Exception {\n\treturn new MapJobRegistry();\n}\n...\n")])])]),t("p",[e._v("有两种方法可以自动填充"),t("code",[e._v("JobRegistry")]),e._v(":使用 Bean 后处理器和使用注册商生命周期组件。这两种机制在下面的部分中进行了描述。")]),e._v(" "),t("h5",{attrs:{id:"jobregistrybeanpostprocessor"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#jobregistrybeanpostprocessor"}},[e._v("#")]),e._v(" jobregistrybeanpostprocessor")]),e._v(" "),t("p",[e._v("这是一个 Bean 后处理器,它可以在创建所有作业时注册它们。")]),e._v(" "),t("p",[e._v("下面的示例展示了如何为 XML 中定义的作业包括"),t("code",[e._v("JobRegistryBeanPostProcessor")]),e._v(":")]),e._v(" "),t("p",[e._v("XML 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n \n\n')])])]),t("p",[e._v("下面的示例展示了如何为在 Java 中定义的作业包括"),t("code",[e._v("JobRegistryBeanPostProcessor")]),e._v(":")]),e._v(" "),t("p",[e._v("Java 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("@Bean\npublic JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor() {\n JobRegistryBeanPostProcessor postProcessor = new JobRegistryBeanPostProcessor();\n postProcessor.setJobRegistry(jobRegistry());\n return postProcessor;\n}\n")])])]),t("p",[e._v("虽然这不是严格必要的,但是在示例中的后处理器已经被赋予了一个 ID,以便它可以被包括在子上下文中(例如作为父 Bean 定义),并导致在那里创建的所有作业也被自动注册。")]),e._v(" "),t("h5",{attrs:{id:"automaticjobregistrar"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#automaticjobregistrar"}},[e._v("#")]),e._v(" "),t("code",[e._v("AutomaticJobRegistrar")])]),e._v(" "),t("p",[e._v("这是一个生命周期组件,它创建子上下文,并在创建这些上下文时从这些上下文注册作业。这样做的一个好处是,虽然子上下文中的作业名称在注册表中仍然必须是全局唯一的,但它们的依赖项可能具有“自然”名称。因此,例如,你可以创建一组 XML 配置文件,每个配置文件只具有一个作业,但所有配置文件都具有具有具有相同 Bean 名称的"),t("code",[e._v("ItemReader")]),e._v("的不同定义,例如“reader”。如果将所有这些文件导入到相同的上下文中,则读写器定义将发生冲突并相互覆盖,但是使用自动注册器可以避免这种情况。这使得集成来自应用程序的独立模块的作业变得更加容易。")]),e._v(" "),t("p",[e._v("下面的示例展示了如何为 XML 中定义的作业包括"),t("code",[e._v("AutomaticJobRegistrar")]),e._v(":")]),e._v(" "),t("p",[e._v("XML 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n \n \n \n \n \n \n \n \n \n \n\n')])])]),t("p",[e._v("下面的示例展示了如何为在 Java 中定义的作业包括"),t("code",[e._v("AutomaticJobRegistrar")]),e._v(":")]),e._v(" "),t("p",[e._v("Java 配置")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("@Bean\npublic AutomaticJobRegistrar registrar() {\n\n AutomaticJobRegistrar registrar = new AutomaticJobRegistrar();\n registrar.setJobLoader(jobLoader());\n registrar.setApplicationContextFactories(applicationContextFactories());\n registrar.afterPropertiesSet();\n return registrar;\n\n}\n")])])]),t("p",[e._v("注册商有两个强制属性,一个是"),t("code",[e._v("ApplicationContextFactory")]),e._v("的数组(这里是从方便的工厂 Bean 创建的),另一个是"),t("code",[e._v("JobLoader")]),e._v("。"),t("code",[e._v("JobLoader")]),e._v("负责管理子上下文的生命周期,并在"),t("code",[e._v("JobRegistry")]),e._v("中注册作业。")]),e._v(" "),t("p",[t("code",[e._v("ApplicationContextFactory")]),e._v("负责创建子上下文,最常见的用法是使用"),t("code",[e._v("ClassPathXmlApplicationContextFactory")]),e._v("。这个工厂的一个特性是,默认情况下,它会将一些配置从父上下文复制到子上下文。因此,例如,如果它应该与父配置相同,则不必重新定义子配置中的"),t("code",[e._v("PropertyPlaceholderConfigurer")]),e._v("或 AOP 配置。")]),e._v(" "),t("p",[e._v("如果需要,"),t("code",[e._v("AutomaticJobRegistrar")]),e._v("可以与"),t("code",[e._v("JobRegistryBeanPostProcessor")]),e._v("一起使用(只要"),t("code",[e._v("DefaultJobLoader")]),e._v("也可以使用)。例如,如果在主父上下文和子位置中定义了作业,那么这可能是可取的。")]),e._v(" "),t("h4",{attrs:{id:"joboperator"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#joboperator"}},[e._v("#")]),e._v(" joboperator")]),e._v(" "),t("p",[e._v("如前所述,"),t("code",[e._v("JobRepository")]),e._v("提供对元数据的增删改查操作,而"),t("code",[e._v("JobExplorer")]),e._v("提供对元数据的只读操作。然而,当这些操作一起用来执行常见的监视任务时,它们是最有用的,例如停止、重新启动或汇总作业,就像批处理操作符通常做的那样。 Spring 批处理通过"),t("code",[e._v("JobOperator")]),e._v("接口提供这些类型的操作:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("public interface JobOperator {\n\n List getExecutions(long instanceId) throws NoSuchJobInstanceException;\n\n List getJobInstances(String jobName, int start, int count)\n throws NoSuchJobException;\n\n Set getRunningExecutions(String jobName) throws NoSuchJobException;\n\n String getParameters(long executionId) throws NoSuchJobExecutionException;\n\n Long start(String jobName, String parameters)\n throws NoSuchJobException, JobInstanceAlreadyExistsException;\n\n Long restart(long executionId)\n throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException,\n NoSuchJobException, JobRestartException;\n\n Long startNextInstance(String jobName)\n throws NoSuchJobException, JobParametersNotFoundException, JobRestartException,\n JobExecutionAlreadyRunningException, JobInstanceAlreadyCompleteException;\n\n boolean stop(long executionId)\n throws NoSuchJobExecutionException, JobExecutionNotRunningException;\n\n String getSummary(long executionId) throws NoSuchJobExecutionException;\n\n Map getStepExecutionSummaries(long executionId)\n throws NoSuchJobExecutionException;\n\n Set getJobNames();\n\n}\n")])])]),t("p",[e._v("上面的操作表示来自许多不同接口的方法,例如"),t("code",[e._v("JobLauncher")]),e._v("、"),t("code",[e._v("JobRepository")]),e._v("、"),t("code",[e._v("JobExplorer")]),e._v("和"),t("code",[e._v("JobRegistry")]),e._v("。由于这个原因,所提供的"),t("code",[e._v("JobOperator")]),e._v("、"),t("code",[e._v("SimpleJobOperator")]),e._v("的实现具有许多依赖性。")]),e._v(" "),t("p",[e._v("下面的示例显示了 XML 中"),t("code",[e._v("SimpleJobOperator")]),e._v("的典型 Bean 定义:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n \n \n \n \n \n \n \n \n\n')])])]),t("p",[e._v("下面的示例显示了 Java 中"),t("code",[e._v("SimpleJobOperator")]),e._v("的典型 Bean 定义:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v(" /**\n * All injected dependencies for this bean are provided by the @EnableBatchProcessing\n * infrastructure out of the box.\n */\n @Bean\n public SimpleJobOperator jobOperator(JobExplorer jobExplorer,\n JobRepository jobRepository,\n JobRegistry jobRegistry) {\n\n\tSimpleJobOperator jobOperator = new SimpleJobOperator();\n\n\tjobOperator.setJobExplorer(jobExplorer);\n\tjobOperator.setJobRepository(jobRepository);\n\tjobOperator.setJobRegistry(jobRegistry);\n\tjobOperator.setJobLauncher(jobLauncher);\n\n\treturn jobOperator;\n }\n")])])]),t("table",[t("thead",[t("tr",[t("th"),e._v(" "),t("th",[e._v("如果你在作业存储库上设置了表前缀,请不要忘记在作业资源管理器上也设置它。")])])]),e._v(" "),t("tbody")]),e._v(" "),t("h4",{attrs:{id:"jobparametersincrementer"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#jobparametersincrementer"}},[e._v("#")]),e._v(" JobParametersIncrementer")]),e._v(" "),t("p",[e._v("关于"),t("code",[e._v("JobOperator")]),e._v("的大多数方法都是不言自明的,更详细的解释可以在"),t("a",{attrs:{href:"https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/core/launch/JobOperator.html",target:"_blank",rel:"noopener noreferrer"}},[e._v("接口的 Javadoc"),t("OutboundLink")],1),e._v("上找到。然而,"),t("code",[e._v("startNextInstance")]),e._v("方法是值得注意的。这个方法总是会启动一个作业的新实例。如果"),t("code",[e._v("JobExecution")]),e._v("中存在严重问题,并且需要从一开始就重新开始工作,那么这将非常有用。与"),t("code",[e._v("JobLauncher")]),e._v("不同,"),t("code",[e._v("JobLauncher")]),e._v("需要一个新的"),t("code",[e._v("JobParameters")]),e._v("对象,如果参数与以前的任何一组参数不同,则该对象将触发一个新的"),t("code",[e._v("JobInstance")]),e._v(","),t("code",[e._v("startNextInstance")]),e._v("方法将使用绑定到"),t("code",[e._v("JobParametersIncrementer")]),e._v("的"),t("code",[e._v("Job")]),e._v("来强制将"),t("code",[e._v("Job")]),e._v("转换为一个新实例:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v("public interface JobParametersIncrementer {\n\n JobParameters getNext(JobParameters parameters);\n\n}\n")])])]),t("p",[t("code",[e._v("JobParametersIncrementer")]),e._v("的约定是,给定一个"),t("a",{attrs:{href:"#jobParameters"}},[e._v("工作参数")]),e._v("对象,它将通过递增它可能包含的任何必要值来返回“next”JobParameters 对象。这个策略是有用的,因为框架无法知道"),t("code",[e._v("JobParameters")]),e._v("的更改是什么,使它成为“下一个”实例。例如,如果"),t("code",[e._v("JobParameters")]),e._v("中的唯一值是日期,并且应该创建下一个实例,那么该值应该增加一天吗?或者一周(例如,如果工作是每周一次的话)?对于有助于识别工作的任何数值,也可以这样说,如下所示:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('public class SampleIncrementer implements JobParametersIncrementer {\n\n public JobParameters getNext(JobParameters parameters) {\n if (parameters==null || parameters.isEmpty()) {\n return new JobParametersBuilder().addLong("run.id", 1L).toJobParameters();\n }\n long id = parameters.getLong("run.id",1L) + 1;\n return new JobParametersBuilder().addLong("run.id", id).toJobParameters();\n }\n}\n')])])]),t("p",[e._v("在本例中,使用带有“run.id”键的值来区分"),t("code",[e._v("JobInstances")]),e._v("。如果传入的"),t("code",[e._v("JobParameters")]),e._v("为空,则可以假定"),t("code",[e._v("Job")]),e._v("以前从未运行过,因此可以返回其初始状态。但是,如果不是,则获得旧值,将其递增 1 并返回。")]),e._v(" "),t("p",[e._v("对于 XML 中定义的作业,Incrementer 可以通过名称空间中的’Incrementer’属性与"),t("code",[e._v("Job")]),e._v("关联,如下所示:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('\n ...\n\n')])])]),t("p",[e._v("对于在 Java 中定义的作业,增量程序可以通过构建器中提供的"),t("code",[e._v("incrementer")]),e._v("方法与“作业”关联,如下所示:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('@Bean\npublic Job footballJob() {\n return this.jobBuilderFactory.get("footballJob")\n \t\t\t\t .incrementer(sampleIncrementer())\n \t\t\t\t ...\n .build();\n}\n')])])]),t("h4",{attrs:{id:"停止工作"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#停止工作"}},[e._v("#")]),e._v(" 停止工作")]),e._v(" "),t("p",[t("code",[e._v("JobOperator")]),e._v("最常见的用例之一是优雅地停止一项工作:")]),e._v(" "),t("div",{staticClass:"language- extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[e._v('Set executions = jobOperator.getRunningExecutions("sampleJob");\njobOperator.stop(executions.iterator().next());\n')])])]),t("p",[e._v("关闭不是立即的,因为无法强制立即关闭,特别是如果当前执行的是框架无法控制的开发人员代码,例如业务服务。但是,一旦将控件返回到框架中,就会将当前"),t("code",[e._v("StepExecution")]),e._v("的状态设置为"),t("code",[e._v("BatchStatus.STOPPED")]),e._v(",保存它,然后在完成之前对"),t("code",[e._v("JobExecution")]),e._v("执行相同的操作。")]),e._v(" "),t("h4",{attrs:{id:"终止作业"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#终止作业"}},[e._v("#")]),e._v(" 终止作业")]),e._v(" "),t("p",[e._v("可以重新启动"),t("code",[e._v("FAILED")]),e._v("的作业执行(如果"),t("code",[e._v("Job")]),e._v("是可重启的)。状态为"),t("code",[e._v("ABANDONED")]),e._v("的作业执行将不会被框架重新启动。在步骤执行中,"),t("code",[e._v("ABANDONED")]),e._v("状态也被用于在重新启动的作业执行中将其标记为可跳过的:如果作业正在执行,并且遇到了在上一个失败的作业执行中标记"),t("code",[e._v("ABANDONED")]),e._v("的步骤,它将进入下一个步骤(由作业流定义和步骤执行退出状态决定)。")]),e._v(" "),t("p",[e._v("如果进程死了("),t("code",[e._v('"kill -9"')]),e._v("或服务器故障),作业当然不在运行,但是"),t("code",[e._v("JobRepository")]),e._v("无法知道,因为在进程死之前没有人告诉它。你必须手动告诉它,你知道执行失败或应该被视为已中止(将其状态更改为"),t("code",[e._v("FAILED")]),e._v("或"),t("code",[e._v("ABANDONED")]),e._v(")-这是一个业务决策,没有办法使其自动化。如果状态不是可重启的,或者你知道重新启动数据是有效的,则仅将状态更改为"),t("code",[e._v("FAILED")]),e._v("。 Spring batch admin"),t("code",[e._v("JobService")]),e._v("中有一个实用程序来中止作业执行。")])])}),[],!1,null,null,null);a.default=r.exports}}]);