アルパカ三銃士

〜アルパカに酔いしれる獣たちへ捧げる〜

Go で sqlx 拡張のトランザクションマネージャを書いた

sqlx-transactionmanager

github.com

これは Perl でいう @nekokak さん作の DBIx-TransactionManager をベースに作ったもの。今担当している会社のプロジェクト内で github.com/jmoiron/sqlx を使っており、それの transaction manager があるといいよねーとなったので作成した。

既に sqlx を使っている場合、import に書かれているパッケージ名を次のように変えるだけで、sqlx で提供されている全てのメソッドが今までと同じように使える。

import sqlx "github.com/Code-Hex/sqlx-transactionmanager"

単純に sqlx をラップしているだけなため、これだけでエラーをあまり吐かずに置き換えることが可能。しかし、database/sql の DB struct へアクセスする場合は少し冗長になってしまうため SQL() というメソッドを提供した。この辺くらいは少し弄る必要がありそう。

それで、肝心なトランザクションの管理は次のように行うことができる。

// DoTransaction is example for transaction
// See transaction_manager_test.go if you want to know detail.
func DoTransaction(db *sqlx.DB) func(*sqlx.DB) {
    return func(db *sqlx.DB) {
        tx, err := db.BeginTxm()
        if err != nil {
            panic(err)
        }
        // Do rollbacks if fail something in transaction.
        // But do not commits if already commits in transaction.
        defer func() {
            if err := tx.Rollback(); err != nil {
                // Actually, you should do something...
                panic(err)
            }
        }()

        tx.MustExec(tx.Rebind("INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?)"), "Code", "Hex", "x00.x7f@gmail.com")
        tx.MustExec(tx.Rebind("UPDATE person SET email = ? WHERE first_name = ? AND last_name = ?"), "a@b.com", "Code", "Hex")

        var p Person
        if err := tx.Get(&p, "SELECT * FROM person LIMIT 1"); err != nil {
            panic(err)
        }

        if err := tx.Commit(); err != nil {
            panic(err)
        }
        println(&p)
    }
}

defer で Rollback を指定してたとしても内部でカウンターを持っているため、もし Commit がされていても Rollback が走ることはない。詳しい使い方はリポジトリ内に含まれている example やテストコードを見ると良さそう。

もし簡単にトランザクションのコードを置き換えられない場合、プロジェクト内でのトランザクションの扱い方が間違ってる可能性がある。