MongoDBのOffical Go言語Driver v1.0.0が3月にリリースされたのでGo言語の勉強も兼ねて使ってみました。
https://docs.mongodb.com/ecosystem/drivers/go/
実行環境
- Windows 10
- go.12 windows/amd64
- MongoDB Community版 4.0.7
- MongoDB Go Driver 1.0.0
- Goパッケージ管理ツール dep
- エディタ VS Code & Go Extension
前提
- MongoDBがインストールされている
- Go言語がインストールされている
準備
MongoDBを1ノードだけのレプリカセットとして起動。トランザクション処理をしたいため。
MongoDBレプリカセットとしてmongodの起動
mongod --dbpath db --replSet repl
ちなみに、Standaloneでもレプリカセットとして設定するとトランザクションが使えて、さらにoplogに更新履歴が残るため、後で確かめるのに便利です。
MongoDBレプリカセットの初期化
mongo
> rs.initiate()
メッセージが表示される
repl:SECONDARY>
何回かenterキーを押すとSECONDARYがPRIMARYに変わる
repl:PRIMARY>
depのインストール
go get -u github.com/golang/dep/cmd/dep
Go アプリのフォルダの作成
%GOPATH%\src\mongo1
mkdir mongo1
cd mongo1
dep init
MongoDB Driverをimportしたソース(main.go)を作成
package main
import "go.mongodb.org/mongo-driver/mongo"
func main() {
}
Go言語 Driverのインストール
上記のmain.goを作成してからインストールしましょう
dep ensure -add "go.mongodb.org/mongo-driver/mongo@~1.0.0"
https://github.com/mongodb/mongo-go-driver のInstallationを参照
Insertするドキュメントの作成方法
ドキュメントの表現方法を3つ紹介します
- bson.D
- bson.M
- 構造体(struct) 自分で定義します
なお、bson.D,bson.Mにおいて配列にはbson.Aを使います
上記の方法で表すJSONは次の通りとします
{"str1": "abc", "num1":1, "str2": "xyz", "num2": [2,3,4], "subdoc": {"str": "subdoc", "num": 987},"date": 現在の日付}
bson.D
bsonD := bson.D{
{"str1", "abc"},
{"num1", 1},
{"str2", "xyz"},
{"num2", bson.A{2,3,4}},
{"subdoc", bson.D{{"str", "subdoc"}, {"num", 987}}},
{"date", time.Now()},
}
bson.Dはprimitive.Dでありprimitive.Eのスライスとして表されます
https://godoc.org/go.mongodb.org/mongo-driver/bson/primitive#D
https://godoc.org/go.mongodb.org/mongo-driver/bson/primitive#E
bson.M
bsonM := bson.M{
"str1": "abc",
"num1": 1,
"str2": "xyz",
"num2": bson.A{2,3,4},
"subdoc": bson.M{"str": "subdoc", "num": 987},
"date": time.Now(),
}
bson.Mはprimitive.Mであり**map[string]interface{}**として表されます
https://godoc.org/go.mongodb.org/mongo-driver/bson/primitive#M
bson.Dとbson.Mの一番の違い
bson.Dは項目の順序**(str1,num1,str2,num2,subdoc,date)**が保証されますが、bson.Mは項目順序が保証されません。後にInsertを繰り返し実行して確かめたいと思います。
bson.Mで定義したsubdoc内の項目の順序が大事なときはbson.M内でもbson.Dを使います。
bsonM := bson.M{
"subdoc": bson.D{{"str", "subdoc"}, {"num", 987}},
}
構造体(struct)
type myType struct {
Str1 string
Num1 int
Str2 string
Num2 []int
Subdoc struct {
Str string
Num int
}
Date time.Time
}
doc := myType{
"abc",
1,
"xyz",
[]int{2, 3, 4},
struct {
Str string
Num int
}{"subdoc", 987},
time.Now(),
}
MongoDBへのInsert処理
-
context.WithTimeoutは使っていません
- ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
- Insertするデータは各3タイプごとに少し変えています
- bson.Mのデータにつていは同じデータを10回insertしています。Insertされた項目の順序が保証されないことが確かめられると思います
- トランザクション処理はしていません
package main
import (
"context"
"log"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func insertBsonD(col *mongo.Collection) error {
bsonD := bson.D{
{"str1", "abc"},
{"num1", 1},
{"str2", "xyz"},
{"num2", bson.A{2, 3, 4}},
{"subdoc", bson.D{{"str", "subdoc"}, {"num", 987}}},
{"date", time.Now()},
}
_, err := col.InsertOne(context.Background(), bsonD)
return err
}
func insertBsonM(col *mongo.Collection) error {
bsonM := bson.M{
"str1": "efg",
"num1": 11,
"str2": "opq",
"num2": bson.A{12, 13, 14},
"subdoc": bson.M{"str": "subdoc", "num": 987},
"date": time.Now(),
}
for i := 0; i < 10; i++ {
_, err := col.InsertOne(context.Background(), bsonM)
if err != nil {
return err
}
}
return nil
}
type myType struct {
Str1 string
Num1 int
Str2 string
Num2 []int
Subdoc struct {
Str string
Num int
}
Date time.Time
}
func insertStruct(col *mongo.Collection) error {
doc := myType{
"hij",
21,
"rst",
[]int{22, 23, 24},
struct {
Str string
Num int
}{"subdoc", 987},
time.Now(),
}
_, err := col.InsertOne(context.Background(), doc)
return err
}
func mainMain() error {
client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
return err
}
if err = client.Connect(context.Background()); err != nil {
return err
}
defer client.Disconnect(context.Background())
col := client.Database("test").Collection("col")
if err = insertBsonD(col); err != nil {
return err
}
if err = insertBsonM(col); err != nil {
return err
}
if err = insertStruct(col); err != nil {
return err
}
return nil
}
func main() {
if err := mainMain(); err != nil {
log.Fatal(err)
}
log.Println("normal end.")
}
構造体で定義した項目の一部をInsertしたくないとき
例えばstr2をInsertから除くとき、bson.D,bson.Mのときはデータを追加しなければInsertされませんので、必要に応じてstr2の項目をInsertしたりしなかったりできます。構造体の場合はstr2を含むものと含まないものを2つ用意すれば可能ですが、1つの構造体で行う方法を紹介します。
Str2 *string ",omitempty"
または
Str2 *string `bson:",omitempty"`
str2をstringのポインタにしてomitemptyタグを追加します。
ポインタの値がnilであればInsert項目から除かれます。もしomitemptyタグが無くnilのときは"str2":nullとしてInsertされます。
構造体のタグについては次のドキュメントを参照してください。
https://godoc.org/go.mongodb.org/mongo-driver/bson/bsoncodec#StructTagParserFunc
type myType struct {
Str1 string
Num1 int
Str2 *string ",omitempty"
Num2 []int
Subdoc struct {
Str string
Num int
}
Date time.Time
}
func insertStruct(col *mongo.Collection) error {
doc := myType{
"hij",
21,
nil,
[]int{22, 23, 24},
struct {
Str string
Num int
}{"subdoc", 987},
time.Now(),
}
//str2はinsertされない
_, err := col.InsertOne(context.Background(), doc)
if err != nil {
return err
}
doc = myType{
"hij",
21,
new(string),
[]int{22, 23, 24},
struct {
Str string
Num int
}{"subdoc", 987},
time.Now(
}
*doc.Str2 = "rst"
//str2は "rst"がinsertされる
_, err = col.InsertOne(context.Background(), doc)
return err
}
_idの扱いと同じ項目に複数タイプのデータを入れたいとき
_idが含まれていないときはMongoDBが自動的にObjectIdを生成して登録します。構造体で**_idを定義するときは項目名をIDなどとしてタグを使って_idとします。ObjectIdのタイプはprimitive.ObjectIDですが、string,intなど好きなデータタイプが使用できます。下記の例はprimitive.ObjectID**を使ったものです。
例えばstr2にstring, intなど複数タイプのデータを入れたいときは**interface{}**として定義します。
type myType struct {
ID primitive.ObjectID "_id"
Str1 string
Num1 int
Str2 interface{} ",omitempty"
Num2 []int
Subdoc struct {
Str string
Num int
}
Date time.Time
}
doc := myType{
primitive.NewObjectID(),
"hij",
21,
"rst", //string
[]int{22, 23, 24},
struct {
Str string
Num int
}{"subdoc", 987},
time.Now(),
}
doc2 := myType{
primitive.NewObjectID(),
"hij",
21,
567, //int
[]int{22, 23, 24},
struct {
Str string
Num int
}{"subdoc", 987},
time.Now(),
}
doc3 := myType{
primitive.NewObjectID(),
"hij",
21,
nil, //str2は",omitempty"があるのでinsertされない
[]int{22, 23, 24},
struct {
Str string
Num int
}{"subdoc", 987},
time.Now(),
}
InsertMany
InsertManyの引数は**[]interface{}なので、いろいろな構造のデータを一度にInsertとできます。次の例はbson.D,bson.M, struct**との3タイプで定義されたドキュメントを一度にInsertする例です.
package main
import (
"context"
"log"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func makeBsonD() interface{} {
return bson.D{
{"str1", "abc"},
{"num1", 1},
{"str2", "xyz"},
{"num2", bson.A{2, 3, 4}},
{"subdoc", bson.D{{"str", "subdoc"}, {"num", 987}}},
{"date", time.Now()},
}
}
func makeBsonM() interface{} {
return bson.M{
"str1": "efg",
"num1": 11,
"str2": "opq",
"num2": bson.A{12, 13, 14},
"subdoc": bson.M{"str": "subdoc", "num": 987},
"date": time.Now(),
}
}
type myType struct {
Str1 string
Num1 int
Str2 string
Num2 []int
Subdoc struct {
Str string
Num int
}
Date time.Time
}
func makeStruct() interface{} {
return myType{
"hij",
21,
"rst",
[]int{22, 23, 24},
struct {
Str string
Num int
}{"subdoc", 987},
time.Now(),
}
}
func mainMain() error {
client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
return err
}
if err = client.Connect(context.Background()); err != nil {
return err
}
defer client.Disconnect(context.Background())
col := client.Database("test").Collection("col")
insertDocuments := []interface{}{
makeBsonD(),
makeBsonM(),
makeStruct(),
}
_, err = col.InsertMany(context.Background(), insertDocuments)
return err
}
func main() {
if err := mainMain(); err != nil {
log.Fatal(err)
}
log.Println("normal end.")
}
トランザクション処理(Insertのみの例)
トランザクション処理を行うにはclient.UseSession()を使ってクロージャ内でStartTransaction()でトランザクション処理を開始します。 終了はCommitTransaction()またはAbortTransaction()を使ってInsertを確定するか取り消すかをします。またInsertOneのcontext.ContextにはUseSessionで渡されたmongo.SessionContextを使います。
**ctx.CommitTransaction(ctx)をコメントにして、代わりにコメントになっているctx.AbortTransaction(ctx)**を有効にしてみてください。1件もInsertされないはずです。
(注意) 通常、コレクションが存在しない状態でinsertするとコレクションが作成されます。しかし、トランザクション処理内でコレクションを作成することは出来ませんので、あらかじめコレクションを作成してください。よって、何度も繰り返しトランザクション処理のテストするときはdb.col.drop()ではなくdb.col.remove({})としてコレクションを削除しないでください。
package main
import (
"context"
"log"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func insertBsonD(ctx mongo.SessionContext, col *mongo.Collection) error {
bsonD := bson.D{
{"str1", "abc"},
{"num1", 1},
{"str2", "xyz"},
{"num2", bson.A{2, 3, 4}},
{"subdoc", bson.D{{"str", "subdoc"}, {"num", 987}}},
{"date", time.Now()},
}
_, err := col.InsertOne(ctx, bsonD)
return err
}
func insertBsonM(ctx mongo.SessionContext, col *mongo.Collection) error {
bsonM := bson.M{
"str1": "efg",
"num1": 11,
"str2": "opq",
"num2": bson.A{12, 13, 14},
"subdoc": bson.M{"str": "subdoc", "num": 987},
"date": time.Now(),
}
for i := 0; i < 10; i++ {
_, err := col.InsertOne(ctx, bsonM)
if err != nil {
return err
}
}
return nil
}
type myType struct {
Str1 string
Num1 int
Str2 string
Num2 []int
Subdoc struct {
Str string
Num int
}
Date time.Time
}
func insertStruct(ctx mongo.SessionContext, col *mongo.Collection) error {
doc := myType{
"hij",
21,
"rst",
[]int{22, 23, 24},
struct {
Str string
Num int
}{"subdoc", 987},
time.Now(),
}
_, err := col.InsertOne(ctx, doc)
return err
}
func mainMain() error {
client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
return err
}
if err = client.Connect(context.Background()); err != nil {
return err
}
defer client.Disconnect(context.Background())
col := client.Database("test").Collection("col")
err = client.UseSession(
context.Background(),
func(ctx mongo.SessionContext) error {
var err2 error
//トランザクション処理の開始
if err2 = ctx.StartTransaction(); err2 != nil {
return err2
} else if err2 = insertBsonD(ctx, col); err2 != nil {
} else if err2 = insertBsonM(ctx, col); err2 != nil {
} else if err2 = insertStruct(ctx, col); err2 != nil {
}
if err2 == nil {
ctx.CommitTransaction(ctx)
//ctx.AbortTransaction(ctx)
} else {
ctx.AbortTransaction(ctx)
}
return err2
})
return err
}
func main() {
if err := mainMain(); err != nil {
log.Fatal(err)
}
log.Println("normal end.")
}
mongo.WithSessionを使ったトランザクション
**mainMain()**のみ記載します。
func mainMain() error {
client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
return err
}
if err = client.Connect(context.Background()); err != nil {
return err
}
defer client.Disconnect(context.Background())
col := client.Database("test").Collection("col")
sess, err := client.StartSession()
if err != nil {
return err
}
err = mongo.WithSession(
context.Background(),
sess,
func(ctx mongo.SessionContext) error {
var err2 error
//トランザクション処理の開始
if err2 = ctx.StartTransaction(); err2 != nil {
return err2
} else if err2 = insertBsonD(ctx, col); err2 != nil {
} else if err2 = insertBsonM(ctx, col); err2 != nil {
} else if err2 = insertStruct(ctx, col); err2 != nil {
}
if err2 == nil {
ctx.CommitTransaction(ctx)
//ctx.AbortTransaction(ctx)
} else {
ctx.AbortTransaction(ctx)
}
return err2
})
return err
}
Find処理は(2)として次回の投稿予定です。