2015년 5월 19일 화요일

Spring Batch - Read multiple file

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하는 것이다!!!!

결론

문서를 제대로 읽자..ㅎㅎ 사실 내 수준에서 어려운 문제기도 했지만 문서를 제대로 읽지 않아 에러를 잡는데 더 오래걸렸다. 물론 문제를 해결하는 과정에서 많은것을 배웠지만.
원인이라고 적어둔 것은 나와 팀장님의 추측임을 밝힌다. 좀 더 명확한 해답을 알고 있다면 댓글이나 이메일로 알려주시길 바란다..ㅠㅠ
하..여튼 풀었다.

참조