Go その2 Advent Calendar 2015 – Qiita 1日目のエントリ第一弾です。(第二弾: Echo – Go 言語 Web Framework の紹介)
当社では現在プロダクトの開発に Go言語 を利用しています。開発の中で得られた知見を徐々に公開していきます。
Go 言語で利用されるデータベース関連パッケージでは、O/R Mapper に位置づけられる gorm や gorp、 QueryBuilder の squirrel が広く知られています。
今回のエントリでは、おすすめの O/R Mapper dbr を紹介します。
dbr は、現在 Star の数では gorm や gorp に比べ少ないですが、2015年9月に更新された V2.0 で機能が大幅に強化され、これから人気が高まるのではないかと予想しています。
dbr は MySQL と PostgreSQL に対応した O/R Mapper です。
公式には
gocraft/dbr provides additions to Go’s database/sql for super fast performance and convenience.
とうたわれています。
多機能な gorm と シンプルな gorp の中間に位置すると捉えています。
主な機能は
以上の2点です。Migration に関する機能は現在実装されていません。
実際に使ってみてよいと感じた点をあげます。
以下に、基本的な使いかたを示します。
利用するパッケージは以下のとおりです。
1 2 3 4 5 6 7 8 9 |
// go get "github.com/gocraft/dbr" import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/gocraft/dbr" "github.com/gocraft/dbr/dialect" "log" ) |
以下の struct (とデータベースのテーブル)を利用します。
1 2 3 4 5 6 7 8 9 10 11 |
type Member struct { Number int64 `db:"number"` Name string `db:"name"` IsValid bool `db:"is_valid"` } type LogPurchase struct { id uint64 `db:"id"` NumberId int64 `db:"number_id"` Amount int64 `db:"amount"` } |
dbr はデータベースと対応付けるさい、 CamelCase(NumberId)の Field 名を snake_case (number_id) に変換してくれます。したがって上記の例の場合 db tag は省略可能です。CamelCase と snake_case の対応が規則的でない場合のみ db tag を付与するだけでもよいのですが、当社では習慣的に db tag は必ず付与しています。
セッションを生成します。
1 2 3 |
conn, _ := dbr.Open("mysql", "user:password@tcp(127.0.0.1:3306)/mydb", nil) sess := conn.NewSession(nil) |
以下、ここで生成した sess
を利用して DB を操作します。
なお、公式の dbr/README.md に基本的な内容は書かれていますが、省略が多くわかりにくいところがあります(ドキュメント拡充の pull-req を出しています)。そのため本エントリではなるべく詳しく解説していきます。
INSERT 文の基本です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// INSERT result, err := sess.InsertInto("member"). Columns("number", "name", "is_valid"). Values(10, "Lionel", true). Exec() if err != nil { log.Fatal(err) } else { count, _ := result.RowsAffected() fmt.Println(count) // => 1 } // struct を利用した INSERT member := &Member{9, "Luis", false} sess.InsertInto("member"). Columns("number", "name", "is_valid"). Record(member). Exec() |
struct を初期化して INSERT 文の Values とする場合は、Record()
を用います。組み立てた Query をその場で実行するには Exec()
を利用します。
Exec()
行わない場合、Statement を 以下のように利用します。
1 2 3 4 5 6 7 8 9 |
buf := dbr.NewBuffer() insertStmt := sess.InsertInto("member"). Columns("number", "name", "is_valid"). Values(10, "Lionel", true) insertStmt.Build(dialect.MySQL, buf) // dialect.PostgreSQL も利用できます fmt.Println(buf.String()) // INSERT INTO `member` (`id`,`name`,`is_valid`) VALUES (?,?,?) fmt.Println(buf.Value()) // [10 Lionel true] |
続いて SELECT です。
1 2 3 4 |
var m []Member sess.Select("*").From("member").Load(&m) fmt.Println(m) // [{10 Lionel true} {9 Louis false}] |
Load()
の部分で 変数 m
に SELECT の結果がマッピングされています。
WHERE 句を利用します。
1 2 3 4 5 6 7 |
var m Member sess.Select("*"). From("member"). Where("is_valid = ?", true). Load(&m) fmt.Println(m) // {10 Lionel true} |
Where()
は fmt.Printf()
と似た使いかたです。複数条件は Where("is_valid = ? AND name = ?", true, "Lionel")
のように記述できます。
SELECT の結果が複数行の場合に利用する LoadStructs()
と1行の場合に利用する LoadStruct()
が用意されていますが、結果行数を意識せずにすむ Load()
を利用しています。
複数条件の WHERE 句には AndMap()
や OrMap()
が便利です。
1 2 3 4 5 6 |
var m Member condition := dbr.AndMap{"is_valid": true, "name": "Lionel"} sess.Select("number", "name", "is_valid"). From("member"). Where(condition). Load(&m) |
上記の WHERE 句は is_valid = TRUE AND name = "Lionel"
と等価です。
UPDATE です。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
result, err := sess. Update("member"). Set("name", "Andres"). Where("name = ?", "Luis"). Exec() if err != nil { log.Fatal(err) } else { // 変更されたレコード数を取得 count, _ := result.RowsAffected() fmt.Println(count) // => 1 } |
Set()
の代わりに SetMap()
を利用する例です。
1 2 3 4 5 |
attrsMap := map[string]interface{}{"number": 13, "name": "Claudio"} sess.Update("member"). SetMap(attrsMap). Where("name = ?", "Andres"). Exec() |
あらかじめ定義した マップ attrsMap
を SetMap()
の引数にあてています。
DELETE です。
1 2 3 |
sess.DeleteFrom("member"). Where("name = ?", "Claudio"). Exec() |
JOIN を行う例です。
1 2 3 4 5 6 7 8 9 |
type MemberPurchase struct { Name string Amount int64 } var mp []MemberPurchase sess.Select("name", "amount").From(dbr.I("member").As("m")). LeftJoin(dbr.I("log_purchase").As("l"), "m.id = l.member_id"). Load(&mp) |
dbr.I("member").As("m")
はテーブルネームのエイリアス(AS) を定義しています。エイリアスが不要な場合は単に ("member")
とします。
LeftJoin()
は第一引数に JOIN 対象のテーブル名を、第二引数に JOIN のキーとするカラムを指定します。同様に、エイリアスが必要ない場合テーブル名は単に ("log_purchase")
とできます。
上記の例は LEFTJOIN log_purchase AS l ON m.id = l.member_id
と等価です。
Join(= INNERT JOIN), LeftJoin, RightJoin, FullJoin (= FULL OUTER JOIN) があります。
SQL 文を直接記述する方法もあります。SelectBySql()
を利用します。
1 2 |
var m []Member sess.SelectBySql("SELECT number, name, is_valid FROM member").Load(&m) |
InsertBySql()
UpdateBySql()
など、CRUD の各々に同様のメソッドが用意されています。
トランザクションを利用する例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
tx, _ := sess.Begin() // トランザクションを開始 _, err := sess.InsertInto("member"). Columns("number", "name", "is_valid"). Values(14, "Javier", true). Exec() if err != nil { // エラーが発生したらロールバック tx.Rollback() } else { // エラーが発生しなければコミット tx.Commit() } |
また、 Go 言語の defer Statement と組み合わせての利用が推奨されているRollbackUnlessCommitted()
があります。名前の通り、完了していないトランザクションがあればロールバックして終了するメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
func createMember() error { tx, _ := sess.Begin() // トランザクションを開始 defer tx.RollbackUnlessCommitted() // この function の終了時に実行される _, err := sess.InsertInto("member"). Columns("id", "name", "is_valid"). Values("14", "Javier", true). Exec() if err != { // エラー発生時の処理 } else { tx.Commit() } return err } |
以上、CURD と トランザクションの使いかたを解説しました。
gorm と gorp の二択で迷っているかたもいると思います。dbr を選択肢にいれてみてはいかがでしょうか。