データベースとSQL
gorpとは
Webアプリケーションの構築にデータベースは欠かせません。ここでは「gorp」パッケージを利用したデータベースの操作方法について解説します。
gorpは軽量なORM(Object-Relational Mapping)ライブラリで、ちょうどGojiと net/http の関係と同じように、標準パッケージ「database/sql」に対する薄いラッパーという位置づけになっています。Ruby on RailsのActiveRecordライブラリのように多機能なものではありませんが、その代わりにシンプルで理解が容易です。
gorpを使ってMySQLに接続する
接続するデータベースに「SQLite」を用いれば解説がしやすいのですが、Windows環境+Go本体だけを前提にするとSQLiteのドライバを導入する手順が煩雑になるため、本稿ではMySQLを使用します。MySQLが稼働しているホスト、データベース名、認証情報については接続が可能であれば任意のもので問題ありません。
まずはgorp本体とMySQLドライバ「go-sql-driver/mysql」をインストールします。
C:¥> go get -u github.com/go-sql-driver/mysql C:¥> go get -u github.com/coopernurse/gorp
問題がなければ、データベースへ接続してみます。
package main
import (
"fmt"
"log"
"database/sql"
"github.com/coopernurse/gorp"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "username:pasword@tcp(localhost:3306)/gorp?charset=utf8&parseTime=true")
if err != nil {
log.Fatal(err)
}
if err = db.Ping(); err != nil {
log.Fatal(err)
}
dbmap := &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "UTF8"}}
fmt.Printf("%#v", dbmap)
}
接続情報は次のフォーマットで、文字列として与えます。使用する環境に合わせて書き換えてください。
"[ユーザ]:[パスワード]@[接続プロトコル]/[データベース名]?[その他パラメータ]"
sql.Open() にドライバの種類を表す文字列と接続情報文字列を渡すと sql.Db 型の戻り値を得ます。最初のエラーチェックは主にドライバ名などに問題があった場合に対応しています。次に db.Ping() を利用してデータベースに実際に接続できるかのチェックを行っています。いずれの箇所でも、エラーが発生した際には log.Fatal() を呼び出しています。エラーを出力しつつプログラムを中断してくれる便利な書き方です。
データベース接続の確認がとれたら gorp.DbMap 型を作成します。gorpによるデータベース操作の起点になる構造体です。gorp.DbMap 型に設定している Dialect には、データベースによってそれぞれ異なるSQLの方言を吸収してくれる構造体を渡します。
レコードを作成する
gorp.DbMap 構造体へテーブル情報を登録します。接続するMySQLには次の構造を持つテーブルがあるものとします。テーブル名は「users」です。
| カラム名 | 型 | 主キー | その他 |
|---|---|---|---|
| id | int(11) | ⃝ | オートインクリメント |
| name | varchar(255) | NOT NULL | |
| varchar(255) | NOT NULL | ||
| created_at | datetime | NOT NULL | |
| updated_at | datetime | NOT NULL |
users テーブルのレコードに対応する構造体を定義します。db タグを指定することで、構造体のフィールド名とテーブルのカラム名間の対応関係を定義できます。
type User struct {
Id int `db:"id"`
Name string `db:"name"`
Email string `db:"email"`
CreatedAt time.Time `db:"created_at"` // import "time"が必要
UpdatedAt time.Time `db:"updated_at"` // 同上
}
gorp.DbMap 構造体へ User 型とテーブルの対応関係を登録します。
dbmap.AddTableWithName(User{}, "users").SetKeys(true, "Id")
上記のコードの gorp.TableMap.Setkeys() の部分は、第1引数で「主キーがオートインクリメンタル(自動採番)である」ことを、第2引数で「主キーのカラム名」に対応する「User 構造体のフィールド名」を指定しています。
準備は整ったので、まずは users テーブルにレコードを INSERT してみましょう。
tx, _ := dbmap.Begin()
err := tx.Insert(&User{Name: "山田太郎", Email: "yamada.taro@example.com", CreatedAt: time.Now(), UpdatedAt: time.Now()})
if err != nil {
tx.Rollback()
log.Fatal(err)
}
tx.Commit()
gorp.DbMap.Begin() でトランザクションを開始してローカル変数 tx に gorp.Transaction 構造体を割り当てています。tx.Insert() へ初期化した User 構造体のポインタを渡すことで自動的に INSERT 文が発行されます。users テーブルの id カラムはオートインクリメントされるため、対応するフィールドの初期化は必要ありません。
INSERT処理が成功した場合には、tx.Commit() の位置でトランザクションをコミットします。エラーが発生した場合には tx.Rollback() の位置でロールバックを発行しプログラムを中断しています[1]。
[1]
dbmap.Begin()、tx.Commit()、tx.Rollback()のいずれの箇所もエラーを返す可能性があります。エラー処理を省略していることに注意してください。
SQLのログを確認する
User 構造体を渡すだけで自動的に INSERT 文が生成されて実行されることは分かりましたが、実際にどのようなSQL文が発行されているのかを確認したいときには、どうすればよいでしょうか。
gorp内部で生成しているSQLを確認するには、gorp.DbMap.TraceOn() を設定します。次の例では os.Stdout を指定して標準出力に出力させています。
dbmap.TraceOn("[Test]", log.New(os.Stdout, "test:", log.Lmicroseconds))
標準出力以外にも、「log」パッケージの機能を利用して任意のファイルに書き出すこともできます。
SELECT文を発行する
テーブルへのレコード追加が成功したので、次はクエリを発行してそのレコードを取り出してみましょう。次のように gorp.DbMap.Select() に User 構造体型と任意の SELECT 文を与えると、レコードを取り出すことができます。
users, _ := dbmap.Select(User{}, "SELECT * FROM users")
for _, u := range users {
fmt.Printf("%v¥n", u)
}
// 出力結果
// &{1 山田太郎 yamada.taro@example.com 2014-12-02 08:44:58 +0000 UTC 2014-12-02 08:44:58 +0000 UTC}
この例では発行するSQL文を単純な文字列で与えていますが、SQL文にプレースホルダを設定してパラメータに引数をバインドすることも可能です。むしろ「SQLインジェクション」などの脆弱性を作らないために、こちらの方法を推奨します。
dbmap.Select(User{}, "SELECT * FROM users WHERE id = ?", 1)
// => SELECT * FROM users WHERE id = 1
dbmap.Select(User{}, "SELECT * FROM users WHERE id IN (?)", []int{1,2,3})
// => SELECT * FROM users WHERE id IN (1,2,3)
gorp には、ここまでで紹介した以外にも数多くの機能があります。gorp のトップページに掲示されているドキュメントに主な機能が網羅されているので、参考にしてみてください。
