0JUUU / spring-batch

Spring Boot 기반으로 개발하는 Spring Batch
1 stars 0 forks source link

섹션 8. 스프링 배치 청크 프로세스 활용 - ItemReader #12

Open 0JUUU opened 2 years ago

0JUUU commented 2 years ago
0JUUU commented 2 years ago

FlatFileItemReader - 개념 및 API 소개

image

Resource (인터페이스, 스프링 배치에서 여러 타입의 구현체 제공)

LineMapper

image

0JUUU commented 2 years ago

FlatFileItemReader - delimetedlinetokenizer

image

0JUUU commented 2 years ago

FlatFileItemReader - fixedlengthtokenizer

정상작동

@RequiredArgsConstructor
@Configuration
public class FlatFilesFixedLengthConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job job() {
        return jobBuilderFactory.get("batchJob")
            .start(step1())
            .next(step2())
            .build();
    }

    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
            .<String, String>chunk(3)
            .reader(itemReader())
            .writer(new ItemWriter() {
                @Override
                public void write(List items) throws Exception {
                    items.forEach(item -> System.out.println(item));
                }
            })
            .build();
    }

    public FlatFileItemReader itemReader() {
        return new FlatFileItemReaderBuilder<Customer>()
            .name("flatFile")
            .resource(new FileSystemResource(
                "절대경로")) // 파일시스템 사용
            .fieldSetMapper(new BeanWrapperFieldSetMapper<>())
            .targetType(Customer.class)
            .linesToSkip(1)
            .fixedLength()
            .addColumns(new Range(1, 5))
            .addColumns(new Range(6, 9))
            .addColumns(new Range(10, 11))
            .names("name", "year", "age")
            .build();
    }

    @Bean
    public Step step2() {
        return stepBuilderFactory.get("step2")
            .tasklet((contribution, chunkContext) -> {
                System.out.println("step2 has executed");
                return RepeatStatus.FINISHED;
            })
            .build();
    }
}
image

new Range(1) 이런식으로 열려있게 하면

            .addColumns(new Range(1))
            .addColumns(new Range(6))
            .addColumns(new Range(10))
image
0JUUU commented 2 years ago

FlatFileItemReader - Exception Handling

image

의도적으로 txt 파일에서 11자를 넘게 설정

name,year,age
user11974481
user2197547
user3197646
user4197745
user5197844
user6197943
user7198042
user8198141
user9198240

strict 값을 설정 ❌ ➡️ default : true

image

strict(false) 설정

image
0JUUU commented 2 years ago

XML StaxEventItemReader - 개념 및 API 소개

StAX 방식 (Streaming API for XML)

Spring-OXM

image

0JUUU commented 2 years ago

XML StaxEventItemReader - 예제

image

XStreamMarshaller

image

image
0JUUU commented 2 years ago

JsonItemReader

image

0JUUU commented 2 years ago

DB - Cursor & Paging 이해

Cursor Based 처리

Paging Based 처리

image

0JUUU commented 2 years ago

DB - JdbcCursorItemReader

image

  1. 커서 오픈 ➡️ DB 커넥션 연결, PrepareStatement 생성, ResultSet 생성 등의 준비작업
  2. JdbcCursorItemReader에서 데이터를 한 건씩 가져오는 작업을 Chunk Size만큼 반복
  3. 모든 배치 작업이 완료되고 더 이상 읽을 데이터가 없어지면 커넥션 종료 등의 리소스를 해제하고 작업 종료
@Bean
public ItemReader<? extends Customer> customItemReader() {
    return new JdbcCursorItemReaderBuilder<Customer>()
        .name("jdbcCursorItemReader")
        .fetchSize(CHUNK_SIZE)
        .sql("select id, firstName, lastName, birthdate from customer where firstName like ? order by lastName, firstName")
        .beanRowMapper(Customer.class)  // 매핑할 클래스 타입
        .queryArguments("A%")
        .maxItemCount(3)
        .currentItemCount(2)
        .dataSource(dataSource)
        .build();
}

A로 시작하는 firstName을 가진 데이터들 뽑아내기

image

maxItemCount 추가

           .maxItemCount(3)
image

currentItemCount 추가

        .currentItemCount(2)
image
0JUUU commented 2 years ago

DB - JpaCursorItemReader

image

  1. open하는 과정 ➡️ 엔티티 매니저 생성, 작성한 JPQL 실행시켜 DB에서 값을 받아와 ResultStream에 데이터를 담음. 실상 open 작업에서 데이터 가져오는 작업 끝
  2. JpaCursorItemReader는 ResultStream에서 iterator로 하나씩 데이터를 가져옴
  3. close에서는 EntityManager만 닫음
@Bean
public ItemReader<? extends Customer> customItemReader() {

    Map<String, Object> parameters = new HashMap<>();
    parameters.put("firstname", "A%");

    return new JpaCursorItemReaderBuilder<Customer>()
        .name("jpaCursorItemReader")
        .entityManagerFactory(entityManagerFactory)
        .queryString("select c from Customer c where firstname like :firstname")
        .parameterValues(parameters)
        .build();
}

정상 작동

image
0JUUU commented 2 years ago

DB - JdbcPagingItemReader

PagingQueryProvider

image

  1. open에서 update로 ExecutionContext에 상태정보 업데이트
  2. JdbcPagingItemReader에서 JbdcTemplate을 이용해 쿼리를 날리고 페이지 사이즈만큼 데이터를 가져옴 (커넥션 얻고 종료)
  3. 이 과정을 Chunk Size만큼 반복 ( 보통 Chunk Size와 Paging Size를 일치시킴)
  4. 더 이상 처리할 것이 없으면 종료
0JUUU commented 2 years ago

DB - JpaPagingItemReader

image

  1. open에서 EntityManager 생성
  2. JpaPagingItemReader에서 EntityManager를 사용하여 쿼리를 날리고 데이터를 가지고 옴 (커넥션 얻고 종료)
  3. 이 과정을 Chunk Size만큼 반복
  4. 더 이상 읽을 데이터가 없다면 EntityManager 종료

N+1 문제 발생

@Bean
public ItemReader<Customer> customItemReader() {
    return new JpaPagingItemReaderBuilder<Customer>()
        .name("jpaPagingItemReader")
        .entityManagerFactory(entityManagerFactory)
        .pageSize(10)
        .queryString("select c from Customer c")
        .build();
}
image

N+1 해결

.queryString("select c from Customer c join fetch c.address")
image
0JUUU commented 2 years ago

ItemReaderAdapter

image

0JUUU commented 2 years ago

🤔 Page Size와 Chunk Size는 왜 일치시켜야할까?

Chunk Size가 50이고 Page Size가 10이라고 가정했을 때, Chunk Size를 채우기 위해 5번의 Read가 발생 5번의 Read가 발생한 뒤에 itemProcessor로 넘기게 되는데 itemProcessor에서 만약 item의 LazyLoading이 발생한다면 이때가 문제

5번의 Read가 발생하는 동안 각각 트랜잭션이 초기화되기 때문 이 문제는 Page Size와 Chunk Size를 일치시키면 해결 가능