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ルーチンにするだけです。