Spring Batch - Read multiple file
tag : ReaderNotOpenException or reader must be open before it can be read
목적
Partitioner 에서 여러 파일을 읽어 한 DB에 저장시키고 싶었다.- partioner 코드 : Resource에서 .csv 파일을 모두 읽어서 DB에 저장 file:src/main/resources/*.csv 밑에는 3개의 csv 파일이 있다.
@Bean
public Partitioner partitioner() {
System.out.println(">>> partitioner");
MultiResourcePartitioner partitioner = new MultiResourcePartitioner();
Resource[] resources;
try {
resources = resourcePatternResolver.getResources("file:src/main/resources/*.csv");
//System.out.println(">>> resources : "+ resources.length);
} catch(IOException e) {
throw new RuntimeException("I/O problems when resolving the input file pattern", e);
}
partitioner.setResources(resources);
return partitioner;
}
문제
- 수정 전 reader code : ItemReader를 반환하고 sample-data.csv를 읽으라고 hard coding 되어있다. 이렇게 실행 시키니 위에 resource에서는 3개의 파일이 있으니 sample-data.csv <-resource 만 3번 읽는다..ㅠㅠ
@Bean
@StepScope
public ItemReader<Person> reader(){
FlatFileItemReader<Person> reader = new FlatFileItemReader<Person>();
reader.setResource(new FileSystemResource("sample-data.csv");
reader.setStrict(true);
reader.setLineMapper(new DefaultLineMapper<Person>() {{
setLineTokenizer(new DelimitedLineTokenizer() {{
setNames(new String[] {"firstName", "lastName"});
}});
setFieldSetMapper(new BeanWrapperFieldSetMapper<Person>() {{
setTargetType(Person.class);
}});
}});
System.out.println(">>> reader" );
return reader;
}
해결
partitioner 메소드는 resource를 잘 읽기 때문에 reader에서 여러파일을 읽으려먼 아래와 같이 수정한다.@Bean
@StepScope
public FlatFileItemReader<Person> reader(@Value("#{stepExecutionContext[fileName]}") String fileName){
FlatFileItemReader<Person> reader = new FlatFileItemReader<Person>();
System.out.println(">>> file :" + fileName.substring(5));
reader.setResource(new FileSystemResource(fileName.substring(5)));
...
return reader
- @StepScope는 reader가 매 StepExecution 마다 생성함을 지시한다. reader는 singleton 이기 때문에 매 StepExecution 마다 생성해 줘야 한다.
- reader를 생성하는 메소드는 파일경로를 담는 String fileName을 가지고 있고, 여기에 @Value annotaiton을 이용하여 fileName 이란 이름으로 stepExecutionContext를 주입한다. (fileName이란 변수명이 중요하다.) (지금 나는 stepExecutionContext에서 여러 파일을 읽기 때문에 stepExecutionContext를 사용했지만, job parameter나, jobExecutionContext도 같은 방법으로 사용한다)
- reader.setResource(new FileSystemResource(fileName))을 넣어준다.
- 마지막으로 return type을 ItemReader -> FlatFileItemReader 로 바꿔준다!!!
- 그렇지 않으면 “ReaderNotOpenException or reader must be open before it can be read” 에러가 발생한다. (이게 제일 어려웠음 ㅠㅠ) reader가 읽히지 전에 open 되어야 하고 때문에 reader는 null값이다.
원인은 이거다(이것 일 것이다..). itemReader interface안에는 read()라는 추상 메소드 하나가 있다. 파일 하나를 읽을때는 우리가 하드코딩한 (reader.setResource(new FileSystemResource(“sample-data.csv”)) 파일만 읽으면 되기때문에 read만 있는것이 문제되지 않는다.하지만 여러 파일을 읽을 때는 파일 하나를 읽고 닫은후 다시 새로운 파일을 읽어야 한다. 추상메소드 read()를 구현하는 곳에는 open() , update() close()가 없기 때문에 위와같은 에러가 발생한것이다.
반면 FlatFileItemReader에는 ResourceAwareItemReaderItemStream를 구현하고 있는데 ResourceAwareItemReaderItemStream 는 ItemStreamReader 를 상속 받고 있는데, ItemStreamReader에는 ItemStream, ItemReader를 또 상속받고있다. 그리고 ItemStream 안에는 open(), update(), close()를 구현하고 있어서 FlatFileItemReader에서 override하는 것이다!!!!
결론
문서를 제대로 읽자..ㅎㅎ 사실 내 수준에서 어려운 문제기도 했지만 문서를 제대로 읽지 않아 에러를 잡는데 더 오래걸렸다. 물론 문제를 해결하는 과정에서 많은것을 배웠지만.원인이라고 적어둔 것은 나와 팀장님의 추측임을 밝힌다. 좀 더 명확한 해답을 알고 있다면 댓글이나 이메일로 알려주시길 바란다..ㅠㅠ
하..여튼 풀었다.
참조