11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【GORM】セッションを維持して別処理を呼び出す

Posted at

共通処理を呼び出してテーブル更新するAPIをGo言語で実装していたときに、共通処理含めてロールバックする方法を作りました。
GORMガイドでは、トランザクションの説明はあるものの、処理をまたいだロールバックについては記載されていないようなので、記事としてまとめてみました。

環境

Golang 1.14.4

例題

図書館のシステムに、
「利用者が本を借りたとき、利用者の所持数を加算し、書架テーブルの貸出中フラグを1に更新する」処理があったとする。
この処理をコードで表すと以下の通り。

func bookRent() error {
    // リクエスト情報を受け取る。今回は割愛して固定値で指定する。
    userID := 1
    bookID := 23

    // DB接続用の情報を設定する。    
    DBMS := mysql
    CONNECT := root:mysql@tcp(localhost:3306)/testDB
    db, err := gorm.Open(DBMS,CONNECT)
    if err != nil {
        return err.ERROR()
    }

    // セッションを開始する。
    tx := db.Begin()

    // 予期せぬエラー時のロールバックを行う。
    defer func() {
        if r := recover(); r != nil {
            tx.Rollback()
        }
    }

    // 利用者の貸し出し冊数に加算する。
    var user model.User
    if err := tx.Model(&user).Where(user_id = ?  AND delete_flg = 0, userID).Update(rental_count, gorm.Expr(rental_count + 1)).Error; err != nil {
        tx.Rollback()
        return err.ERROR
    }

    // 蔵書に対して、貸し出し中フラグを「1:貸し出し中」に更新する。
    var liblary model.Library
    if err := tx.Model(&liblary).Where(book_id = ? AND delete_flg = 0, bookID).Update(rental_flg, 1).Error; err != nil {
        tx.Rollback()
        return err.ERROR
    }

    // すべての更新処理が終わったら、コミットを行う。
    tx.Commit()

    return nil
}

この処理に対し「貸出中フラグを更新する処理を共通化し、共通化処理を呼び出す形で実装したい」という課題が出た。
この時、正常にロールバックが機能する実装方法を考える。

解決方法

共通処理の引数に、セッション情報を持たせる。


// rentalBoook は、対象となった本を一括で貸し出し中に更新する共通処理です。
func rentalBook(tx *gorm.DB, bookIDs ...int) error {
    // 蔵書に対して、貸し出し中フラグを「1:貸し出し中」に更新する。
    var liblary model.Library
    if err := tx.Model(&liblary).Where(book_id IN (?) AND delete_flg = 0, bookIDs).Update(rental_flg, 1).Error; err != nil {
        tx.Rollback()
        return err.ERROR
    }

    return nil
}

もともとの処理であるbookRentは、以下のように修正する。(変更箇所近辺のみ抜粋)


    // セッションを開始する。
    tx := db.Begin()

    // 予期せぬエラー時のロールバックを行う。
    defer func() {
        if r := recover(); r != nil {
            tx.Rollback()
            return err.ERROR()
        }
    }

    // 利用者の貸し出し冊数に加算する。
    var user model.User
    if err := tx.Model(&user).Where(user_id = ?  AND delete_flg = 0, userID).Update(rental_count, gorm.Expr(rental_count + 1)).Error; err != nil {
        tx.Rollback()
        return err.ERROR()
    }

    // 共通処理を呼び出して、貸し出しフラグを「1:貸し出し中」に更新する。
    bookErr := rentalBook(tx,[]int{bookID}...)
    if bookErr != nil {
        // 共通処理側でロールバックしているので、こちらではロールバックしない。
        return bookErr.ERROR()
    }

    // すべての更新処理が終わったら、コミットを行う。
    tx.Commit()

共通処理を呼び出した際のロールバックについては、呼び出し元の処理で行うか、共通処理側でやるかという問題があると思います。
共通処理で問題が発生し次第ロールバックしたほうが安全だと思うので、上記の例ではそう記載しています。

11
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?