【Go言語】dbrライブラリの使い方 – ORM入門

  • URLをコピーしました!
目次

はじめに

GoのORMライブラリの一つであるgocraft/dbrライブラリの基本的な使い方をまとめます。sqlドライバーにはmattn/go-sqlite3を用いました。
database/sqlパッケージによるデータベース操作についても以前に記事を書いたので、比較しながら本記事を読んでいただけるとより理解が深まると思います。

あわせて読みたい
【Go言語】database/sqlパッケージによるデータベース操作入門 – sqlite3 database/sqlパッケージを利用したデータベース操作の基本をまとめます。今回はsqlドライバーにmattn/go-sqlite3を用いました。データベースの作成からCRUD処理までの基本操作を説明していきます。database/sqlパッケージは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を指定します。第一引数のドライバ名はpostgresmysqlsqlite3の三種類から指定することができます。

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.DBDialectEventReceiver型を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リクエストを送信する

よかったらシェアしてね!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次