eurie Inc.
✕
  • Home
  • About Us
    • Company Profile
    • Our Philosophy
    • Team
  • Products & Services
  • Blog
  • Contact

dbr – Go 言語 O/R Mapper の紹介

2015-12-01Takahiro IkeuchiEngineering
このエントリーをはてなブックマークに追加

Go その2 Advent Calendar 2015 – Qiita 1日目のエントリ第一弾です。(第二弾: Echo – Go 言語 Web Framework の紹介)

当社では現在プロダクトの開発に Go言語 を利用しています。開発の中で得られた知見を徐々に公開していきます。

Go 言語で利用されるデータベース関連パッケージでは、O/R Mapper に位置づけられる gorm や gorp、 QueryBuilder の squirrel が広く知られています。

今回のエントリでは、おすすめの O/R Mapper dbr を紹介します。

  • gocraft/dbr – Github

dbr は、現在 Star の数では gorm や gorp に比べ少ないですが、2015年9月に更新された V2.0 で機能が大幅に強化され、これから人気が高まるのではないかと予想しています。

dbr の特徴

dbr は MySQL と PostgreSQL に対応した O/R Mapper です。

公式には

gocraft/dbr provides additions to Go’s database/sql for super fast performance and convenience.

とうたわれています。

多機能な gorm と シンプルな gorp の中間に位置すると捉えています。

主な機能は

  • Model(struct)とデータベースの Mapping
  • Query Builder

以上の2点です。Migration に関する機能は現在実装されていません。

実際に使ってみてよいと感じた点をあげます。

  • Query Builder がシンプルで柔軟なこと
    • As(), SetMap(), AndMaps() などを利用して不自由なく Query を組み立てられる
  • NullString NullInt64 などの null 許容カラムに対応する struct があらかじめ定義されていること 1

基本的な使いかた

以下に、基本的な使いかたを示します。

インポートと初期化

利用するパッケージは以下のとおりです。

Package import
Go
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 (とデータベースのテーブル)を利用します。

Struct
Go
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 は必ず付与しています。

セッションを生成します。

Init
Go
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

INSERT 文の基本です。

INSERT
Go
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 を 以下のように利用します。

Query Builder
Go
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

続いて SELECT です。

SELECT
Go
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 句を利用します。

SELECT WHERE
Go
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() が便利です。

SELECT WHERE AndMap
Go
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

UPDATE です。

UPDATE
Go
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() を利用する例です。

UPDATE SetMap()
Go
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

DELETE です。

DELETE
Go
1
2
3
sess.DeleteFrom("member").
    Where("name = ?", "Claudio").
    Exec()

JOIN

JOIN を行う例です。

SELECT JOIN
Go
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 を直接記述する

SQL 文を直接記述する方法もあります。SelectBySql() を利用します。

SELECT By SQL
Go
1
2
var m []Member
sess.SelectBySql("SELECT number, name, is_valid FROM member").Load(&m)

InsertBySql() UpdateBySql()など、CRUD の各々に同様のメソッドが用意されています。

トランザクション管理

トランザクションを利用する例です。

Transaction
Go
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() があります。名前の通り、完了していないトランザクションがあればロールバックして終了するメソッドです。

RollbackUnlessCommitted
Go
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 を選択肢にいれてみてはいかがでしょうか。

あわせて読みたい

  • Echo – Go 言語 Web Framework の紹介

  1. database/sql の sql.NullString struct は REST API で JSON のレスポンス返すさいに相性が悪く解消のためのプラクティスが知られています。 dbr.NullString はあらかじめこれに対処しているものです。 ↩
このエントリーをはてなブックマークに追加
DataBase, dbr, Go lang
Takahiro Ikeuchi
Founder & CEO @iktakahiro
Previous post Echo – Go 言語 Web Framework の紹介 Next post Tシャツが完成しました

最近の投稿

  • Echo – Go 言語 Web Framework の紹介
  • dbr – Go 言語 O/R Mapper の紹介
  • Tシャツが完成しました
  • PyCon JP 2015 ポスターセッション についてのお知らせ
  • PyCon JP 2015 ジョブフェア についてのお知らせ

Archive

  • 2015年12月
  • 2015年10月
  • 2015年9月

Category

  • Announce
  • Engineering
  • Home
  • About Us
    • Company Profile
    • Our Philosophy
    • Team
  • Products & Services
  • Blog
  • Contact
© 2015 eurie inc.