Skip to content

Commit 7c5e9b5

Browse files
committed
整理Sean翻译的4. Configuring and Running a Job
1 parent 07bb85e commit 7c5e9b5

13 files changed

+545
-0
lines changed

04_config_job/41.md

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# 4.1 Configuring a Job
2+
3+
[Job接口](http://docs.spring.io/spring-batch/trunk/reference/html/configureJob.html) 的实现有多个,但是在配置上命名空间存在着不同。必须依赖的只有三项:名称 **name****JobRespository****Step** 的列表:
4+
5+
<job id="footballJob">
6+
<step id="playerload" parent="s1" next="gameLoad"/>
7+
<step id="gameLoad" parent="s2" next="playerSummarization"/>
8+
<step id="playerSummarization" parent="s3"/>
9+
</job>
10+
11+
在这个例子中使用了父类的bean定义来创建step,更多描述step配置的信息可以参考[step configuration](http://docs.spring.io/spring-batch/trunk/reference/html/configureStep.html)这一节。XML命名空间默认会使用id为'jobRepository'的引用来作为repository的定义。然而可以向如下显式的覆盖:
12+
13+
<job id="footballJob" job-repository="specialRepository">
14+
<step id="playerload" parent="s1" next="gameLoad"/>
15+
<step id="gameLoad" parent="s3" next="playerSummarization"/>
16+
<step id="playerSummarization" parent="s3"/>
17+
</job>
18+
19+
此外,job配置的step还包含其他的元素,有并发处理(<split/>),显示的流程控制(<decision/>)和外化的流程定义(<flow/>)。
20+
21+
## 4.1.1 Restartablity ##
22+
23+
执行批处理任务的一个关键问题是要考虑job被重启后的行为。如果一个 **JobExecution** 已经存在一个特定的 **JobInstance** ,那么这个job启动时可以认为是“重启”。 理想情况下,所有任务都能够在他们中止的地方启动,但是有许多场景这是不可能的。在这种场景中就要有开发者来决定创建一个新的 **JobInstance** ,Spring对此也提供了一些帮助。如果job不需要重启,而是总是作为新的 **JobInstance** 来运行,那么可重启属性可以设置为'false':
24+
25+
<job id="footballJob" restartable="false">
26+
...
27+
</job>
28+
29+
设置重启属性restartable为‘false’表示‘这个job不支持再次启动’,重启一个不可重启的job会抛出JobRestartException的异常:
30+
31+
Job job = new SimpleJob();
32+
job.setRestartable(false);
33+
34+
JobParameters jobParameters = new JobParameters();
35+
36+
JobExecution firstExecution = jobRepository.createJobExecution(job, jobParameters);
37+
jobRepository.saveOrUpdate(firstExecution);
38+
39+
try {
40+
jobRepository.createJobExecution(job, jobParameters);
41+
fail();
42+
}
43+
catch (JobRestartException e) {
44+
//预计抛出JobRestartException异常
45+
}
46+
47+
这个JUnit代码展示了创建一个不可重启的Job后,第一次能够创建 **JobExecution** ,第二次再创建相同的JobExcution会抛出一个 **JobRestartException**
48+
49+
50+
## 4.1.2 Intercepting Job Execution ##
51+
52+
在job执行过程中,自定义代码能够在生命周期中通过事件通知执行会是很有用的。SimpleJob能够在适当的时机调用JobListener:
53+
54+
public interface JobExecutionListener {
55+
56+
void beforeJob(JobExecution jobExecution);
57+
58+
void afterJob(JobExecution jobExecution);
59+
60+
}
61+
62+
JobListener能够添加到SimpleJob中去,作为job的listener元素:
63+
64+
<job id="footballJob">
65+
<step id="playerload" parent="s1" next="gameLoad"/>
66+
<step id="gameLoad" parent="s2" next="playerSummarization"/>
67+
<step id="playerSummarization" parent="s3"/>
68+
<listeners>
69+
<listener ref="sampleListener"/>
70+
</listeners>
71+
</job>
72+
73+
无论job执行成功或是失败都会调用afterJob,都可以从 **JobExecution** 中获取运行结果后,根据结果来进行不同的处理:
74+
75+
public void afterJob(JobExecution jobExecution){
76+
if( jobExecution.getStatus() == BatchStatus.COMPLETED ){
77+
//job执行成功 }
78+
else if(jobExecution.getStatus() == BatchStatus.FAILED){
79+
//job执行失败 }
80+
}
81+
82+
对应于这个interface的annotation为:
83+
84+
- @BeforeJob
85+
- @AfterJob
86+
87+
## 4.1.3 Inheriting from a parent Job ##
88+
89+
如果一组job配置共有相似,但又不是完全相同,那么可以定义一个"父”job,让这些job去继承属性。同Java的类继承一样,子job会把父job的属性和元素合并进来。
90+
91+
下面的例子中,“baseJob”是一个抽象的job定义,只定义了一个监听器列表。名为“job1”的job是一个具体定义,它继承了“baseJob"的监听器,并且与自己的监听器合并,最终生成的job带有两个监听器,以及一个名为”step1“的step。
92+
93+
<job id="baseJob" abstract="true">
94+
<listeners>
95+
<listener ref="listenerOne"/>
96+
<listeners>
97+
</job>
98+
99+
<job id="job1" parent="baseJob">
100+
<step id="step1" parent="standaloneStep"/>
101+
102+
<listeners merge="true">
103+
<listener ref="listenerTwo"/>
104+
<listeners>
105+
</job>
106+
107+
更多信息可参见 [Inheriting from a Parent Step](http://docs.spring.io/spring-batch/trunk/reference/html/configureStep.html#InheritingFromParentStep)
108+
109+
110+
## 4.1.4 JobParametersValidator ##
111+
112+
一个在xml命名空间描述的job或是使用任何抽象job子类的job,可以选择为运行时为job参数定义一个验证器。在job启动时需要保证所有必填参数都存在的场景下,这个功能是很有用的。有一个DefaultJobParametersValidator可以用来限制一些简单的必选和可选参数组合,你也可以实现接口用来处理更复杂的限制。验证器的配置支持使用xml命名空间来作为job的子元素,例如:
113+
114+
<job id="job1" parent="baseJob3">
115+
<step id="step1" parent="standaloneStep"/>
116+
<validator ref="paremetersValidator"/>
117+
</job>
118+
119+
验证器可以作为一个引用(如上)来定义也可以直接内嵌定义在bean的命名空间中。

04_config_job/42.md

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# 4.2 Java Config #
2+
3+
在Spring 3版本中可以采用java程序来配置应用程序,来替代XML配置的方式。 正如在Spring Batch 2.2.0版本中,批处理任务中可以使用相同的java配置项来对其进行配置。关于Java的基础配置的两个组成部分分别是: `@EnableBatchConfiguration` 注释和两个builder。
4+
5+
在Spring的体系中 `@EnableBatchProcessing` 注释的工作原理与其它的带有 `@Enable *` 的注释类似。在这种情况下,`@EnableBatchProcessing` 提供了构建批处理任务的基本配置。在这个基本的配置中,除了创建了一个 **StepScope** 的实例,还可以将一系列可用的bean进行自动装配:
6+
7+
- **JobRepository** bean 名称 "jobRepository"
8+
- **JobLauncher** bean名称"jobLauncher"
9+
- **JobRegistry** bean名称"jobRegistry"
10+
- **PlatformTransactionManager** bean名称 "transactionManager"
11+
- **JobBuilderFactory** bean名称"jobBuilders"
12+
- **StepBuilderFactory** bean名称"stepBuilders"
13+
14+
这种配置的核心接口是 **BatchConfigurer**。它为以上所述的bean提供了默认的实现方式,并要求在context中提供一个bean,即 **DataSource** 。数据库连接池由被 **JobRepository** 使用。
15+
16+
**注意**
17+
只有一个配置类需要有@ enablebatchprocessing注释。只要有一个类添加了这个注释,则以上所有的bean都是可以使用的。
18+
19+
在基本配置中,用户可以使用所提供的builder factory来配置一个job。下面的例子是通过 **JobBuilderFactory****StepBuilderFactory** 配置的两个step job 。
20+
21+
@Configuration
22+
@EnableBatchProcessing
23+
@Import(DataSourceCnfiguration.class)
24+
public class AppConfig {
25+
26+
@Autowired
27+
private JobBuilderFactory jobs;
28+
29+
@Autowired
30+
private StepBuilderFactory steps;
31+
32+
@Bean
33+
public Job job() {
34+
return jobs.get("myJob").start(step1()).next(step2()).build();
35+
}
36+
37+
@Bean
38+
protected Step step1(ItemReader<Person> reader, ItemProcessor<Person, Person> processor, ItemWriter<Person> writer) {
39+
return steps.get("step1")
40+
.<Person, Person> chunk(10)
41+
.reader(reader)
42+
.processor(processor)
43+
.writer(writer)
44+
.build();
45+
}
46+
47+
@Bean
48+
protected Step step2(Tasklet tasklet) {
49+
return steps.get("step2")
50+
.tasklet(tasklet)
51+
.build();
52+
}
53+
}
54+
55+
56+

04_config_job/43.md

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# 4.3 Configuring a JobRepository #
2+
3+
之前说过,**[JobRepository](http://docs.spring.io/spring-batch/trunk/reference/html/configureJob.html)** 是基本的CRUD操作,用于持久化Spring Batch的领域对象(如JobExecution,StepExecution)。许多主要的框架组件(如JobLauncher,Job,Step)都需要使用JobRepository。batch的命名空间中已经抽象走许多JobRepository的实现细节,但是仍然需要一些配置:
4+
5+
<job-repository id="jobRepository"
6+
data-source="dataSource"
7+
transaction-manager="transactionManager"
8+
isolation-level-for-create="SERIALIZABLE"
9+
table-prefix="BATCH_"
10+
max-varchar-length="1000"/>
11+
12+
上面列出的配置除了id外都是可选的。如果没有进行参数配置,默认值就是上面展示的内容,之所以写出来是用于展示给读者。 `max-varchar-length` 的默认值是2500,这表示varchar列的长度,在 [sample schema scripts](http://docs.spring.io/spring-batch/trunk/reference/html/metaDataSchema.html#metaDataSchemaOverview) 中用于存储类似于`exit code`这些描述的字符。如果你不修改schema并且也不会使用多字节编码,那么就不用修改它。
13+
14+
## 4.3.1 JobRepository 的事物配置 ##
15+
16+
如果使用了namespace,repository会被自动加上事务控制,这是为了确保批处理操作元数据以及失败后重启的状态能够被准确的持久化,如果repository的方法不是事务控制的,那么框架的行为就不能够被准确的定义。`create*` 方法的隔离级别会被单独指定,为了确保任务启动时,如果两个操作尝试在同时启动相同的任务,那么只有一个任务能够被成功启动。这种方法默认的隔离级别是 `SERIALIZABLE` ,这是相当激进的做法: `READ_COMMITED` 能达到同样效果;如果两个操作不以这种方式冲突的话 `READ_UNCOMMITED` 也能很好工作。但是,由于调用 `create*` 方法是相当短暂的,只要数据库支持,就不会对性能产生太大影响。它也能被这样覆盖:
17+
18+
<job-repository id="jobRepository"
19+
isolation-level-for-create="REPEATABLE_READ" />
20+
21+
如果factory的namespace没有被使用,那么可以使用AOP来配置repository的事务行为:
22+
23+
<aop:config>
24+
<aop:advisor
25+
pointcut="execution(* org.springframework.batch.core..*Repository+.*(..))"/>
26+
<advice-ref="txAdvice" />
27+
</aop:config>
28+
29+
<tx:advice id="txAdvice" transaction-manager="transactionManager">
30+
<tx:attributes>
31+
<tx:method name="*" />
32+
</tx:attributes>
33+
</tx:advice>
34+
35+
这个配置片段基本上可以不做修改直接使用。记住加上适当的namespace描述去确保spring-tx和spring-aop(或是整个spring)都在classpath中。
36+
37+
## 4.3.2 修改 Table 前缀 ##
38+
39+
**JobRepository** 可以修改的另一个属性是元数据表的表前缀。默认是以BATCH_开头,`BATCH_JOB_EXECUTION``BATCH_STEP_EXECUTION` 就是两个例子。但是,有一些潜在的原因可能需要修改这个前缀。例如schema的名字需要被预置到表名中,或是不止一组的元数据表需要放在同一个schema中,那么表前缀就需要改变:
40+
41+
<job-repository id="jobRepository"
42+
table-prefix="SYSTEM.TEST_" />
43+
44+
按照上面的修改配置,每一个元数据查询都会带上 `SYSTEM.TEST_` 的前缀,`BATCH_JOB_EXECUTION` 将会被更换为`SYSTEM.TEST_JOB_EXECUTION`
45+
46+
47+
**注意**:表名前缀是可配置的,表名和列名是不可配置的。
48+
49+
50+
## 4.3.3 In-Memory Repository ##
51+
52+
有的时候不想把你的领域对象持久化到数据库中,可能是为了运行的更快速,因为每次提交都要开销额外的时间;也可能并不需要为特定任务保存状态。那么Spring Batch还提供了内存Map版本的job仓库:
53+
54+
<bean id="jobRepository"
55+
class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
56+
<property name="transactionManager" ref="transactionManager"/>
57+
</bean>
58+
59+
需要注意的是 **内存 Repository** 是轻量的并且不能在两个JVM实例间重启任务,也不能允许同时启动带有相同参数的任务,不适合在多线程的任务或是一个本地分片任务的场景下使用。而使用数据库版本的Repository则能够拥有这些特性。
60+
61+
但是也需要定义一个事务管理器,因为仓库需要回滚语义,也因为商业逻辑要求事务性(例如RDBMS访问)。经过测试许多人觉得 **ResourcelessTransactionManager** 是很有用的。
62+
63+
64+
## 4.3.4 Non-standard Database Types in a Repository ##
65+
66+
如果使用的数据库平台不在支持的平台列表中,在SQL类型类似的情况下你可以使用近似的数据库类型。使用原生的 **JobRepositoryFactoryBean** 来取代命名空间缩写后设置一个相似的数据库类型:
67+
68+
<bean id="jobRepository" class="org...JobRepositoryFactoryBean">
69+
<property name="databaseType" value="db2"/>
70+
<property name="dataSource" ref="dataSource"/>
71+
</bean>
72+
73+
(如果没有指定 `databaseType`**JobRepositoryFactoryBean** 会通过DataSource自动检测数据库的类型).平台之间的主要不同之处在于主键的计算策略,也可能需要覆盖 `incrementerFactory` (使用Spring Framework提供的标准实现)。
74+
如果它还不能工作,或是你不使用RDBMS,那么唯一的选择是让 **SimpleJobRepository** 使用Spring方式依赖并且绑定在手工实现的各种Dao接口上。
75+

04_config_job/44.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# 4.4 Configuring a JobLauncher #
2+
3+
`JobLauncher` 最基本的实现是 `SimpleJobLauncher`,它唯一的依赖是通过 `JobRepository` 获取一个 `execution`
4+
5+
<bean id="jobLauncher"
6+
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
7+
<property name="jobRepository" ref="jobRepository" />
8+
</bean>
9+
10+
一旦获取到 **[JobExecution](http://docs.spring.io/spring-batch/trunk/reference/html/domain.html#jobExecution)** ,那么可以通过执行`Job`的方法,最终将`JobExecution`返回给调用者:
11+
12+
![同步执行的Job加载时序图](./44_1_job-launcher-sequence-sync.png)
13+
14+
从调度启动时,整个序列能够很好的直接工作,但是,从HTTP请求中启动则会出现一些问题。在这种场景中,启动任务需要异步操作,让**SimpleJobLauncher**能够立刻返回结果给调用者,如果让HTTP请求一直等待很长时间知道批处理任务完成获取到执行结果,是很糟糕的操作体验。一个流程如下图所示:
15+
16+
![异步加载时序图](./44_1_job-launcher-sequence-sync.png)
17+
18+
通过配置`TaskExecutor`可以很容易的将`SimpleJobLauncher`配置成异步操作:
19+
20+
<bean id="jobLauncher"
21+
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
22+
<property name="jobRepository" ref="jobRepository" />
23+
<property name="taskExecutor">
24+
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor" />
25+
</property>
26+
</bean>
27+
28+
**TaskExecutor** 接口的任何实现都能够用来控制`job`的异步执行。
Loading
Loading

0 commit comments

Comments
 (0)