Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
178
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

@umisama

golangでSQLを叩くライブラリまとめ[基本/クエリビルダ/ORM]

アプリケーションを作る上でデータベースを避けて通ることはできません。その中で最もポピュラーでパワフル(かつ普及している)のはSQLでしょう。
golangでも快適にSQLを操作したい。

このエントリでやること

golangでSQLを操作する方法を、「基本(databse/sql)」から、SQLクエリを自動生成する「クエリジェネレータ」、最後に「ORM」という順序で解説します。
ライブラリの特徴などをいくつか抑えていくだけであって、網羅的ではない。雰囲気だけ。

操作方法

基本(database/sql)

標準ライブラリだけを使って頑張る。基本的にSQLを手書きして、変数とのマッピングも手で当てることになる。

sql.(*DB).Exec()を使うと、SQLクエリをそのまま叩ける。プリペアドステートメントはクエリのあとにガシガシ置いていくカンジ。

database/sql-create_table
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
    return err
}

_, err = db.Exec(
    `CREATE TABLE "HOGE" ("id" INTEGER, "hoge" VARCHAR(255))`,
)
if err != nil {
    return err
}

_, err = db.Exec(
    `INSERT INTO "HOGE" ("id", "hoge") VALUES (?, ?) `,
    1,
    "hogege",
)
if err != nil {
    return err
}

SELECTでentityを取りに行く時は、sql.(*DB).Query()を使う。こいつは列ごとにデータを拾ってきてくれるので、Scan()でマッピングする。

database/sql-query_execution_and_reading
rows, err := db.Query(
    `SELECT * FROM "HOGE"`,
)
if err != nil {
    return err
}

defer rows.Close()
for rows.Next() {
    id, hoge := 0, ""
    err = rows.Scan(&id, &hoge)
    if err != nil {
        return err
    }

    fmt.Println(id, hoge)
}

リレーションの解決やSQLのテーブルとgoのstruct(モデル?)との紐付けは、この中でガシガシと書いていく。非常に厳しいが、イロイロ悩まなくて済むのでまぁアリッちゃアリかもしれない。規模が小さいか、極端に大きいなら。

クエリビルダ

SQLをまるっと手で書くのは厳しいので、クエリビルダを使うという手がある。クエリビルダはORMのようにデータとオブジェクトを紐付けてくれるものではないが、SQLクエリそのものを作ることを手助けしてくれる。
プレーンテキストなSQLはSQLとしてvalidであることを保証できないが、クエリビルダを使えばある程度それができるのが良いし、コードとしての見栄えも良い。(見栄えは好みかな?) 反面、クエリビルダはあらゆるSQLを吐き出すことが出来るわけではないので、自由度は若干下がる。
クエリビルダとしては、次の2つの実装が有名だとおもう。

sqlbuilderはDropboxが作ったクエリビルダで、かなりシッカリとした造りになっている。テーブルの定義からSQLクエリを作る形になっているので、テーブルに存在しないカラムなんかも検出できる。

sqlbuilder
import sb "github.com/dropbox/godropbox/database/sqlbuilder"

t := sb.NewTable(
    "HOGE",
    sb.IntColumn("id", false),
    sb.StrColumn(
        "hoge",
        sb.UTF8,
        sb.UTF8CaseSensitive,
        false,
    ),
)

query, err := t.Select(t.C("id"), t.C("hoge")).Where(sb.EqL(t.C("id"), 0)).String("test")
if err != nil {
    return err
}

// query == "SELECT `HOGE`.`id`,`HOGE`.`hoge` FROM `test`.`HOGE` WHERE `HOGE`.`id`=0"
// err == nil

rows, err := db.Query(query)
// 〜〜以下略〜〜

吐出されたSQLをdatabase/sqlなりなんなりで実行すれば良い。ちなみに、godropboxのsqlbuilderはMySQLにしか対応していないのが残念ポイント。
# ちなみに、プリペアドステートメントを使ってくれないのもすごく不安になる。一応エスケープはするんだけれど、これで充分なことを信用して良いものか。

それに対して、squirrelはSQLの構文をそのままgoの構文に置き換えるようなシンプルなAPIになっている。使いやすいが、カラム名が正しいかなどはコーダの集中力以上の保証が無い。

squirrel-create_query
query, attrs, err := sq.Select("*").From("HOGE").Where(sq.Eq{
    "id": 1,
}).ToSql()
if err != nil {
    return err
}
// query == "SELECT * FROM HOGE WHERE id = ?"
// attrs == []interface{}{1}
// err == nil

rows, err := db.Query(query, attrs...)
// 〜〜以下略〜〜

ちなみに、squirrelには作ったSQLをそのまま吐き出すだけでなく、直接DBを叩いて結果を返してくれる使い方もある。地味に嬉しい便利機能。

squirrel-exec_query
rows, err := sq.Select("*").From("HOGE").Where(sq.Eq{
    "id": 1,
}).RunWith(db).Query()

squirrelは対応している構文に制限があったりして少し辛いのだけれど、気軽に使えるしソコソコ気にいっている。

ORM

まあ、データベース触るならORM使いたい。生SQLを書くのは個人的には3K業務(キツイ、キビシイ、カエリタイ)といえる。

gorpはORMとは名乗っていない("Go Relational Persistence"としている)し、実際にモデル間のリレーションには関知しないが、マァわりとORMに近い何かである。うーん、SQLのテーブルと、goのstructの間を繋ぐマッパーというのが正確か。

gorp-get_object
import "github.com/coopernurse/gorp"

type Hoge struct {
    Id   int    `db:"id"`
    Hoge string `db:"string"`
}

m := new(Hoge)
_, err := dbmap.Get(m, 1)
if err != nil {
    return err
}

// m == &Hoge{Id: 1, Hoge: "hogestring"}
// err == nil

モデルを定義しておけばクエリの結果を自動的に充ててくれるので、sqlパッケージを使うよりも楽。
また、クエリの実行後/実行前に関数を実行させることもできるので、これにリレーションの面倒を見させれば、少しORMに近づくことができる。

gorp-relation_resolving
type Hoge struct {
    Id   int    `db:"id"`
    Hoge string `db:"string"`
}

type Piyo struct {
    Id     int `db:"id"`
    HogeId int `db:"hoge_id"`

    // relation
    Hoge *Hoge `db:"-"`
}

func (m *Piyo) PostGet(s gorp.SqlExecutor) error {
    hoge := new(Hoge)
    _, err := s.Get(hoge, m.HogeId)
    if err != nil {
        return nil
    }

    m.Hoge = hoge
    return nil
}

m := new(Piyo)
_, err := dbmap.Get(m, 1)
if err != nil {
    return err
}

// m == &Piyo{Id: 1, HogeId: 1, Hoge: &Hoge{Id: 1, Hoge: "hogestring"}}
// err == nil

PostGet()はSELECTが終わったあとに呼ばれるインジェクション的な関数で、この中で関係しているオブジェクトを呼んでくると、リレーションが解決されたORM的な結果が得られる。
残念なのは、SQLのjoinとかを使えないので、パフォーマンスが最悪。シンプルだけど実用に厳しい面がある。

gorpは人気があり、そこそこ使われているようだ。ちなみに、似たコンセプトのORMに"genmai"というのもあって、こちらも便利機能がイロイロあって良い。また、"xorm"というキャッシュ機能付きの同様なライブラリもある。goのORMは"ORM"というよりは"Table-structマッパー"みたいなカンジのが多い。

それらに対してもっと筋肉質なORM、というかORMらしいORMとしてgormというのもある。gormはまさにORMで、リレーションの面倒も見てくれる。かなり強引なマイグレーション機能などの便利っぽい機能もイロイロ持っている。

gorm-define_relation
type Piyo struct {
    Id   int
    Hoge []Hoge `gorm:many2many:hoge_id`
}

struct tagで"many2many"と指示してやれば、多対多のリレーションとして認識してくれて、オブジェクトの取得時に適当に拾ってきてくれる。このようにgormはオブジェクト間のリレーションをきちんと解釈してくれるので、golangで"ORM"と言われるものの中では最もORMらしい動きをすると思う。
そういうわけでgormは便利そうなんだけれど、個人的には魔法っぽい機能が多くて安心できていない(コード追いかけるのが辛い)。

まとめ

golangでSQLデータベースを操作する方法として、"database/sql"のオーバービュからクエリビルダ、ORMまでよく使われているあたりをおさらいしてみた。
最近はgolangでデータベースを弄るための選択肢も、そこそこ充実してきたと思う。

ちなみに、僕個人のはなしをすると、(特段の事情が無い限りは)squirrelで作ったクエリをgorpでstructにマッピングさせている。制限はいろいろ有るんだけれど、まぁまぁ戦える。

雑感

でもgolangでSQLやるのは正直キビシイ。関係データベースとgolangの型構造の親和性がはっきり言って良くない。ORM達があんまりORMしていないのもそういう背景がある。
人生はキビシイ。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
178
Help us understand the problem. What are the problem?