第四章节、配置以及运行一个任务(上) – spring batch
(申明:初尝翻译,未经校验,请勿转载)
在前一章节,我们讨论了整体的架构设计,如下图解作为参考:
上图中的“任务(Job)”看起来就像是“步骤(steps)”的容器,有相当多的配置选项需要开发人员注意。同时,有关怎样使得一个“任务”运行起来,以及它的元数据在运行时如何存储,还有很多细节需要引起注意。这个章节将要解释众多的配置选项和运行时一个任务需要关心的地方。
4.1、配置一个任务
有两种“任务”接口的实现方式,然而通过抽象,配置上它们却是一致的。只需要三种依赖项:一个名称,“任务存储库”,和若干“步骤”。
上面的例子中使用了一个父类“bean”定义来创建那些“步骤”;在步骤配置区域可以看到更多选项,在一行里申明了具体步骤的详细信息。这段XML的域默认映射到id为“jobRepository”的存储仓库,将它用作默认值是易于理解的。但你也可以重载它:
在特定的情况下,针对任务步骤的配置,可以包含其他的元素,用来帮助进行切分(<split/>)、流程策略控制(<decision/>)以及流程扩展(<flow/>)。
4.1.1、可重启的能力
当执行一个批量任务时,值得关心的一个重要环节是,它会在何时重启。当指定的“任务实例”已经存在“任务执行实例”时,运行这个“任务”会被视作“重启”。理论上,所有任务都应该可以被启动、如果它没在运行的话,但有很多的场景却行不通。这样的场景就只能依赖于开发人员创建一个新的“任务实例”。但在spring batch中大可不必这样。如果一个“任务”不能被重启,但总作为“任务实例”的一部分启动,那么属性“restartable”设置为“false”就可以了:
换句话说,将这个属性设置为false意味着“这个任务不支持被再次启动”。重启一个“不支持被再次重启”的任务,会引发“JobRestartException”:
这段JUnit的代码片段,展示了意图为一个不可重启的“任务”第一次创建一个“任务执行过程”一切都好,但在第二次创建时抛出了我们提到的异常。
4.1.2、任务执行过程的拦截
在任务的执行过程之中,辅以若干事件用来加入第三方代码还是很有用处的。SimpleJob 允许在适当的时候监听一些事件:
通过元素 listeners,JobListeners 可以被添加到 SimpleJob:
无论任务执行成功与否,应该注意到,afterJob 都会被调用。如果需要做些区别处理, 可以在“任务执行”类中进行:
具体上面接口同等作用的注解是:
4.1.3、从父任务继承
如果一组任务拥有类似但有所不同的配置项,那么定义一个“父任务”,让“子任务”继承它的属性或许较为简便。与java中的类继承类似,子任务将会累加自己与父任务的元素和属性。
在下面的例子中,“baseJob”是一个抽象定义的任务,只包含若干事件监听器。子任务“job1”是一个具体的任务,继承了父任务的事件监听器,还有自己定义的事件监听器,使得该任务拥有两组事件监听器以及一个步骤,“step1”:
4.1.4、任务参数校验器
通过XML节点属性或者继承自某些抽象任务,可以为运行时的任务申明一个参数校验器。在具有必选参数的任务实例里,这个非常有用。如下是“DefaultJobParametersValidator”的例子,用来校验那些必选以及可选的参数组合。更多复杂的校验算法你可以通过接口继承自行实现。在XML配置文件中,可以在Job的子节点中增加验证器配置项子节点,等等:
验证器既可以被当做一个引用处理,亦或者在beans域内,作为一个内部bean定义。
4.2、配置任务存储库
如同早期讨论过的,“任务存储库”用来针对各色具体的实体使用spring batch做基本的“增删查改(CRUD)”操作,比如“任务执行类”和“步骤执行类”。这在主框架中应用颇多,比如“任务启动类”、“任务类”和“步骤类”,都有涉及。框架抽象掉了许多“任务存储库”实现的细节和关联关系。即便如此,仍然有一些配置选项是有用的:
配置项中除了id之外,其他都是可选项。当没有设置时,可选项将取默认值。上图中之所以显示出来只是为了提醒读者它的存在。属性“max-varchar-length”默认值2500,这个用来设定所有类型为“varchar”的列,在样例架构脚本中曾用来存储“退出类型码描述”这样的字段等。如果你不修改架构,并且你使用不到双字节,你根本不需要去更改它。
4.2.1、任务储存库的事务配置
如果使用指定属性,事务功能将自动被添加到存储库中。这个用来确认批量元数据,包括错误之后重启时必需的状态,都将被正确的持久化。框架的行为只有定义在存储库的方法都是事务性的基础上,才显得优雅。在诸多用以创建(create*)的方法属性中,隔离级别被定义为“明确的独立进行(specified separately)”,就是为了确定当任务被启动时,如果两个进程尝试同时启动相同的任务,只有一个进程能成功。针对那个方法的默认隔离级别是“可序列化的(SERIALIZABLE)”,这样做是极有风险的:“读提交(READ_COMMITTED)”将运作正常;“读未提交(READ_UNCOMMITTED)”将只在两个进程不冲突时表现良好。但不管怎样,考虑到调用一个创建的方法非常的简短,看上去“序列化”不会造成什么麻烦,只要数据库平台能够支持它。然后,接下来这个就有问题了:
即便属性或者工厂beans没有被使用,使用AOP配置存储库的事务行为也是非常基本的:
这个代码段可以原封不动被引用。记住在使用相应属性申明的同事,不要忘记引用spring-tx和spring-aop(或者整个spring)包。
4.2.2、更改表前缀
另一个“任务存储库(JobRepository)可以被更改的属性,是元数据表的前缀。默认情况下,它们都以“BATCH_”开头。BATCH_JOB_EXECUTION 和 BATCH_STEP_EXECUTION 就是两个例子。但还是有潜在的可能性需要更改前缀。如果架构的名字需要被放置到表名称里,或者不止一个元数据的集合需要放置在同一个架构里,那么表前缀就需要被更改了:
上例中的更改,每一次对元数据的请求都会被冠以“SYSTEM.TEST_”。BATCH_JOB_EXECUTION”将被映射为SYSTEM.TEST_JOB_EXECUTION.
注意 :只有表前缀可以被配置。表以及列名称是不可以的。
4.2.3、基于内存的存储库
某些场景下你可能不希望将的域实体持久化到数据库中。可能是速度的考虑;在每一个提交的点存储域实体着实需要一些时间。另一个原因你可能就是不需要持久化某个特殊任务的状态。出于这些考虑,spring batch提供了内存映射版本的任务存储库:
注意内存存储库是不稳定的,因此不允许在JVM的实例之间进行重启。同样也不能允许两个任务实例具有相同的参数并同时启动,不适用于多线程任务,或本地的分布式步骤。那么如果你需要这些特性,就得使用数据库版本的存储库了。
然而还是需要定义一个事务管理器,因为存储库出于回滚需要,因为业务逻辑还是事务性的。出于测试考虑,许多人发现ResourcelessTransactionManager很有用。
4.2.4、存储库中的非标准数据库类型
如果你使用的数据库平台不在所支持的平台范围之内,那么就选用一个被支持的数据库类型,只要sql的语法足够相近。尝试使用尚不大成熟的 JobRepositoryFactoryBean 替换简短的属性申明,使用它去设置最相近的数据库类型:
( JobRepositoryFactoryBean 尝试从数据源(DataSource)自动甄别数据库类型,如果它没有被指定的话)不同平台间的主要区别,基本上是生成主键的策略问题,因此通常还是有必要重载incrementerFactory(使用spring框架中某一个标准实现)。
如果这样还不能工作,或者你没在使用 RDBMS,那么唯一的选择就是实现SimpleJobRepository 所依赖的那些个数据访问层(Dao)接口了,使用一般的spring方法逐一的手动操作。