2WaySQLができるライブラリ Doma 2系 ってのを、Spring Bootなプロジェクトで使ってみたけど...

⇧  選べな~い、あたいには、選べないぃぃぃぃぃ~...

かどや製油株式会社さんの遊び心~、はい、どうも、ボクです。

ってなわけで、そんな、どっちも好っきやねん、っていう症状を何というでしょうか?

www.men-joy.jp

⇧ ブタ野郎~!まったく、モテる輩はこれだから...ブタ野郎~!大事なことなので2回言ってみました。

ところで、

ccc2019fall.java-users.jp

⇧  JJUG CCC 2019 Fall に行ってきましたよ~。

さっそく、まとめてくださってる方がおりました。

yujisoftware.hatenablog.com

qiita.com

プロダクション環境におけるアプリケーションのマイクロサービス化、サーバレス化ってものが徐々に進んでいて、それを実現する技術も成熟してきたと。

GraalVMの性能が鍵を握りそうですとも。

いや~、勉強することがどんどん増えてゆく...

技術系のイベントに参加すると、登壇者やボランティアの方々、スポンサーの企業の努力に頭が下がりますかね。

 

はい、そんなわけで、今回は、世間一般では二股はNGですが、2つとも選んで大丈夫という2WaySQLってものについてです。レッツトライ~。

この記事は、2019年11月9日(土)ぐらいから書き始めたので、およそ、1ヵ月...どんなけ泥沼にハマってるんだよって感じですかね(涙)。

お時間ある方のみ、ご照覧ください。

 

2WaySQLって?

残念ながら、Wikipediaさんが扱ってないという...

2WaySQLの1つである、uroboroSQLのサイトで、

future-architect.github.io

⇧  紹介されてたのですが、日本発祥のフレームワークである SeasarS2Dao が起源らしいですと。日本独自の文化らしい、和の心!

『シーザァアアアアアア―――――ッ!(【ジョジョの奇妙な冒険 - 第二部 ジョセフ・ジョースター ―その誇り高き血統「10巻、93話:シーザー最期の波紋の巻」 】)』

ちなみに、Seaser の読み方は、『シーサー』ですかね。

というわけで、Seaserは、

tenten0213.hatenablog.com

⇧  上記サイト様によりますと、いろんなものに影響を与えてるらしいですと。

 

んで、2WaySQLって、具体的にどんなものなのよ?

Mirage-SQL ってライブラリで説明されとりました。

github.com

What's 2WaySQL?

2WaySQL is just a plain old SQL-Template. You can specify parameters and conditions using special SQL comments. This way the template is executable using SQL client tools. This feature is based on the Japanese O/R mapping framework S2JDBC.

2WaySQL · mirage-sql/mirage Wiki · GitHub

S2JDBC にインスパイアされてるんよと。

S2JDBCって?

S2JDBCは、データベースプログラミングの生産性を10倍以上高めることを目標として作成した Seasar2のO/R Mapperです。何に比べて生産性が10倍かというとJava標準のJPA(Java Persistence API)に対してです。次のような特徴があります。

Seasar2 - S2JDBC - S2JDBCとは

⇧ シーザァアアアアアア―――――ッ!

JPAに対して物申す、ってことですかね、まぁ、確かに、Spring Data JPAとかも複雑なSQLになってくると実装が難しくなるんだそうですね...

要するに、2WaySQLってのは、名前の通り、

  • SQLファイルを、普通のSQLとして使える
  • SQLファイルを、プログラミングでも使える

ってな具合に、2つの使い方ができるよってことらしい。

最近のビジネスバッグは、3way とかも出てますけどね...はい、すみませんでした。

 

2WaySQLに拘らず、SQLテンプレートってのは、

cero-t.hatenadiary.jp

⇧  「Velocity」ってのが有名だったらしい。(今も新規プロジェクトで使われるのかは分かりません。)

 

Spring Boot で、Doma 2系を使ってみる

そんな、2WaySQLですが、Java だと、Doma 2系ってのがイケてるらしいですと。

というわけで、Spring Boot のプロジェクトで試してみました。

Doma 2系については、

doma.readthedocs.io

⇧  公式のドキュメントがあるっぽいです。

www.saka-en.com

qiita.com

⇧  上記サイト様を参考にさせていただきました。

んで、Doma 2系を使う場合は、

の2つを組み合わせるのが一般的?みたいです。

Doma-Gen-2については、

doma-gen.readthedocs.io

Doma-Gen 2 is a code generation tool for Doma 2.

Doma-Gen will generate:

  • Java source code and SQL files from database metadata
  • Java source code from the results of SQL executions
  • test cases for SQL statements from SQL files

Welcome to Doma-Gen 2 — Doma-Gen documentation

Doma-Gen is an Ant task, which can be used by many build tools such as Ant and Gradle.

Doma-Gen generates various files using FreeMarker. You can customize all generation code by changing FreeMarker template files.

Welcome to Doma-Gen 2 — Doma-Gen documentation

⇧ ってことらしい。日本発祥なんだから、ドキュメントを英語にしなくたって良いじゃないって思うんだけど、データベースの情報から、Javaファイルを生成してくれるんだと、要するにORMを自動でやってくるってことかと。

 

今回の手順としては、事前にデータベースを用意しておくとして、

  1. Spring Bootのプロジェクト作成
    依存関係
  2. Doma 2系を使うための依存関係の追加、設定ファイルを用意
    • doma
    • doma-spring-boot-starter
    • doma-gen ← ファイルの自動生成とか。
    • freemarker ← 親Pomで定義されてました、あと、doma-gen でfreemarkerって使われてるから、別途依存関係への追加は不要っぽい
  3. Doma Tool(Eclipseプラグイン)追加 ← 必須ではないらしい
  4. プロジェクトの注釈処理の設定
  5. Doma-Gen でファイルの自動生成

みたいな流れになるかと。(よく分からんですが、doma-gen 無くてもファイルの自動生成やってくれそうです。)

※ 2019年12月8日(日)現在、Domaのバージョンは、1系、2系が存在するようです。今回は、2系を使います。

そんでは、作業していきますか。

 

まずは、データベースをDockerコンテナで用意しておきます。今回は、みんな大好きMySQLのコンテナを用意で。 

なので、Dockerを使える環境を用意してない人は用意しちゃいましょう。

ちなみに、私の環境は、Windows 10 Home なので、Docker ToolBox、Virtual Box の組み合わせです。

今回は、docker-compose コマンドで、コンテナを生成しようと思うので、

qiita.com

noumenon-th.net

⇧  上記サイト様を参考にさせていただきました。

Data Volume」とかは、docker-compose.yml で指定したものが、自動的に生成されるっぽいです。

logファイルがマウントされないっぽかったので、 

stackoverflow.com

And update your docker-compose with below settings

    entrypoint: ""
    command: bash -c "chown -R mysql:mysql /var/log/mysql && exec /entrypoint.sh mysqld"

docker - Mount logs of mysql container in host directory - Stack Overflow

⇧  上記サイト様を参考にさせていただきました。

 

docker-compose する用のファイル群を用意。

f:id:ts0818:20191124113639p:plain

一応、ファイルの中身を。

C:\Users\Toshinobu\Desktop\soft_work\docker_work\dockerMysql\docker-compose.yml

version: "3"
services:
  db:
    image: mysql:latest
    volumes:
      - db-store:/var/lib/mysql
      - ./logs:/var/log/mysql
      - ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
    environment:
      - MYSQL_DATABASE=${DB_NAME}
      - MYSQL_USER=${DB_USER}
      - MYSQL_PASSWORD=${DB_PASS}
      - MYSQL_ROOT_PASSWORD=${DB_PASS}
      - TZ=${TZ}
    ports:
      - ${DB_PORT}:3306
    entrypoint: ""
    command: bash -c "chown -R mysql:mysql /var/log/mysql && exec /entrypoint.sh mysqld"

  db-testing:
    image: mysql:latest
    volumes:
      - ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
    tmpfs:
      - /var/lib/mysql
      - /var/log/mysql
    environment:
      - MYSQL_DATABASE=${DB_NAME}
      - MYSQL_USER=${DB_USER}
      - MYSQL_PASSWORD=${DB_PASS}
      - MYSQL_ROOT_PASSWORD=${DB_PASS}
      - TZ=${TZ}

volumes:
  db-store:    

C:\Users\Toshinobu\Desktop\soft_work\docker_work\dockerMysql\.env

DB_NAME=mysql8
DB_USER=ts0818
DB_PASS=mysql
DB_PORT=13306
TZ=Asia/Tokyo

 

「"C:\Users\Toshinobu\Desktop\soft_work\docker_work\dockerMysql\docker\mysql\my.cnf"」は、参考サイト様と同じ内容なので割愛。

Windowsで、「.〇〇」なファイルを作成する方法は、

forest.watch.impress.co.jp

⇧  上記サイト様を参考にさせていただきました。

そんじゃ、仮想マシンを起動で。(仮想マシンが無い場合は、適当に作成しちゃいましょう。)

f:id:ts0818:20191124113236p:plain

f:id:ts0818:20191124113524p:plain

んで、docker-compose.yml のあるディレクトリに移動し、

docker-compose up -d

f:id:ts0818:20191124114413p:plain

f:id:ts0818:20191124114627p:plain

docker ps または、docker-compose ps でコンテナが起動できてるか確認できます。

コンテナにログインしてみたところ、無事、mysqlのログファイルもありました。

f:id:ts0818:20191124115039p:plain

MySQLにもログインできたので、動いてそうですね。

f:id:ts0818:20191124115257p:plain

f:id:ts0818:20191124115453p:plain

⇧  データベースも、「.env」で設定したものが作成されてるので、MySQLの準備は問題なさそうです。 

2019年12月8日(日)追記:↓ ここから

どうも、volumes のマウントの設定が上手くいってないのか、分からんのだけれど、仮想マシンを再起動して、コンテナを再起動しようとすると、エラーになってしまう。

Restarting dockermysql_db-testing_1 ... error                                                                                                                                                                      Restarting dockermysql_db_1         ... error
ERROR: for dockermysql_db-testing_1  Cannot restart container ead32dac20048e865c79f2591169cd3c7dec5d2028aa6cb7bfe691e048fda660: OCI runtime create failed: container_linux.go:346: starting container process caused "process_linux.go:449: container init caused \"rootfs_linux.go:58: mounting \\\"/c/Users/Toshinobu/Desktop/soft_work/dockerMysql/docker/mysql/my.cnf\\\" to rootfs \\\"/mnt/sda1/var/lib/docker/overlay2/392ff2d86df064e10ef28e2d177eb4aaf4cad3bccc56bb80c0c03ccd9a1282cb/merged\\\" at \\\"/mnt/sda1/var/lib/docker/overlay2/392ff2d86df064e10ef28e2d177eb4aaf4cad3bccc56bb80c0c03ccd9a1282cb/merged/etc/mysql/conf.d/my.cnf\\\" caused \\\"not a directory\\\"\"": unknown: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type

ERROR: for dockermysql_db_1  Cannot restart container 41adf69fdabdd70f4240fc9555fd1f533e244b32e9b18db1d48ab89ded820ed1: OCI runtime create failed: container_linux.go:346: starting container process caused "process_linux.go:449: container init caused \"rootfs_linux.go:58: mounting \\\"/c/Users/Toshinobu/Desktop/soft_work/dockerMysql/docker/mysql/my.cnf\\\" to rootfs \\\"/mnt/sda1/var/lib/docker/overlay2/f3a3a232a958bc60bcffc7ce5c92f3c745abfb7966c95e9878bba6e0817cd47f/merged\\\" at \\\"/mnt/sda1/var/lib/docker/overlay2/f3a3a232a958bc60bcffc7ce5c92f3c745abfb7966c95e9878bba6e0817cd47f/merged/etc/mysql/conf.d/my.cnf\\\" caused \\\"not a directory\\\"\"": unknown: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type

なんか、 

free-engineer.xrea.jp

⇧  仮想マシンの共有フォルダの設定じゃないかと仰っている人がおられたので、チェックしてみた。Docker ToolBoxの場合は、Virtual Boxマネージャーで見れるらしい。

f:id:ts0818:20191208144735p:plain

「共有フォルダー」 の設定できるらしい。

f:id:ts0818:20191208144829p:plain

⇧  Docker ToolBoxのマウントできるディレクトリが、「C:\Users」配下のディレクトリに限定されるっていうのは、こういう理由なのかな?

まぁ、今回は、マウントしてるディレクトリが、「C:\Users\Toshinobu\Desktop\soft_work\docker_work\dockerMysql」に属するディレクトリなので、条件に合っている気がするんだが...

原因が分からず、一旦、コンテナ削除して、コンテナ再作成しました、毎回初期化してるデータベースになってしまっているという...意味ねぇ~、解決できず面目ねぇ~。

2019年12月8日(日)追記:↑ ここまで 

 

1.Spring Bootのプロジェクト作成 

Eclipseを起動して、「ファイル(F)」>「新規(N)」>「その他(O)...」で。

f:id:ts0818:20191120213448p:plain

「Spring Boot」>「Spring スターター・プロジェクト」を選択で、「次へ(N) >」

f:id:ts0818:20191120213612p:plain

「名前」を適当に入力し、「次へ(N) >」。(「型:」は、Gradle にしたほうが良かったかも...あとで変換することになるので)

f:id:ts0818:20191120213810p:plain

以下の依存関係を追加します。「完了(F)」で。

f:id:ts0818:20191120214410p:plain

 

2. Doma 2系 を使うための依存関係を追加、設定ファイルを用意

今回はビルドツールにMavenを使ってるんですが、Doma-gen を実行できるのは、

  • Ant
  • Gradle

のどっちか(どっちも最近のEclipseには内蔵されている)を使うしかなく、Antを使う場合は、pom.xml に依存関係を追加すれば良いんだけど、Gradleを使う場合は、build.gradle ってファイルを作って依存関係を追加する感じになると。

Maven、Gradleが全盛の時代にAntは使いたくないけど、MavenとGradleの併用とかってできるのかも分からんし、どうすりゃ良いんじゃ~。

って思ったら、GradleとAntは密接な関係だったとさ...

gradle.monochromeroad.com

⇧  そのわりには、ant.sql とかの情報が載ってないっていうね...Antを切り捨てたいのか、継続してAntを利用していく方針なのか、いまいちハッキリしないよね。

 

とりあえず、Gradleで試してみることに。(地獄の始まりでしたわ...)

なんか、Gradleプロジェクトに変換しないとGradleタスクが実行できないらしい、Gradleメインで生きていったほうが良いのか...これ気づかずにむっちゃ時間浪費したしね(涙)。変換方法については、

howtodoinjava.com

⇧  上記サイト様を参考にさせていただきました。

残念ながら、Eclipse 内蔵のGradleは、コマンド実行できるようになってないので、Gradleを別途インストールするしかないかと。

インストールされてたみたいだけど、バージョンが旧いし...

f:id:ts0818:20191124231705p:plain

chocolatey でインストールしてたんで、アップグレードしました。(chocolateyを使うには、chocolateyをインストールする必要があります。)

f:id:ts0818:20191125203218p:plain

f:id:ts0818:20191125203724p:plain

コマンドプロンプトを新たに開いて、更新できるてことを確認。

f:id:ts0818:20191125203908p:plain

んじゃ、MavenプロジェクトをGradleプロジェクトへ変換。

gradle init --type pom -PjavaVersion=1.11 -Dorg.gradle.java.home=[JAVA_HOME]

f:id:ts0818:20191125220608p:plain

んで、どう頑張っても、出来上がるbuild.gradleのJavaのバージョンが、1.8 になってしまう...Eclipse 2019-09内蔵のAdoptOpenJDKでデフォルトだと、バージョン11なはずなんだけど...

f:id:ts0818:20191125220751p:plain

どうやら、元となる、pom.xmlの親pomのJavaのバージョンが、1.8 になってるからじゃないかという気がする...

f:id:ts0818:20191125221037p:plain

とりあえず、11にしてもエラーが消えず。

f:id:ts0818:20191125221733p:plain

起こられてる内容が、「Gradle プロジェクト構成ファイルの欠落: .settings/org.eclipse.buildship.core.prefs」ってなってるんだけど、

qiita.com

⇧  上記サイト様によりますと、build.gradle に追加してあげれば良さげらしい。

そして、MavenプロジェクトからGradleプロジェクトへ変換したせいか、自分でGradleタスクの対象プロジェクトを教えてあげる必要があるらしい。

netbusinesstips.seesaa.net

⇧  上記サイト様を参考に設定。

f:id:ts0818:20191126204039p:plain

「Gradle Task」で、「Gradle タスク」タブで、「作業ディレクトリー:」に、プロジェクトを指定しました。

f:id:ts0818:20191126204126p:plain

「Gradle 実行」は行われたんだけど、

f:id:ts0818:20191126204505p:plain

「Gradle タスク」は、変化なし...なんでやねん...ちなみに、「Gradle タスク」が出てない場合は、「ウィンドウ(W)」>「ビューの表示(V)」>「その他(O)...」から、「Gradle 」>「Gradle タスク」で。

f:id:ts0818:20191126204628p:plain

 

www.geekfeed.co.jp

⇧  上記サイト様によりますと、Eclipseに認識されとらんらしい。なんで、build.gradleのplugins に、eclipse を追加。(apply plugin って書き方から変わったらしい)

docs.gradle.org

なんで、以下のように追加して、

f:id:ts0818:20191126225453p:plain

コマンドプロンプトとかで、

gradle eclipse

f:id:ts0818:20191126223116p:plain

んで、build.gradleのjavaのバージョンを修正で。

f:id:ts0818:20191126225956p:plain

漸く、Gradleプロジェクトとして整ったようです。

f:id:ts0818:20191126230118p:plain

新規プロジェクトとかの場合は、まず、「Maven」でいくのか「Gradle」でいくのか、または違うビルドツールでいくのか、事前に決めたほうが良いですね、途中で変換はシンドイ(涙)。

 

そんでは、本題に戻ってきたということで。

依存関係を追加、なんですが、通常の、Doma 2系と、

mvnrepository.com

Spring Boot 用の Doma 2系があるのですが、

mvnrepository.com

⇧  どっちも入れといた方が良いようです。この記事でページ下部に「NG集」を載せてますが、自分は、これに気づかず、「doma-spring-boot-starter」を依存関係に追加せず1ヵ月ぐらい試行錯誤してました(涙)。

shinsuke789.hatenablog.jp

⇧ 上記サイト様で知りました。

datasource.gradle、schema.sql、data.sql、application.yml を追加。

f:id:ts0818:20191201205928p:plain


んで、 ファイルを編集。値とかは、ご自分の環境に合わせてください。

spring:
  datasource:
    url: jdbc:mysql://localhost:13307/mysql8
#?allowPublicKeyRetrieval=true&useSSL=false
    username: ts0818
    password: mysql
    driver-class-name: com.mysql.cj.jdbc.Driver
# ↓MySQL に接続できなくなる場合の対策
    tomcat:
      testWhileIdle: true
      timeBetweenEvictionRunsMillis: 60000
      validationQuery: SELECT 1

# ↓明示的に初期化を指定しないと、テーブルの初期化とかしてくれない
    initialization-mode: always
# ↓ Doma の設定
# 有効な値:  [DB2, H2, HSQL, MSSQL, MSSQL2008, MYSQL, ORACLE, POSTGRES, SQLITE, STANDARD, db2, h2, hsql, mssql, mssql2008, mysql, oracle, postgres, sqlite, standard]
doma:
  dialect: mysql
# 有効な値: [GREEDY_CACHE, NO_CACHE, greedy-cache, no-cache]
  sql-file-repository: no-cache

なんか、Spring Bootで yamlファイルのプロパティのフォーマットが、kebab-case になったせいで、gradleファイルの中で、「.」で変数を繋げていくのでエラーなっちゃう部分が出てきたんだけど、

nobeans.hatenablog.com

Groovy公式ドキュメントでもこの「展開ドット演算子の代入用法」について説明がないので、若干不安なところではありますが、かなり初期の頃からあった隠し仕様のようなので突然廃止されることはないと思います。まあ、その辺はひとつ自己責任でお願いいたします。

Gradleで文字エンコーディングを指定する方法 - 豆無日記

⇧ 上記サイト様によりますと、「展開ドット演算子」っていうらしい。 

つまり、

ext.driver = yamlObj.spring.datasource.driver-class-name

みたいな書き方ができない...「driver-class-name」みたいにハイフンが含まれたプロパティが処理できないらしい...なんで、Springは、yamlのフォーマットをkebab-case にしたんですかね、Javaのフィールドとも相性良くないでしょうに...

snakeYaml ライブラリ側で対応するのか、Groovy側で対応するのか、どっちにしろSpringの取った方針は迷惑な話 でしかないんですがね。

致し方ないので、ハイフン含むプロパティについては、値を直で代入という... 

import org.yaml.snakeyaml.*

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.yaml:snakeyaml:1.25") // ← snakeyaml-1.25.jar を「.gradle」ファイルで使えるように、依存関係、クラスパスに追加
    }
}

task datasource {
    Yaml parser = new Yaml() // ←snakeYamlで、Yamlのインスタンスを使うことで、「.yml」ファイルを扱う
    ant.loadfile ( // doma-genがantのタスクを使うので、そこで利用できる設定を準備
        srcfile: 'src/main/resources/application.yml',
        property: 'yaml'
    )
    // ↓ant.loadfile() で設定した内容で、「.yml」の中身を、groovy で扱えるようにする(Javaでもいける)
    def yamlObj = parser.load("${ant.properties.yaml}")
    // ↑データ型を指定しない場合は、load()で。その場合は、class java.util.LinkedHashMap がデフォルトらしい。
    // ↓データ型を指定する場合は、loadAs() を使うらしい。
    // def yamlObj = parser.loadAs("${ant.properties.yaml}", java.util.LinkedHashMap.class) // ←データ型を指定もできる
    // println "yamlObjのクラス:" + yamlObj.getClass() // ←出力結果:class java.util.LinkedHashMap
    // println parser.dump(yamlObj)

    //↓groovyの 「展開ドット演算子」ってので、入れ子になった変数にアクセスできるらしい
    ext.url = yamlObj.spring.datasource.url // ←application.ymlの「spring.datasource.url」プロパティの値を設定
    ext.user = yamlObj.spring.datasource.username // ←application.ymlの「spring.datasource.username」プロパティの値を設定
    ext.password = yamlObj.spring.datasource.password // ←application.ymlの「spring.datasource.password」プロパティの値を設定
    // ↓Springが、yamlのプロパティのkebab-caseなフォーマットにしたせいで、snakeYamlが対応してないので、値を直に設定
    ext.driver = "com.mysql.cj.jdbc.Driver" // ←application.ymlの「spring.datasource.driver-class-name」プロパティの値を設定
}

 

/*
 * This file was generated by the Gradle 'init' task.
 */

plugins {
    id 'java'
    id 'maven-publish'
    id 'eclipse'
}

repositories {
    mavenLocal()
    maven {
        url "http://repo.maven.apache.org/maven2"
    }
    maven { // Doma-Gen-2 の依存関係の取得先リポジトリ
        url "https://oss.sonatype.org/content/repositories/snapshots/"
    }
}

configurations { // Doma-Gen-2 のタスクを登録(build.gradleファイル内で使えるようにかと)
    domaGenRuntime
}

dependencies {
    def springBootVersion = "2.2.1.RELEASE"
    def mysqlVersion = "8.0.18"
    implementation (
        "org.springframework.boot:spring-boot-starter-data-jdbc:${springBootVersion}",
        "org.springframework.boot:spring-boot-starter-web:${springBootVersion}",
    )
    testImplementation "org.springframework.boot:spring-boot-starter-test:${springBootVersion}"
    implementation "mysql:mysql-connector-java:${mysqlVersion}"

    // Generating Your Own Metadata by Using the Annotation Processor
    // ⇩Doma2で、「Doma uses Pluggable Annotation Processing API at compile time.」ってあるので、不要かな?
    // annotationProcessor "org.springframework.boot:spring-boot-configuration-processor:${springBootVersion}"

    // Doma2 に必要な依存関係を追加
    def domaVersion = "2.25.1"
    def domaSpringVersion = "1.2.1"
    annotationProcessor "org.seasar.doma:doma:${domaVersion}"
    implementation "org.seasar.doma:doma:${domaVersion}"
    // Doma2 をSpring Bootで使う際の依存関係を追加
    implementation "org.seasar.doma.boot:doma-spring-boot-starter:${domaSpringVersion}"
    // Doma-Gen-2 に必要な依存関係を追加
    domaGenRuntime "org.seasar.doma:doma-gen:${domaVersion}"
    domaGenRuntime "mysql:mysql-connector-java:${mysqlVersion}"

}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
description = 'HelloDoma'
sourceCompatibility = '11' // ← Eclipseの実行JDKのバージョンに合わせました

//compileJava.dependsOn(processResources)

publishing {
    publications {
        maven(MavenPublication) {
            from(components.java)
        }
    }
}

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

tasks.eclipse.doFirst {
    File prefs = file(".settings/org.eclipse.buildship.core.prefs")
    if(!prefs.exists()){
        prefs.append('''
            connection.project.dir=
            eclipse.preferences.version=1
            '''.stripIndent())
    }
}

apply from: 'gradle/datasource.gradle'
// JavaクラスとSQLファイルの出力先ディレクトリを同じにする
processResources.destinationDir = compileJava.destinationDir
// Doma-Genでコンパイルより前にSQLファイルを出力先ディレクトリにコピーするために依存関係を逆転する
compileJava.dependsOn processResources

task gen { // doma-genって定義したRuntimeタスクに、genって実タスクを作成
    group = 'doma-gen'
    doLast {
        // ↓パッケージは各々の環境に合わせてください
        def destBaseDir = 'src/main/java/com/example/demo'
        def basePackage = 'com.example.demo.domain'
        def resourceDir = 'src/main/resources'

        ant.taskdef(resource: 'domagentask.properties',
            classpath: configurations.domaGenRuntime.asPath)
         // Doma-Gen のタスク前に、テーブルが無かったら作成
         // ↓ Gradleの公式ドキュメントで、ant.sql の情報が無い...もはや過去の遺産?
        ant.sql(classpath: configurations.domaGenRuntime.asPath,
            driver: "${datasource.driver}",
            url: "${datasource.url}",
            userid: "${datasource.user}",
            password: "${datasource.password}") {
                fileset(dir: "${resourceDir}") {
                  include(name: "schema.sql")
                  //include(name: "data.sql") // ←ファイルの実行順とか制御できないっぽい...
                }
            }
        ant.sql(classpath: configurations.domaGenRuntime.asPath,
            driver: "${datasource.driver}",
            url: "${datasource.url}",
            userid: "${datasource.user}",
            password: "${datasource.password}") {
                fileset(dir: "${resourceDir}") {
                  //include(name: "schema.sql")
                  include(name: "data.sql")
                }
            }
       ant.gen(url: "${datasource.url}", user: "${datasource.user}", password: "${datasource.password}", templatePrimaryDir: "${resourceDir}/config/doma/templates", tableNamePattern: 'mst_role') {
            entityConfig(packageName: "${basePackage}.entity", useAccessor: false, useListener: false)
            daoConfig(packageName: "${basePackage}.dao", overwrite: true)
            sqlConfig()
        }
    }
}

 

CREATE DATABASE IF NOT EXISTS mysql8;
USE mysql8;
CREATE TABLE IF NOT EXISTS mst_role
(
    role_id varchar(20) NOT NULL COMMENT '権限ID',
    role_name varchar(100) COMMENT '権限名',
    version int COMMENT 'バージョン',
    insert_user varchar(20) COMMENT '登録ユーザ',
    insert_date datetime COMMENT '登録日時',
    update_user varchar(20) COMMENT '更新ユーザ',
    update_date datetime COMMENT '更新日時',
    PRIMARY KEY (role_id)
) COMMENT = '権限マスタテーブル';

 

INSERT INTO mst_role (role_id, role_name) values (1, "エーリヒ・ガンマ") on duplicate key update role_id='1', role_name='エーリヒ・ガンマ';
INSERT INTO mst_role (role_id, role_name) values (2, "リチャード・ヘルム") on duplicate key update role_id='2', role_name='リチャード・ヘルム';
INSERT INTO mst_role (role_id, role_name) values (3, "ラルフ・ジョンソン") on duplicate key update role_id='3', role_name='ラルフ・ジョンソン';
INSERT INTO mst_role (role_id, role_name) values (4, "ジョン・ブリシディース") on duplicate key update role_id='4', role_name='ジョン・ブリシディース';

 

 

3. Doma Tool(Eclipseプラグイン)追加 ← 必須ではないらしい 

一応、入れておきます。 

f:id:ts0818:20191120220329p:plain

「作業対象(W):」に「http://dl.bintray.com/domaframework/eclipse/」って入力して、Enter。「Doma」って表示されたら、チェックして、「次へ(N) > 」。

f:id:ts0818:20191120220750p:plain

「次へ(N) >」。

f:id:ts0818:20191120221001p:plain

「使用条件の条項に同意します(A)」にチェックし、「完了(F)」で。

f:id:ts0818:20191120221041p:plain

「インストール(I)」で。

f:id:ts0818:20191120221151p:plain

「今すぐ再起動(R)」で。

f:id:ts0818:20191120221256p:plain

 

 

4.プロジェクトの注釈処理(Annotation processing)の設定

なんか、Eclipseを使う場合は、JARをダウンロードしておく必要があるそうな。

doma.readthedocs.io

You can pull the jar file of the Doma framework from the Maven central repository. The group id and artifact id are as follows:

GroupId: org.seasar.doma
ArtifactId: doma

See also: https://search.maven.org/artifact/org.seasar.doma/doma/

Building an application — Doma documentation

 

というわけで、

https://search.maven.org/artifact/org.seasar.doma/doma/

にアクセスして、今回は、「2.25.1」で。build.gradle に追加した依存関係のバージョンと合わせる必要があるようです。

f:id:ts0818:20191127222303p:plain

んで、jar をダウンロード。

f:id:ts0818:20191127221812p:plain

そしたらば、プロジェクト直下とかに、「lib」ディレクトリを作って配置。

f:id:ts0818:20191127222813p:plain

配置し終えたら、プロジェクトを選択し右クリックで、「プロパティー(R)」で、

f:id:ts0818:20191124155800p:plain

Java コンパイラー」>「注釈処理」で、「プロジェクト固有の設定を可能にする(O)」にチェック。 

f:id:ts0818:20191124155929p:plain

Java コンパイラー」>「注釈処理」>「ファクトリー・パス」で、「JARの追加(J)...」で、

f:id:ts0818:20191127221008p:plain

「lib」ディレクトリに配置していた、doma-2.25.1.jar を選択し、「OK」で。

f:id:ts0818:20191127223049p:plain

「適用して閉じる」で。

f:id:ts0818:20191127223138p:plain

「はい(Y)」で。

f:id:ts0818:20191127223207p:plain

 

カスタムテンプレート

qiita.com

自動生成されたDaoに@ConfigAutowireableを自動で付与させたいときは作りましょう。自動じゃなくてもいいのであれば必要ありません。
1. doma-genからdao.ftllib.ftlを持ってきます。templatePrimaryDirで指定したディレクトリかデフォルトのディレクトリに配置します。
lib.ftlは空ファイルなので、普通に新規作成してもいいです。
2. dao.ftlで使用するアノテーション名とパッケージのハッシュをlib.ftlに定義します。

springbootでdoma-genを使う - Qiita

⇧  というわけで、カスタムテンプレート配置用のディレクトリ作成で。ファイルも配置で。

f:id:ts0818:20191208173253p:plain



ファイルの中身。 

/HelloDoma/src/main/resources/config/doma/templates/lib.ftl

<#assign author="k_kawasaki">
<#assign annotationHash={ "ConfigAutowireable": "org.seasar.doma.boot.ConfigAutowireable" }>

/HelloDoma/src/main/resources/config/doma/templates/dao.ftl

<#-- このテンプレートに対応するデータモデルのクラスは org.seasar.doma.extension.gen.DaoDesc です -->
<#import "/lib.ftl" as lib>
<#if lib.copyright??>
${lib.copyright}
</#if>
<#if packageName??>
package ${packageName};
</#if>

<#list importNames as importName>
import ${importName};
</#list>
<#-- ↓追加 ここから -->
<#list lib.annotationHash?values as annotationImportName>
import ${annotationImportName};
</#list>
<#-- ↑追加 ここまで -->
/**
<#if lib.author??>
 * @author ${lib.author}
</#if>
 */

@Dao<#if configClassSimpleName??>(config = ${configClassSimpleName}.class)</#if>
<#-- ↓追加 ここから -->
<#list lib.annotationHash?keys as annotation>
@${annotation}
</#list>
<#-- ↑追加 ここまで -->
public interface ${simpleName} {

<#if entityDesc.idEntityPropertyDescs?size gt 0>
    /**
<#list entityDesc.idEntityPropertyDescs as property>
     * @param ${property.name}
</#list>
     * @return the <#if entityDesc.entityPrefix??>${entityDesc.entityPrefix}</#if>${entityDesc.simpleName}<#if entityDesc.entitySuffix??>${entityDesc.entitySuffix}</#if> entity
     */
    @Select
    <#if entityDesc.entityPrefix??>${entityDesc.entityPrefix}</#if>${entityDesc.simpleName}<#if entityDesc.entitySuffix??>${entityDesc.entitySuffix}</#if> selectById(<#list entityDesc.idEntityPropertyDescs as property>${property.propertyClassSimpleName} ${property.name}<#if property_has_next>, </#if></#list>);

</#if>
<#if entityDesc.idEntityPropertyDescs?size gt 0 && entityDesc.versionEntityPropertyDesc??>
    /**
<#list entityDesc.idEntityPropertyDescs as property>
     * @param ${property.name}
</#list>
     * @param ${entityDesc.versionEntityPropertyDesc.name}
     * @return the <#if entityDesc.entityPrefix??>${entityDesc.entityPrefix}</#if>${entityDesc.simpleName}<#if entityDesc.entitySuffix??>${entityDesc.entitySuffix}</#if> entity
     */
    @Select(ensureResult = true)
    <#if entityDesc.entityPrefix??>${entityDesc.entityPrefix}</#if>${entityDesc.simpleName}<#if entityDesc.entitySuffix??>${entityDesc.entitySuffix}</#if> selectByIdAndVersion(<#list entityDesc.idEntityPropertyDescs as property>${property.propertyClassSimpleName} ${property.name}, </#list>${entityDesc.versionEntityPropertyDesc.propertyClassSimpleName} ${entityDesc.versionEntityPropertyDesc.name});

</#if>
    /**
     * @param entity
     * @return affected rows
     */
    @Insert
    int insert(<#if entityDesc.entityPrefix??>${entityDesc.entityPrefix}</#if>${entityDesc.simpleName}<#if entityDesc.entitySuffix??>${entityDesc.entitySuffix}</#if> entity);

    /**
     * @param entity
     * @return affected rows
     */
    @Update
    int update(<#if entityDesc.entityPrefix??>${entityDesc.entityPrefix}</#if>${entityDesc.simpleName}<#if entityDesc.entitySuffix??>${entityDesc.entitySuffix}</#if> entity);

    /**
     * @param entity
     * @return affected rows
     */
    @Delete
    int delete(<#if entityDesc.entityPrefix??>${entityDesc.entityPrefix}</#if>${entityDesc.simpleName}<#if entityDesc.entitySuffix??>${entityDesc.entitySuffix}</#if> entity);
} 

 

 

5.Doma-Gen でファイルの自動生成 

んで、「Gradle タスク」でDoma-genのタスクを実行が正常に完了したんですが、

f:id:ts0818:20191208173928p:plain

f:id:ts0818:20191208174018p:plain

ファイルは自動生成されるも、

f:id:ts0818:20191208174406p:plain

エラーが出ている...

f:id:ts0818:20191208174544p:plain

[DOMA4011] The annotation processing for the class "com.example.demo.domain.entity.MstRole" is failed. The cause is as follows: javax.annotation.processing.FilerException: Source file already created: /HelloDoma/.apt_generated/com/example/demo/domain/entity/_MstRole.java    

⇧  どうやら、Eclipseの「ウィンドウ(W)」>「設定(P)」から「Java コンパイラー」>「注釈処理」で、「プロジェクト固有の設定を可能にする(O)」にチェックして、

f:id:ts0818:20191124155929p:plain

⇧ 「生成されるソース・ディレクトリー(G):」「生成されるテスト・ソース・ディレクトリー(G):」の指定がされてたようで、「build.gradle」のDoma-gen のタスクでも別のディレクトリに生成されるディレクトリーを指定していたのが問題だったような気がするんですが、ハッキリしたことが分からんです...。

っていうか、Doma-gen なくてもDoma2だけで自動生成してくれるってことなのかな?分からん~、分からんことが多過ぎる...。

一旦、「/HelloDoma/.apt_generated/com/example/demo/domain/entity」を削除して、

f:id:ts0818:20191208180416p:plain

プロジェクトをクリーンしたら、

f:id:ts0818:20191208180559p:plain

エラーは消えました。

f:id:ts0818:20191208180727p:plain

⇧  クリーンすると、「/HelloDoma/.apt_generated/com/example/demo/domain/entity/_MstRole.java」がまた作成されるけども...

とりあえず、ファイルはできたということで、ですが、「/HelloDoma/src/main/java/com/example/demo/domain/dao/MstRoleDao.java」は中身がまったくないという...。

このへんは時間のあるときに調査していきたいですかね。

う~ん、いつも思うんだけど、フレームワークとかライブラリって、開発効率を上げるためのはずの存在だと思うんだけど、逆に開発効率を下げるものになってるんではないかって気がしてしまうのは、やっぱり経験が足りないからですかね。

まぁ、何て言うか環境構築系で時間が取られるせいで、肝心のプログラミングの学習がまったくできないし、モチベーションダダ下がりですかね...。

今回も、1ヵ月ちかく浪費したにもかかわらず、モヤモヤしか残らなかった...徒労感が半端ない...

今回はこのへんで。

 

 

NG集

依存関係 で、「doma-spring-boot-starter」を、build.gradle に入れずに進めてて泥沼にハマった軌跡を一応載せておきます。

Spring BootでDoma2を使う場合は、必ず、

mvnrepository.com

mvnrepository.com

⇧  のどちらも入れるようにしましょう。

これ気づかずに、1ヶ月ぐらい無駄にしました(涙)

なんか、Doma2のドキュメントにもうちょっと分かりやすく情報を載せてほしいもんだけどって気がしてしまった。

 

⇩  こっから、駄目駄目なNG集です。

ちなみに、MavenプロジェクトからGradleプロジェクトへの変換が済んでスタートラインに立ったところからです。

qiita.com

⇧  上記サイト様を参考に、確認してみたところ、親Pomで設定されてるライブラリもあり、Spring Boot 2.2.1 では「freemarker」は既に、親Pomで追加されてるようでした。

ただ、Gradleを使う場合は、「doma-gen-2」に「freemarker」が導入されとるらしいんで、特に「freemarker」の依存関係を追加は不要っぽい。

datasource.gradle、schema.sql、data.sql、application.yml を追加。

f:id:ts0818:20191201205928p:plain


んで、 ファイルを編集。値とかは、ご自分の環境に合わせてください。

spring:
  datasource:
    url: jdbc:mysql://localhost:13307/mysql8
#?allowPublicKeyRetrieval=true&useSSL=false
    username: ts0818
    password: mysql
    driver-class-name: com.mysql.cj.jdbc.Driver
# ↓MySQL に接続できなくなる場合の対策
    tomcat:
      testWhileIdle: true
      timeBetweenEvictionRunsMillis: 60000
      validationQuery: SELECT 1

# ↓明示的に初期化を指定しないと、テーブルの初期化とかしてくれない
    initialization-mode: always
# ↓ Doma の設定
# 有効な値:  [DB2, H2, HSQL, MSSQL, MSSQL2008, MYSQL, ORACLE, POSTGRES, SQLITE, STANDARD, db2, h2, hsql, mssql, mssql2008, mysql, oracle, postgres, sqlite, standard]
doma:
  dialect: mysql
# 有効な値: [GREEDY_CACHE, NO_CACHE, greedy-cache, no-cache]
  sql-file-repository: no-cache

application.yml については、domaって部分で、警告が出るんですが、

f:id:ts0818:20191127214859p:plain

公式には、特に対応方法の記載が無いんですよね、っていうか、そもそも、domaっていうプロパティを、application.properties、ないし、application.yml のどっちについても使うっていう記載が無い...。

ちなみに、Spring Bootの場合は、

blog.tagbangers.co.jp

よく使われるのはapplication.propertiesやapplication.ymlファイルですよね。

SpringApplicationは、これらのプロパティファイルを以下の場所から探し出します(優先順位高い順):

1./configという名前のサブディレクトリ(file:./config/)

2.カレントディレクトリ(file:./)

3.クラスパスの/configパッケージ(classpath:/config/)

4.クラスパスルート(classpath:/)

Spring Bootがプロパティファイルを読み込む方法に一同驚愕!! | Tagbangers Blog

⇧ ってことらしく、

...あれ?src/main/resources/じゃないの?

もしそう思ったら、あなたは一人じゃありません。僕も思いました。

実はsrc/main/resourcesディレクトリはMavenのデフォルトのクラスパスの一つです。

そのためsrc/main/resourcesディレクトリは4.クラスパスルートに含まれ、その配下にあるapplication.propertiesファイルはSpring Bootが自動で読み込んでくれます。

Spring Bootがプロパティファイルを読み込む方法に一同驚愕!! | Tagbangers Blog

⇧ ってことらしく、 

もし上記4つ以外の場所からプロパティを読み込ませたい場合は、@Configurationクラスに@PropertySourceアノテーションを付与し、任意のクラスパスを指定してあげるとそこにもプロパティを探しに行ってくれます。

 @Configuration
 @PropertySource("classpath:/com/myco/app.properties")
 public class AppConfig {

Spring Bootがプロパティファイルを読み込む方法に一同驚愕!! | Tagbangers Blog

⇧ ってことらしい。つまり、プロパティ系のファイルを読み込むパスはデフォルトで決められているから、自分の好きなパスを指定したい時は、自分で設定してあげる必要があると。

はい、脱線しました。

どうも、Pivotalの槙さんがGitHubにサンプルを上げてくれてるんですが、application.propeties だと問題なさそうな感じですかね。

github.com

⇧  ただ、使ってるIDEIntelliJ IDEAっぽい...

一応、自分はEclipseで、application.propeties だと警告は出なかったので、

  • application.yamlの問題
  • IDEの問題

 のどっちかの問題だとは思うんだけど、謎過ぎる...って思ったら、

なんか、分からんけど、

github.com

⇧  どうも、STS(Spring Tool Suite)のバグなんじゃないかって..なんか、3.9.5.RELEASEで、issueがcloseされとらんのだが...

ちなみに、Eclipse 2019-09は、STSが内蔵していて、バージョンは4.4.0なんだけど、もしかしてずっとこのissue引きずってんのかな?

一つだけハッキリしたのは、理由は分からんのだけれども、ymlについて「annotation processor」が上手く機能してない?らしいけど、後半の「4.プロジェクトの注釈処理の設定」をすれば解決するかもという期待を込め、一旦、先に進みます。

 

なんか、Spring Bootでのyaml のプロパティのフォーマットが、kebab-case になったせいで、gradleファイルの中で、「.」で変数を繋げていくのでエラーなっちゃう部分が出てきたんだけど、

nobeans.hatenablog.com

Groovy公式ドキュメントでもこの「展開ドット演算子の代入用法」について説明がないので、若干不安なところではありますが、かなり初期の頃からあった隠し仕様のようなので突然廃止されることはないと思います。まあ、その辺はひとつ自己責任でお願いいたします。

Gradleで文字エンコーディングを指定する方法 - 豆無日記

⇧ 上記サイト様によりますと、「展開ドット演算子」っていうらしい。 

つまり、

ext.driver = yamlObj.spring.datasource.driver-class-name

みたいな書き方ができない...「driver-class-name」みたいにハイフンが含まれたプロパティが処理できないらしい...なんで、Springは、yamlのフォーマットをkebab-case にしたんですかね、Javaのフィールドとも相性良くないでしょうに...

snakeYaml ライブラリ側で対応するのか、Groovy側で対応するのか、どっちにしろSpringの取った方針は迷惑な話 でしかないんですがね。

致し方ないので、ハイフン含むプロパティについては、値を直で代入という... 

import org.yaml.snakeyaml.*

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.yaml:snakeyaml:1.25")
    }
}

task datasource {
    Yaml parser = new Yaml()
    ant.loadfile (
        srcfile: 'src/main/resources/application.yml',
        property: 'yaml'
    )
    def yamlObj = parser.load("${ant.properties.yaml}")
    ext.url = yamlObj.spring.datasource.url
    ext.user = yamlObj.spring.datasource.username
    ext.password = yamlObj.spring.datasource.password
    ext.driver = "com.mysql.cj.jdbc.Driver"
}

 

/*
 * This file was generated by the Gradle 'init' task.
 */

plugins {
    id 'java'
    id 'maven-publish'
    id 'eclipse'
}

repositories {
    mavenLocal()
    maven {
        url "http://repo.maven.apache.org/maven2"
    }
    maven {
        url 'https://oss.sonatype.org/content/repositories/snapshots/'
    }
}

configurations {
    domaGenRuntime
}

dependencies {
    def springBootVersion = "2.2.1.RELEASE"
    def mysqlVersion = "8.0.18"
    implementation (
        "org.springframework.boot:spring-boot-starter-data-jdbc:${springBootVersion}",
        "org.springframework.boot:spring-boot-starter-web:${springBootVersion}"
    )
    testImplementation "org.springframework.boot:spring-boot-starter-test:${springBootVersion}"
    implementation "mysql:mysql-connector-java:${mysqlVersion}"

    // Doma2 に必要な依存関係を追加
    def domaVersion = "2.25.1"
    annotationProcessor "org.seasar.doma:doma:${domaVersion}"
    implementation "org.seasar.doma:doma:${domaVersion}"
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
description = 'HelloDoma'
sourceCompatibility = '11'

publishing {
    publications {
        maven(MavenPublication) {
            from(components.java)
        }
    }
}

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

tasks.eclipse.doFirst {
    File prefs = file(".settings/org.eclipse.buildship.core.prefs")
    if(!prefs.exists()){
        prefs.append('''
            connection.project.dir=
            eclipse.preferences.version=1
            '''.stripIndent())
    }
}

apply from: 'gradle/datasource.gradle'
// JavaクラスとSQLファイルの出力先ディレクトリを同じにする
processResources.destinationDir = compileJava.destinationDir
// Doma-Genでコンパイルより前にSQLファイルを出力先ディレクトリにコピーするために依存関係を逆転する
compileJava.dependsOn processResources

task domaGen {
    group = 'doma-gen'
    doLast {
        def destBaseDir = 'src/main/java/com/example/demo'
        def basePackage = 'com.example.demo.domain'
        def resourceDir = 'src/main/resources'

        ant.taskdef(resource: 'domagentask.properties',
            classpath: configurations.domaGenRuntime.asPath)
         // Doma-Gen のタスク前に、テーブルが無かったら作成
         // ↓ Gradleの公式ドキュメントで、ant.sql の情報が無い...もはや過去の遺産?
        ant.sql(classpath: configurations.domaGenRuntime.asPath,
            driver: "${datasource.driver}",
            url: "${datasource.url}",
            userid: "${datasource.user}",
            password: "${datasource.password}") {
                fileset(dir: "${resourceDir}") {
                  include(name: "schema.sql")
                  //include(name: "data.sql") // ←ファイルの実行順とか制御できないっぽいので...
                }
            }
        // ↓ DRY(Don't Repeat Yourself)に反しまくまってますな...
        ant.sql(classpath: configurations.domaGenRuntime.asPath,
            driver: "${datasource.driver}",
            url: "${datasource.url}",
            userid: "${datasource.user}",
            password: "${datasource.password}") {
                fileset(dir: "${resourceDir}") {
                  //include(name: "schema.sql")
                  include(name: "data.sql")
                }
            }
       ant.gen(url: "${datasource.url}", user: "${datasource.user}", password: "${datasource.password}", templatePrimaryDir: "${resourceDir}/config/doma/templates", tableNamePattern: 'mst_role') {
            entityConfig(packageName: "${basePackage}.entity", useAccessor: false, useListener: false)
            daoConfig(packageName: "${basePackage}.dao", overwrite: true)
            sqlConfig()
        }
    }
}

 

CREATE DATABASE IF NOT EXISTS mysql8;
USE mysql8;
CREATE TABLE IF NOT EXISTS mst_role
(
    role_id varchar(20) NOT NULL COMMENT '権限ID',
    role_name varchar(100) COMMENT '権限名',
    version int COMMENT 'バージョン',
    insert_user varchar(20) COMMENT '登録ユーザ',
    insert_date datetime COMMENT '登録日時',
    update_user varchar(20) COMMENT '更新ユーザ',
    update_date datetime COMMENT '更新日時',
    PRIMARY KEY (role_id)
) COMMENT = '権限マスタテーブル';

 

INSERT INTO mst_role (role_id, role_name) values (1, "エーリヒ・ガンマ") on duplicate key update role_id='1', role_name='エーリヒ・ガンマ';
INSERT INTO mst_role (role_id, role_name) values (2, "リチャード・ヘルム") on duplicate key update role_id='2', role_name='リチャード・ヘルム';
INSERT INTO mst_role (role_id, role_name) values (3, "ラルフ・ジョンソン") on duplicate key update role_id='3', role_name='ラルフ・ジョンソン';
INSERT INTO mst_role (role_id, role_name) values (4, "ジョン・ブリシディース") on duplicate key update role_id='4', role_name='ジョン・ブリシディース';

 

 

3. Doma Tool(Eclipseプラグイン)追加 ← 必須ではないらしい 

一応、入れておきます。 

f:id:ts0818:20191120220329p:plain

「作業対象(W):」に「http://dl.bintray.com/domaframework/eclipse/」って入力して、Enter。「Doma」って表示されたら、チェックして、「次へ(N) > 」。

f:id:ts0818:20191120220750p:plain

「次へ(N) >」。

f:id:ts0818:20191120221001p:plain

「使用条件の条項に同意します(A)」にチェックし、「完了(F)」で。

f:id:ts0818:20191120221041p:plain

「インストール(I)」で。

f:id:ts0818:20191120221151p:plain

「今すぐ再起動(R)」で。

f:id:ts0818:20191120221256p:plain

 

 

4.プロジェクトの注釈処理(Annotation processing)の設定

なんか、Eclipseを使う場合は、JARをダウンロードしておく必要があるそうな。

doma.readthedocs.io

You can pull the jar file of the Doma framework from the Maven central repository. The group id and artifact id are as follows:

GroupId: org.seasar.doma
ArtifactId: doma

See also: https://search.maven.org/artifact/org.seasar.doma/doma/

Building an application — Doma documentation

 

というわけで、

https://search.maven.org/artifact/org.seasar.doma/doma/

にアクセスして、今回は、「2.25.1」で。build.gradle に追加した依存関係のバージョンと合わせる必要があるようです。

f:id:ts0818:20191127222303p:plain

んで、jar をダウンロード。

f:id:ts0818:20191127221812p:plain

そしたらば、プロジェクト直下とかに、「lib」ディレクトリを作って配置。

f:id:ts0818:20191127222813p:plain

配置し終えたら、プロジェクトを選択し右クリックで、「プロパティー(R)」で、

f:id:ts0818:20191124155800p:plain

Java コンパイラー」>「注釈処理」で、「プロジェクト固有の設定を可能にする(O)」にチェック。 

f:id:ts0818:20191124155929p:plain

Java コンパイラー」>「注釈処理」>「ファクトリー・パス」で、「JARの追加(J)...」で、

f:id:ts0818:20191127221008p:plain

「lib」ディレクトリに配置していた、doma-2.25.1.jar を選択し、「OK」で。

f:id:ts0818:20191127223049p:plain

「適用して閉じる」で。

f:id:ts0818:20191127223138p:plain

「はい(Y)」で。

f:id:ts0818:20191127223207p:plain

んで、application.ymlの警告が、

f:id:ts0818:20191201214959p:plain
消えないやんけ~!

 

www.bunkei-programmer.net

⇧  上記サイト様によりますと、application.yml には限らないと思うけど、yml の設定値で使いたいプロパティ値に関するクラスを作ってあげる必要があると。

 

プラスアルファで、

examples.javacodegeeks.com

If you scroll through the properties, you will see their data type, description, and in some cases their default value.  The properties and their attributes are contained in the configuration metadata for Spring Boot.  In the Project Explorer expand Maven Dependencies > spring-boot-2.1.3.RELEASE.jar > META-INF.  In the META-INF folder, you will see two JSON files.

  • spring-configuration-metadata.json
  • additional-spring-configuration-metadata.json

Spring Boot Application Properties Example | Examples Java Code Geeks - 2019

⇧  プロパティは、上記のファイルで定義されとるんだと。

f:id:ts0818:20191203213719p:plain

⇧  確かに、spring-bootのjarファイルん中身のMETA-INFに、

  • spring-configuration-metadata.json
  • additional-spring-configuration-metadata.json

っておりますね。

んで、独自のカスタム・プロパティを、「.properties」「.yml」みたいなファイルで警告なしで使いたい場合は、

Creating Metadata for a Custom Property

This will create a file additional-spring-configuration-metadata.json under the META-INF folder in your project.  Open the file with the JSON editor and edit the description. 

Spring Boot Application Properties Example | Examples Java Code Geeks - 2019

⇧ 「additional-spring-configuration-metadata.json」ってファイルにプロパティの設定をして、プロジェクトのMETA-INFディレクトリに配置しないといかんらしい...

で す が、

github.com

⇧ サンプルでは、特に、META-INFに「additional-spring-configuration-metadata.json」を配置してなさそうなんすけどね...

ただ、

doma-spring-boot/doma-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

ってのがおると。

spring.factories って?

qiita.com

@EnableAutoConfigurationには、別のコンフィギュレーションクラスをインポートすることを示すアノテーション(@Import)が指定されており、インポートされるコンフィギュレーションクラスはorg.springframework.boot.autoconfigure.EnableAutoConfigurationImportSelectorクラスの実装によって決まります。
EnableAutoConfigurationImportSelectorの実装では、クラスパス上の/META-INF/spring.factoriesよりインポート対象のコンフィギュレーションクラスを取得するようになっており、

Spring BootのAutoConfigureの仕組みを理解する - Qiita

⇧  ってありますよと。

そもそも、Spring Framework では、 

This chapter covers the Spring Framework implementation of the Inversion of Control (IoC) principle. IoC is also known as dependency injection (DI).

It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method.

The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes or a mechanism such as the Service Locator pattern.

https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html

⇧  IoC(Inversion of Control:制御の反転)って仕組み(仕様のことかな?)があり、DI(Dependency Injection:依存性注入)と呼ばれることの方が多いんだけど、この仕組みを実装したものが、Spring IoC Container(DIコンテナと呼ばれるらしい) っていうコンテナっちゅうものらしく、

このSpring IoC Container っていうやつは、

f:id:ts0818:20191203221228p:plain

In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application. Beans, and the dependencies among them, are reflected in the configuration metadata used by a container.

https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html

⇧  ってあるように、アプリケーションで使用するオブジェクト(具体的に何を指すか明示されとらんし...)を、Bean としてブールしといてくれてるらしいですと。

Spring が起動に時間かかるってのも、このへんが原因なんじゃって気がするんだけど、どうなんだろう?教えて、偉い人。 

BeanをDIコンテナに登録するには、Beanの定義となるConfigulation Metadata ってのが必要らしいんだけど、方法は、3つほど存在するらしい。

For information about using other forms of metadata with the Spring container, see:

https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html

⇧ 3つの方法って言うのが、

ってことかと。んで、Spring Boot を使うと、このへんも良しなにやってくれるらしく、アプリケーションの開発者が意識する必要がない、らしいんだけど、Spring Bootで用意されてないものを使おうとすると、このへんを意識しないといけないらしい...

だから、結局のところ、仕組みを知っとかないといけないってことで、学習コストが嵩むな~。  

で、改めて、 

AutoConfigureは、Springが提供している各種プロジェクト(例えばSpring Framework(Spring MVC), Spring Security, Spring Data, Spring Cloud, Spring Integration, Spring Batchなど)や3rdパーティ製のOSSライブラリなどをSpring上で利用する際に必要となるBean定義を、Spring Bootが自動で行ってくれる仕組みです。「自動で」という表現をしましたが、もうすこし正確に表現すると・・・Spring Bootが予め用意しているAutoConfigure用のBean定義ファイル(コンフィギュレーションクラス)がインポートされ、インポートされたコンフィギュレーションクラスの定義に従ってBean定義が行われます。

Spring BootのAutoConfigureの仕組みを理解する - Qiita

⇧  ってな感じで、このAutoConfigure用のクラス(Bean定義ファイル、つまりコンフィギュレーションクラス)ってのが、Spring Bootで用意されてるから、DIコンテナへのBean登録とかを意識する必要がなくなるらしいですと。

なんで、「[プロジェクトと外部の依存関係]/[spring-boot-x.x.x.RELEASE.jar ]/META-INF/spring.factories」っていうのは、あらかじめSpring Bootで用意されてるコンフィギュレーションクラスのリストみたいなもんだと。

「spring.factories」は、Beanの定義となるConfigulation Metadata の一覧ってことにもなるんだとは思うんだけど、

なお、「コンフィギュレーションクラスをインポートする仕組み」や「特定の条件をみたす時だけBean定義を適用する仕組み」自体は、Spring Bootのオリジナル機能ではなく、Spring Frameworkから提供されている機能になります。

Spring BootのAutoConfigureの仕組みを理解する - Qiita

⇧  DI(Dependency Injection:依存性注入)が適用できるんであると。

なので、「[プロジェクト]/src/main/resources/META-INF/spring.factories 」を用意して、Spring Bootが用意している「[プロジェクトと外部の依存関係]/[spring-boot-x.x.x.RELEASE.jar ]/META-INF/spring.factories」には準備されてない独自のコンフィギュレーションクラスを追加すれば、AutoConfigureでインポート対象にできるってことかと。

 

で、結局、application.yml で、カスタム・プロパティを警告なしにするにはどうすれば良いのよ?

kazuhira-r.hatenablog.com

@ConfigurationPropertiesを使ったサンプルは後で出てきますが、こちらを使用するとPluggable Annotation Processing API
アノテーションからメタデータを生成し(META-INF/spring-configuration-metadata.jsonというファイルができます)、
IDEでコード補完を行うことができるようになります。

Spring BootのAutoConfigurationを自分で作ってみる - CLOVER🍀

⇧  上記サイト様によりますと、@ConfigurationProperties を付与したプロパティ用のクラスを事前に作っておかないと、Pluggable Annotation Processing API によってspring-configuration-metadata.json が作成されないんだと。

んで、

qiita.com

Pluggable Annotation Processing API とは

Java 1.6 から追加された、コンパイル時にアノテーションを処理するための仕組み。Lombok とか JPA の Metamodel とかで利用されている。
つまり、コンパイル時にアノテーションを読み込んでソースコードを自動生成したりできる。

Java 1.5 でアノテーションが追加されたときに、同様にアノテーションコンパイル時に処理する仕組みとして Annotation Processing Tool (apt) が追加されていたが、これとは別物らしい。

Pluggable Annotation Processing API 使い方メモ - Qiita

⇧  んで、Java 1.6 から追加されたんだ~と。

Doma2系のドキュメントにも、

doma.readthedocs.io

Doma uses Pluggable Annotation Processing API at compile time.

In this document, we describe the options for the annotation processors in Doma and show you how to pass them to build tools.

Annotation processing — Doma documentation

ってなって、options を用意してますってなってて、方法的には、

  • Setting options in Eclipse
  • Setting options in IntelliJ IDEA
  • Setting options in javac
  • Setting options in Gradle
  • Setting options with configuration file

⇧  これだけあるらしい。 

んで、Eclipseの場合は、 

Setting options in Eclipse

  • Select “Project > Properties” from the menu bar and open the dialog
  • Select “Java Compiler > Annotation Processing” from the left menu of the dialog
  • Add “Processor options”

Annotation processing — Doma documentation

⇧  ...殴って良いですか? って言うか、説明が面倒くさいのかもしらんけど、キャプチャを貼ってくれ~って感じ。

Eclipseのプロジェクトを選択した状態で右クリックで、「プロパティ(R)」を選択後の「Java コンパイラー」>「注釈処理」にある、「プロセッサー・オプション(-Akey=値):」を追加してくってことかと。(オプションは特に必須ってわけでもないのかも...ドキュメントに例とかの記載もなかったので、すみません分かりませんでした)

f:id:ts0818:20191204225436j:plain

 

あれ?でも、注釈処理(Annotation processing)の設定したけど?って思った人、ワシもじゃ、ワシもじゃみんな!!

どうやら、Doma2では、Annotation processing の設定を、

の2つで行うということだったらしく、コンパイル時の設定が抜けてたので、解決できなかったらしいってことなのかな?

だから、Spring BootでDoma2を使ったサンプルに、「additional-spring-configuration-metadata.json」が無かったのは、Doma2が Pluggable Annotation Processing API を使ってコンパイル時に 「additional-spring-configuration-metadata.json」を生成するっていう仕様だったということで、そのためには、Eclipseで設定が必要だったと、分かりにくいな~。

 

なので、Doma2の場合に、appliaction.yml でカスタム・プロパティを使えるようにするのは、ちょっと特殊なのか分からんのだけど、

ってことかと。 

プロパティ用のクラスは、

github.com

⇧  サンプルの「doma-spring-boot/doma-spring-boot-autoconfigure/src/main/java/org/seasar/doma/boot/autoconfigure/」 以下のJavaファイルを使わせていただくということで。

って思ったんだけど、部分的に使うってのが厳しいと。

Mavenでマルチプロジェクトになってるらしいんで、

qiita.com

⇧  上記サイト様を参考に、Gradleのマルチプロジェクトとしていこうかと思ったら、pom.xmlで、親プロジェクトを参照してるみたいで、MavenからGradleへの変換をしようとしてエラーになってしまったんだ(他プロジェクトに依存しちゃってるっぽい)で、諦めました。

う~ん、とりあえず、プロパティ用のクラスを用意してコンパイルすれば良いのかな?

と思ったら、

blog.nijohando.jp

codeday.me

docs.spring.io

⇧  なんか、「spring-boot-configuration-processor」を依存性に追加って言ってるんだけど、

⇧  Doma2 はこのへんをやってくれているってことなんだと思うから、「spring-boot-configuration-processor」は不要でしょうということで。

んで、プロパティ用のクラスを用意して、

f:id:ts0818:20191207152012p:plain

「Gradle タスク」で、build してみたんですが、

f:id:ts0818:20191207151840p:plain

f:id:ts0818:20191207152145p:plain

コンパイルされて、中間ファイルである「DomaProperties.class」ができたんだけど、

f:id:ts0818:20191207152523p:plain

変わらんやんけ~!

f:id:ts0818:20191207152815p:plain

う~ン、仕方が無いので、先に進みます。

 

カスタムテンプレート

qiita.com

自動生成されたDaoに@ConfigAutowireableを自動で付与させたいときは作りましょう。自動じゃなくてもいいのであれば必要ありません。
1. doma-genからdao.ftllib.ftlを持ってきます。templatePrimaryDirで指定したディレクトリかデフォルトのディレクトリに配置します。
lib.ftlは空ファイルなので、普通に新規作成してもいいです。
2. dao.ftlで使用するアノテーション名とパッケージのハッシュをlib.ftlに定義します。

springbootでdoma-genを使う - Qiita

⇧  というわけで、カスタムテンプレート配置用のディレクトリ作成で。ファイルも配置で。

f:id:ts0818:20191124161605p:plain

ファイルの中身。 

<#assign author="k_kawasaki">
<#assign annotationHash={ "ConfigAutowireable": "org.seasar.doma.boot.ConfigAutowireable" }>
<#-- このテンプレートに対応するデータモデルのクラスは org.seasar.doma.extension.gen.DaoDesc です -->
<#import "/lib.ftl" as lib>
<#if lib.copyright??>
${lib.copyright}
</#if>
<#if packageName??>
package ${packageName};
</#if>

<#list importNames as importName>
import ${importName};
</#list>
<#-- ↓追加 ここから -->
<#list lib.annotationHash?values as annotationImportName>
import ${annotationImportName};
</#list>
<#-- ↑追加 ここまで -->
/**
<#if lib.author??>
 * @author ${lib.author}
</#if>
 */

@Dao<#if configClassSimpleName??>(config = ${configClassSimpleName}.class)</#if>
<#-- ↓追加 ここから -->
<#list lib.annotationHash?keys as annotation>
@${annotation}
</#list>
<#-- ↑追加 ここまで -->
public interface ${simpleName} {

<#if entityDesc.idEntityPropertyDescs?size gt 0>
    /**
<#list entityDesc.idEntityPropertyDescs as property>
     * @param ${property.name}
</#list>
     * @return the <#if entityDesc.entityPrefix??>${entityDesc.entityPrefix}</#if>${entityDesc.simpleName}<#if entityDesc.entitySuffix??>${entityDesc.entitySuffix}</#if> entity
     */
    @Select
    <#if entityDesc.entityPrefix??>${entityDesc.entityPrefix}</#if>${entityDesc.simpleName}<#if entityDesc.entitySuffix??>${entityDesc.entitySuffix}</#if> selectById(<#list entityDesc.idEntityPropertyDescs as property>${property.propertyClassSimpleName} ${property.name}<#if property_has_next>, </#if></#list>);

</#if>
<#if entityDesc.idEntityPropertyDescs?size gt 0 && entityDesc.versionEntityPropertyDesc??>
    /**
<#list entityDesc.idEntityPropertyDescs as property>
     * @param ${property.name}
</#list>
     * @param ${entityDesc.versionEntityPropertyDesc.name}
     * @return the <#if entityDesc.entityPrefix??>${entityDesc.entityPrefix}</#if>${entityDesc.simpleName}<#if entityDesc.entitySuffix??>${entityDesc.entitySuffix}</#if> entity
     */
    @Select(ensureResult = true)
    <#if entityDesc.entityPrefix??>${entityDesc.entityPrefix}</#if>${entityDesc.simpleName}<#if entityDesc.entitySuffix??>${entityDesc.entitySuffix}</#if> selectByIdAndVersion(<#list entityDesc.idEntityPropertyDescs as property>${property.propertyClassSimpleName} ${property.name}, </#list>${entityDesc.versionEntityPropertyDesc.propertyClassSimpleName} ${entityDesc.versionEntityPropertyDesc.name});

</#if>
    /**
     * @param entity
     * @return affected rows
     */
    @Insert
    int insert(<#if entityDesc.entityPrefix??>${entityDesc.entityPrefix}</#if>${entityDesc.simpleName}<#if entityDesc.entitySuffix??>${entityDesc.entitySuffix}</#if> entity);

    /**
     * @param entity
     * @return affected rows
     */
    @Update
    int update(<#if entityDesc.entityPrefix??>${entityDesc.entityPrefix}</#if>${entityDesc.simpleName}<#if entityDesc.entitySuffix??>${entityDesc.entitySuffix}</#if> entity);

    /**
     * @param entity
     * @return affected rows
     */
    @Delete
    int delete(<#if entityDesc.entityPrefix??>${entityDesc.entityPrefix}</#if>${entityDesc.simpleName}<#if entityDesc.entitySuffix??>${entityDesc.entitySuffix}</#if> entity);
} 

 

5.Doma-gen-2 でファイルの自動生成

そしたらば、Doma-gen-2 でファイルを自動生成するために、Gradleのタスクを実行します。

一応、Eclipse 上の「Gradle  タスク」のビューが開いてない状態であると仮定して、

「ウィンドウ(W)」>「ビューの表示(V)」>「その他(O)...」で。

f:id:ts0818:20191124163050p:plain

「Gradle」>「Gradle タスク」を、「開く(O)」で。

f:id:ts0818:20191124163233p:plain

んで、 「doma-gen」の中の「gen」ってタスクを選択して右クリックで、「Gradle タスクの実行」で。(ダブルクリックでも実行できます。下の方の「gen」で実行しました。)

f:id:ts0818:20191208162018p:plain

んで、実行結果。

f:id:ts0818:20191208162240p:plain

Gradleのタスクは問題なく遂行されて、

f:id:ts0818:20191208162351p:plain

⇧  ファイルの自動生成もされたんですが、

f:id:ts0818:20191208162553p:plain

⇧  @ConfigAutowireable が解決できないのは、依存関係に、「doma-spring-boot-starter」が足りないかららしいですと。

Spring BootでDoma2系を使う時は、依存関係に、「doma-spring-boot-starter」も追加するのを忘れずにってことですかね。

ちなみに、「application.yml」の警告も、依存関係に、「doma-spring-boot-starter」も追加することで解決します。

 

ってことで、何だろうな、頭の良い人たちの作るライブラリのドキュメントってのは私のような頭の悪い人間にとっては、非常に分かりづらいと思ってしまった次第です。

でも、かの偉大な文豪、チャールズ・ブコウスキーは、こう言っております。

An intellectual is a man who says a simple thing in a difficult way.
An artist is a man who says a difficult thing in a simple way.

簡単なことを難しく言うのがインテリ。
難しいことを簡単に表現するのが芸術家。

せめて、心持だけでも芸術家でありたいものですかね。