I have a defined a chunk with commit-interval as 10, skip-limit as 10. A processor class manipulates a field by applying some arithmetic operations. In processor class exception occurs for one of the records (say 6th record). After this, once again records from 1 to 5 are processed, 6th is skipped, 7-10 are processed and written to a XML (a custom XML writer class). Since the processor processes 1-5 records twice, the expected field value is wrong as it is calculated twice. Can you please suggest a solution to have the processor process the records only once, skip only the failed record and write the processed records to XML?
Implemented a SkipListener with onSkipInProcess(), onSkipInRead(), onSkipInWrite(). But the output is still the same.
jobconfig.xml
<batch:job id="job">
<batch:step id="step">
<batch:tasklet>
<batch:chunk reader="itemReader" writer="itemWriter"
processor="itemProcessor" commit-interval="10" skip-limit="5" retry-limit="0" >
<batch:skippable-exception-classes>
<batch:include class="java.lang.Exception"/>
</batch:skippable-exception-classes>
<batch:listeners>
<batch:listener ref="skipListener" />
</batch:listeners>
</batch:chunk>
</batch:tasklet>
</batch:step>
</batch:job>
<bean id="itemWriter" class="a.b.XWriter" scope="step"/>
<bean id="skipListener" class="a.b.SkipListener"/>
<bean id="itemProcessor" class="a.b.XProcessor" scope="step"/>
<bean id="itemReader" class="a.b.XReader"/>
ItemReader class:
public class XReader implements ItemReader {
#Autowired
private XRepository classDao;
private List lst = new ArrayList();
private int index= 0;
public Object read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
if (lst.isEmpty()) {
lst = classDao.findAll();
}
if (index < lst.size()) {
return lst.get(index++);
} else return null;
}
}
ItemProcessor class:
public class XProcessor<T> implements ItemProcessor<T, T> {
public Object process(Object item) throws Exception {
// logic here
}
ItemWriter class:
public class XWriter <T> implements ItemWriter<T> {
public void write(List<? extends T> items) throws Exception {
// logic here to write to XML
}}
SkipListener class:
public class SkipListener<T,S> implements org.springframework.batch.core.SkipListener<T, S> {
public void onSkipInProcess(T arg0, Throwable arg1) {
}
public void onSkipInRead(Throwable arg0) {
}
public void onSkipInWrite(S arg0, Throwable arg1) {
}
}
When using ItemProcessors in a fault tolerant step, they are expected to be idempotent because there is a risk that they will be called multiple times (as seen in your example). You can read more about this in section 6.3.3 of the documentation here: http://docs.spring.io/spring-batch/reference/html/readersAndWriters.html
You need to have a listener implementation like the below one. Whenever some exception occurs it calls the corresponding method, if you want , you can handle otherwise just leave the method empty. so it will not fail the job.
Also it will not call the processor twice.
xml configuration:
<batch:listeners>
<batch:listener ref="recordSkipListener"/>
</batch:listeners>
Listener class:
public class RecordSkipListener implements SkipListener<Model> {
#Override
public void onSkipInRead(Throwable t) {
}
#Override
public void onSkipInWrite(Model item, Throwable t) {
}
#Override
public void onSkipInProcess(Model item, Throwable t) {
}
}
Related
This is my footer class:--
public class SummaryFooterCallback extends StepExecutionListenerSupport implements FlatFileFooterCallback{
private StepExecution stepExecution;
#Override
public void writeFooter(Writer writer) throws IOException {
writer.write("footer - number of items written: " + stepExecution.getWriteCount());
}
#Override
public void beforeStep(StepExecution stepExecution) {
this.stepExecution = stepExecution;
}
}
This is my xml:--
<bean id="writer" class="org.springframework.batch.item.file.FlatFileItemWriter">
<property name="resource" ref="outputResource" />
<property name="lineAggregator">
<bean class="org.springframework.batch.item.file.transform.PassThroughLineAggregator" />
</property>
<property name="headerCallback" ref="headerCopier" />
<property name="footerCallback" ref="footerCallback" />
</bean>
<bean id="footerCallback" class="org.springframework.batch.sample.support.SummaryFooterCallback"/>
Failing at stepExecution.getWriteCount() with nullpointer Exception.
No, I haven't registered callback as a listener in the step. I am new to Java and Spring Batch, referring to your book Pro Spring Batch but not able to get the solution of the assigned task.
You need to set the writer in scope step. Here you have a java based config that worked for me.
#Bean
#StepScope
public ItemStreamWriter<Entity> writer(FlatFileFooterCallback footerCallback) {
FlatFileItemWriter<Entity> writer = new FlatFileItemWriter<Entity>();
...
writer.setFooterCallback(footerCallback);
...
return writer;
}
#Bean
#StepScope
private FlatFileFooterCallback getFooterCallback(#Value("#{stepExecution}") final StepExecution context) {
return new FlatFileFooterCallback() {
#Override
public void writeFooter(Writer writer) throws IOException {
writer.append("count: ").append(String.valueOf(context.getWriteCount()));
}
};
}
I am trying to make a sample application on parallel step execution in java configuration file but get perplexed that how many files(job repository,job launcher and execution etc.) are being configured and initialized and if configured then how?
Simply I need a sample application to clarify the basics of parallel execution of steps in a job.
Here's an example of using splits via java config. In this example, flows 1 and 2 will be executed in parallel:
#Configuration
public class BatchConfiguration {
#Autowired
private JobBuilderFactory jobBuilderFactory;
#Autowired
private StepBuilderFactory stepBuilderFactory;
#Bean
public Tasklet tasklet() {
return new CountingTasklet();
}
#Bean
public Flow flow1() {
return new FlowBuilder<Flow>("flow1")
.start(stepBuilderFactory.get("step1")
.tasklet(tasklet()).build())
.build();
}
#Bean
public Flow flow2() {
return new FlowBuilder<Flow>("flow2")
.start(stepBuilderFactory.get("step2")
.tasklet(tasklet()).build())
.next(stepBuilderFactory.get("step3")
.tasklet(tasklet()).build())
.build();
}
#Bean
public Job job() {
return jobBuilderFactory.get("job")
.start(flow1())
.split(new SimpleAsyncTaskExecutor()).add(flow2())
.end()
.build();
}
public static class CountingTasklet implements Tasklet {
#Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
System.out.println(String.format("%s has been executed on thread %s", chunkContext.getStepContext().getStepName(), Thread.currentThread().getName()));
return RepeatStatus.FINISHED;
}
}
}
Suppose you have steps, A,B1,B2,B3,C. You want to run B1,B2 & B3 in parallel. You first need to create sub-flows for them and then add to one flow with SimpleAsyncTaskExecutor():
#Bean
public Job job()
{
final Flow flowB1 = new FlowBuilder<Flow>("subflowb1").from(stepb1()).end();
final Flow flowB2 = new FlowBuilder<Flow>("subflowb2").from(stepb2()).end();
final Flow flowB3 = new FlowBuilder<Flow>("subflowb3").from(stepb3()).end();
final Flow splitFlow = new FlowBuilder<Flow>("splitFlow")
.start(flowB1)
.split(new SimpleAsyncTaskExecutor())
.add(flowB2, flowB3).build();
return jobBuilderFactory
.flow(stepA())
.next(splitFlow)
.next(stepC())
.end()
.build();
}
here is the basic parallel step execution on different data set, basically you have to provide a Partitioner which will create seprate context for each step and based on context you can work on its data set.
<batch:job id="myJob" job-repository="jobRepository">
<batch:step id="master">
<batch:partition step="step1" partitioner="stepPartitioner ">
<batch:handler grid-size="4" task-executor="taskExecutor"/>
</batch:partition>
</batch:step>
</batch:job>
<batch:step id="step1">
<batch:tasklet>
<batch:chunk reader="myReader" processor="myProcessor" writer="myWriter"
commit-interval="10"/>
</batch:tasklet>
</batch:step>
public class stepPartitioner implements Partitioner {
#Autowired
DaoInterface daoInterface;
#Override
public Map<String, ExecutionContext> partition(int i) {
Map<String, ExecutionContext> result = new HashMap<>();
List<String> keys= daoInterface.getUniqueKeyForStep();
for(String key: keys){
ExecutionContext executionContext = new ExecutionContext();
executionContext.putString("key", key);
result.put(key,executionContext);
}
return result;
}
}
My batch job will generate 2 text files with string format per line. I created a reader
<bean id="myMultiResourceReader"
class=" org.springframework.batch.item.file.MultiResourceItemReader">
<property name="resources" value="file:D:/MY/sample/*.txt" />
</bean>
<bean id="myFinalWriter" class="org.springframework.batch.item.file.FlatFileItemWriter"
scope="step">
<property name="resource" value="${test.file3}" />
<property name="lineAggregator">
<bean
class="org.springframework.batch.item.file.transform.PassThroughLineAggregator" />
</property>
<property name="footerCallback" ref="myFinalCustomItemWriter" />
<property name="headerCallback" ref="myFinalCustomItemWriter" />
</bean>
<bean id="myFinalCustomItemWriter" class="my.process.MyWriter"
scope="step">
<property name="delegate" ref="myFinalWriter" />
<property name="stepContext" value="#{stepExecution.stepName}" />
</bean>
I was getting this error:
Caused by: org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'com.sun.proxy.$Proxy68 implementing org.springframework.batch.item.file.ResourceAwareItemWriterItemStream,org.springframework.beans.factory.InitializingBean,org.springframework.batch.item.ItemStreamWriter,org.springframework.batch.item.ItemStream,org.springframework.aop.scope.ScopedObject,java.io.Serializable,org.springframework.aop.framework.AopInfrastructureBean,org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised' to required type 'org.springframework.batch.item.file.FlatFileItemWriter' for property 'delegate'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [com.sun.proxy.$Proxy68 implementing org.springframework.batch.item.file.ResourceAwareItemWriterItemStream,org.springframework.beans.factory.InitializingBean,org.springframework.batch.item.ItemStreamWriter,org.springframework.batch.item.ItemStream,org.springframework.aop.scope.ScopedObject,java.io.Serializable,org.springframework.aop.framework.AopInfrastructureBean,org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised] to required type [org.springframework.batch.item.file.FlatFileItemWriter] for property 'delegate': no matching editors or conversion strategy found
Basically I just want to combine two plain file, and append the total count at footer. Then delete away the both input file. Can help?
MyWriter.java
public class MyWriter implements ItemWriter<String>, FlatFileFooterCallback, FlatFileHeaderCallback, ItemStream{
private static Logger log = Logger.getLogger(MyWriter.class);
private FlatFileItemWriter<String> delegate;
private int recordCount = 0;
private String stepContext;
public void writeFooter(Writer writer) throws IOException {
writer.write("#" + recordCount);
}
public void writeHeader(Writer writer) throws IOException {
writer.write("#" + StringUtil.getSysDate());
}
public void setDelegate(FlatFileItemWriter<String> delegate) {
this.delegate = delegate;
}
public void write(List<? extends String> list) throws Exception {
int chunkRecord = 0;
for (String item : list) {
chunkRecord++;
}
delegate.write(list);
recordCount += chunkRecord;
}
public void close() throws ItemStreamException {
this.delegate.close();
}
public void open(ExecutionContext arg0) throws ItemStreamException {
this.delegate.open(arg0);
}
public void update(ExecutionContext arg0) throws ItemStreamException {
this.delegate.update(arg0);
}
public void setStepContext(String stepContext) {
this.stepContext = stepContext;
}
}
As Luca Basso Ricci already pointed out, the problem is your delegate definition in MyWriter. Since Spring creates proxies for it beans, it will not recognize your FlatFileItemReader as an actual instance of FlatFileItemWriter and, therefore, the setDelegate(FlatFileItemWriter delegate) will fail.
Use an ItemStreamWriter in MyWriter. As you see in the exception message, the created proxy does provide this interface. Hence, it can be inserted
This will solve the delegation to write, open, close, and update method. In order to write the header and footer, you need to implement a HeaderCallback and FooterCallback and set it directly in the definition of your FlatFileItemWriter.
Implementing the HeaderCallback is not a problem since you only set the systemdate.
As FooterCallback, make your own Bean. Use it in the FlatFileItemWriter to write the footer. Add an "increaseCount" method to it and use it in your MyWriter Bean to increase the written count.
public void write(List<? extends String> list) throws Exception {
myFooterCallback.increaseCount(list.size());
delegate.write(list);
}
Another possible option would be to directly extend MyWriter from FlatFileItemWriter:
public class MyWriter extends FlatFileItemWriter<String> implements FlatFileFooterCallback, FlatFileHeaderCallback{
private static Logger log = Logger.getLogger(MyWriter.class);
private int recordCount = 0;
private String stepContext;
public void writeFooter(Writer writer) throws IOException {
writer.write("#" + recordCount);
}
public void writeHeader(Writer writer) throws IOException {
writer.write("#" + StringUtil.getSysDate());
}
public void afterPropertiesSet() {
setFooterCallback(this);
setHeaderCallback(this);
super.afterPropertiesSet();
}
public void write(List<? extends String> list) throws Exception {
super.write(list);
recordCount += list.size();
}
}
Configuration in your XML would look like this:
<bean id="myFinalCustomItemWriter" class="my.process.MyWriter" scope="step">
<property name="resource" value="${test.file3}" />
<property name="lineAggregator">
<bean class="org.springframework.batch.item.file.transform.PassThroughLineAggregator" />
</property>
<property name="stepContext" value="#{stepExecution.stepName}" />
</bean>
I have defined a footercallback , but the footer info is not being written to the File.
Here is the config and code. Am i missing something?
The afterstep gets called and the WriteCount is being written to the log but not to the file.
The job def:
<job id="sodfeed" job-repository="tplJobRepository" xmlns="http://www.springframework.org/schema/batch">
<step id="readWriteBalances">
<tasklet>
<chunk reader="balancesReader" writer="balancesWriter" commit-interval="100" >
</chunk>
<listeners>
<listener ref="tplBatchFooterCallback" />
<listener ref="tplBatchFailureListener" />
</listeners>
</tasklet>
</step>
</job>
public class FooterCallback extends StepExecutionListenerSupport implements FlatFileFooterCallback{
private StepExecution stepExecution;
public void writeFooter(Writer writer) throws IOException {
writer.write("EOF" + stepExecution.getWriteCount());
System.out.println("**************************EOF" + stepExecution.getWriteCount());
}
public ExitStatus afterStep(StepExecution stepExecution) {
ExitStatus returnStatus = stepExecution.getExitStatus();
logger.info("Number of records written:"+stepExecution.getWriteCount());
return returnStatus;
}
}
Is tplBatchFooterCallback correctly injected into your FlatFileItemWriter? Listeners and callback are used in a different way.
Lookup official javadoc.
I have a simple item writer looks like this:
public class EntityItemWriter<T> implements ItemWriter<T>, InitializingBean {
private String name;
#Override
public void write(List<? extends T> items) throws Exception {
//writes to db
}
#Override
public void afterPropertiesSet() throws Exception {
Assert.hasLength(name); //assertion fails
}
public void setName(String name) {
this.name = name;
}
}
and my job-definition.xml has a bean like this:
<bean id="EntityItemWriter" class="com.example.EntityItemWriter" scope="step">
<property name="name" value="someRandomString" />
</bean>
When the batch job is in writing step, name property of EntityItemWriter is not set to "someRandomString" and stays as null. Is there anything that I am missing?
spring-batch version: 2.1.0.M3
spring version: 3.1.0.RELEASE