このエントリーは、GMOアドマーケティング Advent Calendar 2018【12/8】 の記事です。
GMOアドマーケティングとしては初のAdvent Calendar参戦です。
本日のAdvent Calendarはこちらの記事を参考にTransactionを実装してみます。
##環境
go1.9.2 darwin/amd64
mysql Ver 14.14 Distrib 5.7.19, for osx10.12 (x86_64) using EditLine wrapper
##実装
package src
import (
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql"
)
type MyDB struct {
db *sql.DB
}
var Db MyDB
// Connection
func (m *MyDB) Connection() error {
var err error
m.db, err = sql.Open("mysql", "user:password@tcp(localhost:3306)/testdb")
if err != nil {
return err
}
return nil
}
// Close
func (m *MyDB) Close() {
if m.db != nil {
m.db.Close()
}
}
// Fetch
func (m *MyDB) Fetch(query string, tx *sql.Tx) ([][]interface{}, error) {
rows, err := tx.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
columns, _ := rows.Columns()
count := len(columns)
valuePtrs := make([]interface{}, count)
ret := make([][]interface{}, 0)
for rows.Next() {
values := make([]interface{}, count)
for i, _ := range columns {
valuePtrs[i] = &values[i]
}
rows.Scan(valuePtrs...)
for i, _ := range columns {
var v interface{}
val := values[i]
b, ok := val.([]byte)
if ok {
v = string(b)
} else {
v = val
}
values[i] = v
}
ret = append(ret, values)
}
return ret, nil
}
// Transaction
func (m *MyDB) Transaction(txFunc func(*sql.Tx) error) error {
tx, err := m.db.Begin()
if err != nil {
return err
}
defer func() {
if p := recover(); p != nil {
log.Println("recover")
tx.Rollback()
panic(p)
} else if err != nil {
log.Println("rollback")
tx.Rollback()
} else {
log.Println("commit")
err = tx.Commit()
}
}()
err = txFunc(tx)
return err
}
Transactionで行われていること
- Transaction関数内でトランザクションを開始
- Transaction関数の引数でうけとっった無名関数にトランザクション(tx)を無名関数に渡す
- 無名関数の処理結果(正常終了、error、panic)でdeferでcommitかrollbackを実行する
更新前のレコード
table | test | ||
colmun1 | id | value | 1 |
colmun2 | status | value | start |
##結果を確認してみる
###Transaction内で強制的にpanicをおこす
package main
import (
"./src"
"database/sql"
"log"
)
func main() {
err := src.Db.Connection()
if err != nil {
log.Fatal(err)
}
defer src.Db.Close()
err = src.Db.Transaction(func(tx *sql.Tx) error {
res, _ := src.Db.Fetch("SELECT * FROM test", tx)
if err != nil {
log.Fatal(err)
}
id := res[0][0].(string)
_, err = tx.Exec("UPDATE test SET status = '" + "done" + "' WHERE id=" + id)
if err != nil {
log.Fatal(err)
}
panic("panic")
})
if err != nil {
log.Fatal(err)
}
}
panic発生後はrollbackされるのでレコードが更新前の状態にrollbackされる
table | test | ||
colmun1 | id | value | 1 |
colmun2 | status | value | start |
###Transaction内で強制的にerrorをおこす
package main
import (
"./src"
"database/sql"
"log"
"pkg/errors"
)
func main() {
err := src.Db.Connection()
if err != nil {
log.Fatal(err)
}
defer src.Db.Close()
err = src.Db.Transaction(func(tx *sql.Tx) error {
res, _ := src.Db.Fetch("SELECT * FROM test", tx)
if err != nil {
log.Fatal(err)
}
id := res[0][0].(string)
_, err = tx.Exec("UPDATE test SET status = '" + "done" + "' WHERE id=" + id)
if err != nil {
log.Fatal(err)
}
return errors.New("error")
})
if err != nil {
log.Fatal(err)
}
}
error発生後はrollbackされるのでレコードが更新前の状態にrollbackされる
table | test | ||
colmun1 | id | value | 1 |
colmun2 | status | value | start |
###Transactionを使って正常終了する
package main
import (
"./src"
"database/sql"
"log"
)
func main() {
err := src.Db.Connection()
if err != nil {
log.Fatal(err)
}
defer src.Db.Close()
err = src.Db.Transaction(func(tx *sql.Tx) error {
res, _ := src.Db.Fetch("SELECT * FROM test", tx)
if err != nil {
log.Fatal(err)
}
id := res[0][0].(string)
_, err = tx.Exec("UPDATE test SET status = '" + "done" + "' WHERE id=" + id)
if err != nil {
log.Fatal(err)
}
return nil
})
if err != nil {
log.Fatal(err)
}
}
正常終了後はcommitされるのでstatusが更新されている
table | test | ||
colmun1 | id | value | 1 |
colmun2 | status | value | done |
##まとめ
ラッパー関数を作ることによって、ラッパー関数に処理したい内容を渡すだけでTransaction、Rollback、Commitを意識しなくていいので、だいぶ楽にTransactionを使うことができそうです。
##次回のAdvent Calendar 2018
明日は、【@nodataka】さんの【JavaでTable Driven Testを実装する】についてのお話です。
お楽しみに。
クリスマスまで続くGMOアドマーケティング Advent Calendar 2018
ぜひ今後も投稿をウォッチしてください!