上篇文章中,我们了解了读文件写数据库的流程。这里我们假设,日志文件在某一行的数据异常,比如逗号分隔后不是5部分,而变成了6部分,当程序执行到此行时就会抛出
FlatFileParseException
异常,Job停止执行。我们可以从两部分解决这个问题如果我们想让程序忽略这种异常,可以修改Job的配置方式如下:。
以下场景都是基于Spring Batch 3 - 读文件写数据库这篇文件。
忽略异常
有些情况下,我们想让程序忽略掉这种异常,修改Job配置如下:
<batch:job id="readFile2DBJob">
<batch:step id="readFile2DBStep">
<batch:tasklet>
<batch:chunk reader="logReader" writer="mysqlItemWriter" commit-interval="1000" skip-limit="2000">
<!-- 发生这些异常时,直接跳过继续处理,skippable-exception-classes必须要配合skip-limit属性使用 -->
<batch:skippable-exception-classes>
<batch:include class="org.springframework.batch.item.file.FlatFileParseException"/>
</batch:skippable-exception-classes>
</batch:chunk>
</batch:tasklet>
</batch:step>
</batch:job>
skip-limit="2000"
指可以忽略的最大次数(行数),如果你不知道你有都少个异常数据,想全部忽略掉,就尽量配置的大点
Job重新执行
另外的情况,出错时让程序停止,通过手工或其它方式纠正错误后,让程序继续执行。当然,比如我们处理到2051行错误时,纠正错误数据后,我们希望Job能继续从上次失败的行继续开始处理,而不是从第1行从头再来。 这就是Spring Batch的优势,它会自动记录你上次执行成功的地址,再次执行此job时会从这里继续出来。
我们来实际模拟下,假设我们在2051行的数据为:
2016-11-18 13:31:53,/test/user/user_visit/saveVisitStep,gz_qinrong,3,/test/3.201610171_1/Android,/5.1.1/Mi-4c/4faccbe5-bb26-4a0c-94aa-d9e0805f6367
注意在Android后面多加了一个逗号,导致此行逗号分隔后有6部分,而不是正常的5部分
执行job时我们会发生如下异常:
org.springframework.batch.item.file.FlatFileParseException: Parsing error at line: 2051 in resource=[URL [file:/my/access.log]], input=[2016-11-18 13:31:53,/test/user/user_visit/saveVisitStep,gz_qinrong,3,/test/3.201610171_1/Android,/5.1.1/Mi-4c/4faccbe5-bb26-4a0c-94aa-d9e0805f6367]
at org.springframework.batch.item.file.FlatFileItemReader.doRead(FlatFileItemReader.java:183)
at org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader.read(AbstractItemCountingItemStreamItemReader.java:88)
at org.springframework.batch.core.step.item.SimpleChunkProvider.doRead(SimpleChunkProvider.java:91)
at org.springframework.batch.core.step.item.SimpleChunkProvider.read(SimpleChunkProvider.java:157)
at org.springframework.batch.core.step.item.SimpleChunkProvider$1.doInIteration(SimpleChunkProvider.java:116)
at org.springframework.batch.repeat.support.RepeatTemplate.getNextResult(RepeatTemplate.java:374)
at org.springframework.batch.repeat.support.RepeatTemplate.executeInternal(RepeatTemplate.java:215)
at org.springframework.batch.repeat.support.RepeatTemplate.iterate(RepeatTemplate.java:144)
at org.springframework.batch.core.step.item.SimpleChunkProvider.provide(SimpleChunkProvider.java:110)
at org.springframework.batch.core.step.item.ChunkOrientedTasklet.execute(ChunkOrientedTasklet.java:69)
at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:406)
at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:330)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)
at org.springframework.batch.core.step.tasklet.TaskletStep$2.doInChunkContext(TaskletStep.java:271)
at org.springframework.batch.core.scope.context.StepContextRepeatCallback.doInIteration(StepContextRepeatCallback.java:81)
at org.springframework.batch.repeat.support.RepeatTemplate.getNextResult(RepeatTemplate.java:374)
at org.springframework.batch.repeat.support.RepeatTemplate.executeInternal(RepeatTemplate.java:215)
at org.springframework.batch.repeat.support.RepeatTemplate.iterate(RepeatTemplate.java:144)
at org.springframework.batch.core.step.tasklet.TaskletStep.doExecute(TaskletStep.java:257)
at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:200)
at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:148)
at org.springframework.batch.core.job.flow.JobFlowExecutor.executeStep(JobFlowExecutor.java:64)
at org.springframework.batch.core.job.flow.support.state.StepState.handle(StepState.java:67)
at org.springframework.batch.core.job.flow.support.SimpleFlow.resume(SimpleFlow.java:169)
at org.springframework.batch.core.job.flow.support.SimpleFlow.start(SimpleFlow.java:144)
at org.springframework.batch.core.job.flow.FlowJob.doExecute(FlowJob.java:134)
at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:306)
at org.springframework.batch.core.launch.support.SimpleJobLauncher$1.run(SimpleJobLauncher.java:135)
at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50)
at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:128)
at com.me.springbatch.App.run(App.java:27)
at com.me.springbatch.file2db.File2DBMain.main(File2DBMain.java:9)
Caused by: org.springframework.batch.item.file.transform.IncorrectTokenCountException: Incorrect number of tokens found in record: expected 5 actual 6
at org.springframework.batch.item.file.transform.AbstractLineTokenizer.tokenize(AbstractLineTokenizer.java:125)
at org.springframework.batch.item.file.mapping.DefaultLineMapper.mapLine(DefaultLineMapper.java:43)
at org.springframework.batch.item.file.FlatFileItemReader.doRead(FlatFileItemReader.java:180)
... 31 more
Spring Batch 表BATCH_STEP_EXECUTION中的信息如下:
从图上可以看出,程序读了2050行,写了2000行,提交了2次(我们设定了
commit-interval="1000"
)。
如果我们不处理异常数据(错误依旧存在),再次执行下Job,肯定依旧程序会报异常,表BATCH_STEP_EXECUTION中的信息如下
从表中可以看出。第二行读到50就异常了,写了0行,也就证明了Job是从上次成功的地方(2000行)后开始执行的。
我们手工纠正2051行的数据,去掉多余的逗号,再次执行,直到Job完全执行成功。
我们可以看到,业务表中的总数据量正好=多次执行的write_count合计。日志文件中的数据被完全处理,且未有重复数据。
总结
- 执行时要把
spring-context.xml
中的以下配置注释掉,否则每次执行都重新drop并创建表,也就无法保留上次执行的job信息了。
<!--
初始化脚本,主要用来创建spring-batch运行时的数据,应用生成环境时应该去掉此配置,手工创建下对应的表
spring-batch本身job运行需要的表的创建脚本,spring-batch.jar中默认提供了各种数据库的DDL语句
<jdbc:initialize-database data-source="dataSource">
<jdbc:script location="${batch.schema.script.drop}" />
<jdbc:script location="${batch.schema.script}" />
</jdbc:initialize-database>
-->
- 两次执行Job时,
JobParameters
参数必须要一样,否则Spring Batch会认为两次执行的为不同Job。 - 从这里我们就能看出Spring Batch的灵活与强大,后面的章节我们再看看Spring Batch还有哪些优点