LoginSignup
3
2

More than 5 years have passed since last update.

Go言語でMongoDBのトランザクションを使う

Last updated at Posted at 2019-04-11

MongoDBのトランザクションをGo言語(1.12.1)から利用してみました。

Go言語からMongoDBのを使うには、公式のドライバとmgoという人気のあるライブラリを使う方法などがありますが、mgoの方は現時点ではトランザクションに対応していないっぽいので、公式ドライバを使った方法を記載します。

トランザクションに対応したMongoDBを用意する

前に書いた記事で一瞬で作成できます
https://qiita.com/jp_ibis/items/c00b9509fac87abb32c6

コレクションを作成しておく

MongoDBにはデータをinsertしたら自動でDBとCollectionを作成してくれる機能があるのですが、トランザクション中には使えないようです。事前にDBとCollectionを用意しておきましょう。

MongoDBのシェルを起動してコマンドでCollectionを作成しておきます。上のリンクの記事のようにDockerでMongoDBを立てていた場合は

docker exec -it コンテナ名 mongo

のようにしてシェルを起動してください。MongoDBのシェルで、

use 作成するDB名

としてDB名を決めて、

db.createCollection("作成するコレクション名")

で、空っぽのコレクションを作っておきます。必要なのはこれだけなので、quitでmongoのシェルを抜けておきます。

ライブラリを用意する

go mod を使うのがナウいのですが、VSCodeで補完させようとすると結局 go get も必要なんですよね(´・ω・`)

go get -u go.mongodb.org/mongo-driver/mongo
go get -u go.mongodb.org/mongo-driver/bson

コードを書く

ライブラリのimport部

mongoで接続するときにcontextを使用するので一緒にimportしておきます。

import (
    "context"

    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

MongoDBへの接続

ApplyURIの引数で接続先を指定します。DockerコンテナのMongoDBを使う場合は、コンテナのIPを指定してください。

    // MongoDBへの接続
    ctx := context.Background()

    client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://MongoDBのIPアドレス:ポート番号(省略可)"))
    if err != nil {
        panic(err)
    }

セッションを使用する

公式ドライバにはやり方が何通りもあってどれを選べばいいのかわかりにくいですが、UseSessionを使うのが一番スッキリするかな、と。UseSessionの実装の中に defer defaultSess.EndSession(ctx) というコードがあったので、自分でEndSessionを書く必要は無さそうです。

    // セッション開始
    err = client.UseSession(ctx, func(sc mongo.SessionContext) error {
        // ここにトランザクションの処理を書く
    })
    if err != nil {
        panic(err)
    }

トランザクションを使用する

UseSessionのコールバック関数の中でトランザクションの処理を記述します。コールバックの引数でもらえるSessionContext型のコンテキストを使用することでトランザクションが実現できます。

        // ここからトランザクション開始
        err = sc.StartTransaction()
        if err != nil {
            return err
        }

        // DBとCollection名はここで指定
        db := client.Database("DB名")
        collection := db.Collection("コレクション名")

        // 適当にInsert文などを。context型を求める引数にSessionContextを使うのが重要
        _, err = collection.InsertOne(sc, bson.M{"キー": })
        if err != nil {
            // EndSession内でAbortTransactionが呼ばれるので、ここで無理に呼ぶ必要はない。
            sc.AbortTransaction(sc)
            return err
        }

        // トランザクション中の処理を確定
        return sc.CommitTransaction(sc)

まとめたサンプル

package main

import (
    "context"

    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

func main() {
    // MongoDBへの接続
    ctx := context.Background()

    client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://172.17.0.2:27017"))
    if err != nil {
        panic(err)
    }

    // セッション開始
    err = client.UseSession(ctx, func(sc mongo.SessionContext) error {
        // ここからトランザクション開始
        err = sc.StartTransaction()
        if err != nil {
            return err
        }

        // DBとCollection名はここで指定
        db := client.Database("my_test_db")
        collection := db.Collection("my_test_col")

        // 適当にInsert文などをば。引数にSessionContextを入れているのが重要
        _, err = collection.InsertOne(sc, bson.M{"number": "abc"})
        if err != nil {
            // EndSession内でAbortTransactionが呼ばれるので、ここで無理に呼ぶ必要はない。
            sc.AbortTransaction(sc)
            return err
        }

        // トランザクション中の処理を確定
        return sc.CommitTransaction(sc)
    })
    if err != nil {
        panic(err)
    }
}

おしまい

セッションのところがコールバックでちょっとモヤっとしたけど、JavaScriptみたいに非同期ではないのでまだ使いやすいです。あ、非同期にしたいときはUseSessionのあたりをgoルーチンにするだけです。

3
2
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
3
2