728x90
이번 포스트에서는 springbatch5에서 h2 database으로 테스트하겠습니다
환경
- springboot 3.3.0
- gradle 8.8
- java 17
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.0'
id 'io.spring.dependency-management' version '1.1.5'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-batch'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
testImplementation 'com.h2database:h2'
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'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
테스트하려는 간단한 잡을 생성합니다
chunk 기반 job입니다
import com.example.springbatch.core.domain.PlainText;
import com.example.springbatch.core.domain.ResultText;
import com.example.springbatch.core.repository.PlainTextRepository;
import com.example.springbatch.core.repository.ResultTextRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobScope;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.data.RepositoryItemReader;
import org.springframework.batch.item.data.builder.RepositoryItemReaderBuilder;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.Sort;
import org.springframework.transaction.PlatformTransactionManager;
import java.util.Collections;
import java.util.List;
@Configuration
@RequiredArgsConstructor
public class PlainTextJobConfig {
private final PlainTextRepository plainTextRepository;
private final ResultTextRepository resultTextRepository;
@Bean("plainTextJob")
public Job plainTextJob(JobRepository jobRepository, Step plainTextStep, Step initStep) {
return new JobBuilder("plainTextJob", jobRepository)
.incrementer(new RunIdIncrementer())
.start(initStep) // 초기 데이터 삽입을 위한 step
.next(plainTextStep)
.build();
}
@JobScope
@Bean("initStep")
public Step initStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("initStep", jobRepository)
.tasklet((contribution, chunkContext) -> {
plainTextRepository.saveAll(List.of(
new PlainText(null, "Text 1"),
new PlainText(null, "Text 2"),
new PlainText(null, "Text 3")
));
return RepeatStatus.FINISHED;
}, transactionManager)
.build();
}
@JobScope
@Bean("plainTextStep")
public Step plainTextStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("plainTextStep", jobRepository)
.<PlainText, String>chunk(5, transactionManager)
.reader(plainTextReader())
.processor(plainTextProcessor())
.writer(plainTextWriter())
.build();
}
@StepScope
@Bean
public RepositoryItemReader<PlainText> plainTextReader() {
return new RepositoryItemReaderBuilder<PlainText>()
.name("plainTextReader")
.repository(plainTextRepository)
.methodName("findAll")
.pageSize(5)
.sorts(Collections.singletonMap("id", Sort.Direction.ASC))
.build();
}
@StepScope
@Bean
public ItemProcessor<PlainText, String> plainTextProcessor() {
return item -> "Processed: " + item.getText();
}
@StepScope
@Bean
public ItemWriter<String> plainTextWriter() {
return items -> {
items.forEach(item -> resultTextRepository.save(new ResultText(null, item)));
System.out.println("=== chunk is finished ===");
};
}
}
repository와 entity입니다
import com.example.springbatch.core.domain.PlainText;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PlainTextRepository extends JpaRepository<PlainText, Long> {
Page<PlainText> findBy(Pageable pageable);
}
import com.example.springbatch.core.domain.ResultText;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ResultTextRepository extends JpaRepository<ResultText, Long> {
Page<ResultText> findBy(Pageable pageable);
}
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Setter
@Getter
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "plain_text")
public class PlainText {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "text", nullable = false)
private String text;
}
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Setter
@Getter
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "result_text")
public class ResultText {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "text", nullable = false)
private String text;
}
Job을 테스트합니다
특정 Job을 테스트하기 위해 BatchConfig, TestConfig를 추가했습니다
import com.example.springbatch.core.domain.ResultText;
import com.example.springbatch.core.repository.PlainTextRepository;
import com.example.springbatch.core.repository.ResultTextRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.context.SpringBatchTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBatchTest
@ActiveProfiles("test")
@ContextConfiguration(classes = {BatchTestConfig.class,PlainTextJobConfig.class, TestConfig.class})
class PlainTextJobConfigTest {
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Autowired
private PlainTextRepository plainTextRepository;
@Autowired
private ResultTextRepository resultTextRepository;
@AfterEach
public void tearDown() {
plainTextRepository.deleteAll();
resultTextRepository.deleteAll();
}
@ParameterizedTest
@ValueSource(strings = {"Text 1", "Text 2", "Text 3"})
public void testEachProcessedText(String expectedText) throws Exception {
JobExecution jobExecution = jobLauncherTestUtils.launchJob();
assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
List<ResultText> results = resultTextRepository.findAll();
assertTrue(results.stream()
.anyMatch(result -> result.getText().equals("Processed: " + expectedText)));
}
}
Springbatch5에서 @EnableBatchProcessing을 사용하지 않아도 되지만, SpringBootApplication 애노테이션을 사용하지 않고
@EnableAutoConfiguration을 사용하다보니, 추가했고, EnableJpaRepositories와 EntityScan도 마찬가지입니다
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@Configuration
@EnableBatchProcessing
@EnableJpaRepositories("com.example.springbatch.core.repository")
@EntityScan("com.example.springbatch.core.domain")
@EnableAutoConfiguration
public class BatchTestConfig {
}
SpringBatch metadata 테이블이 없으면 실행이 안되기 때문에 DataSource를 구현합니다
H2DB를 생성하고 metadata 테이블을 생성하기 위한 스크립트를 실행합니다
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import javax.sql.DataSource;
@TestConfiguration
class TestConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:org/springframework/batch/core/schema-h2.sql")
.build();
}
}
application.yml입니다
ddl-auto를 update로 맞춰줍니다
spring:
application:
name: spring-batch
profiles:
active: local
batch:
job:
name: ${job.names:NONE}
---
spring:
config:
activate:
on-profile: test
jpa:
show-sql: true
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: update
batch:
jdbc:
initialize-schema: always
'스프링 프레임워크 > springbatch' 카테고리의 다른 글
스프링배치 FlatFileItemReader 시작하기 (0) | 2024.08.07 |
---|---|
스프링배치 tasklet기반 step 시작하기 (0) | 2024.08.06 |
spring batch chunk기반 step 시작하기 (0) | 2024.07.31 |
spring batch 시작하기 (0) | 2024.07.31 |