はじめに
GoのORMライブラリの一つであるgocraft/dbrライブラリの基本的な使い方をまとめます。sqlドライバーにはmattn/go-sqlite3を用いました。
database/sqlパッケージによるデータベース操作についても以前に記事を書いたので、比較しながら本記事を読んでいただけるとより理解が深まると思います。
ORMとは
ORMとは
ORMはObject Relational Mapperの略語です。RDBはテーブル構造で基本的にはSQL言語で操作を行うため、オブジェクト構造を持つオブジェクト指向型言語でRDBのデータ操作を行おうとすると、構造の違いなどにより不都合や扱いづらい部分が少なからず生じます。ORMはそのRDBとオブジェクト指向型言語における差や扱いづらさを吸収し、扱いやすくしてくれる技術になります。
具体的には次のような機能を持ちます。
- クエリビルダーなどメソッド経由でSQLを組み立てる
- データベースから取得したレコードとオブジェクトを関連づける
dbrの特徴
次の3つのドライバーをサポートしています。
- MySQL
- PostgreSQL
- SQLite3
事前準備
本記事で使用するSQLite3とmattn/go-sqlite3の導入を行います。
※Macに絞った事前準備になります。
SQLite3 導入
ターミナル上で下記コマンドを実行することでSqlite3を導入できます。
brew install sqlite
gcc 導入
go-sqlite3はcgoパッケージであるため、go-sqlite3を使用してアプリをビルドする場合は、gccが必要です。
gccをインストールするには、Xcodeをダウンロードしてインストール後、下記コマンドを実行してください。
xcode-select --install
mattn/go-sqlite3 導入
ターミナル上で下記コマンドを実行することでmattn/go-sqlite3を導入できます。
go get github.com/mattn/go-sqlite3
dbrの使い方
インストール
$ go get -u github.com/gocraft/dbr
データベースに接続する
データベースへの接続にはfunc Open(driver, dsn string, log EventReceiver) (*Connection, error)
メソッドを使います。第一引数にドライバ名、第二引数にドライバ固有のデータソース名、第三引数にEventReceiverを指定します。第一引数のドライバ名はpostgres
、mysql
、sqlite3
の三種類から指定することができます。
func main() {
// create a connection (e.g. "postgres", "mysql", or "sqlite3")
conn, _ := dbr.Open("sqlite3", "./example.sql", nil)
conn.SetMaxOpenConns(10)
// create a session for each business unit of execution (e.g. a web request or goworkers job)
sess := conn.NewSession(nil)
// create a tx from sessions
sess.Begin()
}
戻り値のConnection
型は下記のように*sql.DB
、Dialect
、EventReceiver
型をEmbeddedしている型になります。一つ目のsql.DB
はdatabase/sqlパッケージに含まれるDB
型を指しているため、database/sqlパッケージのDB型と同じ操作ができます。
type Connection struct {
*sql.DB
Dialect
EventReceiver
}
しかし、それではORMの特徴を活かすことできません。そのため、Connectionオブジェクトが持つfunc (conn *Connection) NewSession(log EventReceiver) *Session
メソッドを使用して、Sessionオブジェクトを生成し、そのSessionオブジェクト経由でDB操作を行うことが基本的な使い方になります。
テーブルの作成
残念ながらdbrにはテーブルを作成するメソッドがありません。そのため、database/sqlパッケージが持つfunc (db *DB) Exec(query string, args …interface{}) (Result, error)
メソッドを用いてSQLのCREATE TABLE
命令を実行します。
func main() {
conn, err := dbr.Open("sqlite3", "./example.sql", nil)
if err != nil {
log.Fatal(err)
}
tableName := "user"
cmd := fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id SERIAL PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
age INTEGER NOT NULL,
email TEXT)`, tableName)
_, err = conn.Exec(cmd)
if err != nil {
log.Fatal(err)
}
}
データの挿入
InsertIntoメソッドを使用する方法
データの挿入にはfunc (sess *Session) InsertInto(table string) *InsertStmt
メソッドおよび、戻り値であるInsertStmtオブジェクトが持つメソッド群を使用します。下記の例のように、userオブジェクトをそのまま利用してデータを挿入することができる点が、ORMを活用するメリットになります。
type User struct {
Id int `db:"id"`
Name string `db:"name"`
Age int `db:"age"`
Email string `db:"email"`
}
func main() {
conn, err := dbr.Open("sqlite3", "./example.sql", nil)
if err != nil {
log.Fatal(err)
}
sess := conn.NewSession(nil)
user := User{
Id: 1,
Name: "Taro",
Age: 25,
Email: "xxxxxxxx@test.com",
}
_, err = sess.InsertInto("user").
Columns("id", "name", "age", "email").
Record(user).
Exec()
if err != nil {
log.Fatal(err)
}
}
また、func (b *InsertStmt) Pair(column string, value interface{}) *InsertStmt
メソッドを利用してカラム一つずつに対して値を設定することもできます。
func main() {
conn, err := dbr.Open("sqlite3", "./example.sql", nil)
if err != nil {
log.Fatal(err)
}
sess := conn.NewSession(nil)
_, err = sess.InsertInto("user").
Pair("id", 1).
Pair("name", "Taro").
Pair("age", 25).
Pair("email", "xxxxxxxx@test.com").
Exec()
if err != nil {
log.Fatal(err)
}
}
生のSQL文を実行する
生のSQL文を実行する場合はfunc InsertBySql(query string, value …interface{}) *InsertStmt
メソッドを使用します。値はプレイスホルダーを活用して代入するように注意が必要です。
func main() {
conn, err := dbr.Open("sqlite3", "./example.sql", nil)
if err != nil {
log.Fatal(err)
}
sess := conn.NewSession(nil)
cmd := "INSERT INTO user (id, name, age, email) VALUES (?, ?, ?, ?)"
_, err = sess.InsertBySql(cmd, 4, "Shiro", 18, "zzzzzzzz@test.com").Exec()
if err != nil {
log.Fatal(err)
}
}
データの取得
データの取得にはfunc (sess *Session) SelectBySql(query string, value …interface{}) *SelectStmt
メソッドおよび、戻り値であるSelectStmtオブジェクトが持つメソッド群を使用します。
func main() {
conn, err := dbr.Open("sqlite3", "./example.sql", nil)
if err != nil {
log.Fatal(err)
}
sess := conn.NewSession(nil)
var users []User
count, err := sess.Select("*").From("user").Where("age > ?", 20).Load(&users)
if err != nil {
log.Fatal(err)
}
for _, user := range users {
fmt.Println(user)
}
fmt.Println("count:", count)
}
生のSQL文を実行する場合は、func (sess *Session) SelectBySql(query string, value …interface{}) *SelectStmt
メソッドを使います。
type User struct {
Id int `db:"id"`
Name string `db:"name"`
Age int `db:"age"`
Email string `db:"email"`
}
func main() {
conn, err := dbr.Open("sqlite3", "./example.sql", nil)
if err != nil {
log.Fatal(err)
}
sess := conn.NewSession(nil)
var users []User
// count, err := sess.Select("*").From("user").Where("age > ?", 20).Load(&users)
cmd := "SELECT * FROM user WHERE age > ?"
count, err := sess.SelectBySql(cmd, 20).Load(&users)
if err != nil {
log.Fatal(err)
}
for _, user := range users {
fmt.Println(user)
}
fmt.Println("count:", count)
}
出力結果
{1 Taro 25 xxxxxxxxxx@test.com}
{2 Jiro 22 yyyyyyyyy@test.com}
count: 2
データの更新
データの更新にはfunc (sess *Session) Update(table string) *UpdateStmt
メソッドおよび、戻り値であるUpdateStmtオブジェクトが持つメソッド群を使用します。
func main() {
conn, err := dbr.Open("sqlite3", "./example.sql", nil)
if err != nil {
log.Fatal(err)
}
sess := conn.NewSession(nil)
user := User{
Id: 2,
Name: "Jiro",
Age: 23,
Email: "yyyyyyyyy@test.com",
}
userMap := map[string]interface{}{"id": user.Id, "name": user.Name, "age": user.Age, "email": user.Email}
res, err := sess.Update("user").
SetMap(userMap).
Where("id = ?", user.Id).
Exec()
if err != nil {
log.Fatal(err)
}
}
生のSQL文を実行する場合は、func (sess *Session) UpdateBySql(query string, value …interface{}) *UpdateStmt
メソッドを使用します。
func main() {
conn, err := dbr.Open("sqlite3", "./example.sql", nil)
if err != nil {
log.Fatal(err)
}
sess := conn.NewSession(nil)
user := User{
Id: 2,
Name: "Jiro",
Age: 23,
Email: "yyyyyyyyy@test.com",
}
cmd := "UPDATE user SET age = ? WHERE id = ?"
res, err := sess.UpdateBySql(cmd, user.Age, user.Id).Exec()
if err != nil {
log.Fatal(err)
}
}
データの削除
データの削除には、func DeleteFrom(table string) *DeleteStmt
メソッドおよび、戻り値であるDeleteStmtオブジェクトが持つメソッド群を使用します。
func main() {
conn, err := dbr.Open("sqlite3", "./example.sql", nil)
if err != nil {
log.Fatal(err)
}
sess := conn.NewSession(nil)
user := User{
Id: 2,
Name: "Jiro",
Age: 23,
Email: "yyyyyyyyy@test.com",
}
res, err := sess.DeleteFrom("user").
Where("id = ?", user.Id).
Exec()
if err != nil {
log.Fatal(err)
}
}
トランザクション
func (sess Session) Begin() (Tx, error)
メソッドでトランザクションを開始します。
Tx型
はSession型
と同様にInsertInto、Select、Update、DeleteFromメソッドが実装されているため、今まで説明してきた処理と同じ方法でCRUD処理を行うことができます。トランザクションなので、複数の処理を記述することができ、最後にfunc (tx *Tx) Commit() error
メソッドを実行することで、複数の処理がまとめて実行されます。
また、ロールバック処理を行う場合はfunc (tx *Tx) RollbackUnlessCommitted()
メソッドを使用します。defer文でこのメソッドを呼び出すことで、トランザクションがCommitされずにその関数が終了した場合に必ずロールバック処理が実行されるようになります。
func main() {
conn, err := dbr.Open("sqlite3", "./example.sql", nil)
if err != nil {
log.Fatal(err)
}
sess := conn.NewSession(nil)
tx, err := sess.Begin()
if err != nil {
log.Fatal(err)
}
defer tx.RollbackUnlessCommitted()
user := User{
Id: 2,
Name: "Jiro",
Age: 23,
Email: "yyyyyyyyy@test.com",
}
_, err = tx.InsertInto("user").
Columns("id", "name", "age", "email").
Record(user).
Exec()
if err != nil {
log.Fatal(err)
}
_, err = tx.Update("user").
Set("age", 24).
Where("id = ?", 2).
Exec()
if err != nil {
log.Fatal(err)
}
tx.Commit()
}
まとめ
本記事ではGoのORMライブラリの一つであるgocraft/dbrライブラリの基本的な使い方をまとめました。標準のdatabase/sqlパッケージと比べて、構造体にマッピングしてくれる点で楽になると感じましたが、正直なところ大きく差があるとは感じませんでした。他のORMライブラリもたくさんあるので、触って記事にしようと思います。
閲覧いただきありがとうございました。
他のパッケージやGo言語における基本的な利用方法についても記事を書いていますので、もし興味がありましたらご参照ください。
・【Go言語】JWT認証を実装してみる
・【Go言語】echoフレームワークの使い方入門
・【Go言語】ファイル/ディレクトリ操作方法 – 基本
・【Go言語】database/sqlパッケージによるデータベース操作入門 – sqlite3
・【Go言語】encoding/jsonパッケージでJSONをパースする
・【Go言語】net/httpパッケージでAPIリクエストを送信する
コメント