Spring Batch + Scheduler를 이용해서 기초적인 Job, Step을 등록하고 jsonplaceholder.com api를 연동해서 데이터를 일정 시간마다 불러와서 H2에 저장해보자.
블로그 예제 대부분이 JobBuilderFactory, StepBuilderFactory를 사용하는데 deprecated 되어서 현재 사용할 수 없다.
BatchConfig.java
package com.jv.batchbasic.config;
import com.jv.batchbasic.domain.Post;
import com.jv.batchbasic.repository.PostRepository;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.json.JacksonJsonObjectReader;
import org.springframework.batch.item.json.builder.JsonItemReaderBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.UrlResource;
import org.springframework.transaction.PlatformTransactionManager;
import java.net.MalformedURLException;
import java.net.URL;
@Configuration
@AllArgsConstructor
@Slf4j
public class BatchConfig {
private final PostRepository postRepository;
@Bean
public Job myJob(JobRepository jobRepository, PlatformTransactionManager transactionManager) throws MalformedURLException {
return new JobBuilder("myJob", jobRepository)
.start(myStep( jobRepository, transactionManager))
.build();
}
@Bean
public Step myStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) throws MalformedURLException {
return new StepBuilder("myStep", jobRepository)
.<Post, Post>chunk(10, transactionManager)
.reader(customItemReader())
.writer(customItemWriter())
.build();
}
@Bean
public ItemReader<Post> customItemReader() throws MalformedURLException {
String url = "https://jsonplaceholder.typicode.com/posts";
URL resource = new URL(url);
return new JsonItemReaderBuilder<Post>()
.name("jsonReader")
.resource(new UrlResource(resource))
.jsonObjectReader(new JacksonJsonObjectReader<>(Post.class))
.build();
}
@Bean
public ItemWriter<Post> customItemWriter() {
return items -> {
for (Post post : items) {
postRepository.save(post);
// return; // return;을 추가하면 1분에 10개씩 가지고 온다.
}
};
}
}
@Autowired 대신 생성자 주입으로 @AllArgsConstructor를 사용했다.
JobBuilder와 StepBuilder를 사용해 구현하고 JobRepository를 넣었다.
PlatformTransactionManager를 생략하면 에러가 발생한다.
ItemReader를 구현하지 않으면 에러가 발생한다. 읽고 출력해야 하므로 ItemReader, ItemWriter가 필요하다.
Reference
https://velog.io/@backtony/Spring-Batch-ItemReader
특정 url의 json객체를 받아오는 것이라 JacksonJsonObjectReader, new Url() 등이 사용되었다.
여기서 @EnableBatchProcessing 어노테이션을 추가하면 Batch가 작동되지도, 메타테이블이 생성되지도 않는다.
블로그들은 전부 이 과정을 필수라고 말했는데 JobBuilderFactory 방식을 쓰지 않아서일까? 하는 의문이 남는다.
Post.java
package com.jv.batchbasic.domain;
import jakarta.persistence.*;
import lombok.Getter;
@Entity
@Table
@Getter
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(length = 20, nullable = false)
private Long idx;
@Column(length = 20, nullable = false)
private Integer id;
@Column(length = 20, nullable = false)
private int userId;
@Column(length = 100, nullable = false)
private String title;
@Column(length = 1000, nullable = false)
private String body;
}
jsonplaceholder의 key값들을 잘 보고 iv들을 다 적어야 한다.
api iv들을 전부 적지 않으면 아래처럼 에러 발생.
PostRepository.java
package com.jv.batchbasic.repository;
import com.jv.batchbasic.domain.Post;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostRepository extends JpaRepository<Post, Integer> {
}
여기까지가 Batch 기본이다. 이제 H2 연동을 해보자.
H2 연동 관련은 아래 설정으로 끝.
application.properties
# set h2 properties
spring.h2.console.enabled=true
# ?? ?? ?? ?? (true-?? ??, false-?? ??)
spring.datasource.generate-unique-name=false
# /h2-console? ????? ??? ?? ??
spring.h2.console.path=/h2-console
spring.datasouce.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=test
#jpa
spring.jpa.hibernate.ddl-auto=create
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
spring.jpa.database=h2
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
#logging
logging.level.org.springframework.web=DEBUG
logging.level.org.hibernate.sql=DEBUG
logging.level.jpa=DEBUG
결과 화면
좌측에 Spring Batch를 위한 메타 테이블들을 볼 수 있다. DB 연결 시 자동으로 생성된다.
BatchSceduler.java
package com.jv.batchbasic.config;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.*;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@AllArgsConstructor
@EnableScheduling
public class BatchScheduler {
private final JobLauncher jobLauncher;
private final Job myJob;
@Scheduled(cron= "0 * * * * *")
public void runJob() {
JobParametersBuilder jobParametersBuilder = new JobParametersBuilder();
jobParametersBuilder.addLong("currentTime", System.currentTimeMillis());
try {
jobLauncher.run(myJob, jobParametersBuilder.toJobParameters());
} catch (Exception e) {
e.printStackTrace();
}
}
}
스케줄러를 처음에 BatchConfig에 설정해야 한다고 생각했는데 아래 이슈로 실패했다.
따라서 별도의 스케줄러 클래스를 작성했고, 1분마다 실행되도록 Cron 문법을 사용했다. fixedRate는 서버 재시작마다 시간 영향을 받지만, cron은 일정하다.
더 심화된 공부가 필요하지만 가장 기초적인 예제로 좋았다.
간단한 테스트를 위해서는 JPA + H2 연동이 손쉽고 빠르다.