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
初版発行。