Dropwizard + JDBI で SQL オブジェクトの返り値に Optional を使うときには SingleValueResult アノテーションが必要
Dropwizard で SQL ライブラリ JDBI を使うときのおはなし。
OptionalContainerFactory
dropwizard-jdbi ライブラリは OptionalContainerFactory
クラスを提供してくれていて、JDBI の SQL オブジェクトで返り値に Java 8 で導入された Optional
指定することができます。
ちなみに普通に dropwizard-jdbi の DBIFactory#build
メソッドを使うと自動的に OptionalContainerFactory
を DBI
に登録してくれるので、自分で登録する必要はありません。 (Dropwizard 1.0.5 で確認。)
SingleValueResult
アノテーションが必要 (単に Optional
を指定するだけでは動かない)
SQL オブジェクトのクラス定義で返り値に Optional
を指定すればそれだけで動くのかと思いきや、実はそんなことはありません。 Optional
を返すメソッドに SingleValueResult
アノテーションを付ける必要があります。
import info.vividcode.app.web.example.dropwizard.domain.Person; import org.skife.jdbi.v2.sqlobject.SqlQuery; import org.skife.jdbi.v2.sqlobject.customizers.SingleValueResult; import java.util.Optional; public interface PersonDao { @SqlQuery("SELECT * FROM person LIMIT 1") @SingleValueResult(Person.class) Optional<Person> findOne(); }
ここら辺のドキュメントがないので、ソースコードを読んで確認しました。
- 返り値に応じた
ResultReturnThing
を生成する箇所 : jdbi/ResultReturnThing.java at 3fe1480fcc3c42be94d355f8c7335bd784dbbc13 · jdbi/jdbi · GitHub SingleValueResult
アノテーションがあるかどうかを検査している箇所 : jdbi/ResultReturnThing.java at 3fe1480fcc3c42be94d355f8c7335bd784dbbc13 · jdbi/jdbi · GitHub
Optional
を使う場合に限らず、Iterable
じゃなくてコンテナを使いたい場合は SingleValueResult
アノテーションを付ける必要がありそうですね。
関連ページ
MySQL Connector/J 5.1 系では useLegacyDatetimeCode=false にしよう
JDBC で MySQL に接続するときに使用する MySQL Connector/J (mysql:mysql-connector-java) の話。 サーバー・クライアントのタイムゾーン設定が違っている場合にどう対応するのがいいか。
結論
- MySQL Connector/J 6 (まだ開発版だけど) 以降は自動でやってくれるので気にする必要はない。
- MySQL Connector/J 5.1 では URL に
useLegacyDatetimeCode=false
を入れて、時刻周りの新しい処理が動くようにしろ。- 新しい処理では、タイムゾーンの変換を一貫性をもってやってくれるようになる。
- 『Use code for DATE/TIME/DATETIME/TIMESTAMP handling in result sets and statements that consistently handles time zone conversions from client to server and back again』
- 参考 : 5.1 Driver/Datasource Class Names, URL Syntax and Configuration Properties for Connector/J
- 5.1 系ではデフォルトでは互換のために新しい処理は動かないようになっているので、明示的に新しい処理を使うように URL で指定する必要がある。
- バージョン 5.1.6 で導入された機能なので、それより古いものでは使えない。
問題
そもそもどういう問題に遭遇したのか。
- Java のアプリケーションサーバーのタイムゾーンが JST。
- MySQL サーバーのタイムゾーンが UTC。
- Java アプリケーションから MySQL サーバーには MySQL Connector/J 5.1 系で接続。
- タイムゾーン周りのオプションは何も指定せず。
- SQL 文の
NOW()
関数やDEFAULT CURRENT_TIMESTAMP
で設定された時刻を Java アプリケーション側で取得すると、現在時刻から 9 時間前の時刻が返ってきた。 - → MySQL Connector/J がサーバー・クライアント間のタイムゾーン差を扱ってくれてない。
MySQL Connector/J とタイムゾーン
- もともとは
useTimezone
プロパティやserverTimezone
プロパティを使って対応する必要があった。 - MySQL Connector/J 5.1.6 で時刻周りの処理が書き直されて、
useLegacyDatetimeCode=false
することでタイムゾーン変換などを自動で扱ってくれるようになった。 - MySQL Connector/J 6 では
useLegacyDatetimeCode
プロパティを含め、古いタイムゾーン周りのプロパティは全部削除される。
おわり
タイムゾーンはライブラリ側がちゃんと面倒見てくれるだろう、と思って気にしなかったら、環境を変えて Java アプリケーションと MySQL サーバーのタイムゾーン設定がずれたときにいきなり想定しない動作になったりするので気を付けましょう。
grunt-ts が .baseDir.ts ファイルを作るのを抑制する
Grunt を使っていて TypeScript のビルドを行うタスクを定義する際には grunt-ts を使うことが多いでしょう。 (grunt-typescript もあるけど。) grunt-ts で困ったことがあったので書いておきます。
outDir
オプションを使うと .baseDir.ts ファイルが作られる問題
grunt-ts を使って tsc
コマンドの --outDir
オプション指定を行うには outDir
プロパティが使えるのですが、この値を指定すると、何故か 「.baseDir.ts」 という名前のファイルがソースディレクトリの方に生成されてしまいます。
このファイルは何かというと、TypeScript 1.5 で --rootDir
オプションが導入される前に grunt-ts 独自に --rootDir
相当のことをするために導入されたものです。 grunt-ts のドキュメントを読む限り、baseDir
オプションを使っておらず、fast コンパイルが無効になっている場合には 「.baseDir.ts」 ファイルは作られないはずです。 しかしながら、grunt-ts 5.5.1 で試したところ、baseDir
オプションを使わず fast
オプションに "never"
を指定しても outDir
オプションを指定すると 「.baseDir.ts」 ファイルが作成されてしまいました。 個人的にはバグっぽいなーと思っています *1。 一応 pull request を投げておきました。
ちなみに以下のような Gruntfile.js (一部) で動作確認しました。
grunt.initConfig({ ts: { default: { src: ["src/ts/**/*.ts"], outDir: "build/ts" options: { fast: "never" } } } });
回避方法
現在 (バージョン 5.5.1) の grunt-ts に .baseDir.ts ファイルを作らせないようにする方法はいくつかあります。
outDir
オプションではなく additionalFlags
で --outDir
オプションを指定する
grunt-ts は outDir
オプションを見て 「.baseDir.ts」 ファイルを生成するかどうか決めているので、additionalFlags
の方で --outDir
を指定すると 「.baseDir.ts」 ファイルは生成されません。
grunt.initConfig({ ts: { default: { src: ["src/ts/**/*.ts"], options: { additionalFlags: "--outDir build/ts" } } } });
tsconfig.json ファイルを指定する
tsconfig.json の中で outDir
オプションを指定し、grunt-ts には tsconfig
プロパティで tsconfig.json のパスを指定教えるだけにするという方法もあります。 ただし、普通に指定するだけでは tsconfig.json を解析して grunt-ts が余計なことをするっぽい (tsconfig.json 側で outDir
オプションを指定していると、grunt-ts が .baseDir.ts ファイルを作ってしまう) ので、passThrough
オプションを有効にして、grunt-ts が余計なことをしないようにする必要があります。
grunt.initConfig({ ts: { default: { tsconfig: { tsconfig: "src/ts/tsconfig.json", // このファイルの中でコンパイル対象のファイル群や `outDir` 指定を行う passThrough: true } } } });
おわり
こんなしょうもないことをやってるのは無駄だと思うし grunt-ts はオススメしません。
関連ページ
- Stop creating .baseDir.ts file · Issue #300 · TypeStrong/grunt-ts · GitHub :
rootDir
オプションが有効なら .baseDir.ts ファイルを生成しないとかしてもいいのでは、みたいな話がされてる。 - TypeScript needs a `baseDir` option · Issue #77 · TypeStrong/grunt-ts · GitHub :
baseDir
オプションが導入されることになった経緯。
*1:ドキュメントの記述から察するに仕様ではなさそうだし、コードの実装も微妙に変なので
Android の Instrumented Test で指定のサイズのテストだけ実行する (@SmallTest とか @LargeTest とか)
Android Testing Support Library (ATSL) の話。 バージョン 0.5 時点での情報です。
ライブラリの準備方法などはドキュメントを読んでください。
テストサイズを表すアノテーション
android.support.test.filters
というパッケージがあって、この中にはテストをフィルタするのに使用できるアノテーションが入っています。 その中に、テストのサイズを表すためのアノテーションが 3 つ入っています。
@SmallTest
アノテーション : 200 ms 未満で終わるようなテスト。 ほぼユニットテスト用。 各種リソース (ファイルや DB、ネットワーク) は使用しない。@MediumTest
アノテーション : 1,000 ms 程度で終わるようなテスト。 コンポーネント単体やいくつかのコンポーネントを結合してテストする場合に使用する。 ファイルアクセスや DB アクセス、コンテキストなどは使っていいけど、ネットワークアクセスなどのそこそこ時間がかかる非同期処理はテストに含めない。@LargeTest
アノテーション : アプリケーション全体のコンポーネントを統合してテストするようなもの。
これらはテストクラス自体に付けることもできますし、メソッド単体に付けることもできます。 実装を見たところ、メソッドとクラスの両方にアノテーションが付けられている場合はメソッドのアノテーションが優先されるようです。
ちなみに、Android Testing Support Library じゃなくて android.test.suitebuilder.annotation
パッケージの方にも同名のアノテーションがありますが、そっちは deprecated ぽいし AndroidJUnitRunner と組み合わせて使うことはできないぽいので注意しましょう。
AndroidJUnitRunner
とテストサイズによるフィルタリング
AndroidJUnitRunner
でテストを実行する場合、テストサイズによるフィルタリングが可能です。 下記 Javadoc にいろいろ書かれています。
adb
でテスト実行する場合
上のドキュメントでは、adb
コマンドでテストを実行する際にどういうオプションを渡せばいいかが主に書かれています。 例えば、small サイズのテストのみを実行する場合は、以下のようになります。
adb shell am instrument -w -e size small your.test.target/android.support.test.runner.AndroidJUnitRunner
Gradle の connectedAndroidTest
タスクで実行する場合
AndroidManifest.xml で指定する (ただし現在はバグで動かない)
上のドキュメントには All arguments can also be specified in the in the AndroidManifest via a meta-data tag
ということが書かれています。 すなわち、テスト用のアプリパッケージの AndroidManifest.xml ファイル (app/src/androidTest/AndroidManifest.xml ファイル) に以下のような記述をすると、small サイズのテストのみが実行されるはず、ということです。
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" android:targetPackage="..."> <meta-data android:name="size" android:value="small"/> </instrumentation>
ただし現在はバグで動きません。 (指定が無視されます。) バグ報告を上げたので、対応状況は下記ページをご覧ください。
バグがなければ、上のような記述をしておけば ./gradlew :app:connectedAndroidTest
という感じで Gradle タスクでテストを実行したときにテストサイズのフィルタリングが効きます。
Gradle のビルドスクリプトで指定する
AndroidManifest.xml に書かずに Gradle のビルドスクリプトにオプションを指定することができます。 上の方法の代わりにこの方法を使うことで、上のバグを回避できます。 sumio さんに教えて頂きました。
@nobuoka もしコマンドラインからの実行で良いのであれば
— TOYAMA Sumio (@sumio_tym) May 26, 2016
build.gradle側で、
testInstrumentationRunnerArguments
を指定するので回避できたりしないでしょうか。こちらならManifestではなくargs扱いになるような気がします。
android { defaultConfig { testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunnerArguments size: 'small' } }
上のような感じで書きます。
Android Studio 上でテストを実行する場合
Android Studio 上でテストを実行する場合は、build.gradle での記述が有効にならないので、configurations を弄る必要があります。 下図のような感じで、Extra options に 「-e size small」 と入力すれば良いです。
おわり
@SmallTest
アノテーションとか @LargeTest
アノテーションとかがあって、テスト実行時にそれらのフィルタリングができるという情報を過去に見たのですが、いざやってみるとあまり情報がなくてうまくいかなかったのでまとめてみました。
コミットごとのテストは @SmallTest
だけにして、ある程度開発が終わってレビューに出す前の段階になったら @MediumTest
と @LargeTest
のテストも行うようにする、とかにしたらテストにかかる時間が短くなっていいかもしれませんね。
関連ページ
- Androidオールスターズでテストの話をしました | RECRUIT TECHNOLOGIES Member's blog : テストサイズごとのテストのアプローチなどが書かれていて、思想的な部分も実際の実装の部分でも参考になります。
- GitHub - googlesamples/android-testing-templates : Google による Android Testing Support Library を使ったテスト記述のサンプルプロジェクト。 参考になります。
- Is there a way to only run a specific set of tests in an Android Gradle project? - Stack Overflow : sumio さんに教えてもらった後に気付きましたが、Stack Overflow でも同じ方法が紹介されていました。
ISO 8601 DateFormat 1.0.0 (Java 向けライブラリ) をリリースしました
2016 年 5 月 3 日に ISO 8601 DateFormat の 1.0.0 をリリースしました。 ISO 8601 形式 (もしくは RFC 3339 や W3C-DTF 形式) の日付時刻文字列のパースとフォーマットのための DateFormat
のサブクラスを提供するライブラリです。
Bintray の JCenter リポジトリで公開しています。
現在は、時刻オフセット付きの拡張形式の日付時刻文字列のみをサポートしています。
- 2016-01-01T00:30:21Z
- 2016-01-01T09:30:21+09:00
動機
Java では、ISO 8601 形式の日付時刻文字列をパースする方法がいろいろあります。 例えば、SimpleDateFormat
で “yyyy-MM-dd'T'HH:mm:ssX
” というフォーマットを使う (Java SE 7 以降) とか、Date and Time API (JSR 310; Java SE 8 以降) を使うとか、Joda-Time ライブラリを使うとか、Apache Commons Lang ライブラリを使うなどです。
しかし、Java SE 6 環境や Android プラットフォームでは、大きなライブラリを導入することなく ISO 8601 形式の文字列をパースすることが簡単ではありませんでした。 そのため、このようなライブラリを公開しました。
使い方
Gradle を使っている場合、以下のようにリポジトリと依存を追加します。
repositories {
jcenter()
}
dependencies {
compile 'info.vividcode:date-format-iso8601:1.0.0'
}
あとは、以下のように使うだけです。
import info.vividcode.time.iso8601.Iso8601ExtendedOffsetDateTimeFormat; DateFormat f = new Iso8601ExtendedOffsetDateTimeFormat(); Date d1 = f.parse("1970-01-01T00:00:00Z"); Date d2 = f.parse("1970-01-01T09:00:00+09:00");
最新の情報はリポジトリの README ファイルを見てください。
ISO 8601 関連の情報
小さなライブラリが欲しいのでなければ、Joda-Time や Apache Commons Lang、JSR 310 のバックポートライブラリなどを使うのが良いかもしれません。
- Android プラットフォームと Java SE 環境における
SimpleDateFormat
のパターンの違い : Android と Java では SimpleDateFormat の書き方がこう違う - Qiita- Android プラットフォームでは
Z
で 「±HH:MM」 が解釈されるけど、「Z」 が解釈されないという……。
- Android プラットフォームでは
- ThreeTen ABP を使う方法 : AndroidでJSR310 - Qiita
- Apache Commons Lang を使う方法
- 色々な情報 : JavaでのISO 8601形式の日時の処理 - drambuieの日記