goで使えるO/R Mapperは結構いろいろあるみたいで、とりあえず今日1日 gorpとGORMを使ってみた。
gorp
GitHub - go-gorp/gorp: Go Relational Persistence - an ORM-ish library for Go
GORM
Getting Started with GORM · GORM Guide
gorpはシンプルに使える、GORMは多機能、という印象。とりあえず今回はGORMについてまとめてみる。
ソースは以下に置いてあります。
hatena_blog/go/gorm at master · egawata/hatena_blog · GitHub
下準備
mysql サーバを立て、以下の準備を行う。
databaseの作成
mydb2 という名称のデータベースを作成。
CREATE DATABASE mydb2 DEFAULT CHARSET=utf8;
table の作成
GORMには関係テーブルを便利に扱う機能があるため、今回は2つテーブルを作成し関係を持たせるようにした。1人のMemberが、複数の Hobby を持つようにしてある。
CREATE TABLE member ( id INTEGER NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL, birthday VARCHAR(5), blood_type CHAR(2), created_at DATETIME, updated_at DATETIME, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=UTF8; CREATE TABLE hobby ( id INTEGER NOT NULL AUTO_INCREMENT, member_id INTEGER NOT NULL, name VARCHAR(100) NOT NULL, created_at DATETIME, updated_at DATETIME, PRIMARY KEY (id), CONSTRAINT FOREIGN KEY (member_id) REFERENCES member(id) ) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
ユーザの作成
user1 というユーザを作成した。
GRANT ALL ON mydb2.* TO user1@'localhost' IDENTIFIED BY 'password1';
コーディング
必要なパッケージの import
import ( _ "github.com/go-sql-driver/mysql" "github.com/jinzhu/gorm" )
mysqlから使用する場合、最低限以上の2つが必要。
データを格納する構造体の作成
type Member struct { Id int64 `gorm:"primary_key"` Name string Birthday string BloodType string Hobbies []Hobby CreatedAt time.Time UpdatedAt time.Time } type Hobby struct { Id int64 `gorm:"primary_key"` MemberId int64 Name string CreatedAt time.Time UpdatedAt time.Time }
テーブル/カラム名と構造体メンバ名の関連
上記のような構造体を作成すると、DB上のテーブル、カラムと構造体との関連付けが自動的に行われる。デフォルトでは
- テーブル名 = 構造体の型名の複数形 (例:Member -> members)
- カラム名 = 構造体の中の各メンバ名をsnake_caseにしたもの (例: BloodType -> blood_type)
デフォルトの命名規則を変えることも可能。例えば今回はテーブル名についてはデフォルトと異なり複数形にしていないので、以下のように実際のテーブル名を返すメソッドを追加する。
func (m *Member) TableName() string { return "member" } func (h *Hobby) TableName() string { return "hobby" }
カラム名を変更したい場合は、構造体のメンバ宣言に以下のようにタグをつける。(今回は使用しないけど)
Name string `gorm:"column:another_name"`
Has-Many 関係の表現
上記の Member 構造体の中には、元のmember テーブルにはない Hobbies というメンバが定義されている。
Hobbies []Hobby
これは、1人のMemberが複数の Hobby を持つというテーブル間の関係を表現している。このあたりも GORM がいい感じに処理してくれる。
追加・更新・削除日時の自動更新
CreatedAt, UpdatedAt, DeleteAt という time.Time 型のメンバを追加すると、GORM側でここに追加・更新・削除日時を自動的に入るようになる。
DB接続
const ( dsn = "user1:password1@tcp(127.0.0.1:3306)/mydb2?parseTime=true&loc=Asia%2FTokyo" ) db, err := gorm.Open("mysql", dsn) defer db.Close()
parseTime=true を入れておかないと、time.Time型のメンバに値を取り込む際にエラーとなってしまう。またデフォルトではタイムゾーンが世界標準時となるので、指定したいのなら loc=Asia/Tokyo のようにする(URIエスケープが必要)。
レコード追加
members := []Member{ {Name: "ミク", Birthday: "10/19", BloodType: "AB", Hobbies: []Hobby{{Name: "ブログ"}, {Name: "ショッピング"}}}, {Name: "マホ", Birthday: "1/8", BloodType: "AB", Hobbies: []Hobby{{Name: "漫画"}, {Name: "ゲーム"}}}, {Name: "コヒメ", Birthday: "11/24", BloodType: "O", Hobbies: []Hobby{{Name: "ゲーム"}, {Name: "茶道"}}}, } for _, member := range members { db.Create(&member) }
レコード追加はdb.Create()で行う。あらかじめ Member 構造体にデータを代入しておき、引数でそのアドレスを指定する。
Hobbies の値も同時に指定すると、member, hobby の両テーブルにレコードが挿入される。
すべてのレコードを取得
var allMembers []Member
db.Find(&allMembers)
fmt.Println(allMembers)
結果を受けるMember型の空のスライスを用意しておき、db.Find()の引数でそのアドレスを渡す。
条件を指定してレコードを取得
var miku Member db.Where("name = ?", "ミク").First(&miku) db.Model(&miku).Related(&miku.Hobbies) fmt.Printf("%#v\n", miku)
db.Where()で検索条件を指定。結果を受けるMember型の変数を用意しておき、First()の引数でそのアドレスを渡して結果を受け取る。
ただしこれだけだと member テーブルの値のみが取得され、関係テーブル(hobby)への問い合わせは発生しない。
db.Model().Related()を実行した時点で、関連テーブル(hobby)の情報が 取得できる。
GORMのサイトのマニュアルがなかなかきちんと説明してあるので、詳しくはそちらを参照するといいと思う。