본문으로 바로가기

기존에 실습해봤던 템플릿 기반으로 만든 ItemReaderInterFace 와 거의 흡사합니다. 

 

JdbcBatchItemWriter을 사용하는 이유는

 

1. bulk insert/update/delete처리

예시) insert into person (name, age, address) values (1,2,3), (4,5,6), (7,8,9);

 

* 단건 처리가 아니기 때문에 비교적 높은 성능을 낼 수 있습니다.

 

csv & JDBC & JPA 소스 화면

@Configuration
@Slf4j
public class ItemWriterConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;
    private final DataSource dataSource;
    private final EntityManagerFactory entityManagerFactory;

    public ItemWriterConfiguration(JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory, DataSource dataSource, EntityManagerFactory entityManagerFactory) {
        this.jobBuilderFactory = jobBuilderFactory;
        this.stepBuilderFactory = stepBuilderFactory;
        this.dataSource = dataSource;
        this.entityManagerFactory = entityManagerFactory;
    }

    @Bean
    public Job itemWriterJob() throws Exception {
        return this.jobBuilderFactory.get("itemWriterJob")
                .incrementer(new RunIdIncrementer())
                .start(this.csvItemWriterStep())
                .next(this.jdbcBatchItemWriterStep())
                .next(this.jpaItemWriterStep())
                .build();
    }

    @Bean
    public Step jdbcBatchItemWriterStep() {
        return stepBuilderFactory.get("jdbcBatchItemWriterStep")
                .<Person, Person>chunk(10)
                .reader(itemReader())
                .writer(jdbcBatchItemWriter())
                .build();
    }

    @Bean
    public Step jpaItemWriterStep() throws Exception {
        return stepBuilderFactory.get("jpaItemWriterStep")
                .<Person, Person>chunk(10)
                .reader(itemReader())
                .writer(jpaItemWriter())
                .build();
    }

    private ItemWriter<Person> jpaItemWriter() throws Exception {
        JpaItemWriter<Person> itemWriter = new JpaItemWriterBuilder<Person>()
                .entityManagerFactory(entityManagerFactory)
                .usePersist(true)   // Spring Batch 4.2에서 도입된 persist 모드를 활성화
                .build();

        itemWriter.afterPropertiesSet();

        return itemWriter;
    }

    private ItemWriter<Person> jdbcBatchItemWriter() {
        JdbcBatchItemWriter<Person> itemWriter = new JdbcBatchItemWriterBuilder<Person>()
                .dataSource(dataSource)
                .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>())
                .sql("insert into person(name, age, address) values(:name, :age, :address)")
                .build();

        itemWriter.afterPropertiesSet();

        return itemWriter;
    };

    @Bean
    public Step csvItemWriterStep() throws Exception {
        return this.stepBuilderFactory.get("csvItemWriterStep")
                .<Person, Person>chunk(10)
                .reader(itemReader())
                .writer(csvFileItemWriter())
                .build();
    }

    @Bean
    public ItemWriter<Person> csvFileItemWriter() throws Exception {
        BeanWrapperFieldExtractor<Person> fieldExtractor = new BeanWrapperFieldExtractor<>();
        fieldExtractor.setNames(new String[]{"id", "name", "age", "address"});

        //매핑 설정
        DelimitedLineAggregator<Person> lineAggregator = new DelimitedLineAggregator<>();
        lineAggregator.setDelimiter(",");
        lineAggregator.setFieldExtractor(fieldExtractor);

        FlatFileItemWriter<Person> itemWriter = new FlatFileItemWriterBuilder<Person>()
                .name("csvFileItemWriter")
                .encoding("UTF-8")
                .resource(new FileSystemResource("output/test-output.csv"))
                .lineAggregator(lineAggregator)
                .headerCallback(writer -> writer.write("id,이름,나이,주소"))
                .footerCallback(writer -> writer.write("---------------\n")) // 개행문자를 통해
                .append(true) // 문자열 추가 가능.
                .build();

        itemWriter.afterPropertiesSet();

        return itemWriter;
    }

    private ItemReader<Person> itemReader() {
        return new CustomItemReader<>(getItems());
    }

    private List<Person> getItems() {
        List<Person> items = new ArrayList<>();

        for (int i = 0; i < 100; i++) {
            items.add(new Person("test name" +i, "test age", "test address"));
        }
        
        return items;
    }
}

 

resource를 통해 CSV로 작업하면 output 디렉토리가 생성되면서 해당 파일이 만들어진다.

 

jdbc를 통해 작업하면 db에서 select시 적재된것을 확인할 수 있다.

JPA를 작업하기 위해서는 Person 클래스를 엔티티로 변경한다.

@Entity
@NoArgsConstructor
@Getter
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    private String age;
    private String address;

    public Person(String name, String age, String address) {
        this(0, name, age, address);
    }

    public Person(int id, String name, String age, String address) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.address = address;
    }
}

 

jpa 설정을 추가함으로써 update 설정을 통해 merge를 체크할 수 있다.

# 2.4.2 버전에서 2.5.2로 개편 후 hikari 삭제
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/spring_batch?characterEncoding=UTF-8&serverTimezone=UTC&rewriteBatchedStatments=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
  jpa:
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        show_sql: true
#        format_sql: true
batch:
  initialize-schema: never

 

jpa 적용시 select를 확인하고 insert를 진행한다.

 

JPA를 적용 후 실행하기전에 .next(this.jdbcBatchItemWriterStep())를 주석하고 실습하길 권장한다.

바로 돌리게 될 경우 아래의 오류를 발견할 수 있습니다.

기존에 엔티티를 통해 자동생성 하지않고 Person 직접 생성하여(새로운 엔티티) merge가 존재한것으로 체크

 

Entity를 하나씩 EntityManager.persist 또는 EntityManager.merge로 insert 방식으로 나뉘며,

 

usePersist(true)를 통해 Merge와 달리 Insert쿼리만 발생한 것을 확인할 수 있습니다.

 

 

merge와 persist정도의 간단한 실습을 마쳤으나, 추후에는 아래의 블로그로 참고한것을 실습 해 볼 예정입니다.

 

참조사이트 : Spring Batch ItemWriter 성능 비교