少し前に検証したものだが、改めて整理。
テーブルAとテーブルBを結合した結果を取得したい場合に、普通にSpring DataのRepositoryを作って@Query
のメソッドを定義してもうまくいかない。
例えば以下のようなクエリは表現できない。
select a.id, b.id, b.name from a join b on b.id = a.b_id;
これを何とかRepositoryで表現するための方法について。
少し前に検証したものだが、改めて整理。
テーブルAとテーブルBを結合した結果を取得したい場合に、普通にSpring DataのRepositoryを作って@Query
のメソッドを定義してもうまくいかない。
例えば以下のようなクエリは表現できない。
select a.id, b.id, b.name from a join b on b.id = a.b_id;
これを何とかRepositoryで表現するための方法について。
Boucing Resultsという問題がある。検索結果に重複・欠落が起きる問題。
ソートのキーに使われる項目の値が同じdocumentがあると、検索結果の順序が一定にならない可能性がある。
Elasticsearchのクエリでページネーションを行う場合、各ページへのリクエストは独立しているので、それぞれのアクセスで別のシャードにアクセスする可能性がある。
といった条件が揃うと、1ページ目で返された検索結果が次のページでも返ってくる、という事象が起きる。
これは、Elasticsearch: The Definitive Guide でも説明されている Bouncing Results という問題とのこと。
この問題と思われる事象に遭遇したため、事象の再現と対策の検証を行なった。
Spring Data ElasticsearchのElasticsearchTemplateなどを使って
最終的に実行されたクエリを確認するには以下を application.yml に設定すれば良い。
spring:
data:
elasticsearch:
properties:
index:
search:
slowlog:
threshold:
query:
info: "0s"
忘れがちなのでメモ。
Spring Data ElasticsearchのローカルのノードにHTTPでアクセスするには、application.yml に以下を追加すればいい。
spring:
data:
elasticsearch:
properties:
http:
enabled: true
http://localhost:9200/
でアクセスできる。
ユニーク制約の一部にNullableなカラムを含めたいが、それではユニークにならないので回避したい。
(a, b, c) という複数カラムのユニーク制約を定義したい場合に、この中にNULLを許可するカラムがありNULLが入ってきてしまうと、MySQLなどではユニーク性が担保されない。
これを回避するために NULL の代わりに 0 を使ってみたら良さそうだが、Springではどうやればいいのか?
Spring BootでJDBCを使っていてSQLExceptionが発生した場合に、HibernateのSqlExceptionHelperが以下のようにERRORログを出力してしまう。
2016-10-30 13:56:45.772 WARN 40153 --- [nio-8080-exec-6] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 23502, SQLState: 23502
2016-10-30 13:56:45.772 ERROR 40153 --- [nio-8080-exec-6] o.h.engine.jdbc.spi.SqlExceptionHelper : 列 "TEAM_ID" にはnull値が許されていません
NULL not allowed for column "TEAM_ID"; SQL statement:
insert into tag (id, created_at, updated_at, account_id, name, team_id) values (null, ?, ?, ?, ?, ?) [23502-190]
未知の例外に対してはこの挙動でも問題ないように思うが、例えばデッドロックが発生した時にはリトライしたいとか、自分でハンドリングしたいケースがあった場合には、本当に問題といえる状態だと判断ができるレイヤでERRORログを出力するように制御したい。
…ということで、このログ出力を回避して自分でハンドリングする方法について。
以前のエントリ
Spring BootでJavaScript/CSSライブラリにフィンガープリントをつける
Spring BootでJavaScript/CSSライブラリにフィンガープリントをつける (2)
の方法だけだと、フィンガープリントを付けることはできてもSpring Securityによりキャッシュ無効化のヘッダが付いてしまうためキャッシュされない。
そこで静的リソースについてはキャッシュヘッダを無効化してみて、一見 Cache-Control: no-cache
のようなヘッダはなくなったかのように見えたが、Font Awesomeのアイコンがどうもちらつくな…と思って調べた。
すると、静的リソースについては全体的にキャッシュ系のヘッダを無効化していたものの、Expiresヘッダだけはついてしまっていた。
※Spring Boot 1.3.2 (Spring Security 4.0.3) を使用。
基本的な部分かもしれないが、Thymeleaf、JPAでの型変換を行う方法について。
Thymeleafでは、SpringのConverterが定義されていれば${{variable}}
の形式で書くことでConverterで変換した結果を出力してくれる。
なので、HTML上で分岐を書いたり独自の変換用のBeanを定義するよりはこの仕組みを使ったほうがいい。
例えば以下のような列挙型を定義していたとする。
Bean ValidationのエラーメッセージはValidationMessages.propertiesもしくはValidationMessages_ja.propertiesに書かなければならないと思っていたが、そうではなかった。
以下のようにMessageSourceで読み込んだ内容をLocalValidatorFactoryBeanに渡せば良い。
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Autowired
private MessageSource messageSource;
@Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
localValidatorFactoryBean.setValidationMessageSource(messageSource);
return localValidatorFactoryBean;
}
@Override
public Validator getValidator() {
return validator();
}
}
messages.propertiesはnative2asciiで変換せず直接マルチバイトのメッセージを書いていても動作するので、この方法の方がいいかもしれない。
(コードレビューでも直接日本語のメッセージが読めるし)
APIだとAOPでロギングするのは割と簡単だが、画面の場合にうまいやり方が見つからず試行錯誤したのでメモ。
@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
などを使えばリクエストの前後に処理を挟むことは簡単。
APIの場合は、バリデーションエラーや例外に対して共通的な処理を入れることは容易いが、画面の場合、バリデーションエラーが発生したら共通エラー画面ではなく元の画面を表示したいし、例外に対しても同じ。
例外に関しては個別のControllerのメソッド内でtry-catchを入れてログ出力すればいいが、バリデーションエラーは画面に表示されるだけでログに出力する方法がわかっていなかった。
Spring BootでControllerを含めたテストをする場合に、
MockMvcBuilders.webAppContextSetup()
でセットアップして @WebIntegrationTests
にすると通常のアプリケーションとしてテストできるが、数が多くなってくると重い。
いくつか改善できそうなポイントを調べたので記録しておく。
なお、極力Springを使わないのがベストだが、今回はそれができないケースを想定しているので除外。
以前、JavaとNode.jsでSpringアプリを動かしたが、自作の gradle-web-resource-plugin を使えば既にJava/Gradleだけで済むようになっていることもあり、Heroku の Gradle ビルドパックに切り替えてみた。
網羅的な手順ではなく、上記アプリのビルドパックを切り替える場合の記録なので注意。
Elasticsearchのデータ操作をサーバ上で行いたい場合に、curlでアクセすれば良いが、ちょっと複雑なことをやろうとするとシェルスクリプトでは面倒だったりする。
で、こういうちょっとしたスクリプトを実装するのはSpringではやはり難しいだろうか?と調べてみたら Spring Boot CLI がちょうど良さそうだったので試してみた。
今回は jq を使うことで結局シェルスクリプトでも何とかなるレベルのものにとどまってしまったが、あれこれ試した結果をまとめておく。
「部署」と「社員」という親子関係のあるテーブルがあったとして
という部署一覧を表示したい。適当に社員を結合してしまうと
という社員一覧っぽいものになってしまうが作りたいのはあくまで部署一覧なのでNG。
これをSpring Boot + Spring Data JPA + Querydslで解決する。
Spring Bootで静的コンテンツにフィンガープリントをつける,
Spring BootでJavaScript/CSSライブラリにフィンガープリントをつけるの続き。
前回の内容だと、外部ライブラリに対してGitコミットハッシュを固定のバージョンとして付与できるが、これだと例えば/lib/
というリソースに対して/バージョン/lib/
というパスが生成される。
この場合、/lib/**
は認証対象外、というようなSpring Securityの設定をしているケースではバージョン付与後のパスが認証対象となってしまい、ログイン画面でリソースが読み込めない状態になってしまう。
このエントリでは、これに対処する方法について説明する。
もう1年近く経ってしまうが、Spring Bootで静的コンテンツにフィンガープリントをつける の続き。
以前は、プロジェクト内で書いたCSSやJavaScriptに対して、コンテンツを基にしたハッシュ値によるバージョニングについて確認したが、Bowerなどのライブラリに対してバージョニングする部分は
これはこれでいいが、バージョンの更新を忘れそう。
Gitのコミットハッシュ値を自動的につけられるなら利用したいが…これはまた別の機会に考えてみる。
としたままだった。
このままだと、ライブラリをアップデートしてもクライアント側のキャッシュが更新されず、更新が適用されない、というような問題が生じる。
しかしこれは今になってチャレンジしてみると意外に簡単にできた。
タイトルの内容は、もともとできるんじゃないの?という感じもするかもしれないが、それではうまくいかないケースがあり、それをGradleのContinuous Build機能を使ったら何とか実現できた、という話。
Continuous Build
http://gradle.org/feature-spotlight-continuous-build/
https://docs.gradle.org/current/userguide/continuous_build.html
少し長いので、順序立てて説明。
Spring Boot + Gradle で開発している時、特にWebの画面をもつアプリを開発している場合は「gradlew bootRunでのアプリ起動中にThymeleafテンプレートやpropertiesなどの静的リソースを変更したら、再起動なしで反映するようにしたい」と考えると思う。
もともと Spring Boot 1.2.x までは、デフォルトで src/main/resources 以下がbootRunタスクのクラスパスに追加されていたため、変更して画面をリロードすればすぐに反映されていた。
Spring Boot 1.3.0では、Automatic Restart、Live Reloadといった機能が DevTools として実装されたのだが、どうも意図したように再起動・リロードしてくれない。静的リソースを変更しただけで再起動してしまうし、Live Reload もうまく動作していない。
またこの機能が入ったことにより、bootRun タスクでは src/main/resources をクラスパスに含めないのがデフォルトとなったのだが、結局うまく動作しないので以下のように build.gradle に設定して、1.2.x までのようにクラスパスに入れてくれるようにしている。
bootRun {
addResources = true
}
しかし、この方法にはいくつか問題がある。
Spring Boot のドキュメントでも、bootRun.addResources = false をデフォルトにした理由として書いてあったのだが、この方法の問題は、 src/main/resources 以下のファイルの内容がそのまま Spring Boot に読み取られてしまうことだ。
bootRun は開発用のタスクであり、実際にリリースする場合は当然JARファイルにしてデプロイする。その際には src/main/resources が丸ごとJARに入って読み取られるわけではなく、processResources タスクでフィルタリングすることができ、ファイル内容を加工してからJARに入るようにしたり、一部のファイルだけを含めるようにしたりすることができる。
設定ファイルの一部に変数を埋め込んでおいて、processResources タスクで置換することもできる。
こうした処理をスキップして直接 src/main/resources 以下が読み取られてしまうので、processResources タスクなどでリソースに手を加える必要がある場合は、開発時とリリース時で異なる状態になってしまう。
Propertiesファイルの仕様としては、native2asciiをかけてUnicodeエスケープされていなければいけない。
だから、bootRunタスクでリソースを直接読み取らせるようにするならば、Unicodeエスケープされた状態でファイルを管理していなければならない。例えばIntelliJ で開発するなら Transparent native to ascii conversionの設定 をしておく必要がある。
これが非常に扱いづらい。
何と言っても、bootRun実行中にpropertiesを編集したりするとIntelliJが変換してくれないことがあるし、新規ファイルを追加した後はIntelliJを再起動しないと変換対象ファイルとして認識してくれなかったりする(これは動かしてみての推測で、仕様/不具合なのかどうかは不明)。
さらに、Unicodeエスケープされた状態のファイルをバージョン管理することになるので、ソースコードレビューする際にもUnicodeエスケープされた状態で差分が出てくる。
まあ、Thymeleafに限って言えば、native2asciiしてなくてもきちんと表示されるのだが、@PropertySourcesとかProperties#load()などを使ってプロパティを読み込む場合はきちんと変換していなければUnicode文字を表示できない。
というわけで、以下のように管理・開発できるのが望ましいなと考えた。
src/main/resources以下のファイルを加工するタスクは、processResourcesでもいいし独自のタスクでも構わない。
processResourcesはデフォルトではbuild/resources/mainにファイルを出力するので、独自タスクを用意する場合は、このディレクトリにアウトプットすればいい。
このタスクを手動実行すれば話は簡単なのだが、開発効率が下がるので避けたい。
そこで解決策となるのは、冒頭に書いた通り、Gradle に最近追加された Continuous Build という機能。-tオプションをつけてタスクを実行すると、そのタスクの入力ファイルの変更を検知してタスクを再実行してくれる。この機能を使って、src/main/resources以下のファイルを随時加工していけばいい。
フロントエンド界隈のGulpとかGruntなどだと既に提供されている機能だけど、Gradleでもできるようになっていた。
例えば、src/main/resources/messages.propertiesをnative2asciiする場合は以下のようにすればいい。
task native2ascii {
inputs.files files("src/main/resources/messages.properties")
doLast {
ant.native2ascii(
src: "${projectDir}/src/main/resources",
dest: "${buildDir}/resources/main")
}
}
そして、次のように-tをつけてnative2asciiタスクを実行すれば、messages.propertiesを変更するたびにbuild/resources/以下に変換され、出力される。
./gradlew -t native2ascii
なお、これだけだと当然bootRunタスクは動かないので、
別のターミナルでbootRunタスクを実行しておく必要がある。
これで、bootRun.addResources = trueとしていなくても、
アプリ実行中に静的リソースの編集→反映ができて開発効率も落ちず
ソースコードレビューもしやすくなる。
Gradleを同じプロジェクトの中で複数動かすため、時々タスクの起動に失敗してしまう。正確には、Configurating …と表示されたまま止まってしまう。
(一度両方のタスクを停止して起動しなおせば大概うまくいくが)
それから、2つのタスクを別ターミナルで実行しなければいけないという面倒さ。開発が進行中のプロジェクトでは、きちんと周知しないと「変更が反映されなくなった!」と不満が噴出するかもしれない。
この2つが特に問題にならなければ、この方法でうまくいきそうだ。
今起動しているSpring Bootアプリはどのコミットでビルドされたものなのか?
を確認できるようにしたいと思い、やり方を探ってみた。
前提として、ビルドにはGradleを使う。
AndroidアプリなどでもGradleを使ってSHAハッシュ値を
アプリの中に含めたりしていたが、それと似たようなことをやる方法。
現在Spring Bootを使ったWebアプリを開発しているのだが、Spring Boot 1.3.0のリリースを心待ちにしていて、実際にMilestone/RC版を取り込んでみている。
その中でいくつかハマったこと(ドキュメントをよく読めという話だが)や、これは使ってみたいと思った機能などを書き留めておく。
これは1.3.0のリリース前のM5とRC1のことなので、そこはご注意を。
Spring BootではRuby on Railsのアセットパイプラインのようなものを
どうやって実現するのか?というのを調べていた。
Sprinb Bootのバージョンは1.2.3-RELEASE。
以下のSpringのブログによれば、静的コンテンツのハンドリングはSpring Framework 4.1で改善されたらしい。
Spring Framework 4.1 - handling static web resources