자바17, 스프링부트3.1.3, 스프링배치5.0.3을 이용하여 코드를 작성하였습니다.
스프링 배치로 csv 파일을 읽은 후 필드를 추가하여 새로운 csv 파일을 생성하는 배치 코드입니다
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-batch'
testImplementation 'org.springframework.batch:spring-batch-test'
}
@Data
public class Player implements Serializable {
private String ID;
private String lastName;
private String firstName;
private String position;
private int birthYear;
private int debutYear;
public String toString() {
return "PLAYER:ID=" + ID + ",Last Name=" + lastName +
",First Name=" + firstName + ",Position=" + position +
",Birth Year=" + birthYear + ",DebutYear=" +
debutYear;
}
@Data // 데이터 가공 후 새로운 csv 파일에 담을 객체
public class PlayerYears implements Serializable {
private String ID;
private String lastName;
private String firstName;
private String position;
private int birthYear;
private int debutYear;
private int yearsExperience;
public PlayerYears(Player item) {
this.ID = item.getID();
this.lastName = item.getLastName();
this.firstName = item.getFirstName();
this.position = item.getPosition();
this.birthYear = item.getBirthYear();
this.debutYear = item.getDebutYear();
this.yearsExperience = LocalDateTime.now().getYear() - item.getDebutYear();
}
public String toString() {
return "PLAYER:ID=" + ID + ",Last Name=" + lastName +
",First Name=" + firstName + ",Position=" + position +
",Birth Year=" + birthYear + ",DebutYear=" +
debutYear;
}
ID,lastName,firstName,position,birthYear,debutYear
"AbduKa00,Abdul-Jabbar,Karim,rb,1974,1996",
"AbduRa00,Abdullah,Rabih,rb,1975,1999",
"AberWa00,Abercrombie,Walter,rb,1959,1982",
"AbraDa00,Abramowicz,Danny,wr,1945,1967",
"AdamBo00,Adams,Bob,te,1946,1969",
"AdamCh00,Adams,Charlie,wr,1979,2003"
public class PlayerFieldSetMapper implements FieldSetMapper<Player> {
public Player mapFieldSet(FieldSet fieldSet) {
Player player = new Player();
player.setID(fieldSet.readString(0));
player.setLastName(fieldSet.readString(1));
player.setFirstName(fieldSet.readString(2));
player.setPosition(fieldSet.readString(3));
player.setBirthYear(fieldSet.readInt(4));
player.setDebutYear(fieldSet.readInt(5));
return player;
}
}
FieldSetMapper: 특수한 타입의 필드를 변환할 때 사용
@Configuration
@RequiredArgsConstructor
public class FileDataReadWriteConfig {
//Job
@Bean
public Job fileReadWriteJob(JobRepository jobRepository, PlatformTransactionManager transactionManager) throws Exception {
return new JobBuilder("fileReadWriteJob", jobRepository)
.incrementer(new RunIdIncrementer())
.start(fileReadStep(jobRepository, transactionManager
))
.build();
}
// Step
@JobScope
@Bean
public Step fileReadStep(
JobRepository jobRepository,
PlatformTransactionManager transactionManager
) throws Exception {
return new StepBuilder("fileReadWriteStep", jobRepository)
.<Player, PlayerYears>chunk(5, transactionManager)
.reader(playerFlatFileItemReader())
.processor(playerYearsItemProcessor())
.writer(playerFlatFileItemWriter())
.build();
}
// ItemWriter
@StepScope
@Bean
public FlatFileItemWriter<PlayerYears> playerFlatFileItemWriter() {
// 데이터의 첫줄 필드명을 만들어 줌
BeanWrapperFieldExtractor<PlayerYears> fieldExtractor = new BeanWrapperFieldExtractor<>();
fieldExtractor.setNames(new String[]{"ID", "lastName", "position", "yearsExperience"});
fieldExtractor.afterPropertiesSet();
// 구분자로 ','를 사용
DelimitedLineAggregator<PlayerYears> lineAggregator = new DelimitedLineAggregator<>();
lineAggregator.setDelimiter(",");
lineAggregator.setFieldExtractor(fieldExtractor);
// 저장할 파일의 위치와 이름을 지정
FileSystemResource outputResource = new FileSystemResource("src/main/resources/players_output.txt");
return new FlatFileItemWriterBuilder<PlayerYears>()
.name("playerItemWriter")
.resource(outputResource)
.lineAggregator(lineAggregator)
.build();
}
// ItemProcessor
@StepScope
@Bean
public ItemProcessor<Player, PlayerYears> playerYearsItemProcessor() {
return new ItemProcessor<Player, PlayerYears>() {
@Override
public PlayerYears process(Player item) throws Exception {
// 데이터를 Player -> PlayerYears로 가공하여 반환
return new PlayerYears(item);
}
};
}
//ItemReader
@StepScope
@Bean
public FlatFileItemReader<Player> playerFlatFileItemReader() throws Exception {
return new FlatFileItemReaderBuilder<Player>()
.name("playerItemReader")
// 해당 위치에 있는 파일을 읽음
.resource(new FileSystemResource("src/main/resources/players.csv"))
// ','을 기준으로 데이터를 자름
.lineTokenizer(new DelimitedLineTokenizer())
// 파일의 첫 번째 줄은 스킵
.linesToSkip(1)
.fieldSetMapper(new PlayerFieldSetMapper())
.build();
}
}
JobRepository: 메타데이터 관리, 배치 수행 관련 데이터 관리
JobLauncher: Job을 실행 시켜줌
Job: 하나의 배치 작업, flow라고도 함, Step을 가져야 함
Step: Job의 세부적인 내용, 읽기- 가공 - 쓰기의 묶음으로 되어 있음