Gradle で Multi-project を作成する ( その10 )( doma2lib+cmdapp+webapp編、sample-cmdapp プロジェクトを作成する2 )
概要
記事一覧はこちらです。
Gradle で Multi-project を作成する ( その9 )( doma2lib+cmdapp+webapp編、sample-cmdapp プロジェクトを作成する ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
参照したサイト・書籍
Shortest way to get File object from resource in Groovy
https://stackoverflow.com/questions/39245934/shortest-way-to-get-file-object-from-resource-in-groovySpring start a transaction with object created by new
https://stackoverflow.com/questions/4707193/spring-start-a-transaction-with-object-created-by-newapplicationContext.getAutowireCapableBeanFactory().applyBeanPostProcessorsAfterInitialization(...)
で手動で Bean を登録する方法を参考にしました。
目次
手順
テストを作成する
最初にテストで利用する CSV ファイルを作成します。sample-cmdapp/src/test/resources/employee.csv を新規作成し、以下の内容を記述します。
"name","age","sex" "高橋 蓮","15","男" "渡辺 結月","24","女"
次に sample-cmdapp/src/main/java/ksbysample/app/samplecmdapp/EmployeeDataCsvToDbLoader.java のテストクラスを作成します。
@SpringBootTest アノテーションが付与されたクラスのテストメソッドを実行するとテストメソッド内の処理が実行される前に CommandLineRunner インターフェースを実装したクラスの run メソッドが実行されてしまうのですが、以下の方法で run メソッドが自動で実行されないようにします。
- EmployeeDataCsvToDbLoader クラスに
@ConditionalOnProperty(value = { "batch.execute" }, havingValue = "EmployeeDataCsvToDbLoader")
アノテーションを付与して、batch.execute=EmployeeDataCsvToDbLoader
が設定されない限り自動で Bean に登録されないようにします。 - EmployeeDataCsvToDbLoader クラスのコンストラクタに渡すオブジェクト(Bean)は、テストクラスのフィールドに
@Autowired
アノテーションを付与して定義しておきます。 - テストメソッドの最初で EmployeeDataCsvToDbLoader クラスのインスタンスを生成してから
context.getAutowireCapableBeanFactory().applyBeanPostProcessorsAfterInitialization(...)
を呼び出して手動で Bean に登録します。手動で登録すると CommandLineRunner#run メソッドは自動で実行されません。
sample-cmdapp/src/test/groovy/ksbysample/app/samplecmdapp/EmployeeDataCsvToDbLoaderTest.groovy が新規作成されますので、以下の内容を記述します。
package ksbysample.app.samplecmdapp import groovy.sql.Sql import ksbysample.lib.doma2lib.dao.EmployeeDao import org.modelmapper.ModelMapper import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.context.ApplicationContext import spock.lang.Specification 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 EmployeeDataCsvToDbLoaderTest extends Specification { @Autowired private ApplicationContext context @Autowired private DataSource dataSource @Autowired private final EmployeeDao employeeDao @Autowired private final ModelMapper modelMapper def sql void setup() { sql = new Sql(dataSource) } void cleanup() { sql.close() } def "EmployeeDataCsvToDbLoader.run メソッドを実行すると employee.csv のデータが employee テーブルに登録される"() { setup: // EmployeeDataCsvToDbLoader クラスは batch.execute=EmployeeDataCsvToDbLoader が指定されていないと Bean として // 登録されず run メソッドが自動で実行されない。テストでは手動で Bean に登録することで run メソッドが自動実行されることを // 回避する。 EmployeeDataCsvToDbLoader employeeDataCsvToDbLoader = new EmployeeDataCsvToDbLoader(employeeDao, modelMapper) employeeDataCsvToDbLoader = (EmployeeDataCsvToDbLoader) context .getAutowireCapableBeanFactory() .applyBeanPostProcessorsAfterInitialization(employeeDataCsvToDbLoader, "employeeDataCsvToDbLoader") // src/test/resources の下の employee.csv の File オブジェクトを取得する def url = getClass().getResource("/employee.csv") def employeeCsvFile = new File(url.toURI()) // employee テーブルからテストデータを削除する sql.execute("delete from employee where name in ('高橋 蓮', '渡辺 結月')") expect: employeeDataCsvToDbLoader.run("-csvfile=${employeeCsvFile.absolutePath}") def results = sql.rows("select name, age, sex from employee where name in ('高橋 蓮', '渡辺 結月')") results == [ [name: "高橋 蓮", age: 15, sex: "男"], [name: "渡辺 結月", age: 24, sex: "女"] ] cleanup: sql.execute("delete from employee where name in ('高橋 蓮', '渡辺 結月')") } }
テストを実行して成功することを確認します。
コマンドラインから実行して動作確認する
employee テーブルにテストデータが登録されていないことを確認した後、
sample-cmdapp-1.0.0-RELEASE.jar が生成されている D:\project-springboot\ksbysample-boot-miscellaneous\gradle-multiprj-doma2lib-cmdwebapp\sample-cmdapp\build\libs
に移動してから 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
コマンドを実行します。
employee テーブルを確認するとテストデータが登録されています。
問題なさそうです。
最後に sample-cmdapp プロジェクトのディレクトリ構成を記載します。
sample-cmdapp-1.0.0-RELEASE.jar の中身を見てみると以下のようになっており doma2-lib-1.0.0-RELEASE.jar が lib ディレクトリの下に入っています。
補足
sample-cmdapp でも Flyway は動作している
現在の設定では sample-cmdapp を実行した時も Flyway は動作しています。確認してみます。
docker-compose down
コマンドを実行後、docker/mysql/data の下をクリアします。
docker-compose.yml の flyway の設定をコメントアウトした後、
version: '3' services: .......... # flyway: # image: boxfuse/flyway:${FLYWAY_VERSION}-alpine # container_name: flyway # environment: # - TZ=Asia/Tokyo # volumes: # - ./doma2-lib/src/main/resources/db/migration:/flyway/sql # command: -url=${FLYWAY_URL} -user=${FLYWAY_USER} -password=${FLYWAY_PASSWORD} -connectRetries=60 migrate # depends_on: - mysql # 下の3行は debug 用 # うまく動かない時はコメントアウトを解除した後、 # docker exec -it flyway /bin/sh # で接続してから # flyway <command に記述した文字列> # を実行してみる # # entrypoint: /bin/sh # stdin_open: true # tty: true
docker-compose up -d
コマンドを実行します。
adminer にログインして sampledb を見るとテーブルは何もありませんが、
コマンドラインから 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
を実行してから、
sampledb を確認すると employee と flyway_schema_history の2つのテーブルが作成されており、
employee テーブルにも doma2-lib/src/main/resources/db/migration/V1__create_table.sql の2件と sample-cmdapp/src/test/resources/employee.csv の2件の合計4件のデータが登録されています。
履歴
2019/05/01
初版発行。
Gradle で Multi-project を作成する ( その9 )( doma2lib+cmdapp+webapp編、sample-cmdapp プロジェクトを作成する )
概要
記事一覧はこちらです。
Gradle で Multi-project を作成する ( その8 )( doma2lib+cmdapp+webapp編、doma2-lib プロジェクトを作成する ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
参照したサイト・書籍
- univocity-parsers/src/test/java/com/univocity/parsers/examples/RoutineExamples.java
https://github.com/uniVocity/univocity-parsers/blob/master/src/test/java/com/univocity/parsers/examples/RoutineExamples.java
目次
- Spring Initializr で sample-cmdapp プロジェクトを作成する
- sample-cmdapp の build.gradle を変更する
- gradle-multiprj-doma2lib-cmdwebapp の settings.gradle に
include 'sample-cmdapp'
を追加する - gradle-multiprj-doma2lib-cmdwebapp の build.gradle の configure の適用対象に sample-cmdapp を追加する
- CSV ファイルを読み込んで DB のテーブルにデータを登録する機能を実装する
- 続く。。。
手順
Spring Initializr で sample-cmdapp プロジェクトを作成する
IntelliJ IDEA から Spring Initializr を利用して sample-cmdapp プロジェクトを作成します。
※dependencies のダイアログでは何もチェックしません。
作成後 IntelliJ IDEA のウィンドウが開きますが、何もせずに閉じます。
src ディレクトリと build.gradle だけ残して、それ以外のディレクトリファイルは全て削除します。
また sample-cmdapp/src/test/java/ksbysample/app/samplecmdapp/SampleCmdappApplicationTests.java も削除し、sample-cmdapp/src/test/java/ の下はクリアします。
sample-cmdapp の build.gradle を変更する
sample-cmdapp の build.gradle を以下のように変更します。
dependencies { implementation("org.springframework.boot:spring-boot-starter") implementation 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") }
- doma2-lib プロジェクトへの依存関係と、CSV ファイルを処理するためのライブラリである univocity-parsers、コマンドラインオプションを処理するためのライブラリ args4j、ModelMapper を利用するためのライブラリ com.github.rozidan:modelmapper-spring-boot-starter への依存関係を追加します。
gradle-multiprj-doma2lib-cmdwebapp の settings.gradle に include 'sample-cmdapp'
を追加する
settings.gradle に include 'sample-cmdapp'
を追加します。
rootProject.name = 'gradle-multiprj-doma2lib-cmdwebapp' include 'doma2-lib' include 'sample-cmdapp'
gradle-multiprj-doma2lib-cmdwebapp の build.gradle の configure の適用対象に sample-cmdapp を追加する
gradle-multiprj-doma2lib-cmdwebapp の build.gradle の configure の適用対象に sample-cmdapp を追加します。
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 { 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") } }
/^(doma2-lib)$/
→/^(doma2-lib|sample-cmdapp)$/
に変更します。
変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。Gradle Tool Window に sample-cmdapp が表示されます。
また Run/Debug Configuration の Spring Boot の所に doma2-lib と sample-cmdapp の2つの設定が追加されるので、IntelliJ IDEA の画面右下に「Run Dashboard」のダイアログが表示されます。「Show run configurations in Run Dashboard」リンクをクリックして Run Dashboard Tool Window を表示します。
Doma2LibApplication の Run/Debug Configuration は不要なので削除します。
clean タスク実行 → Rebuild Project 実行 → build タスク実行を行い、警告・エラーが出ずに BUILD SUCCESSFUL が出力されることを確認します。
CSV ファイルを読み込んで DB のテーブルにデータを登録する機能を実装する
以下の仕様で実装します。
- コマンドラインから
java -jar -Dspring.profiles.active=<develop|product> -jar sample-cmdapp-1.0.0-RELEASE.jar -csvfile=<CSVファイルのパス>
で実行します。 - CSV ファイルは、
- CSV ファイルのデータチェックは行いません。
doma2-lib プロジェクト内の ksbysample.lib パッケージの下も Component Scan の対象にするために
sample-cmdapp/src/main/java/ksbysample/app/samplecmdapp/SampleCmdappApplication.java に @ComponentScan アノテーションを追加し、ksbysample.lib
、ksbysample.app
の2つの package を指定します(ksbysample
だけでも良いのですが複数指定するサンプルを作成したかったので2つ指定しています)。
package ksbysample.app.samplecmdapp; 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 SampleCmdappApplication { public static void main(String[] args) { SpringApplication.run(SampleCmdappApplication.class, args); } }
Lombok を使用したいので gradle-multiprj-doma2lib-cmdwebapp の build.gradle の configure の dependencies に追加します。
.......... 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") // for lombok // testAnnotationProcessor、testCompileOnly を併記しなくてよいよう configurations で設定している annotationProcessor("org.projectlombok:lombok:${lombokVersion}") compileOnly("org.projectlombok:lombok:${lombokVersion}") } }
- configure の dependencies に以下の3行を追加します。
def lombokVersion = "1.18.6"
annotationProcessor("org.projectlombok:lombok:${lombokVersion}")
compileOnly("org.projectlombok:lombok:${lombokVersion}")
変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
CSV ファイルの1レコードのデータをセットするための POJO クラスを実装します。Univocity Parsers で利用します。sample-cmdapp/src/main/java/ksbysample/app/samplecmdapp/EmployeeCsvRecord.java を新規作成し、以下の内容を記述します。
package ksbysample.app.samplecmdapp; import com.univocity.parsers.annotations.Parsed; import lombok.Data; @Data public class EmployeeCsvRecord { @Parsed(field = "name") private String name; @Parsed(field = "age") private Integer age; @Parsed(field = "sex") private String sex; }
null のフィールドを insert 文に出力しないようにしたいので、doma2-lib/src/main/java/ksbysample/lib/doma2lib/dao/EmployeeDao.java の insert メソッドの @Insert アノテーションに excludeNull = true
を追加します。この設定を行わないと insert 文で update_time のカラムに null がセットされて MySQL 側で日時をセットしてくれません。
@Dao @ConfigAutowireable public interface EmployeeDao { /** * @param id * @return the Employee entity */ @Select Employee selectById(Integer id); /** * @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); }
CSV ファイルを読み込んで DB のテーブルにデータを登録する処理を実行するクラスを実装します。sample-cmdapp/src/main/java/ksbysample/app/samplecmdapp/EmployeeDataCsvToDbLoader.java を新規作成し、以下の内容を記述します。
package ksbysample.app.samplecmdapp; import com.univocity.parsers.csv.CsvParserSettings; import com.univocity.parsers.csv.CsvRoutines; import ksbysample.lib.doma2lib.dao.EmployeeDao; import ksbysample.lib.doma2lib.entity.Employee; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.Option; import org.modelmapper.ModelMapper; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import java.io.BufferedReader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; @Component @ConditionalOnProperty(value = { "batch.execute" }, havingValue = "EmployeeDataCsvToDbLoader") public class EmployeeDataCsvToDbLoader implements CommandLineRunner { @Option(name = "-csvfile", metaVar = "<path>", usage = "specifies a path to employee csv file") private String csvfile; private final EmployeeDao employeeDao; private final ModelMapper modelMapper; public EmployeeDataCsvToDbLoader(EmployeeDao employeeDao , ModelMapper modelMapper) { this.employeeDao = employeeDao; this.modelMapper = modelMapper; } @Override @Transactional public void run(String... args) throws Exception { // コマンドラインオプションを解析して @Option アノテーションを付加しているフィールドに値を設定する CmdLineParser cmdLineParser = new CmdLineParser(this); cmdLineParser.parseArgument(args); // Univocity Parses で CSV ファイルを読み込むための準備をする CsvParserSettings settings = new CsvParserSettings(); settings.getFormat().setLineSeparator("\r\n"); // 改行コードは CRLF settings.setHeaderExtractionEnabled(true); // ヘッダ行はスキップする CsvRoutines routines = new CsvRoutines(settings); // 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); } } } }
clean タスク実行 → Rebuild Project 実行 → build タスク実行を行い、警告・エラーが出ずに BUILD SUCCESSFUL が出力されることを確認します。
続く。。。
長くなったので一旦区切ります。次回は sample-cmdapp のテストの作成と、コマンドラインから実行した動作確認を行います。
履歴
2019/04/30
初版発行。
Gradle で Multi-project を作成する ( その8 )( doma2lib+cmdapp+webapp編、doma2-lib プロジェクトを作成する )
概要
記事一覧はこちらです。
- 今回の手順で確認できるのは以下の内容です。
参照したサイト・書籍
Spring Boot 2 Gradle plugin without executable jar
https://stackoverflow.com/questions/49352837/spring-boot-2-gradle-plugin-without-executable-jarSpring Boot and multiple external configuration files
https://stackoverflow.com/questions/25855795/spring-boot-and-multiple-external-configuration-filesI am using a combination of @PropertySource and @ConfigurationProperties but I want to overwrite them with an external properties file
https://stackoverflow.com/questions/47042237/i-am-using-a-combination-of-propertysource-and-configurationproperties-but-i-wJava11からMySQL8への接続時に起きた問題の解決
https://qiita.com/talesleaves/items/d96ba7d74127799b1523MySQL 8.0は何が優れていて、どこに注意すべきか。データベース専門家が新機能を徹底解説
https://employment.en-japan.com/engineerhub/entry/2018/09/18/110000Amazon RDS での MySQL
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/CHAP_MySQL.html
目次
- gradle-multiprj-doma2lib-cmdwebapp の build.gradle を変更する
- gradle-multiprj-doma2lib-cmdwebapp の settings.gradle に
include 'doma2-lib'
を追加する - doma2-lib プロジェクトから不要なファイルを削除する
- doma2-lib の build.gradle を変更する
- ksbysample.lib.doma2lib パッケージを作成する
- domaGen タスクを実行して employee テーブルの Dao インターフェース、Entity クラスを生成する
- db-develop.properties、db-product.properties を作成する
- DataSourceConfig クラスを作成する
- EmployeeDao インターフェースのテストクラスを作成する
- clean タスク実行 → Rebuild Project 実行 → build タスク実行を行う
- メモ書き
手順
gradle-multiprj-doma2lib-cmdwebapp の build.gradle を変更する
サブプロジェクト共通の設定を gradle-multiprj-doma2lib-cmdwebapp の build.gradle に記述します。
buildscript { repositories { mavenCentral() maven { url "https://plugins.gradle.org/m2/" } maven { url "https://repo.spring.io/release/" } } dependencies { classpath "io.spring.gradle:dependency-management-plugin:1.0.7.RELEASE" classpath "org.springframework.boot:spring-boot-gradle-plugin:2.1.4.RELEASE" } } allprojects { repositories { mavenCentral() } } subprojects { group "ksby.ksbysample-boot-miscellaneous" version "1.0.0-RELEASE" apply plugin: "java" apply plugin: "groovy" apply plugin: "io.spring.dependency-management" apply plugin: "idea" sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 [compileJava, compileTestGroovy, compileTestJava]*.options*.encoding = "UTF-8" [compileJava, compileTestGroovy, compileTestJava]*.options*.compilerArgs = ["-Xlint:all,-options,-processing,-path"] idea { module { inheritOutputDirs = false outputDir = file("$buildDir/classes/main/") } } configurations { // annotationProcessor と testAnnotationProcessor、compileOnly と testCompileOnly を併記不要にする testAnnotationProcessor.extendsFrom annotationProcessor testImplementation.extendsFrom compileOnly } dependencyManagement { imports { mavenBom("org.junit:junit-bom:5.4.2") } } dependencies { def assertjVersion = "3.12.2" def spockVersion = "1.3-groovy-2.5" // for Spock testImplementation("org.spockframework:spock-core:${spockVersion}") testImplementation("org.spockframework:spock-spring:${spockVersion}") // for JUnit 5 + AssertJ // junit-jupiter で junit-jupiter-api, junit-jupiter-params, junit-jupiter-engine の3つが依存関係に追加される testCompile("org.junit.jupiter:junit-jupiter") testRuntime("org.junit.platform:junit-platform-launcher") testImplementation("org.assertj:assertj-core:${assertjVersion}") } def jvmArgsDefault = [ "-ea", "-Dfile.encoding=UTF-8", "-Dsun.nio.cs.map=x-windows-iso2022jp/ISO-2022-JP" ] def jvmArgsAddOpens = [ "--add-opens=java.base/java.io=ALL-UNNAMED", "--add-opens=java.base/java.lang=ALL-UNNAMED", "--add-opens=java.base/java.lang.invoke=ALL-UNNAMED", "--add-opens=java.base/java.lang.ref=ALL-UNNAMED", "--add-opens=java.base/java.lang.reflect=ALL-UNNAMED", "--add-opens=java.base/java.net=ALL-UNNAMED", "--add-opens=java.base/java.security=ALL-UNNAMED", "--add-opens=java.base/java.util=ALL-UNNAMED" ] 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)" } } task testJUnit4AndSpock(type: Test) { jvmArgs = jvmArgsDefault + jvmArgsAddOpens testLogging { afterSuite printTestCount } } test.dependsOn testJUnit4AndSpock test { jvmArgs = jvmArgsDefault + jvmArgsAddOpens // for JUnit 5 useJUnitPlatform() testLogging { afterSuite printTestCount } } } configure(subprojects.findAll { it.name ==~ /^(doma2-lib)$/ }) { 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 { 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") } }
- 今回作成するサブプロジェクトは全て Spring ベースで作成するので
subprojects { ... }
の中に設定を記述してもよいのですが、Spring ベースでないサブプロジェクトを作成することも考慮して Spring ベースの設定はconfigure(subprojects.findAll { it.name ==~ /^(...)$/ }) { ... }
に記述します。 org.seasar.doma.boot:doma-spring-boot-starter
はorg.seasar.doma:doma:2.16.1
に依存していたので、Doma 2 の最新バージョンである 2.24.0 が入るように別途指定します。
gradle-multiprj-doma2lib-cmdwebapp の settings.gradle に include 'doma2-lib'
を追加する
gradle-multiprj-doma2lib-cmdwebapp の settings.gradle に include 'doma2-lib'
を追加します。
rootProject.name = 'gradle-multiprj-doma2lib-cmdwebapp' include 'doma2-lib'
doma2-lib プロジェクトから不要なファイルを削除する
以下のファイルは不要なので削除します。
- doma2-lib/src/main/java/ksbysample/lib/doma2lib/Doma2LibApplication.java
- doma2-lib/src/main/resources/application.properties
- doma2-lib/src/test/java/ksbysample/lib/doma2lib/Doma2LibApplicationTests.java
- doma2-lib/src/test/java の下のディレクトリはクリアします。
doma2-lib の build.gradle を変更する
doma2-lib の build.gradle に記述します。
// for Doma 2 // JavaクラスとSQLファイルの出力先ディレクトリを同じにする processResources.destinationDir = compileJava.destinationDir // コンパイルより前にSQLファイルを出力先ディレクトリにコピーするために依存関係を逆転する compileJava.dependsOn processResources configurations { // for Doma 2 domaGenRuntime } dependencies { implementation("org.springframework.boot:spring-boot-starter") annotationProcessor("org.seasar.doma:doma:${domaVersion}") domaGenRuntime("org.seasar.doma:doma-gen:${domaVersion}") domaGenRuntime("${jdbcDriver}") } bootJar { enabled = false } jar { enabled = true } // for Doma-Gen task domaGen { doLast { // まず変更が必要なもの def rootPackageName = "ksbysample.lib.doma2lib" def daoPackagePath = "src/main/java/ksbysample/lib/doma2lib/dao" def dbUrl = "jdbc:mysql://localhost/sampledb" def dbUser = "sampledb_user" def dbPassword = "xxxxxxxx" def tableNamePattern = "employee" // def tableNamePattern = ".*" // おそらく変更不要なもの def importOfComponentAndAutowiredDomaConfig = "org.seasar.doma.boot.ConfigAutowireable" def workDirPath = "work" def workDaoDirPath = "${workDirPath}/dao" // 作業用ディレクトリを削除する clearDir("${workDirPath}") // 現在の Dao インターフェースのバックアップを取得する copy() { from "${daoPackagePath}" into "${workDaoDirPath}/org" } // Dao インターフェース、Entity クラスを生成する ant.taskdef(resource: "domagentask.properties", classpath: configurations.domaGenRuntime.asPath) ant.gen(url: "${dbUrl}", user: "${dbUser}", password: "${dbPassword}", tableNamePattern: "${tableNamePattern}") { entityConfig(packageName: "${rootPackageName}.entity", useListener: false) daoConfig(packageName: "${rootPackageName}.dao") sqlConfig() } // 生成された Dao インターフェースを作業用ディレクトリにコピーし、 // @ComponentAndAutowiredDomaConfig アノテーションを付加する copy() { from "${daoPackagePath}" into "${workDaoDirPath}/replace" filter { line -> line.replaceAll("import org.seasar.doma.Dao;", "import ${importOfComponentAndAutowiredDomaConfig};\nimport org.seasar.doma.Dao;") .replaceAll("@Dao", "@Dao\n@ConfigAutowireable") } } // @ComponentAndAutowiredDomaConfig アノテーションを付加した Dao インターフェースを // dao パッケージへ戻す copy() { from "${workDaoDirPath}/replace" into "${daoPackagePath}" } // 元々 dao パッケージ内にあったファイルを元に戻す copy() { from "${workDaoDirPath}/org" into "${daoPackagePath}" } // 作業用ディレクトリを削除する clearDir("${workDirPath}") } } void clearDir(String dirPath) { delete dirPath }
- Spring Boot 関連のライブラリに依存するライブラリ系のプロジェクトで build を成功させるために
bootJar { enabled = false }
、jar { enabled = true }
を記述します。
更新後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。Gradle Tool Window に doma2-lib が表示されます。
ksbysample.lib.doma2lib パッケージを作成する
doma2-lib/src/main/java の下に ksbysample.lib.doma2lib パッケージを作成します。
domaGen タスクを実行して employee テーブルの Dao インターフェース、Entity クラスを生成する
Gradle Tool Window から domaGen タスクを実行します。
(.....途中省略.....)
[ant:gen] Loading class com.mysql.jdbc.Driver. This is deprecated. The new driver class is com.mysql.cj.jdbc.Driver. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
というメッセージが出力されますが、domaframework/doma-gen を見ると mysql の場合は com.mysql.jdbc.Driver を使用するようになっていました。JDBC Driver を別途指定できればいいのですが、方法が分かりませんでした。現時点では deprecated で使用できない訳ではないので、このまま進めることにします。
下の画像の赤文字のファイルが生成されます。
■ksbysample.lib.doma2lib.dao.EmployeeDao インターフェース
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; /** */ @Dao @ConfigAutowireable public interface EmployeeDao { /** * @param id * @return the Employee entity */ @Select Employee selectById(Integer id); /** * @param entity * @return affected rows */ @Insert int insert(Employee entity); /** * @param entity * @return affected rows */ @Update int update(Employee entity); /** * @param entity * @return affected rows */ @Delete int delete(Employee entity); }
■ksbysample.lib.doma2lib.entity.Employee クラス
package ksbysample.lib.doma2lib.entity; import java.time.LocalDateTime; import org.seasar.doma.Column; import org.seasar.doma.Entity; import org.seasar.doma.GeneratedValue; import org.seasar.doma.GenerationType; import org.seasar.doma.Id; import org.seasar.doma.Table; /** * */ @Entity @Table(name = "employee") public class Employee { /** */ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") Integer id; /** */ @Column(name = "name") String name; /** */ @Column(name = "age") Integer age; /** */ @Column(name = "sex") String sex; /** */ @Column(name = "update_time") LocalDateTime updateTime; .......... }
db-develop.properties、db-product.properties を作成する
src/main/resources の下に db-develop.properties、db-product.properties を新規作成し、以下の内容を記述します。DB の設定はこれらのファイルに記述し、sample-cmdapp、sample-webapp プロジェクト内では設定しません。
■db-develop.properties
doma.dialect=mysql spring.datasource.hikari.jdbc-url=jdbc:mysql://localhost/sampledb 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
■db-product.properties
# 何も記述しないと git に commit できないので、コメントアウトした行を記述する
最後に動作確認する時に spring.profiles.active に指定する値を変更することで適用する設定ファイルを db-develop.properties か db-product.properties かを切り替えることが出来ることを確認するために2つファイルを用意しますが、今は db-product.properties には何も設定しません。
DataSourceConfig クラスを作成する
doma2-lib/src/main/java/ksbysample/lib/doma2lib の下に config パッケージを作成した後、その下に DataSourceConfig.java を新規作成し以下の内容を記述します。
package ksbysample.lib.doma2lib.config; import com.zaxxer.hikari.HikariDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.jmx.export.MBeanExporter; import javax.sql.DataSource; @Configuration @PropertySource(value = "classpath:db-${spring.profiles.active}.properties") public class DataSourceConfig { private final MBeanExporter mbeanExporter; public DataSourceConfig(@Autowired(required = false) MBeanExporter mbeanExporter) { this.mbeanExporter = mbeanExporter; } @Bean @ConfigurationProperties("spring.datasource.hikari") public DataSource dataSource() { if (mbeanExporter != null) { mbeanExporter.addExcludedBean("dataSource"); } return DataSourceBuilder.create() .type(HikariDataSource.class) .build(); } }
EmployeeDao インターフェースのテストクラスを作成する
@SpringBootApplication アノテーションを付与したクラスが1つもないと @SpringBootTest アノテーションを付与したテストが実行できないので、doma2-lib/src/test/main の下に ksbysample/lib/doma2lib パッケージを作成して TestApplication.java を新規作成し、以下の内容を記述します。生成する jar ファイルに TestApplication クラスを含めないようにするために src/test/main の下に作成しています。
package ksbysample.lib.doma2lib; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } }
EmployeeDao インターフェースのテストクラスを Spock で作成します。
doma2-lib/src/test/groovy/ksbysample/lib/doma2lib/dao/EmployeeDaoTest.groovy が新規作成されますので、以下の内容を記述します。
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 String TESTDATA_NAME = "木村 太郎" @Autowired EmployeeDao employeeDao @Autowired private DataSource dataSource def sql void setup() { sql = new Sql(dataSource) } void cleanup() { sql.close() } @Unroll def "selectById メソッドのテスト(#id --> #name, #age, #sex)"() { setup: def result = employeeDao.selectById(id) expect: result.name == name result.age == age result.sex == sex where: id || name | age | sex 1 || "田中 太郎" | 20 | "男" 2 || "鈴木 花子" | 18 | "女" } def "insert メソッドのテスト"() { setup: sql.execute("delete from employee where name = ${TESTDATA_NAME}") Employee employee = new Employee(id: null, name: "${TESTDATA_NAME}", age: 35, sex: "男", updateTime: null) employeeDao.insert(employee) expect: def result = sql.firstRow("select * from employee where name = ${TESTDATA_NAME}") result.name == TESTDATA_NAME result.age == 35 result.sex == "男" cleanup: sql.execute("delete from employee where name = ${TESTDATA_NAME}") } }
テストを実行して成功することを確認します。
clean タスク実行 → Rebuild Project 実行 → build タスク実行を行う
clean タスク実行 → Rebuild Project 実行 → build タスク実行を行い、エラーなしで doma2-lib-1.0.0-RELEASE.jar が生成されることを確認します。
(.....途中省略.....)
最後に BUILD SUCCESSFUL のメッセージは出力されましたが、途中で javax.net.ssl.SSLException: closing inbound before receiving peer's close_notify のメッセージが何度も出力されました。
原因を Web で検索してみたところ Java11からMySQL8への接続時に起きた問題の解決 という記事がありました。JDK 11 のバグらしいです。
JDBC の URL に sslMode=DISABLED
を付けて SSL を使用しなければメッセージが出ないようにできるそうなので、今回はそれで対応します。また HikariCP を使用していると文字化けするという情報も記載されていたので characterEncoding=utf8
も付けることにします。
doma2-lib/src/main/resources/db-develop.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
再度 clean タスク実行 → Rebuild Project 実行 → build タスク実行を行うと、今度は警告・エラーのメッセージは表示されずに BUILD SUCCESSFUL のメッセージが出力されました。
doma2-lib/build/libs の下に doma2-lib-1.0.0-RELEASE.jar が生成されています。現在のディレクトリ構成は以下のようになります。
doma2-lib-1.0.0-RELEASE.jar の中身を見てみると以下のようになっています。
ここまでの感想ですが JDK 11+MySQL 8 の組み合わせはまだ安定していないようですね。。。 本番で使うなら MySQL は 5.7 を選択した方が良さそうです。
メモ書き
doma2-lib の build.gradle に bootJar { enabled = false }
、jar { enabled = true }
を記述しないと build は失敗する
doma2-lib の build.gradle には bootJar { enabled = false }
、jar { enabled = true }
を記述しましたが、これらを記述しないと bootJar タスクで実行可能 Jar を生成しようとしても Main class が存在しないため Main class name has not been configured and it could not be resolved
のエラーメッセージが表示されて build タスクが失敗します。
履歴
2019/04/30
初版発行。
Gradle で Multi-project を作成する ( その7 )( doma2lib+cmdapp+webapp編、Multi-project の設定ファイルと docker-compose.yml を作成する )
概要
記事一覧はこちらです。
Gradle で Multi-project を作成する ( その6 )( Multi-project は settings.gradle に include を書くだけでもよいのでは? ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
参照したサイト・書籍
dockerhub - mysql https://hub.docker.com/_/mysql
dockerhub - adminer https://hub.docker.com/_/adminer/
MySQL8.0ではGRANT構文でユーザを作成できない
https://www7390uo.sakura.ne.jp/wordpress/archives/456MySQL Innovation Day Tokyo で MySQL 8 の文字コードについて話した
https://tmtms.hatenablog.com/entry/201805/mysql-innovation-day-tokyoユーザーを作成する(CREATE USER文)
https://www.dbonline.jp/mysql/user/index1.htmlMySQL 8.0 の AUTO_INCREMENT について
https://www.s-style.co.jp/blog/2018/08/2284/【MySQL入門】CREATE TABLE文でテーブルを作成する方法
https://www.sejuku.net/blog/82708MySQL data types
http://zetcode.com/databases/mysqltutorial/datatypes/
目次
- 方針
- gradle-multiprj-doma2lib-cmdwebapp ディレクトリ作成+Gradle Wrapper コピー+Gradle 5.4.1 バージョンアップ+
gradlew init
- IntelliJ IDEA で gradle-multiprj-doma2lib-cmdwebapp プロジェクトをオープンする
- Spring Initializr で doma2-lib プロジェクトを作成する
- MySQL と Flyway を起動するための docmer-compose.yml を作成した後、サーバを起動してデータベースとテーブルを作成する
手順
方針
gradle-multiprj-doma2lib-cmdwebapp ├ doma2-lib <-- Doma 2 の Entity、Dao を提供するライブラリの Project │ ├ src │ │ ├ main │ │ │ └ java │ │ │ └ ksbysample │ │ │ └ lib │ │ │ └ doma2lib │ │ │ ├ dao │ │ │ └ entity │ │ └ resources │ │ ├ db │ │ │ ├ init │ │ │ │ └ create_database.sql │ │ │ └ migration │ │ │ └ V1__create_table.sql <-- Flyway 用 SQL ファイル │ │ ├ db-develop.properties <-- develop profile 用 DB 設定ファイル │ │ └ db-product.properties <-- product profile 用 DB 設定ファイル │ └ build.gradle ├ sample-cmdapp <-- Spring Boot ベースのコマンドラインアプリケーションの Project │ ├ src │ │ ├ main │ │ │ └ java │ │ │ └ ksbysample │ │ │ └ app │ │ │ └ samplecmdapp │ │ └ resources │ └ build.gradle ├ sample-webapp <-- Spring Boot ベースの Web アプリケーションの Project │ ├ src │ │ ├ main │ │ │ └ java │ │ │ └ ksbysample │ │ │ └ app │ │ │ └ samplewebapp │ │ └ resources │ └ build.gradle ├ build.gradle └ settings.gradle
- 今回は全てのプロジェクトを Spring Initializr で作成します。
- develop と product の2つの Profile を作成・使用します。
- テスティングフレームワークは全ての Project で JUnit 5 と Spock を使用できるようにします。
- checkstyle, spotbugs, pmd, error-prone は導入しません。
- Spring Boot ベースの Web アプリケーションでは 8080番ポートを使用します。
- Spring Boot ベースのアプリケーションの build.gradle の共通の設定は各サブプロジェクトの build.gradle ではなく gradle-multiprj-doma2lib-cmdwebapp の build.gradle に記述します。
- DB は MySQL のバージョン 8 を docker-compose で起動して利用します(これまで使ったことがなかったので)。
- DB へのデータ投入には Flyway を使用します。Flyway に必要なファイルは doma2-lib プロジェクトの下に配置します。
- コマンドラインアプリケーションでは CSV ファイルを読み込んで DB のテーブルにデータを登録し、Web アプリケーションでは DB のテーブルに登録されたデータを取得して表示します。
- 最初に doma2-lib-1.0.0-RELEASE.jar を sample-cmdapp-1.0.0-RELEASE.jar、sample-webapp-1.0.0-RELEASE.jar の中に入れる方式(JarLauncher による起動)で一通り書いてから、doma2-lib-1.0.0-RELEASE.jar を外に出す方式(PropertiesLauncher による起動)を書くつもりです。
- draw.io で全体図を描いてみました。
gradle-multiprj-doma2lib-cmdwebapp ディレクトリ作成+Gradle Wrapper コピー+Gradle 5.4.1 バージョンアップ+gradlew init
ksby/ksbysample-boot-miscellaneous の repository を checkout している D:\project-springboot\ksbysample-boot-miscellaneous
の下に gradle-multiprj-doma2lib-cmdwebapp ディレクトリを作成します。
gradle-multiprj-lib-webapp2 プロジェクトから Gradle Wrapper のファイルをコピーします。
- gradle ディレクトリ
- gradlew
- gradlew.bat
コマンドプロンプトから gradlew wrapper --gradle-version=5.4.1
、gradlew --version
、gradlew wrapper
コマンドを実行して Gradle を 5.4 → 5.4.1 へバージョンアップします。
gradle/wrapper/gradle-wrapper.properties を見ると 5.4.1 になっています。
distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists
gradlew init
コマンドを実行します。選択肢は 1: basic
、1: groovy
を選択し、Project name は何も入力せずに Enter キーを押します。
IntelliJ IDEA で gradle-multiprj-doma2lib-cmdwebapp プロジェクトをオープンする
gradle-multiprj-doma2lib-cmdwebapp プロジェクトをオープンします。
Spring Initializr で doma2-lib プロジェクトを作成する
Flyway で使用する SQL ファイルを doma2-lib プロジェクトの下に配置するので、最初に IntelliJ IDEA から Spring Initializr を利用して doma2-lib プロジェクトを作成しておきます。
※dependencies のダイアログでは何もチェックしません。
作成後 IntelliJ IDEA のウィンドウが開きますが、何もせずに閉じます。
src ディレクトリと build.gradle だけ残して、それ以外のディレクトリファイルは全て削除します。gradle-multiprj-doma2lib-cmdwebapp プロジェクトの settings.gradle にはまだ include 文は追加しません。
MySQL と Flyway を起動するための docmer-compose.yml を作成した後、サーバを起動してデータベースとテーブルを作成する
MySQL のデータファイルをローカルディレクトリに保存するために docker/mysql/data を作成します。
doma2-lib/src/main/resources の下に db/init、db/migration の2つのディレクトリを作成します。
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; flush privileges;
doma2-lib/src/main/resources/db/migration の下に V1__create_table.sql を新規作成し、以下の内容を記述します。
create table employee ( id int(11) not null auto_increment, name varchar(128) not null, age int(3) not null, sex enum('男', '女'), update_time datetime default current_timestamp, primary key (id) ); insert into employee (name, age, sex) values ('田中 太郎', 20, '男'); insert into employee (name, age, sex) values ('鈴木 花子', 18, '女');
ルートディレクトリの下に .env を新規作成し、以下の内容を記述します。
MYSQL_VERSION=8.0.16 ADMINER_VERSION=4.7.1 FLYWAY_VERSION=5.2.4 FLYWAY_URL=jdbc:mysql://mysql/sampledb FLYWAY_USER=sampledb_user FLYWAY_PASSWORD=xxxxxxxx
ルートディレクトリの下に docker-compose.yml を新規作成し、以下の内容を記述します。
version: '3' services: # 起動したコンテナに /bin/sh でアクセスする場合には以下のコマンドを実行する # docker exec -it mysql bash mysql: image: mysql:${MYSQL_VERSION} container_name: mysql command: --default-authentication-plugin=mysql_native_password restart: always ports: - 3306:3306 environment: - TZ=Asia/Tokyo - MYSQL_ROOT_PASSWORD=xxxxxxxx volumes: - ./docker/mysql/data:/var/lib/mysql - ./doma2-lib/src/main/resources/db/init/create_database.sql:/docker-entrypoint-initdb.d/create_database.sql # URL # http://localhost:9080/ adminer: image: adminer:${ADMINER_VERSION} container_name: adminer restart: always ports: - 9080:8080 environment: - TZ=Asia/Tokyo - ADMINER_DEFAULT_SERVER=mysql flyway: image: boxfuse/flyway:${FLYWAY_VERSION}-alpine container_name: flyway environment: - TZ=Asia/Tokyo volumes: - ./doma2-lib/src/main/resources/db/migration:/flyway/sql command: -url=${FLYWAY_URL} -user=${FLYWAY_USER} -password=${FLYWAY_PASSWORD} -connectRetries=60 migrate depends_on: - mysql # 下の3行は debug 用 # うまく動かない時はコメントアウトを解除した後、 # docker exec -it flyway /bin/sh # で接続してから # flyway <command に記述した文字列> # を実行してみる # # entrypoint: /bin/sh # stdin_open: true # tty: true
これで必要なファイルを一通り作成しましたので、コマンドラインから docker-compose up -d
コマンドを実行します。
http://localhost:9080/ にアクセスして sampledb が作成されていること、emplayee テーブルにデータが登録されていることを確認します。
履歴
2019/04/29
初版発行。
2019/04/30
* 「参照したサイト・書籍」に MySQL data types を追加した。
* V1__create_table.sql の create table employee 文に age, sex, update_time のカラムを追加し、投入するデータを日本語のものに変更した。
* create database の collate を utf8mb4_0900_as_cs → utf8mb4_ja_0900_as_cs_ks に変更した。
Gradle で Multi-project を作成する ( 番外編 )( Spring Actuator を利用してアプリ起動時にメールサーバに接続できない場合には起動を中断させる )
概要
記事一覧はこちらです。
本編とは全く関係ありません。。。
何となく思いついたことで Spring Actuator を入れると DB サーバやメールサーバの UP/DOWN を検知できますが、起動時にサーバに接続できなかったら起動を中断させることができたりするのかな?、と思って調べた時の内容です。
結論としては HealthChecker の実装に依存しますが、メールサーバは可能でした。
参照したサイト・書籍
- Programmatically shut down Spring Boot application
https://stackoverflow.com/questions/22944144/programmatically-shut-down-spring-boot-application
目次
- 動作確認のための demo プロジェクトを作成する
- Spring Actuator でメールサーバをチェックするよう設定する
- メールサーバを起動せずに demo アプリを起動してみる
- 起動時にメールサーバに接続できなかったら起動を中断するための CustomMailHealthIndicator クラスを作成する
- 動作確認
手順
動作確認のための demo プロジェクトを作成する
IntelliJ IDEA から Spring Initializr を利用して demo プロジェクトを作成します。
※DevTools、Web、Mail、Actuator をチェックします。
IntelliJ IDEA のメイン画面が表示されたら、lombok の @Slf4j
アノテーションを使用したいので build.gradle の dependencies に lombok の依存関係を追加します。
plugins { id 'org.springframework.boot' version '2.1.4.RELEASE' id 'java' } apply plugin: 'io.spring.dependency-management' group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' configurations { // annotationProcessor と testAnnotationProcessor、compileOnly と testCompileOnly を併記不要にする testAnnotationProcessor.extendsFrom annotationProcessor testImplementation.extendsFrom compileOnly } repositories { mavenCentral() } dependencies { def lombokVersion = "1.18.6" implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-mail' 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」ボタンをクリックして更新します。
Spring Actuator でメールサーバをチェックするよう設定する
Spring Actuator でメールサーバの health チェックを行うために application.properties に spring.mail.host=localhost
の設定を追加します。
spring.mail.host=localhost
メールサーバを起動せずに demo アプリを起動してみる
メールサーバを起動しない状態で demo アプリを起動すると、Spring Actuator の MailHealthIndicator クラスから警告ログが出力されますがアプリは終了せず起動したままです。
Health チェックの画面では mail が黄色アイコンで表示されています。
起動時にメールサーバに接続できなかったら起動を中断するための CustomMailHealthIndicator クラスを作成する
警告ログを出力している org.springframework.boot.actuate.mail.MailHealthIndicator クラスを見ると以下のように実装されています。
実際に health チェックしているのが doHealthCheck メソッドで、this.mailSender.testConnection();
で接続してみて例外が throw されなければ builder.up();
を呼び出してステータスを UP に変更する、という実装でした。
MailHealthIndicator クラスには @Component
等のアノテーションが付与されていないので Bean を定義しているクラスを探したところ、org.springframework.boot.actuate.autoconfigure.mail.MailHealthIndicatorAutoConfiguration クラスで定義されていました。
mailHealthIndicator という名前の Bean が存在しない時だけ MailHealthIndicatorAutoConfiguration クラスで Bean を生成しています。
MailHealthIndicator クラスを継承して CustomMailHealthIndicator クラスを作成し、一番最初の health チェックで例外が throw されたら Web アプリを強制終了させるようにしてみます。src/main/java/com/example/demo/CustomMailHealthIndicator.java クラスを新規作成した後、以下の内容を記述します。
package com.example.demo; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.ExitCodeGenerator; import org.springframework.boot.SpringApplication; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.mail.MailHealthIndicator; import org.springframework.context.ApplicationContext; import org.springframework.mail.javamail.JavaMailSenderImpl; import java.util.concurrent.atomic.AtomicBoolean; @Slf4j @Component("mailHealthIndicator") public class CustomMailHealthIndicator extends MailHealthIndicator { private final ApplicationContext context; private final AtomicBoolean isFirstTime = new AtomicBoolean(true); public CustomMailHealthIndicator(JavaMailSenderImpl mailSender, ApplicationContext context) { super(mailSender); this.context = context; } @Override protected void doHealthCheck(Health.Builder builder) throws Exception { try { super.doHealthCheck(builder); } catch (Exception e) { // 一番最初の health チェックでメールサーバに接続できなかった時にはアプリを強制終了させる if (isFirstTime.get()) { log.error("メールサーバが起動していないので強制終了します", e); int exitCode = SpringApplication.exit(context, (ExitCodeGenerator) () -> 1); System.exit(exitCode); } throw e; } finally { isFirstTime.compareAndExchange(true, false); } } }
動作確認
先程と同様にメールサーバを起動しない状態で demo アプリを起動すると、最後に Process finished with exit code 1
のメッセージが出力されてアプリが終了しました。
メールサーバ(smtp4dev)を起動してから demo アプリを起動すると、アプリは終了せずに起動したままとなり、
メールサーバを終了させると、health チェックのエラーログが出力されますがアプリは終了せずに起動したままでした。
想定通りの動きです。
履歴
2019/04/27
初版発行。
Gradle で Multi-project を作成する ( その6 )( Multi-project は settings.gradle に include を書くだけでもよいのでは? )
概要
記事一覧はこちらです。
Gradle で Multi-project を作成する ( その5 )( lib+webappx2編、Spring Boot ベースの Web アプリケーション(スタブ)のプロジェクトを作成する ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
参照したサイト・書籍
目次
- 2つ Multi-project を作成した上での考察。。。include だけでもいいのでは?
- gradle-multiprj-includeonly ディレクトリを作成する
- Gradle Wrapper のファイル一式をコピーする
gradlew init
コマンドを実行する- IntelliJ IDEA で gradle-multiprj-includeonly プロジェクトを開く
- IntelliJ IDEA で sample-lib プロジェクトを新規作成する
- setting.gradle に
include 'sample-lib'
を追加する - sample-lib プロジェクトに StrNumUtils クラスを追加する
- IntelliJ IDEA で sample-webapp プロジェクトを新規作成する
- setting.gradle に
include 'sample-webapp'
を追加する - sample-webapp プロジェクトに SampleController クラスを追加する
- 動作確認
- まとめ
手順
2つ Multi-project を作成した上での考察。。。include だけでもいいのでは?
ここまで2つ Multi-project を作成してみましたが、そこまで凝る必要がなければ setting.gradle に include 文を記述するだけで十分な気がするんですよね。。。
たぶんこんな感じで作っても Multi-project になる気がします。
- ルートディレクトリの直下に build.gradle を作成してサブプロジェクトの共通の設定を書けば確かに便利だが、実はルートディレクトリ直下には build.gradle はなくてもよい(サブプロジェクト毎に別々に build.gradle があるだけでOK)。
- ルートディレクトリの下に作成したサブプロジェクトの中のディレクトリ・ファイルはそのままでもよい(何も削除する必要はない)。
- ルートディレクトリ直下の setting.gradle に
include '<サブプロジェクト名>'
を記述すれば Multi-project になる。
試してみます。以下のような作り方でも Multi-project になるはずです。
- プロジェクトのルートディレクトリを作成する。
- Gradle Wrapper のファイル一式をコピーする。
gradlew init
コマンドを実行する。- IntelliJ IDEA でプロジェクトを開く。
- 後は以下の作業の繰り返し。
gradle-multiprj-includeonly ディレクトリを作成する
D:\project-springboot\ksbysample-boot-miscellaneous
の下に gradle-multiprj-includeonly ディレクトリを作成します。
Gradle Wrapper のファイル一式をコピーする
gradle-multiprj-lib-webapp2 プロジェクトから Gradle Wrapper のファイル一式をコピーします。
gradlew init
コマンドを実行する
コマンドラインから gradlew init
コマンドを実行します。
gradle-multiprj-includeonly ディレクトリ内に setting.gradle、build.gradle が作成されて、
setting.gradle には以下の内容が記述されています。コメントの部分は削除します。
/* * This file was generated by the Gradle 'init' task. * * The settings file is used to specify which projects to include in your build. * * Detailed information about configuring a multi-project build in Gradle can be found * in the user manual at https://docs.gradle.org/5.4/userguide/multi_project_builds.html */ rootProject.name = 'gradle-multiprj-includeonly'
build.gradle は以下の内容が記述されています。今回このファイルは何も変更しません。
/* * This file was generated by the Gradle 'init' task. * * This is a general purpose Gradle build. * Learn how to create Gradle builds at https://guides.gradle.org/creating-new-gradle-builds/ */
IntelliJ IDEA で gradle-multiprj-includeonly プロジェクトを開く
IntelliJ IDEA で gradle-multiprj-includeonly プロジェクトを開きます。Project Tool Window と Gradle Tool Window は以下のように表示されます。
IntelliJ IDEA で sample-lib プロジェクトを新規作成する
gradle-multiprj-includeonly ディレクトリの下に sample-lib プロジェクトを新規作成します。Gradle プロジェクトとして作成します。
ディレクトリ・ファイルは以下のようになります。今回はこのディレクトリの中は何も削除しません(Gradle Wrapper も残したままです)。
setting.gradle に include 'sample-lib'
を追加する
setting.gradle に include 'sample-lib'
を追加します。
rootProject.name = 'gradle-multiprj-includeonly' include 'sample-lib'
Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新すると、Gradle Tool Window に sample-lib プロジェクトが表示されます。
sample-lib プロジェクトに StrNumUtils クラスを追加する
sample-lib/src/main/java の下に ksbysample.lib.samplelib パッケージを作成した後、StrNumUtils.java を新規作成して以下の内容を記述します。
package ksbysample.lib.samplelib; public class StrNumUtils { public static String plus(String v1, String v2) { return String.valueOf(Integer.parseInt(v1) + Integer.parseInt(v2)); } }
IntelliJ IDEA で sample-webapp プロジェクトを新規作成する
gradle-multiprj-includeonly ディレクトリの下に sample-webapp プロジェクトを新規作成します。Spring Initializr で作成します。
ディレクトリ・ファイルは以下のようになります。このディレクトリの中は何も削除しません。
setting.gradle に include 'sample-webapp'
を追加する
setting.gradle に include 'sample-webapp'
を追加します。
rootProject.name = 'gradle-multiprj-includeonly' include 'sample-lib' include 'sample-webapp'
Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新すると、Gradle Tool Window に sample-webapp プロジェクトが表示されます。
sample-webapp プロジェクトに SampleController クラスを追加する
sample-webapp プロジェクトの build.gradle に sample-lib プロジェクトへの依存関係を追加します。
plugins { id 'org.springframework.boot' version '2.1.4.RELEASE' id 'java' } apply plugin: 'io.spring.dependency-management' group = 'ksby.ksbysample-boot-miscellaneous' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' runtimeOnly 'org.springframework.boot:spring-boot-devtools' testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation project(":sample-lib") }
- dependencies block に
implementation project(":sample-lib")
を追加します。
変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
sample-webapp/src/main/java/ksbysample/webapp/samplewebapp/SampleController.java を新規作成して以下の内容を記述します。
package ksbysample.webapp.samplewebapp; import ksbysample.lib.samplelib.StrNumUtils; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping("/sample") public class SampleController { @RequestMapping @ResponseBody public String index() { return StrNumUtils.plus("1", "2"); } }
動作確認
最初に clean タスク実行 → Rebuild Project 実行 → build タスク実行を行うと、BUILD SUCCESSFUL のメッセージが出力されることを確認します。
Run/Debug Configuration に SampleWebappApplication が自動で登録されているので、ここから Tomcat を起動して、
http://localhost:8080/sample にアクセスすると 3
と表示されます。
Project Tool Window で見ると以下のディレクトリ構成になっており、sample-lib-1.0-SNAPSHOT.jar、sample-webapp-0.0.1-SNAPSHOT.jar が生成されています。
コマンドプロンプトから java -jar sample-webapp-0.0.1-SNAPSHOT.jar
コマンドで Tomcat を起動してから、
http://localhost:8080/sample にアクセスしても 3
と表示されます。
まとめ
setting.gradle に include 文を書くだけでも Multi-project とみなされますね。プロジェクトが1つでも最初から Multi-project のディレクトリ・ファイル構成で作っておいて、開発途中にちょっとしたスタブが欲しい時にはサブプロジェクトを作成して setting.gradle に include を追加して対応する、というのもありなのかもしれません。
履歴
2019/04/24
初版発行。
Gradle で Multi-project を作成する ( その5 )( lib+webappx2編、Spring Boot ベースの Web アプリケーション(スタブ)のプロジェクトを作成する )
概要
記事一覧はこちらです。
- 今回の手順で確認できるのは以下の内容です。
- Gradle で Spring を使用しないライブラリ+Spring Boot ベースの Web アプリケーション x 2(メインとスタブ)の Multi-project を作成します。
- sample-stubapp プロジェクト(こちらが sample-lib の StrNumUtils クラスを呼び出します)を作成した後、sample-webapp の SampleController クラスを本実装して Multi-project を完成させます。
参照したサイト・書籍
Spring @WebMvcTest with Spock Framework
https://allegro.tech/2018/04/Spring-WebMvcTest-with-Spock.htmlSpring Boot RestTemplate POST JSON Example
https://howtodoinjava.com/spring-boot2/resttemplate-post-json-example/
目次
- Spring Boot ベースの Web アプリケーション(スタブ)のプロジェクトを作成する
- IntelliJ IDEA で sample-stubapp プロジェクトを作成する
- sample-stubapp プロジェクトから不要なファイルを削除する
- sample-stubapp プロジェクトの build.gradle を変更する
- gradle-multiprj-lib-webapp2 プロジェクトの build.gradle を変更する
- settings.gradle に sample-stubapp プロジェクトの include 文を追加する
- application.properties に
server.port=9080
を追加する - StubWebapiController クラスを新規作成する
- sample-webapp の SampleController クラスを本実装する
- clean タスク実行 → Rebuild Project 実行 → build タスク実行を行う
- Run Dashboard から sample-webapp、sample-stubapp を起動して動作確認する
- sample-webapp、sample-stubapp を jar ファイルから起動して動作確認する
- まとめ
手順
Spring Boot ベースの Web アプリケーション(スタブ)のプロジェクトを作成する
IntelliJ IDEA で sample-stubapp プロジェクトを作成する
IntelliJ IDEA から Spring Initializr を利用して Spring Boot ベースの Web アプリケーションのプロジェクトを作成します。
※DevTools と Web の2つをチェックします。
作成後 IntelliJ IDEA のウィンドウが開きますが、何もせずに閉じます。
sample-stubapp プロジェクトから不要なファイルを削除する
D:\project-springboot\ksbysample-boot-miscellaneous\gradle-multiprj-lib-webapp2\sample-stubapp\
の下には以下のディレクトリ・ファイルがありますが、
src ディレクトリと build.gradle 以外を削除します。削除すると以下の状態になります。
sample-stubapp プロジェクトの build.gradle を変更する
sample-stubapp プロジェクトの build.gradle を以下の内容に変更します。必要な設定は gradle-multiprj-lib-webapp2 の build.gradle に記述したので、このファイルには sample-lib への依存関係だけ記述します。
dependencies {
implementation project(":sample-lib")
}
gradle-multiprj-lib-webapp2 プロジェクトの build.gradle を変更する
gradle-multiprj-lib-webapp2 プロジェクトの build.gradle を以下のように変更します。
.......... configure(subprojects.findAll { it.name ==~ /^(sample-webapp|sample-stubapp)$/ }) { .......... }
it.name ==~ /^(sample-webapp)$/
→it.name ==~ /^(sample-webapp|sample-stubapp)$/
に変更します。
settings.gradle に sample-stubapp プロジェクトの include 文を追加する
D:\project-springboot\ksbysample-boot-miscellaneous\gradle-multiprj-lib-webapp2
の下の settings.gradle に include 'sample-stubapp'
を追加します。
rootProject.name = 'gradle-multiprj-lib-webapp2' include 'sample-lib' include 'sample-webapp' include 'sample-stubapp'
追加後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新すると sample-stubapp が追加されます。
また IntelliJ IDEA の画面右下に「Run Dashboard」のダイアログが表示されます。「Show run configurations Run Dashboard」リンクをクリックすると Run Dashboard Tool Window が表示されます。
※ちなみに Run Dashboard Tool Window は Run/Debug Configurations で Spring Boot に2つ以上設定を追加すれば表示させることができます。
clean タスク実行 → Rebuild Project 実行 → build タスク実行を行うと、BUILD SUCCESSFUL のメッセージが出力されました。
Project Tool Window を見ると以下のディレクトリ構成になっています。sample-stubapp/build/libs の下に sample-stubapp-1.0.0-RELEASE.jar が生成されています。
application.properties に server.port=9080
を追加する
sample-stubapp/src/main/resources/application.properties に server.port=9080
を追加します。
server.port=9080
StubWebapiController クラスを新規作成する
以下の仕様の StubWebapiController クラスを作成します。
- URL は
/plus
にする。 - HTTP メソッドは POST のみ有効にする。
- データは JSON で渡す。
{ "v1": "<数値文字列>", "v2": "<数値文字列>" }
のフォーマットする。 - レスポンスは
{ "result": "<数値文字列>" }
のフォーマットで返す。 - エラー処理は考慮しない。
sample-stubapp/src/main/java/ksbysample/webapp/samplestubapp の下に PlusForm、PlusResponse、StubWebapiController クラスを新規作成し、以下の内容を記述します。
■PlusForm
package ksbysample.webapp.samplestubapp; public class PlusForm { private String v1; private String v2; public String getV1() { return v1; } public void setV1(String v1) { this.v1 = v1; } public String getV2() { return v2; } public void setV2(String v2) { this.v2 = v2; } }
■PlusResponse
package ksbysample.webapp.samplestubapp; public class PlusResponse { private String result; PlusResponse() { } PlusResponse(String result) { this.result = result; } public String getResult() { return result; } public void setResult(String result) { this.result = result; } }
■StubWebapiController
package ksbysample.webapp.samplestubapp; import ksbysample.lib.samplelib.StrNumUtils; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class StubWebapiController { @PostMapping("/plus") public PlusResponse plus(@RequestBody PlusForm plusForm) { String result = StrNumUtils.plus(plusForm.getV1(), plusForm.getV2()); PlusResponse plusResponse = new PlusResponse(result); return plusResponse; } }
テストを作成します。今回は Spock で作成します。
sample-stubapp/src/test/groovy/ksbysample/webapp/samplestubapp/StubWebapiControllerTest.groovy が新規作成されますので、以下の内容を記述します。
package ksbysample.webapp.samplestubapp 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.http.MediaType import org.springframework.test.web.servlet.MockMvc import spock.lang.Specification import spock.lang.Unroll import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.* @SpringBootTest @AutoConfigureMockMvc class StubWebapiControllerTest extends Specification { @Autowired private MockMvc mvc @Unroll def "plus WebAPI のテスト(#v1, #v2 --> #result)"() { expect: mvc.perform(post("/plus") .contentType(MediaType.APPLICATION_JSON_UTF8) .content(""" { "v1": ${v1}, "v2": ${v2} } """)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) .andExpect(jsonPath('$.result').value(result)) where: v1 | v2 || result "1" | "2" || "3" "999" | "1" || "1000" } }
テストを実行すると成功しました。画面左側で WARNING が出ているのは IntelliJ IDEA の JUnit の Run/Debug Configuration に JDK 11 のオプションを 設定していないためです。
sample-stubapp/src/test/java/ksbysample/webapp/samplestubapp/SampleStubappApplicationTests.java は不要なので削除します。
sample-webapp の SampleController クラスを本実装する
sample-webapp/src/main/java/ksbysample/webapp/samplewebapp/SampleWebappApplication.java を以下のように変更します。
package ksbysample.webapp.samplewebapp; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication public class SampleWebappApplication { private final RestTemplateBuilder restTemplateBuilder; public SampleWebappApplication(RestTemplateBuilder restTemplateBuilder) { this.restTemplateBuilder = restTemplateBuilder; } public static void main(String[] args) { SpringApplication.run(SampleWebappApplication.class, args); } @Bean public RestTemplate restTemplate() { return this.restTemplateBuilder .rootUri("http://localhost:9080") .build(); } }
private final RestTemplateBuilder restTemplateBuilder;
とコンストラクタインジェクションの処理を追加します。@Bean public RestTemplate restTemplate() { ... }
を追加します。
sample-stubapp/src/main/java/ksbysample/webapp/samplestubapp の下の PlusForm、PlusResponse クラスを sample-webapp/src/main/java/ksbysample/webapp/samplewebapp の下にコピーします。
sample-webapp/src/main/java/ksbysample.webapp.samplewebapp.SampleController.java を以下の内容に変更します。
package ksbysample.webapp.samplewebapp; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.client.RestTemplate; @Controller @RequestMapping("/sample") public class SampleController { private final RestTemplate restTemplate; public SampleController(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @RequestMapping @ResponseBody public String index() { PlusForm plusForm = new PlusForm(); plusForm.setV1("1"); plusForm.setV2("2"); HttpHeaders httpHeaders = new HttpHeaders(); HttpEntity<PlusForm> request = new HttpEntity<>(plusForm, httpHeaders); ResponseEntity<PlusResponse> response = restTemplate.postForEntity("/plus", request, PlusResponse.class); return response.getBody().getResult(); } }
テストを作成します。こちらも Spock で作成します。
sample-webapp/src/test/groovy/ksbysample/webapp/samplewebapp/SampleControllerTest.groovy が新規作成されますので、以下の内容を記述します。
package ksbysample.webapp.samplewebapp 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.http.HttpMethod import org.springframework.http.MediaType import org.springframework.test.annotation.DirtiesContext import org.springframework.test.web.client.MockRestServiceServer import org.springframework.test.web.servlet.MockMvc import org.springframework.web.client.RestTemplate import spock.lang.Specification import static org.springframework.test.web.client.match.MockRestRequestMatchers.method import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess 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 @SpringBootTest @AutoConfigureMockMvc @DirtiesContext class SampleControllerTest extends Specification { @Autowired private MockMvc mvc @Autowired RestTemplate restTemplate def "/sample にアクセスすると 3 と表示される"() { setup: MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build() mockServer.expect(requestTo("http://localhost:9080/plus")) .andExpect(method(HttpMethod.POST)) .andRespond(withSuccess('{"result": "3"}', MediaType.APPLICATION_JSON_UTF8)) expect: mvc.perform(get("/sample")) .andExpect(status().isOk()) .andExpect(content().string("3")) } }
テストを実行すると成功しました。
sample-webapp/src/test/java/ksbysample/webapp/samplewebapp/SampleWebappApplicationTests.java は不要なので削除します。
clean タスク実行 → Rebuild Project 実行 → build タスク実行を行う
全ての実装が完了したので build タスクが正常に終了するか確認します。clean タスク実行 → Rebuild Project 実行 → build タスク実行を行うと、BUILD SUCCESSFUL のメッセージが出力されました。
Run Dashboard から sample-webapp、sample-stubapp を起動して動作確認する
Run Dashboard から sample-webapp、sample-stubapp を起動します。Run Dashboard から起動すると使用されているポート番号が表示されるのが分かりやすくていいですね。
ブラウザから http://localhost:8080/sample にアクセスすると画面上に "3" の文字が表示されました。
確認後、sample-webapp、sample-stubapp を停止します。
sample-webapp、sample-stubapp を jar ファイルから起動して動作確認する
今度は生成された jar ファイルから起動して確認してみます。コマンドプロンプトから jar ファイルが生成されている build/libs ディレクトリに移動した後、java -jar sample-webapp-1.0.0-RELEASE.jar
、java -jar sample-stubapp-1.0.0-RELEASE.jar
コマンドを実行します。
ブラウザから http://localhost:8080/sample にアクセスすると画面上に "3" の文字が表示されました。
確認後、sample-webapp、sample-stubapp を停止します。
まとめ
- サブプロジェクトに Spring Boot ベースの Web アプリケーションを2つ以上作成する場合、共通の設定はプロジェクトのルートディレクトリの build.gradle にまとめられます。
- Spring Boot ベースのサブプロジェクトと Spring Boot ベースではないサブプロジェクトが混在していて、Spring Boot ベースのサブプロジェクトの設定だけを プロジェクトのルートディレクトリの build.gradle にまとめる場合には
configure(subprojects.findAll { it.name ==~ /^(sample-webapp|sample-stubapp)$/ }) { ... }
のように記述して設定を適用するサブプロジェクトを指定できます。 - IntelliJ IDEA の Run Dashboard を表示させる方法がよく分かっていなかったのですが、Run/Debug Configurations で Spring Boot の下に2つ以上設定を追加すれば自動で現れることが分かりました。
- 簡単なサンプルを書いていたつもりでしたが、テストまで書くと意外に辛いですね。。。 書き方が分からなくて調べながらやっていて結構時間がかかりました。
履歴
2019/04/22
初版発行。