かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Grooy スクリプトをそのまま渡して実行する Spring Boot+Picocli ベースのコマンドラインアプリを作成する ( その5 )( CSV ファイルのデータをテーブルに登録する Groovy スクリプトを作成する )

概要

記事一覧はこちらです。

Grooy スクリプトをそのまま渡して実行する Spring Boot+Picocli ベースのコマンドラインアプリを作成する ( その4 )( テーブルのデータを CSV フォーマットで出力する Groovy スクリプトを作成する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • CSV ファイルのデータを MySQL の testdb の book テーブルに登録する Groovy スクリプトを作成します。

参照したサイト・書籍

目次

  1. CSV ファイルのデータを MySQL の testdb の book テーブルに登録する

手順

CSV ファイルのデータを MySQL の testdb の book テーブルに登録する

groovy-script-executor/src/main/groovy/sample の下に CsvFileToBookTable.groovy を新規作成し、以下のコードを記述します。

  • 今回は commit を明記する方式にします。トランザクションの処理が始まる前に sql.connection.autoCommit = false を記述して AutoCommit を OFF にします。
  • CSV ファイルの publications.csv は PublicationsTableToFileUsingUnivocityParsers.groovy が出力したファイルを使用します。ただしそのままだと全部で 154,729件あって多いので、先頭から 20件だけ使用します。
  • publications.csv の title_author には title だけか、/ を区切り文字として title と authror が入っているので、/ で分割して author はデータがある時だけセットします。
package sample

import com.univocity.parsers.annotations.Parsed
import com.univocity.parsers.common.processor.BeanListProcessor
import com.univocity.parsers.csv.CsvParserSettings
import com.univocity.parsers.csv.CsvRoutines
import groovy.sql.Sql

class CsvFileToBookTable {

    static class CsvRecord {
        @Parsed(index = 0, field = "isbm")
        String isbm
        @Parsed(index = 1, field = "title_author")
        String title_author
    }

    static void main(args) {
        def sql = Sql.newInstance("jdbc:mysql://localhost:3306/testdb?sslMode=DISABLED&characterEncoding=utf8",
                "testdb_user",
                "xxxxxxxx",
                "org.mariadb.jdbc.Driver")
        sql.connection.autoCommit = false

        CsvParserSettings settings = new CsvParserSettings()
        settings.format.lineSeparator = "\r\n"
        settings.headerExtractionEnabled = true
        BeanListProcessor<CsvRecord> rowProcessor = new BeanListProcessor<>(CsvRecord)
        settings.processor = rowProcessor

        sql.execute("truncate table book")

        new File("publications.csv").withReader { reader ->
            CsvRoutines csvRoutines = new CsvRoutines(settings)
            for (CsvRecord csvRecord : csvRoutines.iterate(CsvRecord, reader)) {
                String[] titleAndAuthor = csvRecord.title_author.split(" / ")
                sql.execute("""
                                insert into book (isbm, title, author)
                                values (:isbm, :title, :author)
                            """,
                        isbm: csvRecord.isbm,
                        title: titleAndAuthor[0],
                        author: titleAndAuthor.size() == 2 ? titleAndAuthor[1] : null)
            }
        }

        sql.commit()
        sql.close()
    }

}

CsvFileToBookTable.groovy を D:\tmp にコピーし gse CsvFileToBookTable.groovy を実行すると book テーブルにデータが登録されます。

f:id:ksby:20211107172250p:plain

履歴

2021/11/07
初版発行。

Grooy スクリプトをそのまま渡して実行する Spring Boot+Picocli ベースのコマンドラインアプリを作成する ( その4 )( テーブルのデータを CSV フォーマットで出力する Groovy スクリプトを作成する )

概要

記事一覧はこちらです。

Grooy スクリプトをそのまま渡して実行する Spring Boot+Picocli ベースのコマンドラインアプリを作成する ( その3 )( サンプルの Groovy スクリプトをプロジェクト内に置く+PostgreSQL、MySQL を起動するための docker-compose.yml を作成してデータベース・テーブルを作成する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • PostgreSQL の sampledb の publications テーブルのデータを CSV フォーマットで出力する Groovy スクリプトを作成します。

参照したサイト・書籍

  1. Connecting to an Amazon Aurora DB cluster
    https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Aurora.Connecting.html

  2. Dependency management with Grape
    http://docs.groovy-lang.org/latest/html/documentation/grape.html

  3. Groovy - File I/O
    https://www.tutorialspoint.com/groovy/groovy_file_io.htm

目次

  1. groovy-script-executor.jar に PostgreSQL、MariaDB の JDBC Driver を入れる
  2. PostgreSQL の sampledb の publications テーブルのデータを CSV フォーマットで出力する
    1. 標準出力にカンマ区切りで出力する
    2. ファイルにカンマ区切りで出力する
    3. univocity-parsers を使用してファイルに出力する
  3. groovy-script-executor.jar 内に作成したクラスを Groovy スクリプトから呼び出せるのか?

手順

groovy-script-executor.jar に PostgreSQLMariaDBJDBC Driver を入れる

JDBC Driver は @GrabDependency management with Grape 参照)で指定するのではなく groovy-script-executor.jar に入れます。

build.gradle を以下のように変更します。

dependencies {
    def postgresqlJdbcDriver = "org.postgresql:postgresql:42.3.1"
    def mariadbJdbcDriver = "org.mariadb.jdbc:mariadb-java-client:2.7.4"
    def groovyVersion = "3.0.9"
    ..........

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    runtimeOnly("${postgresqlJdbcDriver}")
    runtimeOnly("${mariadbJdbcDriver}")
    implementation("com.univocity:univocity-parsers:2.9.1")
    ..........
  • dependencies block に以下の行を追加します。
    • def postgresqlJdbcDriver = "org.postgresql:postgresql:42.3.1"
    • def mariadbJdbcDriver = "org.mariadb.jdbc:mariadb-java-client:2.7.4"
    • runtimeOnly("${postgresqlJdbcDriver}")
    • runtimeOnly("${mariadbJdbcDriver}")

Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

build タスクを実行し、生成した groovy-script-executor.jar を D:\tmp にコピーします。

PostgreSQL の sampledb の publications テーブルのデータを CSV フォーマットで出力する

標準出力にカンマ区切りで出力する

groovy-script-executor/src/main/groovy/sample の下に PublicationsTableToSystemOutInCSV.groovy を新規作成し、以下のコードを記述します。

  • Windows のコンソールに出力するので、出力時の文字コードwindows-31j を指定しています。
  • isbm は NULL のデータが存在し、そのままでは NULL と出力されるため、coalesce 関数で NULL の場合には空文字列を取得するようにしています。
  • select 文を実行するのに sql.query(...) { resultSet -> }sql.eachRow(...) { row -> } のメソッドも存在しますが、row.values().join(",") が使用したいので sql.rows(...) メソッドを使用しています。
package sample

import groovy.sql.Sql

class PublicationsTableToSystemOutInCSV {

    static void main(args) {
        def sql = Sql.newInstance("jdbc:postgresql://localhost:5432/sampledb",
                "sampledb_user",
                "xxxxxxxx",
                "org.postgresql.Driver")

        try (Writer writer = System.out.newWriter("windows-31j")) {
            def rows = sql.rows("""\
                select coalesce(isbm, ''),
                       title_author
                from publications
            """)
            rows.each { row ->
                writer.println(row.values().join(","))
            }
        }

        sql.close()
    }

}

PublicationsTableToSystemOutInCSV.groovy を D:\tmp にコピーし gse PublicationsTableToSystemOutInCSV.groovy を実行すると取得したデータが出力されます。

f:id:ksby:20211103100036p:plain

ファイルにカンマ区切りで出力する

groovy-script-executor/src/main/groovy/sample の下に PublicationsTableToFileInCSV.groovy を新規作成し、以下のコードを記述します。

package sample

import groovy.sql.Sql

class PublicationsTableToFileInCSV {

    static void main(String[] args) {
        def sql = Sql.newInstance("jdbc:postgresql://localhost:5432/sampledb",
                "sampledb_user",
                "xxxxxxxx",
                "org.postgresql.Driver")

        new File("publications.csv").withWriter { writer ->
            def rows = sql.rows("""\
                select coalesce(isbm, ''),
                       title_author
                from publications
            """)
            rows.each { row ->
                writer.println(row.values().join(","))
            }
        }

        sql.close()
    }

}

PublicationsTableToFileInCSV.groovy を D:\tmp にコピーし gse PublicationsTableToFileInCSV.groovy を実行すると取得したデータがファイルに出力されます。

f:id:ksby:20211103113754p:plain

univocity-parsers を使用してファイルに出力する

groovy-script-executor/src/main/groovy/sample の下に PublicationsTableToFileUsingUnivocityParsers.groovy を新規作成し、以下のコードを記述します。

package sample

import com.univocity.parsers.annotations.Parsed
import com.univocity.parsers.common.processor.BeanWriterProcessor
import com.univocity.parsers.csv.CsvWriter
import com.univocity.parsers.csv.CsvWriterSettings
import groovy.sql.Sql

class PublicationsTableToFileUsingUnivocityParsers {

    static class CsvRecord {
        @Parsed(index = 0, field = "isbm")
        String isbm
        @Parsed(index = 1, field = "title_author")
        String title_author
    }

    static void main(args) {
        def sql = Sql.newInstance("jdbc:postgresql://localhost:5432/sampledb",
                "sampledb_user",
                "xxxxxxxx",
                "org.postgresql.Driver")

        CsvWriterSettings settings = new CsvWriterSettings()
        settings.setQuoteAllFields(true)
        BeanWriterProcessor<CsvRecord> writerProcessor = new BeanWriterProcessor<>(CsvRecord)
        settings.setRowWriterProcessor(writerProcessor)
        CsvWriter writer = new CsvWriter(new File("publications.csv"), settings)

        writer.writeHeaders()

        CsvRecord publications = new CsvRecord()
        sql.eachRow("select * from publications") { row ->
            publications.isbm = row.isbm
            publications.title_author = row.title_author
            writer.processRecord(publications)
        }

        writer.close()
        sql.close()
    }

}

PublicationsTableToFileInCSV.groovy を D:\tmp にコピーし gse PublicationsTableToFileUsingUnivocityParsers.groovy を実行すると取得したデータがファイルに出力されます。

f:id:ksby:20211103115600p:plain

groovy-script-executor.jar 内に入れている univocity-parsers のモジュールを問題なく呼び出すことが出来ています。

groovy-script-executor.jar 内に作成したクラスを Groovy スクリプトから呼び出せるのか?

groovy-script-executor.jar 内に共通ライブラリとして利用するクラスを作成して Groovy スクリプトから呼び出せるのか試してみます。

groovy-script-executor/src/main/groovy の下に ksby.util パッケージを作成し、その下に DbUtils.groovy を作成して以下のコードを記述します。

package ksby.util

import groovy.sql.Sql

class DbUtils {

    static Sql connectSampleDb() {
        return Sql.newInstance("jdbc:postgresql://localhost:5432/sampledb",
                "sampledb_user",
                "xxxxxxxx",
                "org.postgresql.Driver")
    }

}

groovy-script-executor/src/main/groovy/sample/PublicationsTableToSystemOutInCSV.groovy の DB への接続処理を Sql.newInstance(...)DbUtils.connectSampleDb() に変更します。

package sample

import ksby.util.DbUtils

class PublicationsTableToSystemOutInCSV {

    static void main(args) {
        def sql = DbUtils.connectSampleDb()

        try (Writer writer = System.out.newWriter("windows-31j")) {
            def rows = sql.rows("""\
                select coalesce(isbm, ''),
                       title_author
                from publications
            """)
            rows.each { row ->
                writer.println(row.values().join(","))
            }
        }

        sql.close()
    }

}

build タスクを実行して生成された groovy-script-executor.jar と PublicationsTableToSystemOutInCSV.groovy を D:\tmp へコピーした後、gse PublicationsTableToSystemOutInCSV.groovy を実行すると取得したデータが出力されました。

f:id:ksby:20211103213649p:plain

groovy-script-executor.jar 内に入っているクラスを Groovy スクリプトから問題なく呼び出すことが出来ました。

履歴

2021/11/03
初版発行。

Grooy スクリプトをそのまま渡して実行する Spring Boot+Picocli ベースのコマンドラインアプリを作成する ( その3 )( サンプルの Groovy スクリプトをプロジェクト内に置く+PostgreSQL、MySQL を起動するための docker-compose.yml を作成してデータベース・テーブルを作成する )

概要

記事一覧はこちらです。

Grooy スクリプトをそのまま渡して実行する Spring Boot+Picocli ベースのコマンドラインアプリを作成する ( その2 )( groovy-script-executor.jar を作成する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • サンプルの Groovy スクリプトを置く場所を決めます。
    • サンプルの Groovy スクリプト で利用する PostgreSQLMySQL を起動するための docker-compose.yml を作成します。
    • PostgreSQL に sampledb、MySQL に testdb を作成します。
    • Flyway でテーブルを作成してデータを入れます。

参照したサイト・書籍

  1. Exclude file from jar using Gradle
    https://stackoverflow.com/questions/53394850/exclude-file-from-jar-using-gradle

  2. new collation (ja_JP.UTF-8) is incompatible with the collation of the template database (C)
    https://symfoware.blog.fc2.com/blog-entry-1737.html

  3. ロケール(国際化と地域化)
    https://lets.postgresql.jp/documents/technical/text-processing/2

  4. 国立国会図書館 - オープンデータセット
    https://www.ndl.go.jp/jp/dlib/standards/opendataset/index.html

目次

  1. groovy-script-executor/src/main/groovy の下に sample パッケージを作成し、その下にサンプルの Groovy スクリプトを置く
  2. サンプルの Groovy スクリプト で利用する PostgreSQL、MySQL を起動するための docker-compose.yml を作成する+PostgreSQL に sampledb、MySQL に testdb を作成する
  3. PostgreSQL の sampledb にテーブルを作成する
  4. MySQL の testdb にテーブルを作成する

手順

groovy-script-executor/src/main/groovy の下に sample パッケージを作成し、その下にサンプルの Groovy スクリプトを置く

groovy-script-executor/src/main/groovy の下にサンプルの Groovy スクリプトを置くと jar ファイルを作成した時にサンプルの Groovy スクリプトの class ファイルが入ってしまうと思っていたのですが、Exclude file from jar using Gradle のページを見つけて build.gradle に設定すれば jar ファイルに入らないようにできることが分かりました。

groovy-script-executor/src/main/groovy の下に sample パッケージを作成して、その下にサンプルの Groovy スクリプトを置くことにします。まずは Hello, World を出力する簡単な Groovy スクリプトを作成して動作確認する で作成した HelloWorld.groovy を置きます。

jar ファイル作成時に sample パッケージの下の *.groovy が入らないよう build.gradle に設定を追加します。

[compileJava, compileTestGroovy, compileTestJava]*.options*.encoding = "UTF-8"
[compileJava, compileTestGroovy, compileTestJava]*.options*.compilerArgs = ["-Xlint:all,-options,-processing,-path"]
sourceSets {
    main {
        compileGroovy {
            exclude 'sample/*.groovy'
        }
    }
}
..........
  • sourceSets { ... } の設定を追加します。

sourceSets { ... } の設定がないと生成された jar ファイルの中に HelloWorld.class が入りますが、

f:id:ksby:20211029232710p:plain

sourceSets { ... } の設定があると入りません。

f:id:ksby:20211029233027p:plain

また groovy-script-executor/src/main/groovy/sample の下に置いた Groovy スクリプトは、クラス名か main メソッドの左側に表示されている矢印アイコンをクリックして表示される「Run 'HelloWorld.main()'」メニューを選択する(あるいは Ctrl+Shift+F10 を押す)ことで、

f:id:ksby:20211029234611p:plain

実行して結果を確認することが出来ます。

f:id:ksby:20211029234913p:plain

サンプルの Groovy スクリプト で利用する PostgreSQLMySQL を起動するための docker-compose.yml を作成する+PostgreSQL に sampledb、MySQL に testdb を作成する

プロジェクトの root ディレクトリ直下に .env を作成し、以下の内容を記述します。PostgreSQLMySQL のバージョンは AWS の Aurora でサポートされているバージョンにします(PostgreSQL は LTS の 11.9、MySQL は version 2 の 5.7)。

POSTGRESQL_VERSION=11.9
PGADMIN4_VERSION=6.1

MYSQL_VERSION=5.7.35
ADMINER_VERSION=4.8.1

PostgreSQL には sampledb、MySQL には testdb という名前のデータベースを作成します。

root ディレクトリ直下に docker/flyway/db/init ディレクトリを作成し(この次に Flyway でテーブルを作成するので docker/flyway ディレクトリの下に SQL ファイルを置いています)、その下に create_database_postgresql.sql を作成して以下の内容を記述します。

CREATE USER sampledb_user PASSWORD 'xxxxxxxx';
GRANT pg_read_server_files TO sampledb_user;
CREATE DATABASE sampledb OWNER sampledb_user ENCODING 'UTF8' LC_COLLATE 'ja_JP.UTF-8' LC_CTYPE 'ja_JP.UTF-8';

同じ場所に create_database_mysql.sql を作成して以下の内容を記述します。

create database if not exists testdb character set utf8mb4;

create user 'testdb_user'@'%' identified by 'xxxxxxxx';
grant all privileges ON testdb.* to 'testdb_user'@'%' with grant option;
grant select ON performance_schema.user_variables_by_thread to 'testdb_user'@'%';
flush privileges;

root ディレクトリ直下に docker-compose.yml を作成し、以下の内容を記述します。

# docker-compose build --no-cache
# docker-compose up -d
# docker-compose down
version: '3'

services:
  #############################################################################
  # PostgreSQL
  #
  postgresql:
    image: postgres:${POSTGRESQL_VERSION}-alpine
    container_name: postgresql
    ports:
      - "5432:5432"
    environment:
      - TZ=Asia/Tokyo
      - LANG=ja_JP.UTF-8
      - POSTGRES_PASSWORD=xxxxxxxx
    volumes:
      - ./docker/flyway/db/init/create_database_postgresql.sql:/docker-entrypoint-initdb.d/create_database.sql
      - ./docker/flyway/db/migration/postgresql_sampledb/jm2020.csv:/docker-entrypoint-initdb.d/jm2020.csv

  #############################################################################
  # pgAdmin 4
  #
  # URL
  # http://localhost:12000/
  pgadmin4:
    build:
      context: ./docker/pgadmin4
      args:
        - PGADMIN4_VERSION=${PGADMIN4_VERSION}
    image: dpage/pgadmin4:${PGADMIN4_VERSION}-custom
    container_name: pgadmin4
    ports:
      - "12000:80"
    environment:
      # TZ=Asia/Tokyo を設定してみたが日本時間に変わらなかったのでコメントアウトしておく
      # - TZ=Asia/Tokyo
      # PGADMIN_DEFAULT_EMAIL には接続する PostgreSQL の ユーザ名を設定する(サーバを追加する時楽なため)
      - PGADMIN_DEFAULT_EMAIL=postgres@example.com
      - PGADMIN_DEFAULT_PASSWORD=xxxxxxxx
      # PGADMIN_CONFIG_CONSOLE_LOG_LEVEL は debug 用
      # 設定値は https://www.pgadmin.org/docs/pgadmin4/development/config_py.html の CONSOLE_LOG_LEVEL 参照
      - PGADMIN_CONFIG_CONSOLE_LOG_LEVEL=10
      - PGADMIN_CONFIG_SESSION_DB_PATH='/var/lib/pgadmin_session'
    volumes:
      - ./docker/pgadmin4/data:/var/lib/pgadmin

  #############################################################################
  # MySQL
  #
  mysql:
    image: mysql:${MYSQL_VERSION}
    container_name: mysql
    command: --default-authentication-plugin=mysql_native_password
    ports:
      - 3306:3306
    environment:
      - TZ=Asia/Tokyo
      - MYSQL_ROOT_PASSWORD=xxxxxxxx
    volumes:
      - ./docker/flyway/db/init/create_database_mysql.sql:/docker-entrypoint-initdb.d/create_database.sql

  #############################################################################
  # Adminer
  #
  # URL
  # http://localhost:13000/
  adminer:
    image: adminer:${ADMINER_VERSION}
    container_name: adminer
    ports:
      - 13000:8080
    environment:
      - TZ=Asia/Tokyo
      - ADMINER_DEFAULT_SERVER=mysql

docker-compose up -d コマンドを実行します。

f:id:ksby:20211030223058p:plain

http://localhost:12000/ にアクセス・ログインして pgAdmin 4 で sampledb が作成されていることを確認します。

f:id:ksby:20211030223432p:plain

http://localhost:13000/ にアクセス・ログインして Adminer で testdb が作成されていることを確認します。

f:id:ksby:20211030223646p:plain

docker-compose down コマンドを実行します。

PostgreSQL の sampledb にテーブルを作成する

国立国会図書館オープンデータセット の「国内刊行出版物の書誌情報 2020年分(tsv形式)」を取り込んだテーブルを作成します。

.env に FLYWAY_VERSION と sampledb に接続するための URL、USER、PASSWORD を記述します。

POSTGRESQL_VERSION=11.9
PGADMIN4_VERSION=6.1

MYSQL_VERSION=5.7.35
ADMINER_VERSION=4.8.1

FLYWAY_VERSION=8.0.2
SAMPLEDB_URL=jdbc:postgresql://postgresql/sampledb
SAMPLEDB_USER=sampledb_user
SAMPLEDB_PASSWORD=xxxxxxxx

docker/flyway/db の下に migration/postgresql_sampledb ディレクトリを作成し、その下に V1_0_0__create_publications.sql を作成して以下の内容を記述します。

create table publications
(
    id                bigserial constraint publications_pk primary key,
    url               text,
    fmt               text,
    book_number       text,
    isbm              text,
    issn              text,
    title_author      text,
    edition_indicates text,
    series            text,
    publication       text,
    remarks           text
);

docker-compose.yml の一番下に flyway_sampledb を追加します。

  ..........

  #############################################################################
  # Flyway for postgresql@sampledb
  #
  flyway_sampledb:
    image: flyway/flyway:${FLYWAY_VERSION}-alpine
    container_name: flyway_sampledb
    environment:
      - TZ=Asia/Tokyo
    volumes:
      - ./docker/flyway/db/migration/postgresql_sampledb:/flyway/sql
    command: -url="${SAMPLEDB_URL}" -user=${SAMPLEDB_USER} -password=${SAMPLEDB_PASSWORD} -connectRetries=60 migrate
    depends_on:
      - postgresql
    # 下の3行は debug 用
    # うまく動かない時はコメントアウトを解除した後、
    # docker exec -it flyway_sampledb /bin/sh
    # で接続してから
    # flyway <command に記述した文字列>
    # を実行してみる
    #
    # entrypoint: /bin/sh
    # stdin_open: true
    # tty: true

docker-compose up -d コマンドを実行します。

IntelliJ IDEA の Database Tool Window を設定して publications テーブルが作成されていることを確認します。

f:id:ksby:20211031105032p:plain

国内刊行出版物の書誌情報(直近年1年分) から jm2020.txt をダウンロードし、Database Tool Window で import します。import できないデータが数十件出ますが、それらは無視します。

f:id:ksby:20211031110601p:plain f:id:ksby:20211031110715p:plain

データが import できたので今度は CSV ファイル(jm2020.csv)に export します。

f:id:ksby:20211031111952p:plain f:id:ksby:20211031112048p:plain

export した jm2020.csv を docker/flyway/db/migration/postgresql_sampledb の下に移動した後、docker-compose.yml を以下のように変更します。

  postgresql:
    image: postgres:${POSTGRESQL_VERSION}-alpine
    container_name: postgresql
    ports:
      - "5432:5432"
    environment:
      - TZ=Asia/Tokyo
      - LANG=ja_JP.UTF-8
      - POSTGRES_PASSWORD=xxxxxxxx
    volumes:
      - ./docker/flyway/db/init/create_database_postgresql.sql:/docker-entrypoint-initdb.d/create_database.sql
      - ./docker/flyway/db/migration/postgresql_sampledb/jm2020.csv:/docker-entrypoint-initdb.d/jm2020.csv
  • postgresql の volumes に - ./docker/flyway/db/migration/postgresql_sampledb/jm2020.csv:/docker-entrypoint-initdb.d/jm2020.csv を追加します。

docker/flyway/db/migration/postgresql_sampledb/V1_0_0__create_publications.sql を以下のように変更します。

create table publications
(
    ..........
);

COPY publications
FROM '/docker-entrypoint-initdb.d/jm2020.csv'
WITH CSV;

  • COPY publications FROM '/docker-entrypoint-initdb.d/jm2020.csv' WITH CSV; を追加します。

docker-compose downdocker-compose up -d コマンドを実行し、publications テーブルにデータが取り込まれることを確認します。

f:id:ksby:20211031113453p:plain

MySQL の testdb にテーブルを作成する

.env に testdb に接続するための URL、USER、PASSWORD を記述します。

..........

FLYWAY_VERSION=8.0.2
SAMPLEDB_URL=jdbc:postgresql://postgresql/sampledb
SAMPLEDB_USER=sampledb_user
SAMPLEDB_PASSWORD=xxxxxxxx
TESTDB_URL=jdbc:mysql://mysql:3306/testdb?sslMode=DISABLED&characterEncoding=utf8
TESTDB_USER=testdb_user
TESTDB_PASSWORD=xxxxxxxx

docker/flyway/db/migration の下に mysql_testdb ディレクトリを作成し、その下に V1_0_0__create_book.sql を作成して以下の内容を記述します。

create table book
(
    id     bigint auto_increment primary key,
    isbm   text null,
    title  text null,
    author text null
);

docker-compose.yml の一番下に flyway_testdb を追加します。

  ..........

  #############################################################################
  # Flyway for mysql@testdb
  #
  flyway_testdb:
    image: flyway/flyway:${FLYWAY_VERSION}-alpine
    container_name: flyway_testdb
    environment:
      - TZ=Asia/Tokyo
    volumes:
      - ./docker/flyway/db/migration/mysql_testdb:/flyway/sql
    command: -url="${TESTDB_URL}" -user=${TESTDB_USER} -password=${TESTDB_PASSWORD} -connectRetries=60 migrate
    depends_on:
      - mysql
    # 下の3行は debug 用
    # うまく動かない時はコメントアウトを解除した後、
    # docker exec -it flyway_testdb /bin/sh
    # で接続してから
    # flyway <command に記述した文字列>
    # を実行してみる
    #
    # entrypoint: /bin/sh
    # stdin_open: true
    # tty: true

docker-compose downdocker-compose up -d コマンドを実行します。

IntelliJ IDEA の Database Tool Window を設定して book テーブルが作成されていることを確認します。。。が、作成されていませんね?

f:id:ksby:20211031114908p:plain

flyway_testdb コンテナのログを見ると ERROR: Flyway Teams Edition or MySQL upgrade required: MySQL 5.7 is no longer supported by Flyway Community Edition, but still supported by Flyway Teams Edition. のログが出力されていました。

f:id:ksby:20211031115110p:plain

Flyway のバージョンを 7 系の最新(7.15.0)に変更します。.env で FLYWAY_VERSION=8.0.2FLYWAY_VERSION=7.15.0 に変更します。

..........

FLYWAY_VERSION=7.15.0
SAMPLEDB_URL=jdbc:postgresql://postgresql/sampledb
SAMPLEDB_USER=sampledb_user
SAMPLEDB_PASSWORD=xxxxxxxx
TESTDB_URL=jdbc:mysql://mysql:3306/testdb?sslMode=DISABLED&characterEncoding=utf8
TESTDB_USER=testdb_user
TESTDB_PASSWORD=xxxxxxxx

docker-compose downdocker-compose up -d コマンドを実行すると今度は book テーブルが作成されました。MySQL 5.7 のサポートが終了したのは Flyway 8 からでした。

f:id:ksby:20211031115740p:plain

履歴

2021/10/31
初版発行。

Grooy スクリプトをそのまま渡して実行する Spring Boot+Picocli ベースのコマンドラインアプリを作成する ( その2 )( groovy-script-executor.jar を作成する )

概要

記事一覧はこちらです。

Grooy スクリプトをそのまま渡して実行する Spring Boot+Picocli ベースのコマンドラインアプリを作成する ( その1 )( 概要 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Spring Boot+Picocli ベースのコマンドラインアプリ(groovy-script-executor.jar)を作成します。
    • Hello, World を出力する簡単な Groovy スクリプトを作成して動作確認します。

参照したサイト・書籍

  1. picocli - a mighty tiny command line interface
    https://picocli.info/

目次

  1. https://github.com/ksby/groovy-script-executor を clone して Gradle の Multi-project を作成する
  2. groovy-script-executor サブプロジェクトを作成して実装する
  3. Hello, World を出力する簡単な Groovy スクリプトを作成して動作確認する
  4. 起動時オプションで -Dfile.encoding=UTF-8 等を指定したいので gse.bat を作成する

手順

https://github.com/ksby/groovy-script-executor を clone して Gradle の Multi-project を作成する

https://github.com/ksby/groovy-script-executor を clone した後、Spring Initializr で demo プロジェクトを作成してから以下のファイルをコピーします。

gradlew init コマンドを実行します。

f:id:ksby:20211024174915p:plain

settings.gradle を以下の内容に書き替えます。

rootProject.name = 'groovy-script-executor'

rootDir.eachFileRecurse { f ->
    if (f.name == "build.gradle") {
        String relativePath = f.parentFile.absolutePath - rootDir.absolutePath
        String projectName = relativePath.replaceAll("[\\\\\\/]", ":")
        if (projectName != ":buildSrc") {
            include projectName
        }
    }
}

groovy-script-executor サブプロジェクトを作成して実装する

IntelliJ IDEA のメインメニューから「File」-「New」-「Project...」を選択して「New Project」ダイアログを表示した後、以下の値を入力します。Dependencies では何も選択せず「Finish」ボタンをクリックします。

f:id:ksby:20211024180747p:plain f:id:ksby:20211024180837p:plain

作成されたサブプロジェクトの以下の点を変更します。

  • src ディレクトリ、build.gradle 以外のディレクトリ・ファイルを削除します。
  • GroovyScriptExecutorApplication クラスの名前を Application に変更します。
  • src/test の下をクリアした後、その下に .gitkeep を作成します。

build.gradle を以下の内容にします。

buildscript {
    ext {
        group "ksby"
        version "1.0.0-RELEASE"
    }
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
}

plugins {
    id 'java'
    id 'groovy'
    id 'org.springframework.boot' version '2.5.6'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
}

sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17

[compileJava, compileTestGroovy, compileTestJava]*.options*.encoding = "UTF-8"
[compileJava, compileTestGroovy, compileTestJava]*.options*.compilerArgs = ["-Xlint:all,-options,-processing,-path"]
bootJar {
    duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
jar {
    enabled = false
}

springBoot {
    buildInfo()
}

configurations {
    compileOnly.extendsFrom annotationProcessor

    // annotationProcessor と testAnnotationProcessor、compileOnly と testCompileOnly を併記不要にする
    testAnnotationProcessor.extendsFrom annotationProcessor
    testImplementation.extendsFrom compileOnly

    // JUnit 4 が依存関係に入らないようにする
    all {
        exclude group: "junit", module: "junit"
    }
}

repositories {
    mavenCentral()
}

dependencyManagement {
    imports {
        // bomProperty に指定可能な property は以下の URL の BOM に記述がある
        // https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/2.5.6/spring-boot-dependencies-2.5.6.pom
        mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) {
            // Spring Boot の BOM に定義されているバージョンから変更する場合には、ここに以下のように記述する
            // bomProperty "thymeleaf.version", "3.0.9.RELEASE"
        }
        mavenBom("org.junit:junit-bom:5.8.1")
    }
}

dependencies {
    def groovyVersion = "3.0.9"
    def picocliVersion = "4.6.1"
    def lombokVersion = "1.18.22"
    def spockVersion = "2.0-groovy-3.0"

    // dependency-management-plugin によりバージョン番号が自動で設定されるもの
    // Dependency Versions ( https://docs.spring.io/spring-boot/docs/current/reference/html/dependency-versions.html#dependency-versions ) 参照
    implementation("org.springframework.boot:spring-boot-starter")
    implementation("org.apache.commons:commons-lang3")
    testImplementation("org.springframework.boot:spring-boot-starter-test")

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    implementation("com.univocity:univocity-parsers:2.9.1")
    testImplementation("org.assertj:assertj-core:3.21.0")

    // for Groovy
    implementation("org.codehaus.groovy:groovy-all:${groovyVersion}")

    // for Picocli
    implementation("info.picocli:picocli-spring-boot-starter:${picocliVersion}")

    // for lombok
    // testAnnotationProcessor、testCompileOnly を併記しなくてよいよう configurations で設定している
    annotationProcessor("org.projectlombok:lombok:${lombokVersion}")
    compileOnly("org.projectlombok:lombok:${lombokVersion}")

    // for JUnit 5
    // junit-jupiter で junit-jupiter-api, junit-jupiter-params, junit-jupiter-engine の3つが依存関係に追加される
    testImplementation("org.junit.jupiter:junit-jupiter")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")

    // for Spock
    testImplementation("org.spockframework:spock-core:${spockVersion}")
    testImplementation("org.spockframework:spock-spring:${spockVersion}")
}

def jvmArgsForTask = [
        "-ea",
        "-Dfile.encoding=UTF-8",
        "-XX:TieredStopAtLevel=1",
        "-Dspring.main.lazy-initialization=true"
]
def printTestCount = { desc, result ->
    if (!desc.parent) { // will match the outermost suite
        println "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)"
    }
}

bootRun {
    jvmArgs = jvmArgsForTask
}

clean {
    doLast {
        project.file("out").deleteDir()
        project.file("src/main/generated").deleteDir()
        project.file("src/test/generated_tests").deleteDir()
    }
}

test {
    jvmArgs = jvmArgsForTask

    // for JUnit 5
    useJUnitPlatform()

    testLogging {
        afterSuite printTestCount
    }
}

Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

ksby.cmdapp.groovyscriptexecutor の下に command パッケージを作成した後、GroovyScriptExecutorCommand クラスを新規作成して以下のコードを記述します。

package ksby.cmdapp.groovyscriptexecutor.command;

import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.info.BuildProperties;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.Callable;

import static picocli.CommandLine.*;

@Component
@Command(name = "groovy-script-executor", mixinStandardHelpOptions = true,
        versionProvider = GroovyScriptExecutorCommand.class,
        description = "Groovyスクリプトを実行するコマンド")
public class GroovyScriptExecutorCommand
        implements Callable<Integer>, IExitCodeExceptionMapper, IVersionProvider {

    @Autowired
    private BuildProperties buildProperties;

    @Parameters(index = "0", paramLabel = "Groovyスクリプト",
            description = "実行する Groovyスクリプトを指定する")
    private File groovyScript;

    @Parameters(index = "1..*", paramLabel = "引数",
            description = "Groovyスクリプトに渡す引数を指定する")
    private String[] args;

    @Override
    public Integer call() throws IOException {
        Binding binding = new Binding();
        GroovyShell shell = new GroovyShell(binding);
        shell.run(groovyScript, args);
        return ExitCode.OK;
    }

    @Override
    public int getExitCode(Throwable exception) {
        if (exception instanceof Exception) {
            return 1;
        }

        return ExitCode.OK;
    }

    @Override
    public String[] getVersion() {
        return new String[]{buildProperties.getVersion()};
    }

}

src/main/java/ksby/cmdapp/groovyscriptexecutor/Application.java を以下のコードに書き替えます。

package ksby.cmdapp.groovyscriptexecutor;

import ksby.cmdapp.groovyscriptexecutor.command.GroovyScriptExecutorCommand;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import picocli.CommandLine;

import static picocli.CommandLine.IFactory;

@SpringBootApplication
public class Application implements CommandLineRunner, ExitCodeGenerator {

    private int exitCode;

    private final GroovyScriptExecutorCommand groovyScriptExecutorCommand;

    private final IFactory factory;

    public Application(GroovyScriptExecutorCommand groovyScriptExecutorCommand,
                       IFactory factory) {
        this.groovyScriptExecutorCommand = groovyScriptExecutorCommand;
        this.factory = factory;
    }

    public static void main(String[] args) {
        System.exit(SpringApplication.exit(SpringApplication.run(Application.class, args)));
    }

    @Override
    public void run(String... args) throws Exception {
        exitCode = new CommandLine(groovyScriptExecutorCommand, factory)
                .setExitCodeExceptionMapper(groovyScriptExecutorCommand)
                .execute(args);
    }

    @Override
    public int getExitCode() {
        return exitCode;
    }

}

src/main/resources/application.properties に以下の内容を記述します。

spring.main.banner-mode=off
logging.level.root=OFF

clean タスク実行 → Rebuild Project 実行 → build タスクを実行します。

f:id:ksby:20211026200530p:plain

build/libs/groovy-script-executor-1.0.0-RELEASE.jar が生成されるので D:\tmp の下にコピーした後、java -jar groovy-script-executor-1.0.0-RELEASE.jar -Hjava -jar groovy-script-executor-1.0.0-RELEASE.jar -V が動作することを確認します。

f:id:ksby:20211026200800p:plain

Hello, World を出力する簡単な Groovy スクリプトを作成して動作確認する

src/main の下に groovy ディレクトリを新規作成し、その下に .gitkeep を作成します。

src/main/groovy の下に HelloWorld.groovy を新規作成した後、以下のコードを記述します。

class HelloWorld {

    static void main(args) {
        println "Hello, World"
    }

}

src/main/groovy/HelloWorld.groovy を D:\tmp の下に移動した後、java -jar groovy-script-executor-1.0.0-RELEASE.jar HelloWorld.groovy を実行すると Hello, World が出力されました。コマンド実行直後は build しているので、Hello, World が出力されるまでに 4~5 秒程度かかります。

f:id:ksby:20211026201835p:plain

起動時オプションで -Dfile.encoding=UTF-8 等を指定したいので gse.bat を作成する

D:\tmp の下に gse.bat を新規作成し、以下のコードを記述します。

@echo off

java -Dfile.encoding=UTF-8 ^
     -XX:TieredStopAtLevel=1 ^
     -Dspring.main.lazy-initialization=true ^
     -jar groovy-script-executor-1.0.0-RELEASE.jar ^
     %*

gse HelloWorld.groovy を実行すると java -jar groovy-script-executor-1.0.0-RELEASE.jar HelloWorld.groovy と同様に Hello, World が出力されます。

f:id:ksby:20211026203900p:plain

履歴

2021/10/26
初版発行。

Grooy スクリプトをそのまま渡して実行する Spring Boot+Picocli ベースのコマンドラインアプリを作成する ( その1 )( 概要 )

概要

記事一覧はこちらです。

  • CSVファイルを読み込んで DB のテーブルを更新したり、DB のテーブルのデータを CSVファイルに出力したりするちょっとした Groovy スクリプトを build せずにそのまま実行したいと思ったので、作成してみることにします。
  • Groovy スクリプトは Groovy をインストールすれば groovy コマンドでそのまま実行出来ますが、今回は以下の内容で実現します。
    • Spring Boot+Picocli ベースのコマンドラインアプリ(groovy-script-executor-xxx.jar)を作成します。
    • コマンドラインアプリ(groovy-script-executor-xxx.jar)の中に必要なモジュールを全て入れておきます。
    • java -jar groovy-script-executor.jar <Groovyスクリプト> <引数...> のコマンドで実行できるようにします。
  • @SpringBootApplication アノテーションを付与した Groovy スクリプトのアプリケーションも実行できるといいな。。。 実行できれば Spring Integeration の SFTP Adapters を利用してファイルのアップロード・ダウンロードができるかもしれません。コマンドラインアプリを作成してから試してみることにします。
  • GraalVM で Windows の exe ファイル作成にもチャレンジしてみることにします。

Grooy スクリプトをそのまま渡して実行する Spring Boot+Picocli ベースのコマンドラインアプリを作成する ( 大目次 )

GitHubhttps://github.com/ksby/groovy-script-executor

  1. その1 ( 概要 )
  2. その2 ( groovy-script-executor.jar を作成する )
  3. その3 ( サンプルの Groovy スクリプトをプロジェクト内に置く+PostgreSQL、MySQL を起動するための docker-compose.yml を作成してデータベース・テーブルを作成する )
  4. その4 ( テーブルのデータを CSV フォーマットで出力する Groovy スクリプトを作成する )
  5. その5 ( CSV ファイルのデータをテーブルに登録する Groovy スクリプトを作成する )
  6. その6 ( Groovy スクリプトからログをコンソールやファイルに出力する )
  7. その7 ( @SpringBootApplication アノテーションを付与した Groovy スクリプトで SFTP クライアントを作成する )
  8. その8 ( @SpringBootApplication アノテーションを付与した Groovy スクリプトで REST API サーバを作成する )
  9. 番外編 ( ConEmu 上で WSL 2 の Ubuntu-20.04 の bash を起動する+Ubuntu-20.04 の別インスタンスを作成する )
  10. その9 ( Gradle を 7.2 → 7.3.3 へ、Spring Boot を 2.5.6 → 2.6.2 へバージョンアップする )
  11. その10 ( GraalVM で groovy-script-executor の Windows 版 Native Image を作成する )

Spring Boot 2.4.x の Web アプリを 2.5.x へバージョンアップする ( その15 )( JUnit 4 を依存関係から削除する+他3点の気づいた点を変更する )

概要

記事一覧はこちらです。

Spring Boot 2.4.x の Web アプリを 2.5.x へバージョンアップする ( その14 )( google_checks.xml に Record に関する定義を反映する+bootBuildImage タスクで Java 17 ベースの Docker Image を作成して動作確認する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Spring Boot を 2.5系にバージョンアップしても JUnit 4 が依存関係に残ることに気づいたので除去します。
    • IntelliJ IDEA で Rebuild Project した時に生成される out ディレクトリが Gradle の clean タスクを実行しても残ることに気づいたので削除されるようにします。
    • Mockito.mockStatic を使用するテストでは try-with-resources 構文を使用してリソースが close されるようにしないといけないことに気づいたので修正します。
    • JDK 17 がサポートされた Lombok 1.18.22 がリリースされていたのでバージョンアップします。

参照したサイト・書籍

  1. How to exclude Gradle dependencies
    https://tomgregory.com/how-to-exclude-gradle-dependencies/

  2. @StandardException annotation for generating exception constructors
    https://github.com/projectlombok/lombok/pull/2702

目次

  1. 依存関係から JUnit 4 を除去する
  2. JUnit 4 のモジュールを利用しているクラスを変更・削除する
    1. src/test/java/ksbysample/common/test/extension/db/TestDataExtension.java
    2. src/test/java/ksbysample/common/test/extension/mail/MailServerExtension.java
    3. src/test/java/ksbysample/common/test/extension/mockmvc/SecurityMockMvcExtension.java
    4. src/test/java/ksbysample/common/test/helper/DescriptionWrapper.java
    5. src/test/groovy/ksbysample/webapp/lending/SampleHelperTest.groovy
  3. clean タスクで out ディレクトリが削除されるようにする
  4. Mockito.mockStatic を呼び出しているテストで try-with-resources 構文を使用してリソースが close されるようにする
  5. Lombok を 1.18.20 → 1.18.22 にバージョンアップする
  6. WebApplicationRuntimeException クラスを Lombok の @StandardException を利用して書き直す
  7. build タスクでエラーが出ないことを確認する

手順

依存関係から JUnit 4 を除去する

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その100 )( Gradle を 6.9.1 → 7.2 へ、Spring Boot を 2.4.10 → 2.5.4 へ、Geb を 4.1 → 5.0 へバージョンアップする2 ) の作業で Spring Boot を 2.5系にバージョンアップしても JUnit 4 が依存関係に残っていることに気づいたので、除去することにします。

今回は個々のモジュールに exclude を記述して除外するのではなく configurations block に exclude を記述する方法を採用します。build.gradle を以下のように変更します。

configurations {
    compileOnly.extendsFrom annotationProcessor

    // annotationProcessor と testAnnotationProcessor、compileOnly と testCompileOnly を併記不要にする
    testAnnotationProcessor.extendsFrom annotationProcessor
    testImplementation.extendsFrom compileOnly

    // JUnit 4 が依存関係に入らないようにする
    all {
        exclude group: "junit", module: "junit"
    }

    // for SpotBugs
    spotbugsStylesheets { transitive = false }
}
  • all { exclude group: "junit", module: "junit" } を追加します。

JUnit 4 のモジュールを利用しているクラスを修正する

src/test/java/ksbysample/common/test/extension/db/TestDataExtension.java

@Component
public class TestDataExtension implements BeforeEachCallback, AfterEachCallback {

    ..........

    @Override
    public void beforeEach(ExtensionContext context) {
        before(new ExtensionContextWrapper(context));
    }

    @Override
    public void afterEach(ExtensionContext context) {
        after(new ExtensionContextWrapper(context));
    }

    ..........
  • extends TestWatcher を削除します。
  • 以下のメソッドを削除します。
    • protected void starting(Description description) { ... }
    • protected void finished(Description description) { ... }

src/test/java/ksbysample/common/test/extension/mail/MailServerExtension.java

@Component
public class MailServerExtension implements BeforeEachCallback, AfterEachCallback {

    private GreenMail greenMail = new GreenMail(new ServerSetup(25, "localhost", ServerSetup.PROTOCOL_SMTP));

    @Override
    public void beforeEach(ExtensionContext context) {
        greenMail.start();
    }

    @Override
    public void afterEach(ExtensionContext context) {
        greenMail.stop();
    }

    ..........
  • extends ExternalResource を削除します。
  • 以下のメソッドを削除します。
    • protected void before() { ... }
    • protected void after() { ... }
  • beforeEach メソッド内の before();greenMail.start(); に変更します。
  • afterEach メソッド内の after();greenMail.stop(); に変更します。

src/test/java/ksbysample/common/test/extension/mockmvc/SecurityMockMvcExtension.java

@Component
@ConditionalOnWebApplication
public class SecurityMockMvcExtension implements BeforeEachCallback {

    ..........

    @Override
    public void beforeEach(ExtensionContext context) {
        // 認証ユーザ用MockMvc ( user = tanaka.taro@sample.com )
        UserDetails userDetailsTanakaTaro = userDetailsService.loadUserByUsername(MAILADDR_TANAKA_TARO);
        this.authTanakaTaro = MockMvcBuilders.webAppContextSetup(this.context)
                .defaultRequest(get("/").with(user(userDetailsTanakaTaro)))
                .apply(springSecurity())
                .build();

        // 認証ユーザ用MockMvc ( user = suzuki.hanako@test.co.jp )
        UserDetails userDetailsSuzukiHanako = userDetailsService.loadUserByUsername(MAILADDR_SUZUKI_HANAKO);
        this.authSuzukiHanako = MockMvcBuilders.webAppContextSetup(this.context)
                .defaultRequest(get("/").with(user(userDetailsSuzukiHanako)))
                .apply(springSecurity())
                .build();

        // 認証ユーザ用MockMvc ( user = ito.aoi@test.co.jp )
        UserDetails userDetailsItoAoi = userDetailsService.loadUserByUsername(MAILADDR_ITO_AOI);
        this.authItoAoi = MockMvcBuilders.webAppContextSetup(this.context)
                .defaultRequest(get("/").with(user(userDetailsItoAoi)))
                .apply(springSecurity())
                .build();

        // 非認証ユーザ用MockMvc
        this.noauth = MockMvcBuilders.webAppContextSetup(this.context)
                .apply(springSecurity())
                .build();
    }

}
  • extends ExternalResource を削除します。
  • before メソッド内のコードを beforeEach メソッドへ移動します。
  • 以下のメソッドを削除します。
    • protected void before() { ... }

src/test/java/ksbysample/common/test/helper/DescriptionWrapper.java

このファイルは使用されなくなったので削除します。

src/test/groovy/ksbysample/webapp/lending/SampleHelperTest.groovy

@RunWith(Enclosed) が記述されていたので削除します。

clean タスクで out ディレクトリが削除されるようにする

build.gradle の clean block を以下のように変更します。

clean {
    doLast {
        rootProject.file("out").deleteDir()
        rootProject.file("src/main/generated").deleteDir()
        rootProject.file("src/test/generated_tests").deleteDir()
    }
}
  • rootProject.file("out").deleteDir() を追加します。

Mockito.mockStatic を呼び出しているテストで try-with-resources 構文を使用してリソースが close されるようにする

src/test/groovy/ksbysample/webapp/lending/SampleHelperTest.groovy で以下のように書いているテストを、

    @SpringBootTest
    static class 異常処理のテスト {

        @Autowired
        private SampleHelper sampleHelper

        @Test
        void "SampleHelper_encryptを呼ぶとRuntimeExceptionをthrowする"() {
            // setup:
            Mockito.mockStatic(BrowfishUtils)
            Mockito.when(BrowfishUtils.encrypt(Mockito.any()))
                    .thenThrow(new NoSuchPaddingException())

            // expect:
            assertThatThrownBy(() -> {
                sampleHelper.encrypt("test")
            }).isInstanceOf(RuntimeException)
        }

    }

try-with-resources 構文を使用してリソースが close されるよう書き直します。

    @SpringBootTest
    static class 異常処理のテスト {

        @Autowired
        private SampleHelper sampleHelper

        @Test
        void "SampleHelper_encryptを呼ぶとRuntimeExceptionをthrowする"() {
            try (MockedStatic mockedBrowfishUtils = Mockito.mockStatic(BrowfishUtils)) {
                // setup:
                Mockito.when(BrowfishUtils.encrypt(Mockito.any()))
                        .thenThrow(new NoSuchPaddingException())

                // expect:
                assertThatThrownBy(() -> {
                    sampleHelper.encrypt("test")
                }).isInstanceOf(RuntimeException)
            }
        }

    }

Lombok を 1.18.20 → 1.18.22 にバージョンアップする

IntelliJ IDEA で build.gradle を見ていたら org.projectlombok:lombok: のところの色が変わっていて、マウスオーバーすると 1.8.22 がリリースされていることが分かりました。Alt+Enter を押すとバージョンも書き替えることも出来ます。この機能、すごい便利じゃないですか。。。

f:id:ksby:20211020062725p:plain

https://projectlombok.org/changelog を見ると v1.18.22 で PLATFORM: JDK17 support added. とのことなので、バージョンアップします。

build.gradle の以下の点を変更します。

dependencies {
    def spockVersion = "2.0-groovy-3.0"
    def jdbcDriver = "org.postgresql:postgresql:42.2.24"
    def domaVersion = "2.49.0"
    def lombokVersion = "1.18.22"
    def errorproneVersion = "2.9.0"

    ..........
  • def lombokVersion = "1.18.20"def lombokVersion = "1.18.22" に変更します。

Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

WebApplicationRuntimeException クラスを Lombok の @StandardException を利用して書き直す

https://projectlombok.org/changelog の v1.18.22 のところを見ると @StandardException というアノテーションが追加されたとの記載があり、https://github.com/projectlombok/lombok/pull/2702 を見ると Exception クラスを継承して作成する時の基本的なコンストラクタを4つ自動生成してくれるとのこと。

便利そうなので WebApplicationRuntimeException クラスを @StandardException を利用して書き直してみます。

src/main/java/ksbysample/webapp/lending/exception/WebApplicationRuntimeException.java は現在以下のように実装していますが、

package ksbysample.webapp.lending.exception;

public class WebApplicationRuntimeException extends RuntimeException {

    @SuppressWarnings("PMD.FieldNamingConventions")
    private static final long serialVersionUID = 3845674924872653036L;

    public WebApplicationRuntimeException() {
        super();
    }

    public WebApplicationRuntimeException(String message) {
        super(message);
    }

    public WebApplicationRuntimeException(String message, Throwable cause) {
        super(message, cause);
    }

    public WebApplicationRuntimeException(Throwable cause) {
        super(cause);
    }

}

@StandardException を利用して書き直すと以下のようになります。@SuppressWarnings("PMD.FieldNamingConventions") も build 時に PMD が警告を出さないことに気づいたので削除します。

package ksbysample.webapp.lending.exception;

import lombok.experimental.StandardException;

import java.io.Serial;

@StandardException
public class WebApplicationRuntimeException extends RuntimeException {

    @Serial
    private static final long serialVersionUID = 3845674924872653036L;

}

build タスクでエラーが出ないことを確認する

最後に clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。

f:id:ksby:20211020072446p:plain

履歴

2021/10/20
初版発行。