かんがるーさんの日記

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

IntelliJ IDEA を 2019.1.1 → 2019.1.2 へバージョンアップ

IntelliJ IDEA を 2019.1.1 → 2019.1.2 へバージョンアップする

IntelliJ IDEA の 2019.1.2 がリリースされているのでバージョンアップします。

※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。

  1. IntelliJ IDEA のメインメニューから「Help」-「Check for Updates...」を選択します。

  2. IDE and Plugin Updates」ダイアログが表示されます。左下に「Update and Restart」ボタンが表示されていますので、「Update and Restart」ボタンをクリックします。

    f:id:ksby:20190515205305p:plain

  3. Plugin の update も表示されました。このまま「Update and Restart」ボタンをクリックします。

    f:id:ksby:20190515205410p:plain

  4. Patch がダウンロードされて IntelliJ IDEA が再起動します。

  5. IntelliJ IDEA が起動すると画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。

    f:id:ksby:20190515210152p:plain

  6. IntelliJ IDEA のメインメニューから「Help」-「About」を選択し、2019.1.2 へバージョンアップされていることを確認します。

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

  8. 更新可能な Plugin があるというダイアログが画面右下に表示されたので、再度 IntelliJ IDEA のメインメニューから「Help」-「Check for Updates...」を選択します。

  9. IDE and Plugin Updates」ダイアログが表示されますので「Update」ボタンをクリックします。

  10. Plugin がインストールされます。画面右下に Restart のダイアログが表示されますので、リンクをクリックして IntelliJ IDEA を再起動します。

  11. IntelliJ IDEA が起動すると画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。

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

    f:id:ksby:20190515211804p:plain

  13. Project Tool Window で src/test/groovy/ksbysample、src/test/java/ksbysample でコンテキストメニューを表示して「Run 'Tests in 'ksbysample'' with Coverage」を選択し、テストが全て成功することを確認します。

    f:id:ksby:20190515232215p:plain f:id:ksby:20190515232935p:plain

Gradle で Multi-project を作成する ( 感想 )

記事一覧はこちらです。

  • Multi-project は Gradle Guides の Creating Multi-project Builds を見ながらやればそれ程難しくはない印象です。単に1つのプロジェクトに複数のサブプロジェクトを入れるだけ(スタブやテスト用のアプリケーションを置くだけで設定の共通化をしない)ならば settings.gradle に include を書くだけでよいのは簡単で便利だなと思いました。

  • Spring Boot ベースのプロジェクトで実行可能 Jar ではなくライブラリの jar を作成するには build.gradle に bootJar { enabled = false } jar { enabled = true } を記述すればよいということも今回初めて知りました。

    Spring Boot もバージョン 2.x から bootJar タスクで実行可能 Jar を作成するようになりましたが、バージョン 1.x の頃の記事を見直してみると jar タスクを実行してから bootRepackage タスクを実行していました。これまでこの違いをきちんと認識出来ていませんでした。。。

  • 実行可能 Jar を起動するのに launcher subclass というものがあること、launcher subclass には JarLauncher, WarLauncher, PropertiesLauncher があること、PropertiesLauncher を使用すれば実行可能 Jar の外側に置いたライブラリ Jar を利用することができること、も今回始めて知りました。PropertiesLauncher を使用するかと聞かれると1つの実行可能 Jar にまとめてリリースした方がミスしなさそうなので自分は使用しない気がしますが、実際に本番でも利用されているものなのでしょうか?

  • Vue.js のプロジェクトで生成したファイルを Spring Boot の実行可能 Jar に含めようとするのは意外に大変でした。

    • まさかデフォルトでは build タスクが存在しないとは。。。 Base Plugin を使えばよいのですが、Gradle は Java のプロジェクトで使われることが多く Java のプロジェクトでは Java Plugin が使われるので Base Plugin の存在になかなか気づけませんでした。
    • build タスクを実行した時のサブプロジェクトの実行順がアルファベット順になるということも初めて知りました。今回 backend-app と frontend-app という名前にしたので、frontend-app → backend-app の順に build したいのにいろいろ設定しても backend-app → frontend-app の順が変わりませんでした。最終的には copyDistToStatic.dependsOn ":frontend-app:build" と記述することで解決しましたが、他にも解決策として書かれている記事をいくつか見つけて試したのですがなかなか変わらなかったんですよね。まだまだ Gradle を理解できていませんね。。。
  • 今更ながら draw.io のことを知りましたが、とても便利ですね。これで個人・商用問わず無料らしいとは驚きです。EIP の Shape も結構揃っているので Spring Integration のフローを記述するのにも使えそうです。

Gradle で Multi-project を作成する ( その15 )( vuejs+springboot編、frontend-app プロジェクトを作成する )

概要

記事一覧はこちらです。

Gradle で Multi-project を作成する ( その14 )( vuejs+springboot編、Multi-project のベースと backend-app プロジェクトを作成する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • frontend を Vue.js で、backend を Spring Boot でアプリケーションを実装し、各アプリケーションをサブプロジェクトとする Gradle Multi-project のサンプルを作成します。
    • 今回は Vue.js のアプリケーション(frontend)を作成し、gradle build で実行可能 jar にまとめます。

参照したサイト・書籍

  1. Vue CLI
    https://cli.vuejs.org/

  2. Vuetify
    https://vuetifyjs.com/en/

  3. How to build a web app with Vue, Vuetify and Axios
    https://medium.com/javascript-in-plain-english/implement-movie-app-with-vue-vuetify-axios-open-movie-database-api-d12290318cf9

  4. axios、async/awaitを使ったHTTPリクエスト(Web APIを実行)
    https://qiita.com/shisama/items/61cdcc09dc69fd8d3127

  5. async関数においてtry/catchではなくawait/catchパターンを活用する
    https://qiita.com/akameco/items/cc73afcdb5ac5d0774bc

  6. Vue.jsでビューの変更がされないときに疑うこと+主な解決策方法
    https://cloudpack.media/41984

  7. Vue Test Utils
    https://vue-test-utils.vuejs.org/

  8. Vue.jsのテストでコンポーネントをいい感じにwrapする方法
    https://qiita.com/ykhirao/items/8e8a9547a693c677813c

  9. Vue CLI - Configuration Reference - devServer.proxy
    https://cli.vuejs.org/config/#devserver-proxy

  10. Gradle Docs - The Base Plugin
    https://docs.gradle.org/current/userguide/base_plugin.html

  11. node-gradle/gradle-node-plugin
    https://github.com/node-gradle/gradle-node-plugin

  12. node-gradle/gradle-node-plugin - Node Plugin
    https://github.com/node-gradle/gradle-node-plugin/blob/master/docs/node.md

目次

  1. Vue.js のアプリケーション(frontend)(frontend-app)を作成する
    1. Vue CLI をインストールする
    2. Vue CLI で frontend-app プロジェクトを作成する
    3. Vuetify をインストールする
    4. npm run buildnpm run lintnpm run test:unit 実行時に prettier で自動フォーマットするよう設定する
    5. WebAPI を呼び出して取得したデータを画面に表示する処理を実装する
    6. vue.config.js を作成して webpack-dev-server に proxy の設定を追加する
    7. 動作確認
  2. Gradle の build タスク実行時に npm run test:unitnpm run build が実行されるようにする
  3. Gradle の build タスク実行時に frontend-app の dist ディレクトリの下にあるファイルを backend-app の src/main/resources/static にコピーする
  4. settings.gradle に sample-cmdapp プロジェクトの include 文を追加する
  5. clean タスク実行 → Rebuild Project 実行 → build タスク実行を行う
  6. 実行可能 jar から Tomcat を起動して動作確認する
  7. 次回は。。。

手順

Vue.js のアプリケーション(frontend)(frontend-app)を作成する

Vue CLI をインストールする

以下のコマンドを実行して Vue CLI をインストールします。

  • npm install -g @vue/cli
  • npm install -g @vue/cli-service-global

f:id:ksby:20190506093206p:plain

以下の警告メッセージが出ていますが、TypeScript、GraphQL は使用しないので今回は何もしません。

  • ts-node@8.1.0 requires a peer of typescript@>=2.0 but none is installed.
  • apollo-tracing@0.5.2 requires a peer of graphql@0.10.x - 14.1.x but none is installed.

インストールされた Vue CLI のバージョンを vue --version コマンドで確認すると 3.7.0 でした。

f:id:ksby:20190506101848p:plain

Vue CLI で frontend-app プロジェクトを作成する

コマンドラインから vue create frontend-app を実行して frontend-app プロジェクトを作成します。

f:id:ksby:20190506093745p:plain ※「Please pick a preset:」では「Manually select features」を選択します。

f:id:ksby:20190506093932p:plain ※「Check the features needed for your project:」はデフォルトで「Babel」「Linter / Formatter」が選択済みで、「Unit Testing」を追加で選択します。

f:id:ksby:20190506094156p:plain ※「Pick a linter / formatter config:」では「ESLint + Prettier」を選択します。

f:id:ksby:20190506094643p:plain ※「Pick additional lint features:」はデフォルトで「Lint on save」が選択済みで、そのままにします。

f:id:ksby:20190506094804p:plain ※「Pick a unit testing solution:」では「Jest」を選択します。

f:id:ksby:20190506094922p:plain ※「Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? (Use arrow keys)」では「In dedicated config files」を選択します。

f:id:ksby:20190506095230p:plain ※「Save this as a preset for future projects? (y/N)」は何も入力せずに Enter キーを押します。

インストールが実行されます。

f:id:ksby:20190506095625p:plain

インストール後 frontend-app ディレクトリに移動してから npm run serve コマンドを実行して、

f:id:ksby:20190506101444p:plain f:id:ksby:20190506101541p:plain

http://localhost:8080/ にアクセスすると画面が表示されました。

f:id:ksby:20190506101648p:plain

npm run build コマンドを実行すると、警告・エラーは出ずに終了します。

f:id:ksby:20190506103005p:plain

IntelliJ IDEA の Project Tool Window で frontend-app のディレクトリ構成を見てみると以下のようになっていました。尚、IntelliJ IDEA で最初に表示させる時には「Indexing…」のメッセージが表示されてしばらく時間がかかります(おそらく node_modules の下の大量のファイルを index するのに時間がかかっているためでしょう)。

f:id:ksby:20190506103059p:plain

Vuetify をインストールする

Quick start の記述に従い vue add vuetify コマンドを実行します。

f:id:ksby:20190506110432p:plain ※「Choose a preset: (Use arrow keys)」はデフォルトの「Default (recommended)」のままにします。

f:id:ksby:20190506110617p:plain

インストール直後の状態で npm run build コマンドを実行すると prettier のフォーマットと異なるために eslint が警告を大量に出すので、npm run build コマンド実行時に prettier で自動フォーマットして警告が出ないようにします。

npm run buildnpm run lintnpm run test:unit 実行時に prettier で自動フォーマットするよう設定する

npm install --save-dev npm-run-all コマンドを実行して npm-run-all をインストールします。

f:id:ksby:20190506133306p:plain

frontend-app/package.json"scripts": { ... } を以下のように変更します。

  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "run-s prettier:format vue-cli-service:build",
    "lint": "run-s prettier:format vue-cli-service:lint",
    "test:unit": "run-s prettier:format vue-cli-service:test:unit",
    "vue-cli-service:build": "vue-cli-service build",
    "vue-cli-service:lint": "vue-cli-service lint",
    "vue-cli-service:test:unit": "vue-cli-service test:unit",
    "prettier:format": "prettier --write {src,tests}/**/*.{js,vue}"
  },
  • 以下の4行を追加します。今回初めて知りましたが、Paste した時に Paste した行や1つ上の行の末尾に "," がないと IntelliJ IDEA が自動で付けてくれますね。
    • "vue-cli-service:build": "vue-cli-service build"
    • "vue-cli-service:lint": "vue-cli-service lint"
    • "vue-cli-service:test:unit": "vue-cli-service test:unit"
    • "prettier:format": "prettier --write {src,tests}/**/*.{js,vue}"
  • "build": "vue-cli-service build""build": "run-s prettier:format vue-cli-service:build" に変更します。
  • "lint": "vue-cli-service lint""lint": "run-s prettier:format vue-cli-service:lint" に変更します。
  • "test:unit": "vue-cli-service test:unit""test:unit": "run-s prettier:format vue-cli-service:test:unit" に変更します。

以上で設定は完了です。再度 npm run build コマンドを実行してみると2件 warning が出ていますが Build complete. が表示されました。

f:id:ksby:20190506135038p:plain f:id:ksby:20190506135128p:plain

2件の warning はファイルサイズや lazy load に関するものでしたので、今回は解消せずに先に進みます。

また IntelliJ IDEA 上でも prettier でフォーマットできるように設定します。メインメニューから「File」-「Settings...」を選択して「Settings」ダイアログを表示させた後、画面右上に prettier と入力して Prettier Plugin の設定画面を開いてから、画面右側の「Prettier package」に frontend-app/node_modules/prettier の絶対パスを設定します。

f:id:ksby:20190506143006p:plain

WebAPI を呼び出して取得したデータを画面に表示する処理を実装する

WebAPI を呼び出すのに axios を使用するので npm install --save axios コマンドを実行してインストールします。

f:id:ksby:20190506142722p:plain

frontend-app/src/components/HelloWorld.vue のファイル名を CallSampleWebapi.Vue に変更します。

frontend-app/src/App.vue を以下のように変更します。

<template>
  <v-app>
    <v-content>
      <CallSampleWebapi />
    </v-content>
  </v-app>
</template>

<script>
import CallSampleWebapi from "./components/CallSampleWebapi";

export default {
  name: "App",
  components: {
    CallSampleWebapi: CallSampleWebapi
  },
  data() {
    return {
      //
    };
  }
};
</script>
  • <v-toolbar app>...</v-toolbar> を削除します。
  • HelloWorldCallSampleWebapi に一括置換します。

frontend-app/src/components/CallSampleWebapi.Vue を以下のように変更します。

<template>
  <v-container>
    <v-layout text-xs-center wrap>
      <v-flex xs12>
        <div>code: {{ code }}</div>
        <div>value: {{ value }}</div>
        <v-btn color="info" v-on:click="callSampleWebapi">クリック!</v-btn>
      </v-flex>
    </v-layout>
  </v-container>
</template>

<script>
import axios from "axios";

export default {
  data: function() {
    return {
      code: "(空)",
      value: "(空)"
    };
  },
  methods: {
    callSampleWebapi: async function() {
      try {
        const response = await axios.post("/webapi/sample");
        this.$set(this, "code", response.data.code);
        this.$set(this, "value", response.data.value);
      } catch (err) {
        alert(err);
      }
    }
  }
};
</script>

<style></style>

frontend-app/tests/unit/example.spec.js もファイル名を CallSampleWebapi.spec.js に変更した後、以下の内容に変更します。

import { mount } from "@vue/test-utils";
import Vue from "vue";
import Vuetify from "vuetify";
import CallSampleWebapi from "@/components/CallSampleWebapi.vue";

describe("CallSampleWebapi.vue test", () => {
  it("init render test", () => {
    Vue.use(Vuetify);
    const wrapper = mount(CallSampleWebapi, {});
    expect(wrapper.html()).toContain("<div>code: (空)</div>");
  });
});

vue.config.js を作成して webpack-dev-server に proxy の設定を追加する

npm run serve コマンドで起動した webpack-dev-server に http://localhost:8080/webapi/sample でアクセスしたら backend-app の http://localhost:8081/webapi/sample へリクエストを転送させるための設定を行います。

frontend-app の下に vue.config.js を新規作成した後、以下の内容を記述します。changeOrigin: true を記述すると backend-app 側で @RestController アノテーションを付与したクラスに @CrossOrigin アノテーションを付与して設定しなくてもアクセスできるようになります。

module.exports = {
  devServer: {
    proxy: {
      "^/webapi/sample": {
        target: "http://localhost:8081",
        changeOrigin: true
      }
    }
  }
};

動作確認

backend-app の Tomcat を develop profile で起動してから、

f:id:ksby:20190506202707p:plain

npm run serve コマンドを実行して frontend-app の webpack-dev-server を起動した後、

f:id:ksby:20190506203121p:plain

http://localhost:8080/ にアクセスすると以下の画面が表示されます。

f:id:ksby:20190506203230p:plain

「クリック!」ボタンをクリックすると backend-app の WebAPI を呼び出して取得した code, value の値が画面に表示されました。

f:id:ksby:20190506203339p:plain

Tomcat、webpack-dev-server を停止します。

npm run test:unit コマンドを実行するとテストも成功します。

f:id:ksby:20190506203722p:plain

npm run build コマンドも2件の warning は出たままですが、他の警告・エラーは出ずに Build complete. が表示されました。

f:id:ksby:20190506204058p:plain f:id:ksby:20190506204152p:plain

Gradle の build タスク実行時に npm run test:unitnpm run build が実行されるようにする

frontend-app ディレクトリの下に build.gradle を新規作成し、以下の内容を記述します。

plugins {
    id "base"
    id "com.github.node-gradle.node" version "1.3.0"
}

clean.delete "dist"
task npmTestUnit(type: NpmTask) {
    args = ["run", "test:unit", "2>&1"]
    execOverrides {
        it.standardOutput = new ByteArrayOutputStream()
    }
}
task npmBuild(type: NpmTask) {
    args = ["run", "build", "2>&1"]
    execOverrides {
        it.standardOutput = new ByteArrayOutputStream()
    }
}
npmBuild.dependsOn npmTestUnit
build.dependsOn npmBuild
  • id "base" を記述することで clean や build タスクが使えるようになります。
  • npm run ... コマンドの実行には gradle-node-plugin を使用します。
  • args に "2>&1" を指定して標準エラー出力を標準出力にリダイレクトし、execOverrides { it.standardOutput = new ByteArrayOutputStream() } を記述することで標準出力、標準エラー出力に何も出力されないようにします。

ちなみに標準出力、標準エラー出力の内容をファイルに出力したい場合には、以下のように記述すれば frontend-app/build/tests/stdout.txt に出力されます。

task npmTestUnit(type: NpmTask) {
    args = ["run", "test:unit", "2>&1"]

    def testsDir = "${projectDir}/build/tests"
    execOverrides {
//        it.standardOutput = new ByteArrayOutputStream()
        it.standardOutput = new FileOutputStream("${testsDir}/stdout.txt")
    }
    doFirst {
        mkdir testsDir
    }
}

Gradle の build タスク実行時に frontend-app の dist ディレクトリの下にあるファイルを backend-app の src/main/resources/static にコピーする

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

..........

dependencies {
    ..........
}

clean.delete fileTree("src/main/resources/static").include("**/*")
task copyDistToStatic(type: Copy) {
    from project(":frontend-app").file("dist")
    into "src/main/resources/static"
}
copyDistToStatic.dependsOn ":frontend-app:build"
processResources.dependsOn copyDistToStatic
  • clean.delete fileTree("src/main/resources/static").include("**/*")processResources.dependsOn copyDistToStatic の記述を追加します。

settings.gradle に frontend-app プロジェクトの include 文を追加する

gradle-multiprj-vuejs-springboot の settings.gradle に include 'frontend-app' を追加します。

rootProject.name = 'gradle-multiprj-vuejs-springboot'
include 'backend-app'
include 'frontend-app'

Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。Gradle Tool Window に frontend-app が表示されます。

f:id:ksby:20190507230434p:plain

clean タスク実行 → Rebuild Project 実行 → build タスク実行を行う

clean タスクを実行すると backend-app は src/main/resources/static の下がクリアされて、frontend-app は dist ディレクトリが削除されます。

f:id:ksby:20190507231149p:plain

build タスクを実行すると警告・エラーは出ずに BUILD SUCCESSFUL が表示されて、

f:id:ksby:20190507231420p:plain

frontend-app の dist ディレクトリの下のディレクトリ・ファイル一式が backend-app の src/main/resources/static の下にコピーされます。

f:id:ksby:20190507231706p:plain

実行可能 jar から Tomcat を起動して動作確認する

backend-app/build/libs の下に backend-app-1.0.0-RELEASE.jar が生成されていますので、

f:id:ksby:20190507235208p:plain

コマンドラインから java -Dspring.profiles.active=product -jar backend-app-1.0.0-RELEASE.jar を実行します。

f:id:ksby:20190507235401p:plain

http://localhost:8080/ にアクセスすると以下の画面が表示されて、

f:id:ksby:20190507235509p:plain

「クリック!」ボタンをクリックすると WebAPI を呼び出して取得したデータが画面に表示されました。

f:id:ksby:20190507235605p:plain

次回は。。。

frontend-app の下に作成した build.gradle のタスクを build タスク実行時に動かすための The Base Plugin を見つけるまでが意外に時間がかかりました。書いてある記事が少ないのか、なかなか見つけられなかったんですよね。。。

これでとりあえずやりたいことは全てやったので、最後に感想を書いて終わりにします。

履歴

2019/05/08
初版発行。

Gradle で Multi-project を作成する ( その14 )( vuejs+springboot編、Multi-project のベースと backend-app プロジェクトを作成する )

概要

記事一覧はこちらです。

Gradle で Multi-project を作成する ( その13 )( doma2lib+cmdapp+webapp編、PropertiesLauncher を利用して doma2-lib の jar ファイルを外部に出す ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • frontend を Vue.js で、backend を Spring Boot でアプリケーションを実装し、各アプリケーションをサブプロジェクトとする Gradle Multi-project のサンプルを作成します。
    • Vue CLI で作成した Vue.js のアプリケーションと Spring Boot のアプリケーションをそれぞれ別のディレクトリ(サブプロジェクト)で作成して、最後は必要なファイルを Spring Boot の実行可能 jar にまとめてリリース・実行する想定です。

参照したサイト・書籍

目次

  1. 方針
  2. gradle-multiprj-vuejs-springboot ディレクトリ作成+Gradle Wrapper コピー+gradlew init
  3. IntelliJ IDEA で gradle-multiprj-vuejs-springboot プロジェクトをオープンする
  4. Spring Boot ベースの Web アプリケーション(backend)(backend-app)を作成する
    1. IntelliJ IDEA で backend-app プロジェクトを作成する
    2. settings.gradle に backend-app プロジェクトの include 文を追加する
    3. backend-app プロジェクトの build.gradle を変更する
    4. Vue.js のアプリから呼び出す WebAPI を実装する
    5. 動作確認

手順

方針

gradle-multiprj-vuejs-springboot
├ backend-app    <-- Spring Boot ベースの Web アプリケーション(backend)の Project
└ frontend-app    <-- Vue.js のアプリケーション(frontend)の Project
  • Vue.js のアプリケーション(frontend)でボタンをクリックしたら Spring Boot ベースの Web アプリケーション(backend)の WebAPI を呼び出して、取得したデータを画面上に表示します。
  • Spring Boot ベースの Web アプリケーションの Project では develop と product の2つの Profile を作成・使用します。
  • Spring Boot ベースの Web アプリケーションの Project ではテスティングフレームワークは導入しません(今回はテストなし!)。
  • checkstyle, spotbugs, pmd, error-prone は今回は導入しません。
  • Spring Boot ベースの Web アプリケーション(backend)は develop profile では 8081番ポート、product profile では 8080番ポートを使用します。
  • Vue.js のアプリケーションは Vue CLI を使用してプロジェクトを作成します。
  • Vue.js のアプリケーションの画面は Vuetify を使用して作成します。
  • Vue.js のアプリケーション(frontend)では npm run serve 実行時には 8080番ポートを使用します。
  • Gradle の build タスクで以下の処理を行います。
    • Vue.js のアプリケーション(frontend)の npm run build を実行します。
    • Spring Boot ベースの Web アプリケーション(backend)の processResources タスクの前に src/main/resources/static の下に Vue.js のアプリケーション(frontend)のリリースファイル一式(/dist ディレクトリの下に出来るもの)をコピーします。
  • 開発環境及び本番環境どちらも http://localhost:8080/ でアクセスします。
  • 開発環境では npm run serve で起動した webpack-dev-server から http-proxy-middleware で backend-app の Tomcat へリクエストを転送します。
  • こんなイメージです。

f:id:ksby:20190505095922p:plain

gradle-multiprj-vuejs-springboot ディレクトリ作成+Gradle Wrapper コピー+gradlew init

ksby/ksbysample-boot-miscellaneous の repository を checkout している D:\project-springboot\ksbysample-boot-miscellaneous の下に gradle-multiprj-vuejs-springboot ディレクトリを作成します。

f:id:ksby:20190505100848p:plain

gradle-multiprj-doma2lib-cmdwebapp プロジェクトから Gradle Wrapper のファイルをコピーします。

f:id:ksby:20190505101245p:plain

gradlew init コマンドを実行します。選択肢は 1: basic1: groovy を選択し、Project name は何も入力せずに Enter キーを押します。

f:id:ksby:20190505101506p:plain f:id:ksby:20190505101558p:plain

IntelliJ IDEA で gradle-multiprj-vuejs-springboot プロジェクトをオープンする

gradle-multiprj-vuejs-springboot プロジェクトをオープンします。

f:id:ksby:20190505101832p:plain

f:id:ksby:20190505102147p:plainf:id:ksby:20190505102226p:plain

Spring Boot ベースの Web アプリケーション(backend)(backend-app)を作成する

IntelliJ IDEA で backend-app プロジェクトを作成する

IntelliJ IDEA から Spring Initializr を利用して backend-app プロジェクトを作成します。

f:id:ksby:20190505152830p:plainf:id:ksby:20190505153013p:plain
f:id:ksby:20190505153106p:plainf:id:ksby:20190505153139p:plain
f:id:ksby:20190505153232p:plainf:id:ksby:20190505153301p:plain

f:id:ksby:20190505153401p:plain

作成後 IntelliJ IDEA のウィンドウが開きますが、何もせずに閉じます。

src ディレクトリと build.gradle だけ残して、それ以外のディレクトリ・ファイルは全て削除します。

また backend-app/src/test/java/ksbysample/app/backendapp/BackendAppApplicationTests.java.java も削除し、backend-app/src/test/java/ の下はクリアします。

settings.gradle に backend-app プロジェクトの include 文を追加する

gradle-multiprj-vuejs-springboot の settings.gradle に include 'backend-app' を追加します。

rootProject.name = 'gradle-multiprj-vuejs-springboot'
include 'backend-app'

backend-app プロジェクトの build.gradle を変更する

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

plugins {
    id 'org.springframework.boot' version '2.1.4.RELEASE'
    id 'java'
    id "idea"
}

apply plugin: 'io.spring.dependency-management'

group = 'ksby.ksbysample-boot-miscellaneous.gradle-multiprj-vuejs-springboot'
version = '1.0.0-RELEASE'

sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11

idea {
    module {
        inheritOutputDirs = false
        outputDir = file("$buildDir/classes/main/")
    }
}

repositories {
    mavenCentral()
}

dependencyManagement {
    imports {
        mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
    }
}

dependencies {
    def lombokVersion = "1.18.6"

    implementation 'org.springframework.boot:spring-boot-starter-web'
    runtimeOnly 'org.springframework.boot:spring-boot-devtools'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

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

変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。Gradle Tool Window に backend-app が表示されます。

f:id:ksby:20190505155232p:plain

clean タスク実行 → Rebuild Project 実行 → build タスク実行を行い、警告・エラーが出ずに BUILD SUCCESSFUL が出力されることを確認します。

f:id:ksby:20190505155458p:plain

Vue.js のアプリから呼び出す WebAPI を実装する

POST メソッドで /webapi/sample の URL にアクセスしたら以下の JSON を返す WebAPI を実装します。

{
    "code": "123",
    "value": "sample data"
}

backend-app/src/main/java/ksbysample/app/backendapp の下に SampleResponse.java を新規作成し、以下の内容を記述します。

package ksbysample.app.backendapp;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SampleResponse {

    private String code;

    private String value;

}

backend-app/src/main/java/ksbysample/app/backendapp の下に SampleController.java を新規作成し、以下の内容を記述します。今回は ResponseEntity を使ってみます。

package ksbysample.app.backendapp;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/webapi/sample")
public class SampleController {

    @PostMapping
    public ResponseEntity<SampleResponse> index() {
        SampleResponse sampleResponse =
                SampleResponse.builder()
                        .code("123")
                        .value("sample data")
                        .build();
        return new ResponseEntity<>(sampleResponse, HttpStatus.OK);
    }

}

src/main/resources の下に application-develop.properties を新規作成し、以下の内容を記述します。

server.port=8081

server.port のデフォルトが 8080 なので application-product.properties は作成しません。

動作確認

最初に clean タスク実行 → Rebuild Project 実行 → build タスク実行を行い、BUILD SUCCESSFUL が出力されることを確認します。

f:id:ksby:20190506084133p:plain

次に develop profile で Tomcat を起動した後、

f:id:ksby:20190506084339p:plain

コマンドラインから curl http://localhost:8081/webapi/sample -i -X POST を実行すると JSON データが返ってきました。

f:id:ksby:20190506084651p:plain

Tomcat を停止します。

今度は product profile で実行可能 jar で Tomcat を起動した後、

f:id:ksby:20190506084957p:plain

コマンドラインから curl http://localhost:8080/webapi/sample -i -X POST(ポート番号を 8081 → 8080 に変更しています) を実行するとこちらも問題なく JSON データが返ってきました。

f:id:ksby:20190506085134p:plain

履歴

2019/05/06
初版発行。

Gradle で Multi-project を作成する ( その13 )( doma2lib+cmdapp+webapp編、PropertiesLauncher を利用して doma2-lib の jar ファイルを外部に出す )

概要

記事一覧はこちらです。

Gradle で Multi-project を作成する ( その12 )( doma2lib+cmdapp+webapp編、sample-webapp プロジェクトを作成する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Doma 2 の Entity、Dao を提供するライブラリ+Spring Boot ベースのコマンドラインアプリケーション+Spring Boot ベースの Web アプリケーションの Multi-project を作成します。
    • doma2lib+cmdapp+webapp編のここまでの記事では実行可能 Jar ファイルを起動するのに JarLauncher を利用する方式でしたので sample-cmdapp-1.0.0-RELEASE.jar、sample-webapp-1.0.0-RELEASE.jar の中に doma2-lib-1.0.0-RELEASE.jar が入っていましたが、PropertiesLauncher を利用する方式に変更して doma2-lib-1.0.0-RELEASE.jar を外部に出すようにしてみます。

参照したサイト・書籍

  1. E.3 Launching Executable Jars
    https://docs.spring.io/spring-boot/docs/current/reference/html/executable-jar.html#executable-jar-launching

  2. Spring Boot 2 and external libs with the PropertiesLauncher
    https://medium.com/saas-startup-factory/spring-boot-2-and-external-libs-with-the-propertieslauncher-fc49d2d93636

  3. Gradle Docs 5.4.1 - Copy
    https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Copy.html

目次

  1. gradle-multiprj-doma2lib-cmdwebapp の build.gradle を変更する
  2. sample-cmdapp、sample-webapp の build.gradle を変更する
  3. clean タスク実行 → Rebuild Project 実行 → build タスク実行を行う
  4. 動作確認
  5. sample-webapp を IntelliJ IDEA の Run Dashboard から起動するとどうなるのか?
  6. 次回は。。。

手順

gradle-multiprj-doma2lib-cmdwebapp の build.gradle を変更する

PropertiesLauncher を利用するのに必要な設定と、jar ファイルを gradle-multiprj-doma2lib-cmdwebapp プロジェクトのルートディレクトリ直下の libs ディレクトリ(新規に作成します)にコピーするための task を gradle-multiprj-doma2lib-cmdwebapp の build.gradle に記述します。

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

..........

subprojects {
    ..........

    // サブプロジェクトの build/libs の下に生成された jar ファイルを
    // プロジェクトのルートディレクトリ直下の libs ディレクトリにコピーする
    task copyJarToLibsDir(type: Copy) {
        from "build/libs"
        include "*.jar"
        into "${rootDir}/libs"
    }
    copyJarToLibsDir.dependsOn test
    check.dependsOn copyJarToLibsDir
}

configure(subprojects.findAll { it.name ==~ /^(doma2-lib|sample-cmdapp|sample-webapp)$/ }) {
    ..........
}

configure(subprojects.findAll { it.name ==~ /^(sample-cmdapp|sample-webapp)$/ }) {
    bootJar {
        manifest {
            attributes 'Main-Class': 'org.springframework.boot.loader.PropertiesLauncher'
        }
    }
}
  • subproject block 内に task copyJarToLibsDir(type: Copy) { ... } を追加します。また test タスクの後に実行するために copyJarToLibsDir.dependsOn testcheck.dependsOn copyJarToLibsDir の2行を追加します。
  • configure は複数記述できるようなので、sample-cmdapp、sample-webapp だけに適用する設定を記述する configure(subprojects.findAll { it.name ==~ /^(sample-cmdapp|sample-webapp)$/ }) { ... } を追加します。この中に PropertiesLauncher を利用するのに必要な設定を記述します。

またプロジェクトのルートディレクトリ直下に libs ディレクトリを作成しておきます。

sample-cmdapp、sample-webapp の build.gradle を変更する

sample-cmdapp、sample-webapp の build.gradle では doma2-lib への依存関係を implementation で記述しており、このままでは jar ファイル内に doma2-lib-1.0.0-RELEASE.jar が含まれてしまうので、implementation project(":doma2-lib")compileOnly project(":doma2-lib") に変更します。

■sample-cmdapp の build.gradle

dependencies {
    implementation("org.springframework.boot:spring-boot-starter")

    compileOnly project(":doma2-lib")
    implementation("com.univocity:univocity-parsers:2.8.1")
    implementation("args4j:args4j:2.33")
    implementation("com.github.rozidan:modelmapper-spring-boot-starter:1.0.0")
}

■sample-webapp の build.gradle

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    runtimeOnly("org.springframework.boot:spring-boot-devtools")

    compileOnly project(":doma2-lib")
}

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

clean タスク実行 → Rebuild Project 実行 → build タスク実行を行う

必要な設定は以上で完了です。clean タスク実行 → Rebuild Project 実行 → build タスク実行を行い、jar ファイルを生成し直して libs ディレクトリに集めます。

エラーメッセージは出ずに BUILD SUCCESSFUL が出力されています。各サブプロジェクトの test タスクの後では copyJarToLibsDir タスクが実行されて、

f:id:ksby:20190501224807p:plain f:id:ksby:20190501224943p:plain

libs ディレクトリの下に doma2-lib-1.0.0-RELEASE.jar、sample-cmdapp-1.0.0-RELEASE.jar、sample-webapp-1.0.0-RELEASE.jar がコピーされています。

f:id:ksby:20190501225129p:plain

sample-cmdapp-1.0.0-RELEASE.jar を表示させてみると lib ディレクトリの下には doma2-lib-1.0.0-RELEASE.jar は入っておらず、

f:id:ksby:20190501225355p:plain

sample-webapp-1.0.0-RELEASE.jar の方にも同様に入っていませんでした。

f:id:ksby:20190501225527p:plain

動作確認

employee テーブルのデータは以下の状況です。

f:id:ksby:20190501230241p:plain

PropertiesLauncher を利用する方式の場合、外部に配置した jar ファイルがある場所を loader.path で指定します。今回はコマンドラインからアプリを実行する時に libs ディレクトリに移動した後 -Dloader.path=. を追加したコマンドを使用するようにします。

コマンドラインから java -Dspring.profiles.active=product -Dloader.path=. -Dbatch.execute=EmployeeDataCsvToDbLoader -jar sample-cmdapp-1.0.0-RELEASE.jar -csvfile=D:\project-springboot\ksbysample-boot-miscellaneous\gradle-multiprj-doma2lib-cmdwebapp\sample-cmdapp\src\test\resources\employee.csv コマンドを実行するとエラーは出ずに終了し、

f:id:ksby:20190501230514p:plain f:id:ksby:20190501230635p:plain

employee テーブルにもデータが登録されていました。

f:id:ksby:20190501230750p:plain

java -Dspring.profiles.active=product -Dloader.path=. -jar sample-webapp-1.0.0-RELEASE.jar で sample-webapp を起動した後、

f:id:ksby:20190501231034p:plain f:id:ksby:20190501231141p:plain

http://localhost:8080/sample にアクセスすると employee テーブルに登録されているデータが表示されました。

f:id:ksby:20190501231218p:plain

動作は問題なさそうです。

また libs ディレクトリから doma2-lib-1.0.0-RELEASE.jar を削除した後、sample-cmdapp を実行しようとすると java.lang.NoClassDefFoundError: ksbysample/lib/doma2lib/dao/EmployeeDao が発生してエラーで終了しました。

f:id:ksby:20190501231805p:plain

sample-webapp を起動しようとすると java.lang.ClassNotFoundException: ksbysample.lib.doma2lib.entity.Employee が発生して、こちらもエラーで終了しました。

f:id:ksby:20190501232323p:plain (.....途中省略.....) f:id:ksby:20190501232420p:plain

sample-webapp を IntelliJ IDEA の Run Dashboard から起動するとどうなるのか?

JarLauncher か PropertiesLauncher かは実行可能 Jar で起動する時の話ですので、Run Dashboard から起動する時には関係がありません。

Configuration でも Main class には org.springframework.boot.loader.PropertiesLauncher ではなく ksbysample.app.samplewebapp.SampleWebappApplication を指定しているので、

f:id:ksby:20190501233224p:plain

設定を変更しなくても起動します。

f:id:ksby:20190501233500p:plain

次回は。。。

Doma 2 の Dao インターフェース、Entity クラスを別の jar ファイルにしようとすると意外に大変ですね。今回は知らないことがいろいろありました。

Gradle で Multi-project を作成する ( その1 )( 概要 ) に記載していたパターンは一通り作成しましたが、サブプロジェクトの1つが Javascript のプロジェクト(Vue.js かな?)+1つは Spring Boot ベースのプロジェクトのパターンを作成してみたいと思ったので、もう1パターンだけ作成してみます。

履歴

2019/05/01
初版発行。

Gradle で Multi-project を作成する ( その12 )( doma2lib+cmdapp+webapp編、sample-webapp プロジェクトを作成する )

概要

記事一覧はこちらです。

Gradle で Multi-project を作成する ( その11 )( doma2lib+cmdapp+webapp編、log4jdbc-log4j2 を導入してトランザクションが有効なことを確認する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Doma 2 の Entity、Dao を提供するライブラリ+Spring Boot ベースのコマンドラインアプリケーション+Spring Boot ベースの Web アプリケーションの Multi-project を作成します。
    • 今回は sample-webapp プロジェクトを作成します。

参照したサイト・書籍

目次

  1. Spring Initializr で sample-webapp プロジェクトを作成する
  2. sample-webapp の build.gradle を変更する
  3. gradle-multiprj-doma2lib-cmdwebapp の settings.gradle に include 'sample-webapp' を追加する
  4. gradle-multiprj-doma2lib-cmdwebapp の build.gradle の configure の適用対象に sample-webapp を追加する
  5. DB のテーブルのデータを読み込んで画面に表示する機能を実装する
  6. テストを作成する
  7. 動作確認
  8. 次回は。。。

手順

Spring Initializr で sample-webapp プロジェクトを作成する

IntelliJ IDEA から Spring Initializr を利用して sample-webapp プロジェクトを作成します。

f:id:ksby:20190501125136p:plainf:id:ksby:20190501125253p:plain
f:id:ksby:20190501125406p:plainf:id:ksby:20190501125441p:plain
f:id:ksby:20190501125530p:plainf:id:ksby:20190501125556p:plain

f:id:ksby:20190501125736p:plain

作成後 IntelliJ IDEA のウィンドウが開きますが、何もせずに閉じます。

src ディレクトリと build.gradle だけ残して、それ以外のディレクトリファイルは全て削除します。

また sample-webapp/src/test/java/ksbysample/app/samplewebapp/SampleWebappApplicationTests.java も削除し、sample-cmdapp/src/test/java/ の下はクリアします。

sample-webapp の build.gradle を変更する

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

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    runtimeOnly("org.springframework.boot:spring-boot-devtools")

    implementation project(":doma2-lib")
}
  • doma2-lib プロジェクトへの依存関係を追加します。

gradle-multiprj-doma2lib-cmdwebapp の settings.gradle に include 'sample-webapp' を追加する

settings.gradle に include 'sample-webapp' を追加します。

rootProject.name = 'gradle-multiprj-doma2lib-cmdwebapp'
include 'doma2-lib'
include 'sample-cmdapp'
include 'sample-webapp'

gradle-multiprj-doma2lib-cmdwebapp の build.gradle の configure の適用対象に sample-webapp を追加する

gradle-multiprj-doma2lib-cmdwebapp の build.gradle の configure の適用対象に sample-webapp を追加します。

..........

configure(subprojects.findAll { it.name ==~ /^(doma2-lib|sample-cmdapp|sample-webapp)$/ }) {
    apply plugin: "org.springframework.boot"

    dependencyManagement {
        imports {
            mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
        }
    }

    ext {
        jdbcDriver = "mysql:mysql-connector-java:8.0.15"
        domaVersion = "2.24.0"
    }
    
    dependencies {
        def lombokVersion = "1.18.6"

        testImplementation("org.springframework.boot:spring-boot-starter-test")

        runtimeOnly(jdbcDriver)
        implementation("org.seasar.doma.boot:doma-spring-boot-starter:1.1.1")
        implementation("org.seasar.doma:doma:${domaVersion}")
        implementation("org.flywaydb:flyway-core:5.2.4")
        implementation("com.integralblue:log4jdbc-spring-boot-starter:1.0.2")

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

}
  • /^(doma2-lib|sample-cmdapp)$//^(doma2-lib|sample-cmdapp|sample-webapp)$/ に変更します。

変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。Gradle Tool Window に sample-webapp が表示されます。

f:id:ksby:20190501131037p:plain

Run Dashboard Tool Window にも SampleWebappApplication が追加されています。

f:id:ksby:20190501131248p:plain

clean タスク実行 → Rebuild Project 実行 → build タスク実行を行い、警告・エラーが出ずに BUILD SUCCESSFUL が出力されることを確認します。

f:id:ksby:20190501131516p:plain f:id:ksby:20190501131632p:plain

DB のテーブルのデータを読み込んで画面に表示する機能を実装する

以下の仕様で実装します。

  • employee テーブルからデータを読み込んで name, age, sex のデータを画面上に表示します。
  • URL は http://localhost:8080/sample にします。
  • Thymeleaf は使用しません。@ResponseBody アノテーションを付与してテキストデータを返します。

sample-webapp/src/main/java/ksbysample/app/samplewebapp/SampleWebappApplication.java に @ComponentScan アノテーションを追加し、ksbysample.libksbysample.app の2つの package を指定します

package ksbysample.app.samplewebapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;

@SpringBootApplication
@ComponentScan(
        basePackages = {"ksbysample.lib", "ksbysample.app"},
        excludeFilters = {
                @ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
                @ComponentScan.Filter(type = FilterType.CUSTOM,
                        classes = AutoConfigurationExcludeFilter.class)})
public class SampleWebappApplication {

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

}

employee テーブルからデータ一覧を取得するメソッドがないので、doma2-lib/src/main/java/ksbysample/lib/doma2lib/dao/EmployeeDao.java に selectAll メソッドを追加します。

■doma2-lib/src/main/java/ksbysample/lib/doma2lib/dao/EmployeeDao.java

package ksbysample.lib.doma2lib.dao;

import ksbysample.lib.doma2lib.entity.Employee;
import org.seasar.doma.boot.ConfigAutowireable;
import org.seasar.doma.Dao;
import org.seasar.doma.Delete;
import org.seasar.doma.Insert;
import org.seasar.doma.Select;
import org.seasar.doma.Update;

import java.util.List;

/**
 */
@Dao
@ConfigAutowireable
public interface EmployeeDao {

    /**
     * @param id
     * @return the Employee entity
     */
    @Select
    Employee selectById(Integer id);

    @Select
    List<Employee> selectAll();
    
    /**
     * @param entity
     * @return affected rows
     */
    @Insert(excludeNull = true)
    int insert(Employee entity);

    /**
     * @param entity
     * @return affected rows
     */
    @Update
    int update(Employee entity);

    /**
     * @param entity
     * @return affected rows
     */
    @Delete
    int delete(Employee entity);
}

■doma2-lib/src/main/resources/META-INF/ksbysample/lib/doma2lib/dao/EmployeeDao/selectAll.sql

select
  /*%expand*/*
from
  employee
order by id

DB のテーブルのデータを読み込んで画面に表示するクラスを実装します。sample-webapp/src/main/java/ksbysample/app/samplewebapp/SampleController.java を新規作成し、以下の内容を記述します。

package ksbysample.app.samplewebapp;

import ksbysample.lib.doma2lib.dao.EmployeeDao;
import ksbysample.lib.doma2lib.entity.Employee;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.stream.Collectors;

@Controller
@RequestMapping("/sample")
public class SampleController {

    private final EmployeeDao employeeDao;

    public SampleController(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }

    @RequestMapping(produces = MediaType.TEXT_PLAIN_VALUE)
    @ResponseBody
    public String index() {
        List<Employee> employeeList = employeeDao.selectAll();
        return employeeList.stream()
                .map(employee ->
                        String.format("name = %s, age = %d, sex = %s"
                                , employee.getName()
                                , employee.getAge()
                                , employee.getSex()))
                .collect(Collectors.joining("\n"));
    }

}

clean タスク実行 → Rebuild Project 実行 → build タスク実行を行い、警告・エラーが出ずに BUILD SUCCESSFUL が出力されることを確認します(画面キャプチャは省略します)。

テストを作成する

sample-webapp/src/main/java/ksbysample/app/samplewebapp/SampleController.java のテストを作成します。

f:id:ksby:20190501141041p:plainf:id:ksby:20190501141111p:plain

sample-webapp/src/test/groovy/ksbysample/app/samplewebapp/SampleControllerTest.groovy が新規作成されますので、以下の内容を記述します。

package ksbysample.app.samplewebapp

import groovy.sql.Sql
import ksbysample.lib.doma2lib.entity.Employee
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.servlet.MockMvc
import spock.lang.Specification

import javax.sql.DataSource

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

// 通常は spring.profiles.active は IntelliJ IDEA の JUnit の Run/Debug Configuration と build.gradle に定義するが、
// 今回はテストが1つしかないので @SpringBootTest の properties 属性で指定する
@SpringBootTest(properties = ["spring.profiles.active=develop"])
@AutoConfigureMockMvc
class SampleControllerTest extends Specification {

    static final def TESTDATA = [
            [id: 1, name: "鈴木 太郎", age: 42, sex: "男", update_time: "2019/05/01 00:00:00"],
            [id: 2, name: "渡辺 香", age: 36, sex: "女", update_time: "2019/04/30 16:51:47"],
            [id: 3, name: "木村 結衣", age: 23, sex: "女", update_time: "2019/04/01 09:15:02"]
    ]

    @Autowired
    private MockMvc mvc

    @Autowired
    private DataSource dataSource

    def sql
    List<Employee> backupData

    void setup() {
        sql = new Sql(dataSource)

        // employee テーブルのバックアップを取得後クリアする
        backupData = sql.rows("select * from employee")
        sql.execute("truncate table employee")
    }

    void cleanup() {
        // バックアップからemployee テーブルのデータをリカバリする
        sql.execute("truncate table employee")
        backupData.each {
            sql.execute("insert into employee values (:id, :name, :age, :sex, :update_time)", it)
        }

        sql.close()
    }

    def "employeeテーブルにデータがない場合にはcontentも空になる"() {
        expect:
        mvc.perform(get("/sample"))
                .andExpect(status().isOk())
                .andExpect(content().string(""))
    }

    def "employeeテーブルにデータがある場合には全てのデータが出力される"() {
        setup:
        TESTDATA.each {
            sql.execute("insert into employee values (:id, :name, :age, :sex, :update_time)", it)
        }
        def expectedContent =
                TESTDATA.collect { String.format("name = ${it.name}, age = ${it.age}, sex = ${it.sex}") }
                        .join("\n")

        expect:
        mvc.perform(get("/sample"))
                .andExpect(status().isOk())
                .andExpect(content().string(expectedContent))
    }

}

テストを実行して成功することを確認します。

f:id:ksby:20190501151541p:plain

今回 employee テーブルをバックアップ・リカバリする仕組みを考えたので、doma2-lib/src/test/groovy/ksbysample/lib/doma2lib/dao/EmployeeDaoTest にも反映します。以下のように変更します。

package ksbysample.lib.doma2lib.dao

import groovy.sql.Sql
import ksbysample.lib.doma2lib.entity.Employee
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import spock.lang.Specification
import spock.lang.Unroll

import javax.sql.DataSource

// 通常は spring.profiles.active は IntelliJ IDEA の JUnit の Run/Debug Configuration と build.gradle に定義するが、
// 今回はテストが1つしかないので @SpringBootTest の properties 属性で指定する
@SpringBootTest(properties = ["spring.profiles.active=develop"])
class EmployeeDaoTest extends Specification {

    static final def TESTDATA = [
            [id: 1, name: "鈴木 太郎", age: 42, sex: "男", update_time: "2019/05/01 00:00:00"],
            [id: 2, name: "渡辺 香", age: 36, sex: "女", update_time: "2019/04/30 16:51:47"],
            [id: 3, name: "木村 結衣", age: 23, sex: "女", update_time: "2019/04/01 09:15:02"]
    ]

    static final def TESTDATA2 =
            new Employee(id: null, name: "木村 太郎", age: 35, sex: "男", updateTime: null)

    @Autowired
    EmployeeDao employeeDao

    @Autowired
    private DataSource dataSource

    def sql

    List<Employee> backupData

    void setup() {
        sql = new Sql(dataSource)

        // employee テーブルのバックアップを取得後クリアする
        backupData = sql.rows("select * from employee")
        sql.execute("truncate table employee")
    }

    void cleanup() {
        // バックアップからemployee テーブルのデータをリカバリする
        sql.execute("truncate table employee")
        backupData.each {
            sql.execute("insert into employee values (:id, :name, :age, :sex, :update_time)", it)
        }

        sql.close()
    }

    @Unroll
    def "selectById メソッドのテスト(#id --> #name, #age, #sex)"() {
        setup:
        TESTDATA.each {
            sql.execute("insert into employee values (:id, :name, :age, :sex, :update_time)", it)
        }
        def row = employeeDao.selectById(id)

        expect:
        row.name == name
        row.age == age
        row.sex == sex

        where:
        id || name    | age | sex
        1  || "鈴木 太郎" | 42  | "男"
        2  || "渡辺 香"  | 36  | "女"
    }

    def "insert メソッドのテスト"() {
        setup:
        employeeDao.insert(TESTDATA2)

        expect:
        def row = sql.firstRow("select * from employee where name = ${TESTDATA2.name}")
        row.name == TESTDATA2.name
        row.age == TESTDATA2.age
        row.sex == TESTDATA2.sex
    }

}

テストを実行して成功することを確認します。

f:id:ksby:20190501153435p:plain

動作確認

clean タスク実行 → Rebuild Project 実行 → build タスク実行を行い、警告・エラーが出ずに BUILD SUCCESSFUL が出力されることを確認します。

f:id:ksby:20190501153905p:plain f:id:ksby:20190501154018p:plain

Run Dashboard から sample-webapp を起動して動作確認します。

f:id:ksby:20190501154217p:plain

employee テーブルに以下のデータが登録されている場合、

f:id:ksby:20190501154531p:plain

http://localhost:8080/sample にアクセスすると employee テーブルのデータが出力されていることが確認できます。

f:id:ksby:20190501154706p:plain

コマンドラインから java -Dspring.profiles.active=develop -Dbatch.execute=EmployeeDataCsvToDbLoader -jar sample-cmdapp-1.0.0-RELEASE.jar -csvfile=D:\project-springboot\ksbysample-boot-miscellaneous\gradle-multiprj-doma2lib-cmdwebapp\sample-cmdapp\src\test\resources\employee.csv を実行してデータを追加すると、

f:id:ksby:20190501154853p:plain

http://localhost:8080/sample の画面を更新すると追加されたデータが反映されました。

f:id:ksby:20190501154932p:plain

コマンドラインから sample-webapp を起動して動作確認します。java -Dspring.profiles.active=develop -jar sample-webapp-1.0.0-RELEASE.jar コマンドを実行して起動します。

f:id:ksby:20190501155403p:plain (.....途中省略.....) f:id:ksby:20190501155524p:plain

http://localhost:8080/sample にアクセスすると先程と同じデータが表示されました。

f:id:ksby:20190501155628p:plain

最後に sample-webapp プロジェクトのディレクトリ構成を記載します。

f:id:ksby:20190501161752p:plain

sample-webapp-1.0.0-RELEASE.jar の中身を見てみると以下のようになっており doma2-lib-1.0.0-RELEASE.jar が lib ディレクトリの下に入っています。

f:id:ksby:20190501161925p:plain

次回は。。。

ここまで doma2-lib-1.0.0-RELEASE.jar を各サブプロジェクトの jar の中に入れる方式(JarLauncher による起動)で書きましたので、次は doma2-lib-1.0.0-RELEASE.jar を外に出す方式(PropertiesLauncher による起動)を書く予定です。

履歴

2019/05/01
初版発行。

Gradle で Multi-project を作成する ( その11 )( doma2lib+cmdapp+webapp編、log4jdbc-log4j2 を導入してトランザクションが有効なことを確認する )

概要

記事一覧はこちらです。

Gradle で Multi-project を作成する ( その10 )( doma2lib+cmdapp+webapp編、sample-cmdapp プロジェクトを作成する2 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Doma 2 の Entity、Dao を提供するライブラリ+Spring Boot ベースのコマンドラインアプリケーション+Spring Boot ベースの Web アプリケーションの Multi-project を作成します。
    • ksbysample.app.samplecmdapp.EmployeeDataCsvToDbLoader#run メソッドに @Transactional アノテーションを付与してトランザクションが有効になるように設定したつもりでいましたが、log4jdbc-log4j2 を導入して本当に有効になっているのかを確認します。

参照したサイト・書籍

目次

  1. gradle-multiprj-doma2lib-cmdwebapp の build.gradle を変更する
  2. doma2-lib の src/main/resources の下に application-develop.properties、application-product.properties を作成する
  3. doma2-lib の db-product.properties と同じ内容を db-develop.properties に記述する
  4. EmployeeDataCsvToDbLoaderTest クラスでトランザクションが有効か確認する
  5. java.sql.SQLSyntaxErrorException: SELECT command denied to user 'sampledb_user'@'172.18.0.1' for table 'user_variables_by_thread' を解消する
  6. sample-cmdapp をコマンドラインから実行した時にトランザクションが有効か確認する

手順

gradle-multiprj-doma2lib-cmdwebapp の build.gradle を変更する

gradle-multiprj-doma2lib-cmdwebapp の build.gradle の configure に log4jdbc-log4j2 を利用するための com.integralblue:log4jdbc-spring-boot-starter を依存関係に追加します。

..........

configure(subprojects.findAll { it.name ==~ /^(doma2-lib|sample-cmdapp)$/ }) {
    apply plugin: "org.springframework.boot"

    dependencyManagement {
        imports {
            mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
        }
    }

    ext {
        jdbcDriver = "mysql:mysql-connector-java:8.0.15"
        domaVersion = "2.24.0"
    }
    
    dependencies {
        def lombokVersion = "1.18.6"

        testImplementation("org.springframework.boot:spring-boot-starter-test")

        runtimeOnly(jdbcDriver)
        implementation("org.seasar.doma.boot:doma-spring-boot-starter:1.1.1")
        implementation("org.seasar.doma:doma:${domaVersion}")
        implementation("org.flywaydb:flyway-core:5.2.4")
        implementation("com.integralblue:log4jdbc-spring-boot-starter:1.0.2")

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

}
  • implementation("com.integralblue:log4jdbc-spring-boot-starter:1.0.2") を追加します。

doma2-lib の src/main/resources の下に application-develop.properties、application-product.properties を作成する

develop profile の時には log4jdbc-log4j2 でログが出力されるようにし、product profile の時にはログが出力されないようにします。設定ファイルは doma2-lib プロジェクトの下に作成します(ここに作成しても sample-cmdapp に適用されます)。

doma2-lib/src/main/resources の下に application-develop.properties、application-product.properties を新規作成し、以下の内容を記述します。

■application-develop.properties

# log4jdbc-log4j2
logging.level.jdbc.sqlonly=DEBUG
logging.level.jdbc.sqltiming=INFO
logging.level.jdbc.audit=INFO
logging.level.jdbc.resultset=ERROR
logging.level.jdbc.resultsettable=ERROR
logging.level.jdbc.connection=DEBUG

■application-product.properties

spring.autoconfigure.exclude=com.integralblue.log4jdbc.spring.Log4jdbcAutoConfiguration

doma2-lib の db-product.properties と同じ内容を db-develop.properties に記述する

develop profile ではログが出力され product profile ではログが出力されないことを確認できるようにするために doma2-lib/src/main/resources/db-product.properties の内容を db-develop.properties と同じにします。

■db-product.properties

doma.dialect=mysql

spring.datasource.hikari.jdbc-url=jdbc:mysql://localhost/sampledb?sslMode=DISABLED&characterEncoding=utf8
spring.datasource.hikari.username=sampledb_user
spring.datasource.hikari.password=xxxxxxxx
spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.leak-detection-threshold=60000
spring.datasource.hikari.register-mbeans=true

EmployeeDataCsvToDbLoaderTest クラスでトランザクションが有効か確認する

sample-cmdapp/src/test/java/ksbysample/app/samplecmdapp/EmployeeDataCsvToDbLoaderTest.groovy のテストを実行して、employeeDataCsvToDbLoader = (EmployeeDataCsvToDbLoader) context.getAutowireCapableBeanFactory().applyBeanPostProcessorsAfterInitialization(employeeDataCsvToDbLoader, "employeeDataCsvToDbLoader") で手動で Bean に登録した場合にトランザクションが有効か確認します。

テストを実行すると insert 前に Connection.setAutoCommit(false) が呼び出されて、employeeDataCsvToDbLoader.run(...) で2件 insert した後に Connection.commit() が呼び出されていることが確認できました。

f:id:ksby:20190501082847p:plain

わざと RuntimeException を throw して rollback されるかも確認します。 sample-cmdapp/src/main/java/ksbysample/app/samplecmdapp/EmployeeDataCsvToDbLoader.java を以下のように変更します。

@Component
@ConditionalOnProperty(value = {"batch.execute"}, havingValue = "EmployeeDataCsvToDbLoader")
public class EmployeeDataCsvToDbLoader implements CommandLineRunner {

    ..........

    @Override
    @Transactional
    public void run(String... args) throws Exception {
        ..........

        // CSV ファイルを1行ずつ読み込み employee テーブルに insert する
        try (BufferedReader br = Files.newBufferedReader(Paths.get(this.csvfile), StandardCharsets.UTF_8)) {
            Employee employee = new Employee();
            for (EmployeeCsvRecord employeeCsvRecord : routines.iterate(EmployeeCsvRecord.class, br)) {
                modelMapper.map(employeeCsvRecord, employee);
                employeeDao.insert(employee);
                throw new RuntimeException("error");
            }
        }
    }

}
  • throw new RuntimeException("error"); を追加します。

テストを実行すると1件目の insert 文が実行された後に RuntimeException が throw された結果 Connection.rollback() が呼び出されていました。

f:id:ksby:20190501112522p:plain

トランザクションが有効になっていることが確認できました。

また spring.profiles.active=product に変更すると、

@SpringBootTest(properties = ["spring.profiles.active=product"])
class EmployeeDataCsvToDbLoaderTest extends Specification {

log4jdbc-log4j2 のログは出力されなくなりました。

f:id:ksby:20190501115518p:plain

java.sql.SQLSyntaxErrorException: SELECT command denied to user 'sampledb_user'@'172.18.0.1' for table 'user_variables_by_thread' を解消する

上でテストを実行した時に java.sql.SQLSyntaxErrorException: SELECT command denied to user 'sampledb_user'@'172.18.0.1' for table 'user_variables_by_thread' というエラーメッセージが出ていることに気づきました。flyway が performance_schema.user_variables_by_thread テーブルにアクセスしようとして出来なかったために出力されていました。

f:id:ksby:20190501113132p:plain

doma2-lib/src/main/resources/db/init/create_database.sql を以下のように変更します。

create database if not exists sampledb character set utf8mb4 collate utf8mb4_ja_0900_as_cs_ks;

create user 'sampledb_user'@'%' identified by 'xxxxxxxx';
grant all privileges ON sampledb.* to 'sampledb_user'@'%' with grant option;
grant select ON performance_schema.user_variables_by_thread to 'sampledb_user'@'%';
flush privileges;
  • grant select ON performance_schema.user_variables_by_thread to 'sampledb_user'@'%'; を追加します。

コンテナを再起動しても create_database.sql に追加した grant select ON ... 文が実行されなかったので、adminer に root ユーザでログインして直接 SQL 文を実行します。

f:id:ksby:20190501114610p:plain

テストを実行すると今度はエラーメッセージが出力されなくなりました。

f:id:ksby:20190501114827p:plain

sample-cmdapp をコマンドラインから実行した時にトランザクションが有効か確認する

コマンドラインから実行した時もトランザクションが有効になっているか確認します。まずは clean タスク実行 → Rebuild Project 実行 → build タスク実行を行い、sample-cmdapp-1.0.0-RELEASE.jar を build し直します。

f:id:ksby:20190501120220p:plain f:id:ksby:20190501120330p:plain

java -Dspring.profiles.active=develop -Dbatch.execute=EmployeeDataCsvToDbLoader -jar sample-cmdapp-1.0.0-RELEASE.jar -csvfile=D:\project-springboot\ksbysample-boot-miscellaneous\gradle-multiprj-doma2lib-cmdwebapp\sample-cmdapp\src\test\resources\employee.csv コマンドを実行すると Connection.commit() が呼び出されています。

f:id:ksby:20190501120835p:plain (.....途中省略.....) f:id:ksby:20190501121125p:plain

sample-cmdapp/src/main/java/ksbysample/app/samplecmdapp/EmployeeDataCsvToDbLoader.javathrow new RuntimeException("error"); を追加してから build し直した後、再度コマンドを実行すると1件目を insert した後 Connection.rollback() が呼び出されていました。

f:id:ksby:20190501121804p:plain

コマンドラインから実行した時もトランザクションが有効になっていることが確認できました。

また -Dspring.profiles.active=product に変更した java -Dspring.profiles.active=product -Dbatch.execute=EmployeeDataCsvToDbLoader -jar sample-cmdapp-1.0.0-RELEASE.jar -csvfile=D:\project-springboot\ksbysample-boot-miscellaneous\gradle-multiprj-doma2lib-cmdwebapp\sample-cmdapp\src\test\resources\employee.csv コマンドを実行すると log4jdbc-log4j2 のログは出力されませんでした。

f:id:ksby:20190501122312p:plain f:id:ksby:20190501122422p:plain

履歴

2019/05/01
初版発行。