Spring Batch

yuKeon·2023년 9월 16일
0
post-thumbnail

1. Spring Batch란?

1.1. 개념

Spring Batch는 대용량 레코드에 최적화 및 파티셔닝 기술로 고성능 작업을 가능하게 하는 기능을 제공한다.

Spring Batch에서 Batch가 실패하면 처음부터가 아닌 실패한 지점부터 실행한다.

또한, 중복 실행을 막기 위해 성공한 이력이 있는 Batch는 동일한 Parameters로 실행하는 것을 방지한다.

1.2. 용어

Job

  • 배치처리 과정을 나타내는 객체
  • 배치처리 과정의 최상위 계층

JobInstance

  • Job의 실행 단위
  • Job을 실행하면 하나의 JobInstance가 생성된다.

JobParameters

  • JobInstance를 구별과 JobInstance의 매개변수 역할
  • String, Double, Long, Date 4가지 형식만을 지원

JobExecution

  • JobInstance 실행 시도의 객체
  • JobInstance 실행과 관련된 정보 (시작 시간, 종료 시간, 생성 시간 등)
  • JobInstance가 실패해서 재시작하면 JobExecution 값은 새로 생김

Step

  • Job의 배치처리를 정의하고 각 단계를 캡슐화
  • Job은 최소 한 개 이상의 Step을 가짐

StepExecution

  • Step 실행 시도의 객체

ExecutionContext

  • Job에서 데이터를 공유 할 수 있는 데이터 저장소

JobRepository

  • 모든 배치 처리 정보를 담고있는 저장소

JobLauncher

  • Job과 JobParameters로 Job을 실행하는 객체

ItemReader

  • Step에서 Item을 읽어오는 인터페이스

ItemWriter

  • 처리된 데이터를 Writer 할 때 사용
  • 기본적으로 Item을 Chunk로 묶어 처리

1.3. 구성

https://velog.io/@gillog/Batch-Application-생성하기

  • 하나의 Job에는 데이터를 처리하는 하나 이상의 Step으로 구성된다.
  • Step은 Reader, Processor, Writer로 이뤄진다.

2. 구현

2.1. 의존성 설치 (build.gradle)


dependencies {
		...
    // Batch
    implementation 'org.springframework.boot:spring-boot-starter-batch'
		...
}

2.2. 설정 파일 추가 (BatchConfig.java)

@Slf4j
@Configuration
@EnableBatchProcessing
@RequiredArgsConstructor
public class BatchConfig {}
  • 배치 기능 활성화를 위해 @EnableBatchProcessing 추가

2.3. 배치 작업 추가 (BatchConfig.java)

@Slf4j
@Configuration
@EnableBatchProcessing
@RequiredArgsConstructor
public class BatchConfig {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;
    private final ProblemRepository problemRepository;

    @Bean
    public Job removeDeletedProblemJob() {
        Job job = jobBuilderFactory.get("job") **// (1)**
                .start(removeDeletedProblemStep()) **// (2)**
                .build();
        return job;
    }

    @Bean
    public Step removeDeletedProblemStep() {
        return stepBuilderFactory.get("step")
                .tasklet((contribution, chunkContext) -> { **// (3)**
                    log.info("[문제 완전 삭제] step start");
                    long deletedCount = problemRepository.deleteSoftDeletedProblem(); **// (4)**
                    log.info("[문제 완전 삭제] 삭제된 문제 : {} 문제", deletedCount);
                    return RepeatStatus.FINISHED;
                })
                .build();
    }
}

(1) : JobBuilderFactory를 사용하여 Job을 정의

(2) : Job이 시작되면 removeDeletedProblemStep() 메서드를 실행

(3) : tasklet은 Step의 작업을 정의

(4) : soft delete된 문제를 완전히 삭제한 다음 몇 문제를 삭제했는지 로그로 출력

2.4. 스케쥴러 추가 (BatchScheduler.java)

@Component
@RequiredArgsConstructor
public class BatchScheduler {
    private final JobLauncher jobLauncher;
    private final Job problemDeleteJob;

    @Scheduled(cron = "0 0 0 * * ?") // 자정에 실행
    void deleteProblemJob() throws JobExecutionAlreadyRunningException, JobRestartException,
            JobInstanceAlreadyCompleteException, JobParametersInvalidException {

        JobParameters jobParameters = new JobParametersBuilder()
                .addLong("time", System.currentTimeMillis())
                .toJobParameters();
        jobLauncher.run(problemDeleteJob, jobParameters);
    }
}
  • 매일 자정에 BatchConfig에서 정의한 Job을 실행
  • JobParameters로 현재 시간을 설정
  • 현재 시간을 인자값으로 넣어주는 이유
    • Spring Batch에서 Job은 재실행할 수 있지만, 동일한 JobParameters로는 한 번만 실행할 수 있다.
    • 따라서, 매 밀리초마다 변하는 System.currentTimeMillis()를 인자값으로 넣어서 Job을 여러번 실행할 수 있고, 각 Job 사이의 구분도 가능하다.

3. 결과

3.1. soft delete된지 3일이 지난 문제 3개와 일반 문제 3개를 저장

3.2. 배치 실행

3.3. 삭제 완료


4. 부가 설정

4.1. 실행 시점에 작동 방지 (application.yml)

batch: // (1)
    job:
      enabled: false
    jdbc: // (2)
      initialize-schema: always

(1) : Spring Boot 실행 시점에 배치 작업이 실행되는 것을 방지

(2) : Spring Boot 실행할 때마다 Spring Batch 관련 테이블을 재생성


5. 오류 해결

5.1. 참조 테이블 오류

문제


2023-08-31 00:10:56.751 ERROR 56904 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : Cannot delete or update a parent row: a foreign key constraint fails (`psq`.`bookmark`, CONSTRAINT `FK2p0jv1kw42c9x87wsi1tb2gbf` FOREIGN KEY (`problem_id`) REFERENCES `problem` (`id`))
2023-08-31 00:10:56.762 ERROR 56904 --- [           main] o.s.batch.core.step.AbstractStep         : Encountered an error executing step step in job job

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
...
Caused by: java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`psq`.`bookmark`, CONSTRAINT `FK2p0jv1kw42c9x87wsi1tb2gbf` FOREIGN KEY (`problem_id`) REFERENCES `problem` (`id`))
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117) ~[mysql-connector-j-8.0.32.jar:8.0.32]
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-j-8.0.32.jar:8.0.32]
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:916) ~[mysql-connector-j-8.0.32.jar:8.0.32]
...

원인

  • bookmark 테이블은 member_id와 problem_id를 FK로 갖는다.
  • 이때, bookmark 테이블에 특정 problem_id를 FK로 갖는 데이터를 삭제하지 않고 해당 problem을 삭제해서 발생한 오류다.

해결

@Entity
@NoArgsConstructor
@Getter
public class Bookmark extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "problem_id")
    @OnDelete(action = OnDeleteAction.CASCADE) **// 추가**
    private Problem problem;

    public Bookmark(Member member, Problem problem) {
        this.member = member;
        this.problem = problem;
    }
}
  • @OnDelete(action = OnDeleteAction.CASCADE) 코드를 추가해서 problem 엔티티가 삭제되면 bookmark도 삭제되도록 설정한다.

6. 참고

https://khj93.tistory.com/entry/Spring-Batch란-이해하고-사용하기
https://velog.io/@gillog/Batch-Application-생성하기
https://gimmesome.tistory.com/204
https://hyunjun.kr/16

0개의 댓글