上篇文章中,我们了解了读文件写数据库的流程。这里我们假设,日志文件在某一行的数据异常,比如逗号分隔后不是5部分,而变成了6部分,当程序执行到此行时就会抛出 FlatFileParseException 异常,Job停止执行。我们可以从两部分解决这个问题如果我们想让程序忽略这种异常,可以修改Job的配置方式如下:。
忽略异常
有些情况下,我们想让程序忽略掉这种异常,修改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中的信息如下:
BATCH_STEP_EXECUTION表
从图上可以看出,程序读了2050行,写了2000行,提交了2次(我们设定了commit-interval="1000")。
如果我们不处理异常数据(错误依旧存在),再次执行下Job,肯定依旧程序会报异常,表BATCH_STEP_EXECUTION中的信息如下
BATCH_STEP_EXECUTION表
从表中可以看出。第二行读到50就异常了,写了0行,也就证明了Job是从上次成功的地方(2000行)后开始执行的。
我们手工纠正2051行的数据,去掉多余的逗号,再次执行,直到Job完全执行成功。
日志源文件总共508828行
BATCH_STEP_EXECUTION表
业务表
我们可以看到,业务表中的总数据量正好=多次执行的write_count合计。日志文件中的数据被完全处理,且未有重复数据。
总结
执行时要把spring-context.xml中的以下配置注释掉,否则每次执行都重新drop并创建表,也就无法保留上次执行的job信息了。
两次执行Job时,JobParameters参数必须要一样,否则Spring Batch会认为两次执行的为不同Job。
从这里我们就能看出Spring Batch的灵活与强大,后面的章节我们再看看Spring Batch还有哪些优点
附完整代码