Spring Batch : JpaItemReader + @StepScope NullPointException

    @Configuration
    @RequiredArgsConstructor
    public class SimpleTestConfig {
    
        private final JobBuilderFactory jobBuilderFactory;
        private final StepBuilderFactory stepBuilderFactory;
        private final EntityManagerFactory emf;
        private final DataSource dataSource;
        private AtomicLong myId = new AtomicLong();
    
    
    
        @Bean
        public Job batchJob200() {
            return jobBuilderFactory.get("partitioningJob")
                    .incrementer(new RunIdIncrementer())
                    .start(slaveStep())
                    .build();
        }
    
        @Bean
        public Step slaveStep() {
            return stepBuilderFactory.get("slaveStepMaster")
                    .<Customer, Customer2>chunk(1000)
    //                .reader(pagingItemReader())
                    .reader(batchReader())
                    .writer(batchWriter())
                    .processor(batchProcessor())
                    .build();
        }
    
        @Bean
        @StepScope
        public ItemProcessor<? super Customer, ? extends Customer2> batchProcessor() {
            System.out.println("itemProcessor Here");
            return (ItemProcessor<Customer, Customer2>) item -> Customer2.builder()
                    .id(myId.incrementAndGet())
                    .birthDate(item.getBirthDate())
                    .firstName(item.getFirstName())
                    .lastName(item.getLastName())
                    .build();
        }
    
        @Bean
        @StepScope
        public ItemWriter<? super Customer2> batchWriter() {
            return new JdbcBatchItemWriterBuilder<Customer2>()
                    .sql("INSERT INTO Customer2(customer2_id, birth_date, first_name, last_name) values (:id, :birthDate, :firstName, :lastName)")
                    .dataSource(dataSource)
                    .beanMapped()
                    .build();
        }
    
        @Bean
        @StepScope
        public ItemReader<? extends Customer> batchReader() {
            return new JpaPagingItemReaderBuilder<Customer>()
                    .name("partitionStepJpaReader")
                    .currentItemCount(0)
                    .entityManagerFactory(emf)
                    .maxItemCount(1000)
                    .queryString("select c from Customer c")
                    .build();
        }
    
        @Bean
        @StepScope // 앞쪽 강의 봐야함.
        public JdbcPagingItemReader<Customer> pagingItemReader() {
    
            System.out.println("Target Created");
    
            HashMap<String, Order> sortKeys = new HashMap<>();
            sortKeys.put("customer_id", Order.ASCENDING);
    
            return new JdbcPagingItemReaderBuilder<Customer>()
                    .name("pagingBuilder")
                    .dataSource(dataSource)
                    .fetchSize(1000)
                    .beanRowMapper(Customer.class)
                    .selectClause("customer_id, first_name, last_name, birth_date")
                    .fromClause("from customer")
    //                .whereClause("where customer_id >= " + minValue + " and customer_id <= " + maxValue)
                    .sortKeys(sortKeys)
                    .build();
        }
    
    
        @Bean
        public Partitioner partitioner() {
            SimplePartitioner simplePartitioner = new SimplePartitioner();
            simplePartitioner.partition(4);
            return simplePartitioner;
        }
    
    
    }

    다음과 같이 코드를 작성했다. 실행 도준에 Null Pointer Exception이 발생했다.

    확인해보니 JpaPagingItemReader.doReadPage() 메서드를 실행하는 도중 NullPointerException이 발생했다. 처음에 @StepScope 적용하면서 JpaPagingItemReader 프록시에 타겟값이 제대로 전달되지 않을까?를 먼저 의심했다. 

    그런데 뒷쪽 프록시에서 값을 불러오는 것을 보니 빈을 정상적으로 불러왔고, invoke를 통해서 정상적으로 실행하는 것을 알게 되었다. 

    JpaItemPaginReader.doReadPage()

    와보니 JpaItemPaginReader 타겟값은 제대로 등록이 되었는데, entity Manager가 없는 것을 알 수 있었다.

    entityManager는 doOpen() 메서드를 통해서 읽어와져야한다. 그런데 doOpen()을 실행하지 않기 때문에 EntityManager가 주입이 되지 않아 NullPointerException이 발생하는 것 같다. 

     

    문제 해결 

    Stream.open을 통해서 열어줘야하는데, @StepScope를 확인해보니 SimpleProvider에서 Open하는 시점에 전달되는 ItemSteam이 1개도 없어서, JpaItemReader를 위한 셋팅이 전혀 없는 것을 알 수 있었다. 삽질을 어마어마하게 했는데, 찾아보니 이런 문제가 있었다.

    ItemReader는 doOpen() 메서드가 없다. 나는 JpaItemReader를 ItemReader 타입으로 반환하고 있었는데, 그러면 JpaItemReader는 read() 메서드만 가지게 되는 셈이다.

    그렇지만 JpaItemReader는 ItemStream도 구현했기 때문에 doOpen() 메서드를 가져야 한다. 그런데 나는 ItemReader로 반환했기 때문에 그 과정에서 JpaItemReader에서 EntityManager가 적절히 공급되지 못하고 있었던 것이다.

     

    문제 해결 코드 수정! 

    // 문제 코드
    public ItemReader<Customer> pagingItemReader2(){}
    
    // 해결 코드
    public JpaPagingItemReader<Customer> pagingItemReader2(){}

     ItemStream까지 같이 구현한 JpaPagingItemReader 타입으로 반환하면 문제가 해결된다. 

     

    댓글

    Designed by JB FACTORY