인프런 커뮤니티 질문&답변

jungkh405님의 프로필 이미지
jungkh405

작성한 질문수

스프링 배치

Repositoryitemreader로 조회한 데이터 dirty check 여부 질문

작성

·

825

·

수정됨

0

안녕하세요~ 강사님 강의를 보고 스프링 배치 입문해서 열심히 이론부터 실습까지 잘 배우고 있습니다.
몇 가지 궁금한 점이 있어서 이렇게 질문을 드리게 되었습니다.

1.

ItemReader 중에 Repositoryitemreader라는 구현체가 있어서 이게 Spring Data JPA를 지원해 줘서 사용해 보았는데요. 이해가 가지 않는 부분이 있어서 질문드리고자 합니다.

    @Bean(name = STEP_NAME)
    @JobScope
    public Step step1(@Value("#{jobParameters[chunkSize]}") Long chunkSize) {
        return stepBuilderFactory.get(STEP_NAME)
                .<HistoryEntity, HistoryEntity>chunk(chunkSize.intValue())
                .reader(itemReader(null))
                .processor(itemProcessor())
                .writer(itemWriter())
                .build();
    }

  @Bean(name = JOB_NAME + "_reader")
    @StepScope
    public RepositoryItemReader<HistoryEntity> itemReader(@Value("#{jobParameters[chunkSize]}") Long chunkSize) {

        LocalDateTime now = LocalDateTime.now();

        return new RepositoryItemReaderBuilder<HistoryEntity>()
                .name(JOB_NAME + "_reader")
                .repository(HistoryPagingCrudRepository)
                .methodName("findByLeaveDtLessThanEqual")
                .pageSize(chunkSize.intValue())
                .arguments(List.of(now))
                .sorts(Collections.singletonMap("leaveDt", Sort.Direction.DESC))
                .build();
    }

위와 같은 step과 특정 날짜의 데이터를 조회하는 ItemReader를 구현한 후 아래 ItemWriter 에서

 @Bean(name = JOB_NAME + "_writer")
    @StepScope
    public ItemWriter<HistoryEntity> itemWriter() {

        return item -> {
            item.forEach(historyEntity -> {
                       // Id 값을 null로 만드는 메소드
                       historyEntity.updateRemoveId(); 
                    }
            );
        };
    }

Entity의 값을 변경하게 되면 당연히 하나의 chunk 단위에서는 하나의 트랜잭션 이기 때문에 JPA의 dirty check로 인한 Id 값을 지우는 update 쿼리가 나갈거라고 생각했는데 그렇지 않더라고요.

다만,

 @Bean(name = JOB_NAME + "_writer")
    @StepScope
    public ItemWriter<HistoryEntity> itemWriter() {

        return item -> {
            item.forEach(historyEntity -> {
                       // Id 값을 null로 만드는 메소드
                       historyEntity.updateRemoveId(); 
                       historyPagingCrudRepository.save(historyEntity) 
                    }
            );
        };
    }

위 코드 historyPagingCrudRepository.save(historyEntity) 를 추가하면 merge가 진행되면서 update 쿼리가 실행되긴 하지만 select 쿼리가 한 번 더 실행돼서 비효율적인거 같다는 생각이 들었습니다.

질문을 정리하자면 ItemReader에서 조회한 Entity가 ItemWriter에서도 영속 상태이기 때문에 Dirty Checking 대상이라고 생각했는데 그렇지 않은 이유가 무엇인가요??

혹시 ItemReader 로 데이터 조회 후 ChunkProvider를 통해 Chunk<I> 를 itemProcessor 또는 ItemWriter로 전달하는 과정에서 준영속 상태가 되는것일까요??

 

 

2.

jpaPagingItemReader 으로 데이터 조회 후, ItemWriter 에서 jpaPagingItemReader 의 where 절에 해당하는 컬럼의 값을 수정했을 때 offset(page)로 인해 일부 데이터가 읽히지 않는 문제가 있는것으로 알고있습니다. 이 경우 getPage() 메소드를 override 해서 항상 offset을 0으로 고정시키는 방법으로 해결이 가능한데 Repositoryitemreader 구현체 사용 시 어떤식으로 해결이 가능할까요??

답변 1

0

정수원님의 프로필 이미지
정수원
지식공유자

일단 제가 Repositoryitemreader 를 사용해 보지 못해서 정확한 원인은 테스트 해 봐야 될 것 같습니다

일반적으로 JPA 로 배치 처리를 할 경우 JPA 용 트랜잭션이 기본적으로 작동하기 때문에 영속상태가 유지되는 것은 맞습니다

영속 상태 유지는 트랜잭션 안에서 가능한고 더티체킹 반영은 쓰기 지연 SQL 항목들이 트랜잭션이 커밋되는 시점에 이루어지기 때문에 이 두가지가 제대로 처리되고 있는지를 확인해 봐야 합니다

또한 ItemReader 와 ItemWriter 에서 사용하는 EntityManager 가 동일한 트랜잭션을 계속 타고 있는지도 봐야 합니다

제가 보기에는 ItemWriter 로 넘어온 엔터티가 계속 영속상태인지 명확하지 않는 것 같습니다

즉 Repositoryitemreader 의 영속상태가 초기화가 된 것 같습니다

2번 질문은 Repositoryitemreader 를 사용해 봐야 알 것 같습니다

깃 소스 공유 가능하시면 제가 테스트 해 보도록 하겠습니다

 

jungkh405님의 프로필 이미지
jungkh405

작성한 질문수

질문하기