Java8対応のDBFluteの十四回目のリリース。 DBFlute-1.1.4 です。 DBFlute Top Change Log 移行の注意点 1.1.2 to 1.1.4 LastaFlute/SAFluteを使われている方は、 若干お知らせがありますので、 移行の注意点をお読みください。 今回も、様々な微調整が多いですが、 一番大きなトピックはこちら!
MySQLのミリ秒なしDATETIMEのミリ秒問題
MySQLでは、 もともとDATETIME型がミリ秒をサポートしていませんでした。 つまり、2017/08/20 12:34:56 の秒までしか、 入りませんでした。 そして、MySQL-5.6 のどこかのバージョンから!? ミリ秒やマイクロ秒を指定できるDATETIMEがサポートされました。 DDLでは、DATETIME(3) とか DATETIME(6) などと指定します。 それに伴いJDBCドライバーも、ミリ秒に対する扱いが変更されました。 要は、ミリ秒を意識するようになりました。 ミリ秒があるDATETIMEに対して、 ミリ秒が意識されるのは良いことなので、 それはそれでいいのですが... なんだかんだ、既存のDBだと、 ミリ秒ない時代に作った DATETIME がたくさんあるので、 ミリ秒が意識されるとちょっと変なことになります。
JDBCの「5.1.若い頃バージョン(12とか)」
(ミリ秒のないDATETIMEに対して) insert や update などのとき、 Javaの中で、2017/08/20 12:34:56.789 という風に、 ミリ秒ありの日時をバインド変数に指定しても、 単純に 789 が切り取られるだけでした。 2017/08/20 12:34:56 が登録されます。 また、select の where 句の条件のとき、 Javaの中で、2017/08/20 12:34:56.789 という風に、 ミリ秒ありの日時をバインド変数に指定しても、 同じく単純に 789 が切り取られるだけでした。 2017/08/20 12:34:56 として比較されます。 少なくとも MySQL Connector/J 5.1.12 では、 そのような挙動をしました。 MySQL本体は、5.7.13 で試しています。
JDBCの「5.1.進んだ頃バージョン(37とか)」
(ミリ秒のないDATETIMEに対して) insert や update などのとき、 Javaの中で、2017/08/20 12:34:56.789 という風に、 ミリ秒ありの日時をバインド変数に指定すると... ミリ秒が四捨五入されて 2017/08/20 12:34:57 が登録されます。 秒が一つ増えています。 これつまり... 2017/08/20 23:59:59.789 だと、 2017/08/21 00:00:00:000 になるということです。 また、select の where 句の条件のとき、 Javaの中で、2017/08/20 12:34:56.789 という風に、 ミリ秒ありの日時をバインド変数に指定すると、 ミリ秒が考慮されて 比較されます。 2017/08/20 12:34:56 が入ってるDATETIMEカラムに、 2017/08/20 12:34:56.789 を <= とかやっても、 ヒットしないということです。 where END_DATETIME >= 2017/08/20 12:34:56.789 (2017/08/20 12:34:56が入ってるカラム) これがヒットしません。 四捨五入されるわけでもなく、 カラム側が .000 と解釈されて比較されます。 少なくとも MySQL Connector/J 5.1.37 では、 そのような挙動をしました。 執筆時点での最新 5.1.43 でも変わりませんでした。 MySQL本体は、5.7.13 で試しています。 (フィードバックをくれた現場では、 5.6 系で同じ現象になっているようです) ... MySQLのJDBCドライバーは、 ミリ秒がないDATETIMEに対して 何か特別な処理をするわけでもなく、 ミリ秒があるDATETIMEに対する処理と 同じ扱いをするようです。
登録時の四捨五入のジレンマ
そのギャップでちょっと困ることがあるかもしれません。 例えば、23:59:59 と日時を埋めて登録するとき、 そのままだと四捨五入で次の日になってしまいます。 Java側でミリ秒を切り取ればOKですが、 そこまでDBの挙動を意識した処理が 業務ロジックに入ってしまいますし、 どのみち抜けると思います。 そもそもDB設計的に、 23:59:59を埋めるようなカラムを作らない、 というところかもしれませんが、 既存DBだとそうもいかないでしょう。
検索時の考慮されジレンマ
selectのwhere句の方は、さらに困る事態に。 BEGIN_DATETIME, END_DATETIME と、 有効期間で積み重なるようなテーブルで、 END_DATETIME が「終了直前の最後の瞬間」を表現してる場合、 BEGIN_DATETIME: 2017/08/12 00:00:00 END_DATETIME: 2017/08/19 23:59:59 BEGIN_DATETIME: 2017/08/20 00:00:00 END_DATETIME: 2017/08/31 23:59:59 where BEGIN_DATETIME <= 現在日時 <= END_DATETIME (現在日時: 2017/08/19 23:59:59.001) これで、ヒットしません。 つまり、DB設計としては、 現在日時を指定してヒットしない瞬間がないようにしてても、 「一秒の隙」で何もヒットしない瞬間があるということです。 これは、プログラムの制御によっては、 処理がエラーで落ちてしまう可能性があります。 Java側では、ミリ秒のない日時型ってありませんから、 現在日時を取得するとどうしてもミリ秒は入ります。 現在日時の取得インターフェースでミリ秒を削るか? と言ったら、ミリ秒が必要なこともあるでしょうから、 それはそれで将来への負債へとなりかねません。 (ミリ秒ありDATETIMEが混じったらなおさら) こちらも、END_DATETIMEの仕様を、 「終了直前の最後の瞬間」ではなく、 「終了直後の最初の瞬間」にすればOKですが、 (つまり、次の期間のBEGINと同じ日時にする) 既存DBではそうもいかないでしょう。 # そもそもどっちの方が良い、好き、とか、 # 他の要因でもあったりするでしょうから。
CBの条件でミリ秒を削る
そこで、今回 DBFlute では、 ひとまず、selectのwhere句の方のジレンマだけ、 解決というか回避するオプションを導入しました。 ConditionBeanが、 ミリ秒のないDATETIMEに対する条件値のとき、 引数で指定された日時データのミリ秒を削る というものです。 昔のMySQLのJDBCドライバーがやっていたことを、 ConditionBeanが再現します。 littleAdjustmentMap.dfprop にて、 isDatetimePrecisionTruncationOfCondition を true に設定して自動生成し直すと、 そのような挙動になります。 もし、この問題をすでに抱えている、 もしくは、これを読んで問題があることがわかった方は、 こちらのオプションを検討してみてください。 ... ちなみに、 ミリ秒がないDATETIMEに対してのみの処理になっています。 例えば、DATETIME(1)のような、 ミリ秒の最初の一桁だけサポートしているカラムでも、 論理的には「0.1秒の隙」が発生する可能性があります。 ですが、そもそもミリ秒の最初の一桁だけのDATETIMEを 作ることはほとんどないでしょう、ということで。 ... こちら実現方法はちょっと大変でした。 「DATETIMEのミリ秒ありなし」を判断しないといけません。 ミリ秒ありのDATETIMEに対して何か余計なことを やってはいけないので、ちゃんと判断しないといけません。 でも、JDBCドライバーのメタデータから、 DATETIMEのミリ秒精度を取得することができないのです。 DatabaseMetaData@getColumns()の項目にないのです。 なので、苦肉の策で、INFORMATION_SCHEMAのテーブルを、 直接selectして、DATETIME_PRECISOIN を取得しています。 SchemaXML にも、datetimePrecision が追加されました。 マニアックですが、けっこう手間かかってます笑