Spring boot + Spring batch を試す

こんにちわ、猫好きリーマンのほげPGです。今回は Spring boot +Sprinb batchを試してみます。

1、モジュールの構成

メインは上記構成を構築するHogeCongiとなります。

 

2、プロジェクト

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>sample</groupId>
  <artifactId>hoge-spring-batch</artifactId>
  <packaging>jar</packaging>
  <version>1.0.0-SNAPSHOT</version>
  <name>hogeSpringbatch</name>
  <url>http://maven.apache.org</url>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.1.RELEASE</version>
  </parent>

  <properties>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-batch</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
  </dependencies>

  <build>
    <finalName>hoge</finalName>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

 

HogeApp.java

/**
 * ほげ.
 */
@EnableAutoConfiguration
@ComponentScan
@Slf4j
public class HogeApp {

    public static void main(String[] args) throws Exception {
        log.info("start... {}", String.join(", ", args));
        ApplicationContext context = SpringApplication.run(HogeApp.class, args);
        int exitCode = SpringApplication.exit(context);
        log.info("exit... {}", exitCode);
        System.exit(exitCode);
    }
}

 

HogeConfig.java

@Configuration
@EnableBatchProcessing
@Slf4j
public class HogeConfig {

    @Bean
    DefaultBatchConfigurer batchConfigurer() {
        return new DefaultBatchConfigurer() {
            private JobRepository jobRepository;
            private JobExplorer jobExplorer;
            private JobLauncher jobLauncher;
            @PostConstruct
            private void init() throws Exception {
                MapJobRepositoryFactoryBean jobRepositoryFactory = new MapJobRepositoryFactoryBean();
                jobRepository = jobRepositoryFactory.getObject();
                MapJobExplorerFactoryBean jobExplorerFactory = new MapJobExplorerFactoryBean(jobRepositoryFactory);
                jobExplorer = jobExplorerFactory.getObject();
                SimpleJobLauncher simpleJobLauncher = new SimpleJobLauncher();
                simpleJobLauncher.setJobRepository(jobRepository);
                simpleJobLauncher.afterPropertiesSet();
                jobLauncher = simpleJobLauncher;
            }
            @Override
            public JobRepository getJobRepository() {
                return jobRepository;
            }
            @Override
            public JobExplorer getJobExplorer() {
                return jobExplorer;
            }
            @Override
            public JobLauncher getJobLauncher() {
                return jobLauncher;
            }
        };
    }

    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    @Value("${hoge.chunk:1}")
    private int chunk;

    @Bean
    public Job job() throws IOException {
        log.debug("called.");
        return jobBuilderFactory.get("job1")
                .incrementer(new RunIdIncrementer())
                .listener(listener())
                .start(step())
                .build();
    }

    @Bean
    public JobExecutionListener listener() {
        return new JobListener();
    }

    @Bean
    public Step step() throws IOException {
        log.debug("called.");
        return stepBuilderFactory.get("step")
                .<HogeLine, HogeInfo> chunk(chunk)
                .reader(reader(null))
                .processor(processor())
                .writer(writer(null))
                .faultTolerant()
                .skip(Exception.class)
                .skipLimit(Integer.MAX_VALUE)
                .build();
    }

    @Bean
    @StepScope
    public HogeReader reader(@Value("#{jobParameters[in]}") String fileName) throws IOException {
        return new HogeReader(fileName);
    }

    @Bean
    @StepScope
    public HogeProcessor processor()  {
        return new HogeProcessor();
    }

    @Bean
    @StepScope
    public FlatFileItemWriter<HogeInfo> writer(@Value("#{jobParameters[out]}") String filename) {
        FlatFileItemWriter<HogeInfo> writer = new FlatFileItemWriter<>();
        writer.setResource(new FileSystemResource(filename));
        writer.setLineAggregator(item -> {
            StringBuilder sb = new StringBuilder();
            sb.append(item.getName());
            sb.append("-");
            sb.append(item.getHoge());
            sb.append(item.getHoge());
            return sb.toString();
        });
        return writer;
    }

補足)

起動パラメータで入力ファイル、出力ファイルのファイル名を指定するようにしています。

 

HogeReader.java

@Slf4j
public class HogeReader implements ItemReader<HogeLine> {

    String fileName;
    int lineNo = 0;
    BufferedReader fr;

    public HogeReader(String fn) throws IOException {
        log.debug("called. {}", fn);
        this.fileName = fn;
        fr = new BufferedReader(new FileReader(fileName));
    }

    @Override
    public HogeLine read() throws IOException {
        log.debug("called. {}", lineNo);
        String line = fr.readLine();
        if (line == null) return null;
        lineNo++;
        return new HogeLine(line);
    }

    @PreDestroy
    public void close() throws IOException {
        log.debug("called.");
        fr.close();
    }
}

 

HogeProcessor.java

@Slf4j
public class HogeProcessor implements ItemProcessor<HogeLine, HogeInfo> {

    public HogeProcessor() {
        log.debug("called.");
    }

    @Override
    public HogeInfo process(HogeLine line) throws Exception {
        log.debug("called. {}", line);
        String[] cols = line.getLine().split(",");
        return new HogeInfo(cols[0], cols[1]);
    }
}

 

HogeLine.java

@Value
public class HogeLine {
    String line;
}

 

HogeInfo.java

@Value
public class HogeInfo {
    String name;
    String hoge;
}

 

application.yml

spring.main:
  # 起動バナーなし
  banner-mode: "off"
  # 組み込みWebサーバの自動起動無効
  web-application-type: none

# 起動スクリプト無効
spring.batch.initialize-schema: never

# ログ
logging: 
  pattern:
    console: "%d{HH:mm:ss.SSS} %thread %-5level \\(%file:%line\\) %M - %msg%n"
  level: 
    ROOT: INFO
    jp.co.ois.sample: DEBUG

# ほげ
hoge.chunk: 2

 

In.csv

abc,hoge
def,moge
hoge,HOGE!

 

起動確認

> mvn clean package

> java -jar target/hoge.jar in=in.csv out=out.csv

14:25:30.170 [main] INFO jp.co.ois.sample.batch.HogeApp - start... in=in.csv, out=out.csv
14:25:31.188 main INFO  (StartupInfoLogger.java:55) logStarting - Starting HogeApp v1.0.0-SNAPSHOT on NDYWM7A3420152 with PID 4344 (C:\work\masuda\myrepo\hogeSpringbatch\target\hoge.jar started by horqu in C:\work\masuda\myrepo\hogeSpringbatch)
14:25:31.189 main DEBUG (StartupInfoLogger.java:56) logStarting - Running with Spring Boot v2.2.1.RELEASE, Spring v5.2.1.RELEASE
14:25:31.190 main INFO  (SpringApplication.java:651) logStartupProfileInfo - No active profile set, falling back to default profiles: default
14:25:32.211 main WARN  (DefaultBatchConfigurer.java:63) setDataSource - No transaction manager was provided, using a DataSourceTransactionManager
14:25:32.236 main INFO  (HikariDataSource.java:110) getConnection - HikariPool-1 - Starting...
14:25:32.639 main INFO  (HikariDataSource.java:123) getConnection - HikariPool-1 - Start completed.
14:25:32.653 main INFO  (JobRepositoryFactoryBean.java:184) afterPropertiesSet - No database type set, using meta data indicating: H2
14:25:32.894 main INFO  (SimpleJobLauncher.java:209) afterPropertiesSet - No TaskExecutor has been set, defaulting to synchronous executor.
14:25:32.910 main INFO  (SimpleJobLauncher.java:209) afterPropertiesSet - No TaskExecutor has been set, defaulting to synchronous executor.
14:25:32.912 main DEBUG (HogeConfig.java:84) job - called.
14:25:32.917 main DEBUG (HogeConfig.java:99) step - called.
14:25:33.212 main INFO  (StartupInfoLogger.java:61) logStarted - Started HogeApp in 2.835 seconds (JVM running for 3.691)
14:25:33.213 main INFO  (JobLauncherCommandLineRunner.java:147) run - Running default command line with: [in=in.csv, out=out.csv]
14:25:33.263 main INFO  (SimpleJobLauncher.java:145) run - Job: [SimpleJob: [name=job1]] launched with the following parameters: [{run.id=1, in=in.csv, out=out.csv}]
14:25:33.309 main DEBUG (JobListener.java:16) beforeJob - called. JobExecution: id=0, version=1, startTime=Mon Dec 16 14:25:33 JST 2019, endTime=null, lastUpdated=Mon Dec 16 14:25:33 JST 2019, status=STARTED, exitStatus=exitCode=UNKNOWN;exitDescription=, job=[JobInstance: id=0, version=0, Job=[job1]], jobParameters=[{run.id=1, in=in.csv, out=out.csv}]
14:25:33.319 main INFO  (SimpleStepHandler.java:146) handleStep - Executing step: [step]
14:25:33.425 main DEBUG (HogeReader.java:22) <init> - called. in.csv
14:25:33.430 main DEBUG (HogeReader.java:29) read - called. 0
14:25:33.436 main DEBUG (HogeReader.java:29) read - called. 1
14:25:33.442 main DEBUG (HogeProcessor.java:13) <init> - called.
14:25:33.445 main DEBUG (HogeProcessor.java:18) process - called. HogeLine(line=abc,hoge)
14:25:33.446 main DEBUG (HogeProcessor.java:18) process - called. HogeLine(line=def,moge)
14:25:33.462 main DEBUG (HogeReader.java:29) read - called. 2
14:25:33.463 main DEBUG (HogeReader.java:29) read - called. 3
14:25:33.463 main DEBUG (HogeProcessor.java:18) process - called. HogeLine(line=hoge,HOGE!)
14:25:33.467 main INFO  (AbstractStep.java:272) execute - Step: [step] executed in 148ms
14:25:33.472 main DEBUG (HogeReader.java:38) close - called.
14:25:33.474 main DEBUG (JobListener.java:22) afterJob - called. JobExecution: id=0, version=1, startTime=Mon Dec 16 14:25:33 JST 2019, endTime=Mon Dec 16 14:25:33 JST 2019, lastUpdated=Mon Dec 16 14:25:33 JST 2019, status=COMPLETED, exitStatus=exitCode=COMPLETED;exitDescription=, job=[JobInstance: id=0, version=0, Job=[job1]], jobParameters=[{run.id=1, in=in.csv, out=out.csv}]
14:25:33.477 main INFO  (SimpleJobLauncher.java:149) run - Job: [SimpleJob: [name=job1]] completed with the following parameters: [{run.id=1, in=in.csv, out=out.csv}] and the following status: [COMPLETED] in 169ms
14:25:33.480 main INFO  (HikariDataSource.java:350) close - HikariPool-1 - Shutdown initiated...
14:25:33.481 main INFO  (HikariDataSource.java:352) close - HikariPool-1 - Shutdown completed.
14:25:33.482 main INFO  (HogeApp.java:23) main - exit... 0

 

※メタテーブルは作成しない&触らないようにしましたが、上記のログからHikariDataSourceが動いています。Pom.xml上もcom.h2databaseの依存を消すと起動でエラーになります。この辺りをなんとかしたかったのですが、…あきらめました。

 

プロジェクト一式

hogeSpringbatch.zip

 

今回はここまで。

\ 最新情報をチェック /