LoginSignup
2
2

More than 1 year has passed since last update.

Golangを使用したMySQLで設定したデータ型の注意点

Posted at

概要

今回は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 . .
docker-compose.yml
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
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")
	if err != nil {
		log.Fatalln(err)
	}

	err = Db.Ping()
	if err != nil {
		log.Fatalln(err)
	}
	log.Println("データベース接続完了")

データベースのpassword等は今回は簡易的に表すために平文で記載しています。

本来は環境変数を設定し、configファイルから参照する方が良いです。

次にユーザーの構造体を定義し、ユーザーを取得する関数を作成します。

models/users.go
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を下記のように記載した上で実行してみましょう。

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を下記のように変更します。

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}

ユーザー情報を取得できました。

2
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
2
2