1. 개요

이 예제에서는 Spring Batch에 대한 실용적이고 코드 중심적인 소개를 살펴볼 것입니다. Spring Batch는 강력한 작업 실행을 위해 설계된 처리 프레임워크입니다.

현재 버전 4.3은 Spring 5와 Java 8을 지원합니다. 또한 일괄 처리를 위한 새로운 Java 사양인 JSR-352도 수용합니다.

다음은 프레임워크의 몇 가지 흥미롭고 실용적인 사용 사례입니다.

2. 워크플로 기본

Spring Batch는 작업 리포지토리가 작업을 예약하고 작업과 상호 작용하는 작업을 수행하는 기존 배치 아키텍처를 따릅니다.

작업에는 하나 이상의 단계가 있을 수 있습니다. 그리고 모든 단계는 일반적으로 데이터를 읽고, 처리하고, 쓰는 순서를 따릅니다.

그리고 물론 이 프레임워크는 작업 저장소에 sqlite 를 사용하여 특히 작업을 처리하는 낮은 수준의 지속성 작업과 관련하여 여기에서 우리를 위해 대부분의 힘든 작업을 수행합니다.

2.1. 사용 사례 예시

여기에서 다룰 간단한 사용 사례는 일부 금융 거래 데이터를 CSV에서 XML로 마이그레이션하는 것입니다.

입력 파일은 매우 단순한 구조를 가지고 있습니다.

여기에는 사용자 이름, 사용자 ID, 거래 날짜 및 금액으로 구성된 행당 거래가 포함됩니다.

username, userid, transaction_date, transaction_amount
devendra, 1234, 31/10/2015, 10000
john, 2134, 3/12/2015, 12321
robin, 2134, 2/02/2015, 23411

3. 메이븐 POM

이 프로젝트에 필요한 의존성은 Spring Core, Spring Batch 및 sqlite JDBC 커넥터입니다.

<!-- SQLite database driver -->
<dependency>
    <groupId>org.xerial</groupId>
    <artifactId>sqlite-jdbc</artifactId>
    <version>3.15.1</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-oxm</artifactId>
    <version>5.3.0</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.batch</groupId>
    <artifactId>spring-batch-core</artifactId>
    <version>4.3.0</version>
</dependency>

4. 스프링 배치 설정

가장 먼저 할 일은 XML로 Spring Batch를 구성하는 것입니다.

<!-- connect to SQLite database -->
<bean id="dataSource"
  class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="org.sqlite.JDBC" />
    <property name="url" value="jdbc:sqlite:repository.sqlite" />
    <property name="username" value="" />
    <property name="password" value="" />
</bean>

<!-- create job-meta tables automatically -->
<jdbc:initialize-database data-source="dataSource">
    <jdbc:script
      location="org/springframework/batch/core/schema-drop-sqlite.sql" />
    <jdbc:script location="org/springframework/batch/core/schema-sqlite.sql" />
</jdbc:initialize-database>

<!-- stored job-meta in memory -->
<!-- 
<bean id="jobRepository" 
  class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean"> 
    <property name="transactionManager" ref="transactionManager" />
</bean> 
-->

<!-- stored job-meta in database -->
<bean id="jobRepository"
  class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="transactionManager" ref="transactionManager" />
    <property name="databaseType" value="sqlite" />
</bean>

<bean id="transactionManager" class=
  "org.springframework.batch.support.transaction.ResourcelessTransactionManager" />

<bean id="jobLauncher"
  class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
    <property name="jobRepository" ref="jobRepository" />
</bean>

물론 Java 구성도 사용할 수 있습니다.

@Configuration
@EnableBatchProcessing
@Profile("spring")
public class SpringConfig {

    @Value("org/springframework/batch/core/schema-drop-sqlite.sql")
    private Resource dropReopsitoryTables;

    @Value("org/springframework/batch/core/schema-sqlite.sql")
    private Resource dataReopsitorySchema;

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("org.sqlite.JDBC");
        dataSource.setUrl("jdbc:sqlite:repository.sqlite");
        return dataSource;
    }

    @Bean
    public DataSourceInitializer dataSourceInitializer(DataSource dataSource)
      throws MalformedURLException {
        ResourceDatabasePopulator databasePopulator = 
          new ResourceDatabasePopulator();

        databasePopulator.addScript(dropReopsitoryTables);
        databasePopulator.addScript(dataReopsitorySchema);
        databasePopulator.setIgnoreFailedDrops(true);

        DataSourceInitializer initializer = new DataSourceInitializer();
        initializer.setDataSource(dataSource);
        initializer.setDatabasePopulator(databasePopulator);

        return initializer;
    }

    private JobRepository getJobRepository() throws Exception {
        JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
        factory.setDataSource(dataSource());
        factory.setTransactionManager(getTransactionManager());
        factory.afterPropertiesSet();
        return (JobRepository) factory.getObject();
    }

    private PlatformTransactionManager getTransactionManager() {
        return new ResourcelessTransactionManager();
    }

    public JobLauncher getJobLauncher() throws Exception {
        SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
        jobLauncher.setJobRepository(getJobRepository());
        jobLauncher.afterPropertiesSet();
        return jobLauncher;
    }
}

5. 스프링 배치 작업 설정

이제 CSV에서 XML 작업에 대한 작업 설명을 작성해 보겠습니다.

<import resource="spring.xml" />

<bean id="record" class="com.baeldung.spring_batch_intro.model.Transaction"></bean>
<bean id="itemReader"
  class="org.springframework.batch.item.file.FlatFileItemReader">

    <property name="resource" value="input/record.csv" />

    <property name="lineMapper">
        <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
            <property name="lineTokenizer">
                <bean class=
                  "org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
                    <property name="names" value="username,userid,transactiondate,amount" />
                </bean>
            </property>
            <property name="fieldSetMapper">
                <bean class="com.baeldung.spring_batch_intro.service.RecordFieldSetMapper" />
            </property>
        </bean>
    </property>
</bean>

<bean id="itemProcessor"
  class="com.baeldung.spring_batch_intro.service.CustomItemProcessor" />

<bean id="itemWriter"
  class="org.springframework.batch.item.xml.StaxEventItemWriter">
    <property name="resource" value="file:xml/output.xml" />
    <property name="marshaller" ref="recordMarshaller" />
    <property name="rootTagName" value="transactionRecord" />
</bean>

<bean id="recordMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
    <property name="classesToBeBound">
        <list>
            <value>com.baeldung.spring_batch_intro.model.Transaction</value>
        </list>
    </property>
</bean>
<batch:job id="firstBatchJob">
    <batch:step id="step1">
        <batch:tasklet>
            <batch:chunk reader="itemReader" writer="itemWriter"
              processor="itemProcessor" commit-interval="10">
            </batch:chunk>
        </batch:tasklet>
    </batch:step>
</batch:job>

다음은 유사한 Java 기반 작업 구성입니다.

@Profile("spring")
public class SpringBatchConfig {
    
    @Autowired
    private JobBuilderFactory jobs;

    @Autowired
    private StepBuilderFactory steps;

    @Value("input/record.csv")
    private Resource inputCsv;

    @Value("file:xml/output.xml")
    private Resource outputXml;

    @Bean
    public ItemReader<Transaction> itemReader()
      throws UnexpectedInputException, ParseException {
        FlatFileItemReader<Transaction> reader = new FlatFileItemReader<Transaction>();
        DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
        String[] tokens = { "username", "userid", "transactiondate", "amount" };
        tokenizer.setNames(tokens);
        reader.setResource(inputCsv);
        DefaultLineMapper<Transaction> lineMapper = 
          new DefaultLineMapper<Transaction>();
        lineMapper.setLineTokenizer(tokenizer);
        lineMapper.setFieldSetMapper(new RecordFieldSetMapper());
        reader.setLineMapper(lineMapper);
        return reader;
    }

    @Bean
    public ItemProcessor<Transaction, Transaction> itemProcessor() {
        return new CustomItemProcessor();
    }

    @Bean
    public ItemWriter<Transaction> itemWriter(Marshaller marshaller)
      throws MalformedURLException {
        StaxEventItemWriter<Transaction> itemWriter = 
          new StaxEventItemWriter<Transaction>();
        itemWriter.setMarshaller(marshaller);
        itemWriter.setRootTagName("transactionRecord");
        itemWriter.setResource(outputXml);
        return itemWriter;
    }

    @Bean
    public Marshaller marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setClassesToBeBound(new Class[] { Transaction.class });
        return marshaller;
    }

    @Bean
    protected Step step1(ItemReader<Transaction> reader,
      ItemProcessor<Transaction, Transaction> processor,
      ItemWriter<Transaction> writer) {
        return steps.get("step1").<Transaction, Transaction> chunk(10)
          .reader(reader).processor(processor).writer(writer).build();
    }

    @Bean(name = "firstBatchJob")
    public Job job(@Qualifier("step1") Step step1) {
        return jobs.get("firstBatchJob").start(step1).build();
    }
}

이제 전체 구성이 있으므로 이를 분해하고 논의를 시작하겠습니다.

5.1. ItemReader 로 데이터 읽기 및 객체 생성

먼저 record.csv 에서 데이터를 읽고 이를 Transaction 객체 로 변환할 cvsFileItemReader 를 구성했습니다.

@SuppressWarnings("restriction")
@XmlRootElement(name = "transactionRecord")
public class Transaction {
    private String username;
    private int userId;
    private LocalDateTime transactionDate;
    private double amount;

    /* getters and setters for the attributes */

    @Override
    public String toString() {
        return "Transaction [username=" + username + ", userId=" + userId
          + ", transactionDate=" + transactionDate + ", amount=" + amount
          + "]";
    }
}

이를 위해 사용자 지정 매퍼를 사용합니다.

public class RecordFieldSetMapper implements FieldSetMapper<Transaction> {
 
    public Transaction mapFieldSet(FieldSet fieldSet) throws BindException {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d/M/yyy");
        Transaction transaction = new Transaction();
 
        transaction.setUsername(fieldSet.readString("username"));
        transaction.setUserId(fieldSet.readInt(1));
        transaction.setAmount(fieldSet.readDouble(3));
        String dateString = fieldSet.readString(2);
        transaction.setTransactionDate(LocalDate.parse(dateString, formatter).atStartOfDay());
        return transaction;
    }
}

5.2. ItemProcessor 로 데이터 처리 하기

자체 항목 프로세서 CustomItemProcessor 를 만들었습니다 . 이것은 트랜잭션 객체와 관련된 어떤 것도 처리하지 않습니다.

그것이 하는 일은 독자로부터 오는 원래 객체를 작가에게 전달하는 것뿐입니다.

public class CustomItemProcessor implements ItemProcessor<Transaction, Transaction> {

    public Transaction process(Transaction item) {
        return item;
    }
}

5.3. ItemWriter 를 사용하여 FS에 개체 쓰기

마지막으로 이 트랜잭션 을 xml/output.xml 에 있는 XML 파일에 저장할 것입니다 .

<bean id="itemWriter"
  class="org.springframework.batch.item.xml.StaxEventItemWriter">
    <property name="resource" value="file:xml/output.xml" />
    <property name="marshaller" ref="recordMarshaller" />
    <property name="rootTagName" value="transactionRecord" />
</bean>

5.4. 일괄 작업 구성

따라서 배치:작업 구문 을 사용하여 작업과 점을 연결하기만 하면 됩니다.

commit-interval 에 유의하십시오 . itemWriter 에 일괄 처리를 커밋하기 전에 메모리에 보관할 트랜잭션 수입니다 .

해당 시점까지(또는 입력 데이터가 끝날 때까지) 트랜잭션을 메모리에 보관합니다.

<batch:job id="firstBatchJob">
    <batch:step id="step1">
        <batch:tasklet>
            <batch:chunk reader="itemReader" writer="itemWriter"
              processor="itemProcessor" commit-interval="10">
            </batch:chunk>
        </batch:tasklet>
    </batch:step>
</batch:job>

5.5. 일괄 작업 실행

이제 모든 것을 설정하고 실행해 보겠습니다.

@Profile("spring")
public class App {
    public static void main(String[] args) {
        // Spring Java config
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(SpringConfig.class);
        context.register(SpringBatchConfig.class);
        context.refresh();
        
        JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher");
        Job job = (Job) context.getBean("firstBatchJob");
        System.out.println("Starting the batch job");
        try {
            JobExecution execution = jobLauncher.run(job, new JobParameters());
            System.out.println("Job Status : " + execution.getStatus());
            System.out.println("Job completed");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Job failed");
        }
    }
}

-Dspring.profiles.active=spring profile 을 사용하여 Spring 애플리케이션을 실행 합니다.

다음 섹션에서는 Spring Boot 애플리케이션에서 예제를 구성합니다.

6. 스프링 부트 설정

이 섹션에서는 Spring Boot 애플리케이션을 만들고 Spring Boot 환경에서 실행되도록 이전 Spring Batch Config를 변환합니다. 사실 이것은 이전 Spring Batch 예제와 거의 동일합니다.

6.1. 메이븐 의존성

pom.xml 의 Spring Boot 애플리케이션에서 spring-boot-starter-batch  의존성 을 선언하는 것으로 시작하겠습니다 .

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-batch</artifactId>
</dependency>

Spring Batch 작업 정보를 저장할 데이터베이스가 필요합니다 . 이 사용방법(예제)에서는 메모리 내 HSQLDB 데이터베이스를 사용합니다. 따라서 Spring Boot와 함께 hsqldb 를 사용해야 합니다 .

<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <version>2.7.0</version>
    <scope>runtime</scope>
</dependency>

6.2. 스프링 부트 구성

@Profile 어노테이션을 사용 하여 Spring 및 Spring Boot 구성을 구분합니다. 애플리케이션에서 스프링 부트 프로필을 설정합니다 .

@SpringBootApplication
public class SpringBatchApplication {

    public static void main(String[] args) {
        SpringApplication springApp = new SpringApplication(SpringBatchApplication.class);
        springApp.setAdditionalProfiles("spring-boot");
        springApp.run(args);
    }

}

6.3. 스프링 배치 작업 구성

이전의 SpringBatchConfig 클래스 와 동일한 배치 작업 구성을 사용합니다 .

@Configuration
@EnableBatchProcessing
@Profile("spring-boot")
public class SpringBootBatchConfig {
    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    @Value("input/record.csv")
    private Resource inputCsv;

    @Value("input/recordWithInvalidData.csv")
    private Resource invalidInputCsv;

    @Value("file:xml/output.xml")
    private Resource outputXml;

    // ...
}

Spring @Configuration 어노테이션으로 시작합니다. 그런 다음 클래스에 @EnableBatchProcessing 어노테이션을 추가합니다. @EnableBatchProcessing 어노테이션은 자동으로 dataSource 객체를 생성하여 우리 작업 에 제공합니다 .

7. 결론

이 기사에서는 Spring Batch로 작업하는 방법과 간단한 사용 사례에서 사용하는 방법을 배웠습니다.

우리는 일괄 처리 파이프라인을 쉽게 개발할 수 있는 방법과 읽기, 처리 및 쓰기의 여러 단계를 사용자 정의할 수 있는 방법을 보았습니다.

항상 그렇듯이 이 기사의 전체 구현은 GitHub의 over 에서 찾을 수 있습니다 .

Generic footer banner