spring-batch를 이해하기에 저번 2분 튜토리얼을 부족한 것 같아서 공식문서의 getting-started guide를 하나씩 따라하며 정리해본다. 나와 같이 spring-batch에 대해 감을 잡기 위해 도움이 될까 해서 정리해본다. 정리하면서 궁금한 부분은 GPT에 물어보거나 검색하였다.
implementation 'org.springframework.batch:spring-batch-core'
runtimeOnly 'org.hsqldb:hsqldb'
Jill,Doe
Joe,Doe
Justin,Doe
Jane,Doe
John,Doe
DROP TABLE people IF EXISTS;
CREATE TABLE people (
person_id BIGINT IDENTITY NOT NULL PRIMARY KEY,
first_name VARCHAR(20),
last_name VARCHAR(20)
);
💡 hsql 의존성이 있으면 애플리케이션이 실행되면 Spring Boot가 자동으로 schema-all.sql
파일을 읽고 실행하여 테이블을 생성합니다.
package com.study.gettingstartedguide.entity;
public record Person(String firstName, String lastName) {
}
@FunctionalInterface
public interface ItemProcessor<I, O> {
@Nullable
O process(@NonNull I item) throws Exception;
}
public class PersonItemProcessor implements ItemProcessor<Person, Person> {
private static final Logger log = LoggerFactory.getLogger(PersonItemProcessor.class);
@Override
public Person process(final Person person) {
final String firstName = person.firstName().toUpperCase();
final String lastName = person.lastName().toUpperCase();
final Person transformedPerson = new Person(firstName, lastName);
log.info("Converting (" + person + ") into (" + transformedPerson + ")");
return transformedPerson;
}
}
@Configuration
public class BatchConfiguration {
@Bean
public FlatFileItemReader<Person> reader() {
return new FlatFileItemReaderBuilder<Person>()
.name("personItemReader")
.resource(new ClassPathResource("sample-data.csv"))
.delimited()
.names("firstName", "lastName")
.targetType(Person.class)
.build();
}
@Bean
public PersonItemProcessor processor() {
return new PersonItemProcessor();
}
@Bean
public JdbcBatchItemWriter<Person> writer(DataSource dataSource) {
return new JdbcBatchItemWriterBuilder<Person>()
.sql("INSERT INTO people (first_name, last_name) VALUES (:firstName, :lastName)")
.dataSource(dataSource)
.beanMapped()
.build();
}
}
각각 자세히 살펴보자
💡 객체와 인스턴스의 비교
@Component
public class JobCompletionNotificationListener implements JobExecutionListener {
private static final Logger log = LoggerFactory.getLogger(
JobCompletionNotificationListener.class);
private final JdbcTemplate jdbcTemplate;
public JobCompletionNotificationListener(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void afterJob(JobExecution jobExecution) {
if (jobExecution.getStatus() == BatchStatus.COMPLETED) {
log.info("!!! JOB FINISHED! Time to verify the results");
jdbcTemplate.query("SELECT first_name, last_name FROM people",
new DataClassRowMapper<>(Person.class))
.forEach(person -> log.info("Found <{{}}> in the database.", person));
}
}
}
@Bean
public Step step1(JobRepository jobRepository,
DataSourceTransactionManager transactionManager, FlatFileItemReader<Person> reader,
PersonItemProcessor processor, JdbcBatchItemWriter<Person> writer) {
return new StepBuilder("step1", jobRepository)
.<Person, Person> chunk(3, transactionManager)
.reader(reader)
.processor(processor)
.writer(writer)
.build();
}
@Bean
public Job importUserJob(JobRepository jobRepository, Step step1,
JobCompletionNotificationListener listener) {
return new JobBuilder("importUserJob", jobRepository)
.listener(listener)
.start(step1)
.build();
}
코드를 살펴보자
💡 @EnableBatchProcessing 어노테이션이 없는데도 jobRepository가 만들어지는게 신기하다. 어떤 과정에서 만들어지는 것인가 찾아봤는데 spring-batch-core 의존성이 있으면, @SpringBootApplication 어노테이션에 의해서 batch 관련 component들이 자동 생성된다고 한다. 스프링 부트를 사용하지 않고 스프링을 사용하면 생성되지 않을 듯 하다.