I am using Spring Batch to write multiple reports. Requirement is i will get records with BranchId and name. I need to create one file for each branchId and write respective data into that file along with some header and footer.
Example:
Student A = new Student("A",1);
Student B = new Student("B",2);
Student C = new Student("C",1);
Student D = new Student("D",4);
In this case it should create total 3 files
file1-->1.txt(with A,C)
file2-->2.txt(with B)
file3-->4.txt(with D))
.
I am using ClassifierCompositeItemWriter to create / reuse the FlatFileItemWriter based on data(id in this case) & able to create the files successfully. For header and footer - using callbacks at writer level. Generated files are having only HEADER and DATA. But somehow FOOTER is not at all getting executed.
Looks like some issues with File closing before Footer or issue with usage of STEP SCOPE.
Can someone help me in getting the FOOTER called.
here is the code.
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.file.FlatFileItemWriter;
import org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor;
import org.springframework.batch.item.file.transform.DelimitedLineAggregator;
import org.springframework.batch.item.support.ClassifierCompositeItemWriter;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.classify.Classifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
@Configuration
@EnableBatchProcessing
public class MyJob3 {
public static void main(String[] args) throws Exception {
ApplicationContext context = new AnnotationConfigApplicationContext(MyJob3.class);
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
Job job = context.getBean(Job.class);
jobLauncher.run(job, new JobParameters());
}
@Bean
public Job job(JobBuilderFactory jobs, StepBuilderFactory steps) {
return jobs.get("job").start(steps.get("step").<Student, Student>chunk(5)
.reader(itemReader())
.writer(getStudentItemWriter(itemWriterClassifier()))
.build())
.build();
}
@Bean
@StepScope
public ItemReader<Student> itemReader() {
Student A = new Student("A", 1);
Student B = new Student("B", 2);
Student C = new Student("C", 1);
Student D = new Student("D", 4);
Student E = new Student("E", 4);
return new ListItemReader<>(Arrays.asList(A,B,C,D,E));
}
Map<Integer, FlatFileItemWriter<Student>> map = new HashMap<>();
@Bean
@StepScope
public ClassifierCompositeItemWriter<Student> getStudentItemWriter(Classifier<Student, ItemWriter<? super Student>> classifier) {
ClassifierCompositeItemWriter<Student> compositeItemWriter = new ClassifierCompositeItemWriter<>();
compositeItemWriter.setClassifier(classifier);
return compositeItemWriter;
}
@Bean
@StepScope
public Classifier<Student, ItemWriter<? super Student>> itemWriterClassifier() {
return student -> {
System.out.println("Branch Id ::" + student.getBranchId() + " and Student Name" + student.getName());
if (map.containsKey(student.getBranchId())) {
FlatFileItemWriter<Student> result = map.get(student.getBranchId());
System.out.println("Exising Writer object ::" + result);
return result;
}
String fileName ="Branch_Info_" + student.getBranchId() + ".txt";
BeanWrapperFieldExtractor<Student> fieldExtractor = new BeanWrapperFieldExtractor<>();
fieldExtractor.setNames(new String[] { "branchId", "name" });
DelimitedLineAggregator<Student> lineAggregator = new DelimitedLineAggregator<>();
lineAggregator.setFieldExtractor(fieldExtractor);
FlatFileItemWriter<Student> flatFileItemWriter = new FlatFileItemWriter<>();
flatFileItemWriter.setResource(new FileSystemResource(fileName));
flatFileItemWriter.setAppendAllowed(true);
flatFileItemWriter.setLineAggregator(lineAggregator);
System.out.println("Writing header...");
flatFileItemWriter.setHeaderCallback(writer -> writer.write("Header"));
System.out.println("Writing Footer...");
flatFileItemWriter.setFooterCallback(writer -> writer.write("Footer"));
System.out.println("Writing done...");
flatFileItemWriter.open(new ExecutionContext());
map.put(student.getBranchId(), flatFileItemWriter);
System.out.println("New Writer object ::" + flatFileItemWriter);
return flatFileItemWriter;
};
}
}
In that case, you need to:
select distinct(id) from table
for example or a similar mechanism depending on you input dataItemWriter
beans and register them as streams in your step.The following is an example based on your use case: given a list of students in different groups, the idea is to write them in different files based on their group. Here is a tasklet that pre-calculates the distinct groups and creates/registers item writers dynamically in the application context:
Once that in place, a second step loads those item writers from the application context at runtime and registers them as delegates in a
ClassifierCompositeItemWriter
:I have a complete example here: sample app for SO67604628. Please refer to this guide to see how to checkout a single folder (if you don't want to clone the entire repo). The sample generates 3 files with students grouped by groupId. Note how headers/footers are correctly generated since delegate writers are registered as streams in the step.