はじめに
GoでORMを使用する時の定番である gorm のお話2つ目。
以下動作は Databse MySQL5.7 の場合です。
gorm の Auto Migrations
は便利なんですが time.Time
が絡むと地味に厄介です。
今回のネタは上記の問題を解決して マイクロ秒(6桁)まで格納する
までの検証です。
検証: デフォルト
gormのタグを何も定義せずに time.Time
型 で Auto Migrations
してみましょう。
今回の検証とは関係ないですがMySQL/Goでは time.Time
を使用したい場合DatabaseUrlに?parseTime=true
をしていおかないと上手く time.Time
型にパース出来ない事があります。
以下の実装では
- table作成(Drop/AutoMigrate)
- データ投入 (Create) =>
2006-01-02 15:04:05.999999999
- レコード取得1件 (First)
を行います。
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
"time"
)
type Sample struct {
SampleDate time.Time
}
func main() {
db, err := gorm.Open("mysql", "test:test@/sample?parseTime=true")
if err != nil {
panic("failed to connect database")
}
defer db.Close()
// Drop Table
if err := db.DropTableIfExists(&Sample{}).Error; err != nil {
panic(err.Error())
}
// Migrate the schema
if err := db.AutoMigrate(&Sample{}).Error; err != nil {
panic(err.Error())
}
// Create
dt := time.Date(2006, 1, 2, 15, 4, 5, 999999999, time.UTC)
fmt.Printf("insert data = %+v\n", dt)
db.Create(&Sample{SampleDate: dt})
var result Sample
if err := db.First(&result).Error; err != nil {
panic(err.Error())
}
fmt.Printf("result: %+v\n", result)
}
> SHOW CREATE TABLE samples;
CREATE TABLE `samples` (
`sample_date` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
insert data = 2006-01-02 15:04:05.999999999 +0000 UTC
result: {SampleDate:2006-01-02 15:04:05 +0000 UTC}
timestamp
型でColumnが作られました。
timestamp
型には以下の問題があります。
- デフォルトでは秒までしか入らない
-
2038-01-19 03:14:07
までしか格納できない2038年問題が発生する
Selectの結果を見ても秒までしか取得できていないことが分かります。
解決策: datetime(6)
型で定義する
公式Docに書いてありますが gorm では structタグ で型を定義できます。
SampleDateのタグに gorm:"type:datetime(6)"
を設定します。
type Sample struct {
SampleDate time.Time `gorm:"type:datetime(6)"`
}
実行結果は以下の通り。
insert data = 2006-01-02 15:04:05.999999999 +0000 UTC
result: {SampleDate:2006-01-02 15:04:05.999999 +0000 UTC}
マイクロ秒(6桁)まで正しく取得できました。
マイクロ秒は9桁までありますがMySQLでは datetime(6)
以上の精度では設定できません。
7桁目以降の精度の値は切り捨てられている点は注意が必要です。
おまけ: ミリ秒を使いたい
マイクロ秒ではなくミリ秒を使いたい場合もあると思います。
その場合は、ミリ秒まで格納できるよう datetime(3)
のColumnを定義します。
type Sample struct {
SampleDate time.Time `gorm:"type:datetime(3)"`
}
マイクロ秒まで持ったtime.Time
を格納すると
dt := time.Date(2006, 1, 2, 15, 4, 5, 123999999, time.UTC)
fmt.Printf("insert data = %+v\n", dt)
db.Create(&Sample{SampleDate: dt})
insert data = 2006-01-02 15:04:05.123999999 +0000 UTC
result: {SampleDate:2006-01-02 15:04:05.124 +0000 UTC}
四捨五入されます。
MySQL5.5まではミリ秒以上の精度のデータは切り捨て
MySQL5.6以降の場合はミリ秒以上の精度のデータは四捨五入されます。
テストデータでうっかり四捨五入されてtestに失敗しないよう注意が必要です。
まとめ
-
time.Time
の定義はgormではデフォルトでtimestamp
型になる - structタグで色々出来ます公式Docを読みましょう
- MySQL/Goではマイクロ秒は6桁までしか精度が担保できない
- datetime(3) (ミリ秒) を使う場合はマイクロ秒の精度の値が格納された際の考慮が必要