概要
今回はMySQLを使用して、作成したテーブルのカラムのデータ型でtime.Time
型を指定した際の注意点を共有します。
前提
ディレクトリ構造
.
├── models
│ ├── base.go
│ └── users.go
├── docker-compose.yml
├── Dockerfile
├── go.mod
├── go.sum
└── main.go
今回はDockerを使用して環境構築をしています。
FROM golang:1.19.0-alpine3.16
WORKDIR /app
COPY . .
version: '3.9'
services:
db:
image: mysql:8.0
container_name: mysql
env_file: .env
ports:
- 3306:3306
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DB_NAME}
volumes:
- ./mysql:/var/db/mysql
web:
build:
context: .
container_name: web
volumes:
- .:/app
depends_on:
- db
volumes:
mysql:
今回は下記のようなusersテーブルを作成します。
usersテーブル | 型 |
---|---|
id | INT |
name | VARCHAR(32) |
created_at | DATETIME |
package models
import (
"database/sql"
"fmt"
"log"
"github.com/soicchi/golang-todo-app/config"
"github.com/google/uuid"
_ "github.com/go-sql-driver/mysql"
)
var Db *sql.DB
var err error
const (
tableNameUser = "users"
)
func init() {
Db, err = sql.Open("mysql", "root:password@tcp(mysql)/db")
if err != nil {
log.Fatalln(err)
}
err = Db.Ping()
if err != nil {
log.Fatalln(err)
}
log.Println("データベース接続完了")
データベースのpassword
等は今回は簡易的に表すために平文で記載しています。
本来は環境変数を設定し、configファイルから参照する方が良いです。
次にユーザーの構造体を定義し、ユーザーを取得する関数を作成します。
package models
import (
"log"
"time"
)
type User struct {
ID int
Name string
CreatedAt time.Time
}
func GetUser(id int) (user User, err error) {
user = User{}
cmd := `select * from users where id=?`
err = Db.QueryRow(cmd, id).Scan(
&user.ID,
&user.Name,
&user.CreatedAt,
)
return user, err
エラー内容
では、main.goを下記のように記載した上で実行してみましょう。
package main
import (
"fmt"
"log"
"github.com/soicchi/golang-todo-app/models"
)
func main() {
fmt.Println(models.Db)
user, err := models.GetUser(2)
if err != nil {
log.Fatalln(err)
}
fmt.Println(user)
}
すると下記のようなエラーが出ました。
2022/08/14 01:26:11 main.go:33: sql: Scan error on column index 2, name "created_at": unsupported Scan, storing driver.Value type []uint8 into type *time.Time
exit status 1
どうやらcreated_at
の型が原因でエラーが出ているようです。
解決方法
ドキュメントをみると下記のように書いてありました。
The default internal output type of MySQL DATE and DATETIME values is []byte which allows you to scan the value into a []byte, string or sql.RawBytes variable in your program.
However, many want to scan MySQL DATE and DATETIME values into time.Time variables, which is the logical equivalent in Go to DATE and DATETIME in MySQL. You can do that by changing the internal output type from []byte to time.Time with the DSN parameter parseTime=true. You can set the default time.Time location with the loc DSN parameter.
どうやらtime.Time
型を指定した場合にMySQLの接続情報にパラメータとしてparseTime=true
を含めないといけないようです。
なので先ほどのmodels/base.go
を下記のように変更します。
package models
import (
"database/sql"
"fmt"
"log"
"github.com/soicchi/golang-todo-app/config"
"github.com/google/uuid"
_ "github.com/go-sql-driver/mysql"
)
var Db *sql.DB
var err error
const (
tableNameUser = "users"
)
func init() {
Db, err = sql.Open("mysql", "root:password@tcp(mysql)/db?parseTime=true") // <- 変更点
if err != nil {
log.Fatalln(err)
}
err = Db.Ping()
if err != nil {
log.Fatalln(err)
}
log.Println("データベース接続完了")
もう一度実行してみると
2022/08/14 01:32:12 base.go:34: データベース接続完了
{2 f0e083e0-1b24-11ed-8afc-0242c0a87003 test test@test.com 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8 2022-08-13 16:28:19 +0000 UTC}
ユーザー情報を取得できました。