お題
Golangで有名なORマッパーライブラリのGormには、定義した構造体に応じたRDBのテーブルを自動生成する機能がある。
このオートマイグレーションをいろいろ試してみる。
開発環境
# OS
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="17.10 (Artful Aardvark)"
# Golang
$ go version
go version go1.11.2 linux/amd64
バージョンの切り替えはgoenvで行っている。
# Gorm
Version: v1.9.2
実践
各試行共通の準備
RDBはMySQLを対象とする。
ローカルで↓のようなYamlを作っておく。
(dockerとdocker-composeは当然インストール済)
[docker-compose.yml]
version: '3'
services:
db:
image: mysql:5.7.24
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_USER: testuser
MYSQL_PASSWORD: testpass
MYSQL_DATABASE: testdb
起動。
$ sudo docker-compose up
試しにコンテナ内のDBに接続。
$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
831188d7e688 mysql:5.7.24 "docker-entrypoint.s…" 5 minutes ago Up 5 minutes 0.0.0.0:3306->3306/tcp, 33060/tcp gorm_db_1_46a022cef614
$
$ sudo docker exec -it gorm_db_1_46a022cef614 bash
root@831188d7e688:/# mysql -utestuser -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.24 MySQL Community Server (GPL)
〜〜省略〜〜
mysql>
各試行共通のmain.go
[main.go]
func main() {
db, err := gorm.Open("mysql", "testuser:testpass@tcp(127.0.0.1:3306)/testdb?charset=utf8&parseTime=True&loc=Local")
if err != nil {
panic(err)
}
defer db.Close()
db.LogMode(true)
if err := db.DB().Ping(); err != nil {
panic(err)
}
db.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8").AutoMigrate(
&model.User{},
&model.UserGroup{},
// ★↑のオートマイグレーション対象モデルは各試行によって変わる★
)
}
■gorm.Model
を使ったカラム自動生成
以下のように中身は同じ2つの構造体を定義。
[model/user.go]
type User struct {
gorm.Model
}
[model/usergroup.go]
type UserGroup struct {
gorm.Model
}
マイグレート実行。
$ go run main.go
すると、↓のようなテーブル名は異なるが中身が同じテーブルが生成される。
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| testdb |
+--------------------+
2 rows in set (0.00 sec)
mysql> use testdb;
Database changed
mysql> show tables;
Empty set (0.00 sec)
mysql>
mysql> show tables;
+------------------+
| Tables_in_testdb |
+------------------+
| user_groups |
| users |
+------------------+
2 rows in set (0.00 sec)
mysql> desc users;
+------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
| deleted_at | timestamp | YES | MUL | NULL | |
+------------+------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
mysql> desc user_groups;
+------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
| deleted_at | timestamp | YES | MUL | NULL | |
+------------+------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
明示的に定義しなくても、なんかいろいろカラム作ってくれてる。
■明示的にカラムの定義を指示
[model/hogefuga.go]
type HogeFuga struct {
HogeID int `gorm:"primary_key"` // プライマリーキーの指示
Name string `gorm:"column:hoge_fuga_name"` // プロパティとは別の名前でカラム定義
Field string `gorm:"type:varchar(100)"` // 型・桁を指示
UniqueField string `gorm:"unique"` // 重複不可を指示
NotNullField string `gorm:"not null"` // Not Nullを指示
IgnoreField string `gorm:"-"` // カラム化しない指示
AutoIncField int `gorm:"AUTO_INCREMENT"` // オートインクリメント指示
IndexField int `gorm:"index"` // インデックスを貼る
UniqueIndexField int `gorm:"unique_index"` // ユニークインデックスを貼る
NamedIndexField int `gorm:"index:other_name_idx"` // インデックスの名前を指示
NoInstructionField string // 指定なし
}
こうなる。
mysql> desc hoge_fugas;
+----------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------------+--------------+------+-----+---------+----------------+
| hoge_id | int(11) | NO | PRI | NULL | auto_increment |
| hoge_fuga_name | varchar(255) | YES | | NULL | |
| field | varchar(100) | YES | | NULL | |
| unique_field | varchar(255) | YES | UNI | NULL | |
| not_null_field | varchar(255) | NO | | NULL | |
| auto_inc_field | int(11) | YES | | NULL | |
| index_field | int(11) | YES | MUL | NULL | |
| unique_index_field | int(11) | YES | UNI | NULL | |
| named_index_field | int(11) | YES | MUL | NULL | |
| no_instruction_field | varchar(255) | YES | | NULL | |
+----------------------+--------------+------+-----+---------+----------------+
10 rows in set (0.01 sec)
mysql> show index from hoge_fugas;
+------------+------------+-----------------------------------+--------------+--------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+------------+------------+-----------------------------------+--------------+--------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| hoge_fugas | 0 | PRIMARY | 1 | hoge_id | A | 0 | NULL | NULL | | BTREE | | |
| hoge_fugas | 0 | unique_field | 1 | unique_field | A | 0 | NULL | NULL | YES | BTREE | | |
| hoge_fugas | 0 | uix_hoge_fugas_unique_index_field | 1 | unique_index_field | A | 0 | NULL | NULL | YES | BTREE | | |
| hoge_fugas | 1 | idx_hoge_fugas_index_field | 1 | index_field | A | 0 | NULL | NULL | YES | BTREE | | |
| hoge_fugas | 1 | other_name_idx | 1 | named_index_field | A | 0 | NULL | NULL | YES | BTREE | | |
+------------+------------+-----------------------------------+--------------+--------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
5 rows in set (0.01 sec)
ユニークキー制約を確認。
mysql> insert into hoge_fugas(not_null_field, unique_field) values('one', '001');
Query OK, 1 row affected (0.01 sec)
mysql> insert into hoge_fugas(not_null_field, unique_field) values('one', '001');
ERROR 1062 (23000): Duplicate entry '001' for key 'unique_field'
■テーブル名を指示
[model/foo.go]
type Foo struct {
gorm.Model
}
func (Foo) TableName() string {
return "foo_alias"
}
mysql> show tables;
+------------------+
| Tables_in_testdb |
+------------------+
| foo_alias |
+------------------+
mysql> desc foo_alias;
+------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
| deleted_at | timestamp | YES | MUL | NULL | |
+------------+------------------+------+-----+---------+----------------+
4 rows in set (0.01 sec)
■初期化時のプロパティに応じてテーブル名を変える
[model/baa.go]
type Baa struct {
gorm.Model
Kind int
}
func (b Baa) TableName() string {
if b.Kind == 1 {
return "admin_baa"
} else {
return "user_baa"
}
}
これを↓のようにオートマイグレーション時にプロパティを指示することでテーブル名を変える。
[main.go]
func main() {
db, err := gorm.Open("mysql", "testuser:testpass@tcp(127.0.0.1:3306)/testdb?charset=utf8&parseTime=True&loc=Local")
〜〜省略〜〜
db.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8").AutoMigrate(
&model.Baa{},
&model.Baa{Kind: 1},
)
}
すると、条件に応じたテーブル名のテーブルができている。
mysql> show tables;
+------------------+
| Tables_in_testdb |
+------------------+
| admin_baa |
| user_baa |
+------------------+
まとめ
ひとまず基礎中の基礎のような部分をピックアップ。
次回はリレーション関係にトライしよう。