【JAVA】SPRING BOOTでバッチアプリを作成してみる 12日目 xmlからデータベースに登録

2021/9/4



こんにちは。今回は前回まで作成していたWEBアプリの補足としてバッチによるユーザレコードの挿入を実装してみます。




前準備


バッチ用に新たなプロジェクトを作成します。

eclipse、ファイル -> 新規 -> その他の順に押下。


Spring BootのSpring スターター・プロジェクトを押下。


パッケージの名前のみ"demobatch"にして次へを押下。


Spring Batch、Lombok、Mybatis Framework、PostgresSQL Driverを選択します。

pom.xmlに先ほど選択したライブラリが追加されましたが、
MAVEN 構成問題のエラーが発生したので、<properties>のタグに下記を追加します。
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>

他にもビルド時の自動テストの解除を追加しました。
<maven.test.skip>true</maven.test.skip>

修正後のpom.xmlを記載します。
/demobatch/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <!-- 2.2.6 -->
    <version>2.2.6.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.example</groupId>
  <artifactId>demobatch</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>demobatch</name>
  <description>Demo project for Spring Boot</description>

  <properties>
    <java.version>1.8</java.version>
    <!-- 不明なエラー解消 -->
    <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
    <!-- ビルド時、自動テストしない -->
    <maven.test.skip>true</maven.test.skip>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-batch</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>2.1.0</version>
    </dependency>

    <dependency>
      <groupId>org.postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>org.junit.vintage</groupId>
          <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.springframework.batch</groupId>
      <artifactId>spring-batch-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

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

</project>

Spring Bootの設定
/demobatch/src/main/resources/application.properties
#DB接続の設定
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/web?web_schema
spring.datasource.username=dev
spring.datasource.password=xxxx

#起動するごとにDBを初期化する
spring.datasource.initialization-mode=always

# Spring Batchの実行結果をSpring指定のDBスキーマに出力する
spring.batch.initialize-schema: always

Spring Boot起動時に実行されるSQL
/demobatch/src/main/resources/schema.sql
--DDL
--usersテーブルが存在しなければ作成
CREATE TABLE IF NOT EXISTS web_schema.users (
  id serial PRIMARY KEY,
  name VARCHAR(30),
  email  VARCHAR(50),
  address VARCHAR(255),
  sex VARCHAR(1),
  remark VARCHAR(255)
);

/demobatch/src/main/resources/data.sql
--DML
--usersテーブルの削除
DELETE FROM web_schema.users;

ログの設定
/demobatch/src/main/resources/logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE logback>

<configuration>
    <!--デフォルト設定読み込み -->
    <include resource="org/springframework/boot/logging/logback/base.xml" />

    <!--変数の設定 -->
    <property name="logFilePath" value="C:/log/" />
    <property name="logFileName" value="batch" />

    <!--標準出力 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <target>System.out</target>
        <encoder>
            <charset>UTF-8</charset>
            <!--level   ロギングイベントのレベル -->
            <!--message ロギングイベントに関連付けられたメッセージ -->
            <pattern>%d{yyyy/MM/dd HH:mm:ss} %-5level [%thread] - %message%n</pattern>
        </encoder>
    </appender>

    <!-- アプリケーションログ -->
    <appender name="BATCH_LOG"
        class="ch.qos.logback.core.rolling.RollingFileAppender">

        <!-- 出力先ファイルパス -->
        <file>${logFilePath}${logFileName}.log</file>

        <!-- ログのローテーション設定 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日付けが変わったら年月フォルダ内にzipファイル作成 -->
            <fileNamePattern>${logFilePath}/%d{yyyyMM,aux}/${logFileName}-%d{yyyy-MM-dd}.log.zip</fileNamePattern>
            <!--最大30日間保存 -->
            <maxHistory>30</maxHistory>
        </rollingPolicy>

        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d{yyyy/MM/dd HH:mm:ss} %-5level [%thread] - %message%n</pattern>
        </encoder>
    </appender>

    <!--rootロガー、コンソールとバッチログに出力する -->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="BATCH_LOG" />
    </root>

</configuration>

バッチ処理


シナリオ


これまでに作成したWEBアプリは、ユーザ登録する場合にログイン処理を行い、一人ずつ一覧画面 -> 入力画面 -> 完了画面に遷移し、処理をする必要があります。

通常業務ではこれで良いのですが、一度に1000人分のユーザを手入力するときはきつくなります。

そこで、夜中などに一括でユーザを登録することを想定してバッチ処理を作成します。



バッチ構成


Spring Batchは、バッチ処理のおおきな単位としてジョブがあり、そのジョブに対して実際の処理を定義したステップという単位を定めることで構成されます。

ステップは大きく分けると、指定された件数ごとにバッチ処理を行うchunkと一括でバッチ処理を行うtaskletがあります。

今回は一度に処理するtaskletの方で実装をすすめていきます。


引用:ジョブの構成と実行


タスクレットの実装


web_schema.usersに登録するための入力情報になります。

/demobatch/src/main/resources/users_ins.xml
<?xml version="1.0" encoding="UTF-8" ?>
<users>
  <user>
    <name>武田信玄</name>
    <email>takeda@xxxxx.co.jp</email>
    <address>山梨県</address>
    <sex>1</sex>
    <remark>-</remark>
  </user>
  <user>
    <name>源頼朝</name>
    <email>minamoto@xxxxx.co.jp</email>
    <address>神奈川県</address>
    <sex>1</sex>
    <remark>-</remark>
  </user>
  <user>
    <name>足利尊氏</name>
    <email>asikaga@xxxxx.co.jp</email>
    <address>栃木県</address>
    <sex>1</sex>
    <remark>-</remark>
  </user>
</users>

taskletを実行するための設定クラスを作成します。
ここではjob1というジョブがstep1のステップを呼び出し、UserInsertTaskletを実行させています。

/demobatch/src/main/java/com/example/demo/configuration/UserInsertTaskletConfiguration.java
package com.example.demo.configuration;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.example.demo.listener.JobListener;
import com.example.demo.tasklet.UserInsertTasklet;

/**
*
* Spring Batch 勉強用 Taskletを動かす設定
*
* @version   1.0.0
*/
@Configuration
// バッチ有効化
@EnableBatchProcessing
public class UserInsertTaskletConfiguration {

    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    @Autowired
    private UserInsertTasklet tasklet;

    // @Configulationアノテーション下、@Componentでなく、起動時に設定される@Beanを設定
    @Bean
    public Job job1(Step step1) {
        return jobBuilderFactory.get("job1")
                .incrementer(new RunIdIncrementer())
                .listener(listener())
                .start(step1)
                .build();
    }

    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1").tasklet(tasklet).build();
    }

    @Bean
    public JobExecutionListener listener() {
        // 処理前後にメッセージ出力
        return new JobListener();
    }

}

taskletの実装クラスになります。
Taskletのインターフェースを実装し、入力情報のxmlをUsersオブジェクトに変換。データベースへの登録処理を行います。

/demobatch/src/main/java/com/example/demo/tasklet/UserInsertTasklet.java
package com.example.demo.tasklet;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.List;

import javax.xml.bind.JAXB;
import javax.xml.bind.Unmarshaller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.example.demo.domain.User;
import com.example.demo.mapper.UserMapper;
import com.example.demo.parse.Users;

/**
*
* Spring Batch 勉強用 タスク
*
* @version   1.0.0
*/
//DI コンテナに登録
@Component
public class UserInsertTasklet implements Tasklet {

    private static final String FILE_PATH = "users_ins.xml";
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private UserMapper userMapper;

    private Unmarshaller unmarshaller;

    public Unmarshaller getUnmarshaller() {
        return unmarshaller;
    }

    public void setUnmarshaller(Unmarshaller unmarshaller) {
        this.unmarshaller = unmarshaller;
    }

    /*
     * users_ins.xmlからusersテーブルへデータを登録する
     *
     * {@inheritDoc}
     */
    @Override
    @Transactional
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {

        Resource resource = new ClassPathResource(FILE_PATH);
        BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()));

        // xmlをオブジェクトに変換
        Users users = JAXB.unmarshal(reader, Users.class);
        List<User> userList = users.getUsers();

        for (User user : userList) {

            int result = userMapper.insert(user);

            // 登録結果をログに出力
            logger.info("[登録結果] " + String.valueOf(result) + "件登録");

        }

        return RepeatStatus.FINISHED;
    }

}

ジョブの処理前後にログを出力するリスナークラスです。

/demobatch/src/main/java/com/example/demo/listener/JobListener.java
package com.example.demo.listener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.listener.JobExecutionListenerSupport;

/**
*
* Spring Boot 勉強用 ジョブリスナー
*
* @version   1.0.0
*/
public class JobListener extends JobExecutionListenerSupport {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /*
     * {@inheritDoc}
     */
    @Override
    public void beforeJob(JobExecution jobExecution) {
        super.beforeJob(jobExecution);
        logger.info("ジョブ開始");
    }

    /*
     * {@inheritDoc}
     */
    @Override
    public void afterJob(JobExecution jobExecution) {
        super.afterJob(jobExecution);
        logger.info("ジョブ終了");
    }

}

xmlからデータを取得するusersクラスです。@XmlRootElement、@XmlElementを付けて取得するタグを設定します。

/demobatch/src/main/java/com/example/demo/parse/Users.java
package com.example.demo.parse;

import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import com.example.demo.domain.User;

/**
*
* Spring Batch 勉強用
* xmlをパースしたUserクラスを格納する
*
* @version   1.0.0
*/
// 取得するxmlのルートタグ
@XmlRootElement(name = "users")
public class Users {

    private List<User> users;

    // 取得するxmlのタグ
    @XmlElement(name = "user")
    public List<User> getUsers() {
        return users;
    }

    public void setUsers(List<User> users) {
        this.users = users;
    }

    @Override
    public String toString() {
        if (users == null) {
            return "users=null";
        }
        return "Users [users=" + users.toString() + "]";
    }

}

UsersテーブルへのデータをマッピングするためのEntityです。

/demobatch/src/main/java/com/example/demo/domain/User.java
package com.example.demo.domain;

import lombok.Data;

/**
*
* Spring Batch 勉強用 エンティティ
* UsersテーブルのデータをマッピングするためのEntity
*
* @version   1.0.0
*/
@Data
public class User {

    private String id;
    private String name;
    private String email;
    private String address;
    private String sex;
    private String remark;

}

Mybatisを設定するためのMapperです。実装はMybatisの方で行ってくれます。

/demobatch/src/main/java/com/example/demo/mapper/UserMapper.java
package com.example.demo.mapper;

import org.apache.ibatis.annotations.Mapper;

import com.example.demo.domain.User;

/**
*
* Spring Boot 勉強用 モデル
*
* @version   1.0.0
*/
@Mapper
public interface UserMapper {

    /*
     * usersテーブルにデータを登録する
     *
     * @param  user 登録用データ
     * @return  処理結果
     */
    int insert(User user);

}

Mybatisで実行されるSQLを記載したXMLです。今回はeclipseからでなく、Jarからバッチ実行しますのでMapperと同じ場所に置いてません。

/demobatch/src/main/resources/com/example/demo/mapper/UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">

  <!--usersテーブルにデータを登録する -->
  <insert id="insert" useGeneratedKeys="true" keyProperty="id" parameterType="com.example.demo.domain.User">
    INSERT INTO web_schema.users (name, email, address, sex, remark)
    VALUES (#{name}, #{email}, #{address}, #{sex}, #{remark});
  </insert>

</mapper>


Jarにしてからバッチを実行


それでは実行してみましょう。
プロジェクトを右クリックし、実行からMaven installをクリック。バッチをJarにコンパイルします。

/demobatch/target/demobatch-0.0.1-SNAPSHOT.jarができますので、targetフォルダに移動して下記のコマンドを実行します。
※今回はeclipseのターミナルでなく、Windowsのターミナルから実行します。

chcp 65001(UTF-8表示)

java -jar demobatch-0.0.1-SNAPSHOT.jar --spring.batch.job.names=jobjava -jar demobatch-0.0.1-SNAPSHOT.jar --spring.batch.job.names=job1



今回はここまでです。
気をつけてはいますが、間違いがあったらごめんなさい。またね。

参考
Spring Batch - リファレンスドキュメント
Spring Boot Batchの作成方法
Classpath resource not found when running as jar

お借りした素材
NYN姉貴.png
ブチ切れNYN姉貴BB
SZKのPCの切り抜き
よかったら使ってください 2

JAVA おすすめの本
[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]
スッキリわかるJava入門第2版 [ 中山清喬 ]
価格:2860円(税込、送料無料) (2020/4/12時点)






カテゴリ

このブログを検索

自己紹介

自分の写真
作らなきゃ(使命感)

QooQ