4
2

More than 3 years have passed since last update.

MySQLのTIMESTAMP値をJVMのタイムゾーンに合わせて取得する方法

Last updated at Posted at 2021-08-07

「MySQLサーバのデフォルトタイムゾーン」と「Javaアプリケーションのタイムゾーン」が異なる(例:MySQLサーバがUTCでアプリケーション側がJSTなど)場合、MySQLの TIMESTAMP型 に設定されている値を何の考慮もせずに取得すると・・・意図しない日時なってしまうことがあります。

前提

本エントリーでは、特に断りがない場合は、

  • MySQLサーバのデフォルトタイムゾーンはUTC
  • JavaアプリケーションのタイムゾーンはJST(Asia/Tokyo)

という前提で記載します

意図しない日時となる具体例

意図しない日時となる具体例を見ていきましょう。以下のようなテーブルがあったとします。※TIMESTAMP型とDATETIME型の挙動の違いを示すためにDATETIME型も定義しておきます。

CREATE TABLE DATE_TEST (
    item_timestamp TIMESTAMP(3),
    item_datetime DATETIME(3)
);

このテーブルに、以下のSQLを実行してCURRENT_TIMESTAMPの値を入れます。

INSERT INTO DATE_TEST VALUES(CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);

ここでは、上記SQLを実行するセッションのタイムゾーンはUTCとします。すると・・・TIMESTAMP型およびDATETIME型のカラムには同じ日時を示す値が設定されます。例えば「2021-08-07T06:50:48.679Z」とします。

これを、JST(Asia/Tokyo)で動くJavaアプリケーションで取得するとどうなるかというと・・・以下のようになります(本エントリーではSpringのJdbcTemplateというクラスを使用した結果になります)。

java.time.LocalDateTime  : 2021-08-07T06:50:48.679
java.time.ZonedDateTime  : 2021-08-07T06:50:48.679+09:00[Asia/Tokyo]
java.time.OffsetDateTime : 2021-08-07T06:50:48.679+09:00
java.sql.Timestamp       : 2021-08-07 06:50:48.679

なんかおかしいですよね・・・ :cold_sweat: サーバに保存されている日時がUTC基準なので、その時刻をJSTで表現するなら「2021-08-07T15:50:48.679」になっていないと、異なる日時を示していることになってしまう。例えばOffsetDateTimeが「2021-08-07T06:50:48.679+09:00」になっているけど、これってUTCで表現すると「2021-08-06T21:50:48.679Z」なので・・・9時間時間が巻き戻ってしまいます :scream:

どうすればズレないようにすることができるの?

いろいろやり方はある(関数使ったり、アプリケーション側でタイムゾーン意識した値に変換したり・・)とは思いますが、JDBCドライバのオプションでセッションのタイムゾーンを指定する方法が良いのかな〜と思っています。

以下で紹介するオプションを利用すると、MySQLのコマンドライン上で、

set time_zone = 'Asia/Tokyo';

を実行するのと同義のようです。

MySQL Connector/J(JDBC Driver)

MySQL Connector/Jを使う場合は、8.0.23より追加されたオプション「forceConnectionTimeZoneToSession」を使うと「connectionTimeZone」オプションで指定したタイムゾーンに変換してくれます。「connectionTimeZone」オプションのデフォルトは「LOCAL(=JVMのタイムゾーン)」になります。

jdbc:mysql://localhost:3306/devdb?forceConnectionTimeZoneToSession=true

詳しい内容は リファレンス を参照してください。

MariaDB Connector/J(JDBC Driver)

MariaDB Connector/Jを使う場合は、「sessionVariables」オプションを使用すると「time_zone」に指定したタイムゾーンに変換してくれます。

jdbc:mysql://localhost:3306/devdb?sessionVariables=time_zone='Asia/Tokyo'

詳しい説明はないようですが、念のため リファレンス のリンクを貼っておきます。

DATETIME型だとどうなる?

私も詳しいところはわかっていませんが結論だけ記載すると・・・本エントリーで紹介したパラメータを指定しても、動作は変わりません。 例えば「2021-08-07T07:24:46.658Z」 でDBにINSERTしたものを検索すると・・・

TIMESTAMP型から取得した値は、

java.time.LocalDateTime  : 2021-08-07T16:24:46.658
java.time.ZonedDateTime  : 2021-08-07T16:24:46.658+09:00[Asia/Tokyo]
java.time.OffsetDateTime : 2021-08-07T16:24:46.658+09:00
java.sql.Timestamp       : 2021-08-07 16:24:46.658

という感じで、+9:00された値になりますが、DATETIME型から取得した値は、

java.time.LocalDateTime  : 2021-08-07T07:24:46.658
java.time.ZonedDateTime  : 2021-08-07T07:24:46.658+09:00[Asia/Tokyo]
java.time.OffsetDateTime : 2021-08-07T07:24:46.658+09:00
java.sql.Timestamp       : 2021-08-07 07:24:46.658

となってしまいます。

まとめ

タイムゾーンは合わせましょう!! ま〜パブリッククラウドのマネージドサービスを使うとデフォはUTCだよ〜だとは思いますが(今参画している案件がまさにこの状態w)
そして・・・タイムゾーンも難しいな〜 :sweat_smile:

検証バージョン

動作検証に使った各種プロダクトのバージョンは以下のとおりです。

  • MySQL 5.7 (on Testcontainers 1.5.3)
  • MySQL Connector/J 8.0.26
  • MariaDB Connector/J 2.7.3
  • Spring Boot 2.5.3 (Spring Framework 5.3.9)

検証コード

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2