読者です 読者をやめる 読者になる 読者になる

GORMを使ってみた

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のサイトのマニュアルがなかなかきちんと説明してあるので、詳しくはそちらを参照するといいと思う。