❗️ 자바17, 스프링부트3.1.3, 스프링배치5.0.3, MySql을 이용하여 코드를 작성하였습니다.
❗️ 가입한 회원들을 모아 한 번에 쿠폰을 발급해주는 배치 프로그램입니다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-batch'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.batch:spring-batch-test'
}
spring:
batch:
job:
name: ${job.name:dbDataJob}
enabled: true
jdbc:
initialize-schema: always
datasource:
url: jdbc:mysql://127.0.0.1:3306/batch_study?serverTimezone=Asia/Seoul
driver-class-name: com.mysql.cj.jdbc.Driver
username: cos
password: cos1234
jpa:
show-sql: true
hibernate:
ddl-auto: update # 옵션 종류는 본인이 하고 싶은 것으로 사용하면 된다.
@Entity
@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;
private String name;
private int age;
private String phone;
private String email;
@OneToMany(mappedBy = "user")
private List<SendCoupon> userCouponList = new ArrayList<>();
}
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString
@Getter
public class SendCoupon {
@Id
@GeneratedValue(strategy = IDENTITY)
private Long c_id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id")
private User user;
@CreationTimestamp
private Date issudate;
@Column(columnDefinition = "boolean default false")
private boolean isUsed;
public SendCoupon(User user) {
this.user = user;
}
}
-- user dummy data 생성
insert into batch_study.users(`name`, `age`, `phone`, `email`) values ('최형우', 39, '010-****-****', 'best1@test.com');
insert into batch_study.users(`name`, `age`, `phone`, `email`) values ('김도영', 19, '010-****-****', 'best2@test.com');
insert into batch_study.users(`name`, `age`, `phone`, `email`) values ('나성범', 33, '010-****-****', 'best3@test.com');
insert into batch_study.users(`name`, `age`, `phone`, `email`) values ('양현종', 35, '010-****-****', 'best4@test.com');
insert into batch_study.users(`name`, `age`, `phone`, `email`) values ('윤영철', 19, '010-****-****', 'best5@test.com')
select * from batch_study.users;
select * from batch_study.send_coupon;
@Configuration
@RequiredArgsConstructor
public class DbDataReadWriteConfig {
@Autowired
EntityManagerFactory em;
@Bean
public Job trMigrationJob(JobRepository jobRepository, PlatformTransactionManager transactionManager){
return new JobBuilder("dbDataJob", jobRepository)
.incrementer(new RunIdIncrementer())
.start(dbDataStep(jobRepository, transactionManager))
.build();
}
@JobScope
@Bean
public Step dbDataStep(
JobRepository jobRepository,
PlatformTransactionManager transactionManager
) {
return new StepBuilder("dbDataStep", jobRepository)
.<User, SendCoupon>chunk(5, transactionManager)
// chunk는 하나의 트랜잭션으로 보면 됨, 5개 단위로 사용
.reader(dbDataReader())
.processor(dbDataProcessor())
.writer(dbDataWriter())
.build();
}
@StepScope
@Bean
public JpaItemWriter<SendCoupon> dbDataWriter(){
return new JpaItemWriterBuilder<SendCoupon>()
.entityManagerFactory(em)
.build();
}
@StepScope
@Bean
public ItemProcessor<User, SendCoupon> dbDataProcessor(){
return new ItemProcessor<User, SendCoupon>() {
@Override
public SendCoupon process(User user) throws Exception {
return new SendCoupon(user);
}
};
}
// 1. db에서 데이터를 읽어옴
@StepScope
@Bean
public JpaPagingItemReader<User> dbDataReader(){
return new JpaPagingItemReaderBuilder<User>()
.name("dbReader")
.entityManagerFactory(em)
.queryString("select o from User o")
.pageSize(5)
.build();
}
- chunk
chunk는 간단히 말하자면 한 번의 커밋 때 처리할 데이터 개수다. 롤백 또한 chunk 단위로 작동한다. 내가 작성한 chunk 단위는 5이기 때문에 데이터 5개가 모였을 때 ItemWriter가 실행 된다. 그리고 그림처럼 ItemReader도 chuck 단위가 채워질 때까지 읽는 것을 반복한다.
🔗 chunk 이해에 도움이 되는 글