上篇文章中这橙,我們了解了讀文件寫數(shù)據(jù)庫的流程。這里我們假設(shè)导披,日志文件在某一行的數(shù)據(jù)異常屈扎,比如逗號(hào)分隔后不是5部分,而變成了6部分撩匕,當(dāng)程序執(zhí)行到此行時(shí)就會(huì)拋出
FlatFileParseException
異常鹰晨,Job停止執(zhí)行。我們可以從兩部分解決這個(gè)問題如果我們想讓程序忽略這種異常滑沧,可以修改Job的配置方式如下:并村。
以下場景都是基于Spring Batch 3 - 讀文件寫數(shù)據(jù)庫這篇文件巍实。
忽略異常
有些情況下滓技,我們想讓程序忽略掉這種異常,修改Job配置如下:
<batch:job id="readFile2DBJob">
<batch:step id="readFile2DBStep">
<batch:tasklet>
<batch:chunk reader="logReader" writer="mysqlItemWriter" commit-interval="1000" skip-limit="2000">
<!-- 發(fā)生這些異常時(shí)棚潦,直接跳過繼續(xù)處理,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"
指可以忽略的最大次數(shù)(行數(shù))令漂,如果你不知道你有都少個(gè)異常數(shù)據(jù),想全部忽略掉丸边,就盡量配置的大點(diǎn)
Job重新執(zhí)行
另外的情況叠必,出錯(cuò)時(shí)讓程序停止,通過手工或其它方式糾正錯(cuò)誤后妹窖,讓程序繼續(xù)執(zhí)行纬朝。當(dāng)然,比如我們處理到2051行錯(cuò)誤時(shí)骄呼,糾正錯(cuò)誤數(shù)據(jù)后共苛,我們希望Job能繼續(xù)從上次失敗的行繼續(xù)開始處理,而不是從第1行從頭再來蜓萄。 這就是Spring Batch的優(yōu)勢隅茎,它會(huì)自動(dòng)記錄你上次執(zhí)行成功的地址,再次執(zhí)行此job時(shí)會(huì)從這里繼續(xù)出來嫉沽。
我們來實(shí)際模擬下辟犀,假設(shè)我們在2051行的數(shù)據(jù)為:
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后面多加了一個(gè)逗號(hào),導(dǎo)致此行逗號(hào)分隔后有6部分绸硕,而不是正常的5部分
執(zhí)行job時(shí)我們會(huì)發(fā)生如下異常:
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次(我們設(shè)定了
commit-interval="1000"
)跃捣。
如果我們不處理異常數(shù)據(jù)(錯(cuò)誤依舊存在)漱牵,再次執(zhí)行下Job,肯定依舊程序會(huì)報(bào)異常疚漆,表BATCH_STEP_EXECUTION中的信息如下
從表中可以看出酣胀。第二行讀到50就異常了,寫了0行娶聘,也就證明了Job是從上次成功的地方(2000行)后開始執(zhí)行的闻镶。
我們手工糾正2051行的數(shù)據(jù),去掉多余的逗號(hào)丸升,再次執(zhí)行铆农,直到Job完全執(zhí)行成功。
我們可以看到狡耻,業(yè)務(wù)表中的總數(shù)據(jù)量正好=多次執(zhí)行的write_count合計(jì)墩剖。日志文件中的數(shù)據(jù)被完全處理,且未有重復(fù)數(shù)據(jù)夷狰。
總結(jié)
- 執(zhí)行時(shí)要把
spring-context.xml
中的以下配置注釋掉岭皂,否則每次執(zhí)行都重新drop并創(chuàng)建表,也就無法保留上次執(zhí)行的job信息了沼头。
<!--
初始化腳本爷绘,主要用來創(chuàng)建spring-batch運(yùn)行時(shí)的數(shù)據(jù),應(yīng)用生成環(huán)境時(shí)應(yīng)該去掉此配置进倍,手工創(chuàng)建下對應(yīng)的表
spring-batch本身job運(yùn)行需要的表的創(chuàng)建腳本土至,spring-batch.jar中默認(rèn)提供了各種數(shù)據(jù)庫的DDL語句
<jdbc:initialize-database data-source="dataSource">
<jdbc:script location="${batch.schema.script.drop}" />
<jdbc:script location="${batch.schema.script}" />
</jdbc:initialize-database>
-->
- 兩次執(zhí)行Job時(shí),
JobParameters
參數(shù)必須要一樣猾昆,否則Spring Batch會(huì)認(rèn)為兩次執(zhí)行的為不同Job陶因。 - 從這里我們就能看出Spring Batch的靈活與強(qiáng)大,后面的章節(jié)我們再看看Spring Batch還有哪些優(yōu)點(diǎn)