ページ

2016/12/31

Spring Data JPAで複数テーブルを結合した結果を返すクエリを作る

|

少し前に検証したものだが、改めて整理。

テーブルAとテーブルBを結合した結果を取得したい場合に、普通にSpring DataのRepositoryを作って@Query のメソッドを定義してもうまくいかない。
例えば以下のようなクエリは表現できない。

select a.id, b.id, b.name from a join b on b.id = a.b_id;

これを何とかRepositoryで表現するための方法について。

ElasticsearchのBouncing Results問題

|

Boucing Resultsという問題がある。検索結果に重複・欠落が起きる問題。

ソートのキーに使われる項目の値が同じdocumentがあると、検索結果の順序が一定にならない可能性がある。
Elasticsearchのクエリでページネーションを行う場合、各ページへのリクエストは独立しているので、それぞれのアクセスで別のシャードにアクセスする可能性がある。

  • 対象のElasticsearchクラスタがレプリカを持っている
  • ソート対象項目の値が同一の複数documentがページを跨っている
  • それらのページへのリクエストが別のシャードに割り当てられる
  • それぞれのシャードが対象のdocumentを返す順番が異なる

といった条件が揃うと、1ページ目で返された検索結果が次のページでも返ってくる、という事象が起きる。

これは、Elasticsearch: The Definitive Guide でも説明されている Bouncing Results という問題とのこと。

この問題と思われる事象に遭遇したため、事象の再現と対策の検証を行なった。

Thymeleafの部品化

|

th:fragment によって部品を作るのは比較的簡単にできるが、
類似の部品が出てきた時に、その中の一部を変更したい場合にどうするか?

例えば以下のようなイメージ。
barタグとbazタグだけが差分であり、その他は同じ構成になっている。

<foo id="foo1">
  <div></div>
  <bar></bar>
</foo>

<foo id="foo2">
  <div></div>
  <baz></baz>
</foo>

それぞれのfooタグを部品として定義するのも良いが、それでは重複コードが多すぎる。

Gradleプラグインのup-to-dateを確認するテスト

|

GradleプラグインのTaskにInputとOutputを設定して、いずれかが変化した時にだけタスクが再実行されるようにしたいとき。
実装は簡単でも、テストはどうするか。
up-to-dateがきちんと判定できないと、変更が入った時に処理が実行されないなど致命的な不具合になる。
※Gradleのバージョンが古い(2.x)ので、最新では少し話が違うかもしれない。

Spockで複数ステップのテストを書く

|

Spockでテストを書くとき、setup:, when:, then: という流れで書けば良いが
複数の処理を実行し、最後の結果だけでなく途中の経過も含めてassertする場合にどうすればいいか。

単純に when:then: を繰り返して書けばいい。
and: などが一見それっぽいもののようにも思えてしまうが、and: はこの用途ではないので注意。

def ...() {
    setup:
    // 初期設定

    when:
    // 最初の処理

    then:
    // 最初の処理の結果をassert

    when:
    // 2つ目の処理

    then:
    // 2つ目の処理の結果をassert
}

Spring Data Elasticsearchが実行するクエリを確認する

|

Spring Data ElasticsearchのElasticsearchTemplateなどを使って
最終的に実行されたクエリを確認するには以下を application.yml に設定すれば良い。

spring:
    data:
        elasticsearch:
            properties:
                index:
                    search:
                        slowlog:
                            threshold:
                                query:
                                    info: "0s"

Spring Data ElasticsearchのローカルのノードにHTTPでアクセスする

|

忘れがちなのでメモ。
Spring Data ElasticsearchのローカルのノードにHTTPでアクセスするには、application.yml に以下を追加すればいい。

spring:
    data:
        elasticsearch:
            properties:
                http:
                    enabled: true

http://localhost:9200/ でアクセスできる。

2016/10/30

Springでauto_incrementなカラムに対して0を指定する

|

ユニーク制約の一部にNullableなカラムを含めたいが、それではユニークにならないので回避したい。

(a, b, c) という複数カラムのユニーク制約を定義したい場合に、この中にNULLを許可するカラムがありNULLが入ってきてしまうと、MySQLなどではユニーク性が担保されない。

これを回避するために NULL の代わりに 0 を使ってみたら良さそうだが、Springではどうやればいいのか?

SpringのAOPでSQLエラーをハンドリングする

|

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 Security 4.0ではHeaderWriterを使って静的リソースのキャッシュヘッダを適切につける

|

以前のエントリ
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のConversion

|

基本的な部分かもしれないが、Thymeleaf、JPAでの型変換を行う方法について。

Thymeleafでの型変換

Thymeleafでは、SpringのConverterが定義されていれば${{variable}}の形式で書くことでConverterで変換した結果を出力してくれる。
なので、HTML上で分岐を書いたり独自の変換用のBeanを定義するよりはこの仕組みを使ったほうがいい。

例えば以下のような列挙型を定義していたとする。

Springのバリデーションエラーメッセージを通常メッセージに統合する

|

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で変換せず直接マルチバイトのメッセージを書いていても動作するので、この方法の方がいいかもしれない。
(コードレビューでも直接日本語のメッセージが読めるし)

2016/10/16

SpringのControllerメソッド実行時にAOPでログ出力する

|

APIだとAOPでロギングするのは割と簡単だが、画面の場合にうまいやり方が見つからず試行錯誤したのでメモ。

@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")

などを使えばリクエストの前後に処理を挟むことは簡単。
APIの場合は、バリデーションエラーや例外に対して共通的な処理を入れることは容易いが、画面の場合、バリデーションエラーが発生したら共通エラー画面ではなく元の画面を表示したいし、例外に対しても同じ。
例外に関しては個別のControllerのメソッド内でtry-catchを入れてログ出力すればいいが、バリデーションエラーは画面に表示されるだけでログに出力する方法がわかっていなかった。

2016/10/09

Springのテストを速くする工夫

|

Spring BootでControllerを含めたテストをする場合に、
MockMvcBuilders.webAppContextSetup() でセットアップして @WebIntegrationTests にすると通常のアプリケーションとしてテストできるが、数が多くなってくると重い。

いくつか改善できそうなポイントを調べたので記録しておく。

なお、極力Springを使わないのがベストだが、今回はそれができないケースを想定しているので除外。

HerokuのGradleビルドパックでSpringアプリを動かす

|

以前、JavaとNode.jsでSpringアプリを動かしたが、自作の gradle-web-resource-plugin を使えば既にJava/Gradleだけで済むようになっていることもあり、Heroku の Gradle ビルドパックに切り替えてみた。

網羅的な手順ではなく、上記アプリのビルドパックを切り替える場合の記録なので注意。

2016/09/26

Spring Boot CLIでElasticsearchにアクセスするクライアントを作る

|

Elasticsearchのデータ操作をサーバ上で行いたい場合に、curlでアクセすれば良いが、ちょっと複雑なことをやろうとするとシェルスクリプトでは面倒だったりする。

で、こういうちょっとしたスクリプトを実装するのはSpringではやはり難しいだろうか?と調べてみたら Spring Boot CLI がちょうど良さそうだったので試してみた。

今回は jq を使うことで結局シェルスクリプトでも何とかなるレベルのものにとどまってしまったが、あれこれ試した結果をまとめておく。

2016/08/22

Querydslで親子テーブルの複数の子レコードを親レコードの1カラムにまとめる

|

「部署」と「社員」という親子関係のあるテーブルがあったとして

  • 部署1: 社員1, 社員2
  • 部署2: 社員3, 社員4, 社員5

という部署一覧を表示したい。適当に社員を結合してしまうと

  • 部署1: 社員1
  • 部署1: 社員2
  • 部署2: 社員3
  • 部署2: 社員4
  • 部署2: 社員5

という社員一覧っぽいものになってしまうが作りたいのはあくまで部署一覧なのでNG。

これをSpring Boot + Spring Data JPA + Querydslで解決する。

2016/03/30

GitLabのページをきれいに印刷するためのChrome Extension

|

GitLabを使っていると、モニタを用意できない場所で打ち合わせや作業をするときに、MarkdownのドキュメントやIssueなどの内容を印刷したいシーンがある。
しかしGitLabのレイアウトは印刷用には作られていないので、印刷しようとすると余白や不要な部品だらけになってしまう。そのため、レイアウト調整をする gitlab-print というChrome Extensionを作った。

以下のイメージの左側のように余白がかなり入ってしまうのだが、
これを除去して(右側)きれいに印刷できるようにする。

イメージ

2016/03/23

Spring BootでJavaScript/CSSライブラリにフィンガープリントをつける (2)

|

Spring Bootで静的コンテンツにフィンガープリントをつける,
Spring BootでJavaScript/CSSライブラリにフィンガープリントをつけるの続き。

前回の内容だと、外部ライブラリに対してGitコミットハッシュを固定のバージョンとして付与できるが、これだと例えば/lib/というリソースに対して/バージョン/lib/というパスが生成される。

この場合、/lib/**は認証対象外、というようなSpring Securityの設定をしているケースではバージョン付与後のパスが認証対象となってしまい、ログイン画面でリソースが読み込めない状態になってしまう。
このエントリでは、これに対処する方法について説明する。

2016/03/21

Spring BootでJavaScript/CSSライブラリにフィンガープリントをつける

|

もう1年近く経ってしまうが、Spring Bootで静的コンテンツにフィンガープリントをつける の続き。

以前は、プロジェクト内で書いたCSSやJavaScriptに対して、コンテンツを基にしたハッシュ値によるバージョニングについて確認したが、Bowerなどのライブラリに対してバージョニングする部分は

これはこれでいいが、バージョンの更新を忘れそう。
Gitのコミットハッシュ値を自動的につけられるなら利用したいが…これはまた別の機会に考えてみる。

としたままだった。

このままだと、ライブラリをアップデートしてもクライアント側のキャッシュが更新されず、更新が適用されない、というような問題が生じる。

しかしこれは今になってチャレンジしてみると意外に簡単にできた。

2016/01/11

Gradleで特定の名前のサブディレクトリリストを取得する方法

|

滅多に使わないかもしれないマニアックな話題。
Gradle+IntelliJ IDEAで開発している時、
idea.module.excludeDirsプロパティにディレクトリのfileオブジェクト
のリストを設定すると、そのディレクトリはスキャンの対象から
外され、内容が変化してもIntelliJの動作がもたつくことがなくなる。
参考:IntelliJのインデックス対象から除外する方法

このとき、excludeDirsはただのリストなので、
a, b, c, というディレクトリの下にあるbuild/というディレクトリを
まとめて無視したいと思っても、a/build, b/build, c/build をそれぞれ
設定する必要がある。
これをbuildという名前で一括する方法の話。

2016/01/04

MacのSourceTree が異常に重たい場合の対処

|

新年最初のエントリ、大したことのない内容ですが・・

Gitの操作ではコマンドとSourceTreeを併用しているのだが
最近SourceTreeが異常に重たかった。

下記にあるように、内臓GitではなくシステムGitを使うように設定することで改善した。
Source tree painfully slow on mac

以前は日本語でこの問題をGoogle検索していて見つからなかったのだが、英語で検索したらあっさり見つかった。
・・ということで、改めて言うことでもないが、教訓。
問題にぶつかったら英語で検索すること。
当たり前だが、日本語と英語では情報量が違いすぎる。