MongoDBの Offical Go言語 Driverを使ってみる（１）準備・Insert

MongoDBのOffical Go言語Driver v1.0.0が３月にリリースされたので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の起動
```bash
mongod --dbpath db --replSet repl
```

ちなみに、Standaloneでもレプリカセットとして設定するとトランザクションが使えて、さらにoplogに更新履歴が残るため、後で確かめるのに便利です。

### MongoDBレプリカセットの初期化

```bash
mongo
> rs.initiate()
     メッセージが表示される
repl:SECONDARY>
     何回かenterキーを押すとSECONDARYがPRIMARYに変わる
repl:PRIMARY>
```

### depのインストール
```bash
go get -u github.com/golang/dep/cmd/dep
```

### Go アプリのフォルダの作成

%GOPATH%\src\mongo1

```bash
mkdir mongo1
cd mongo1
dep init
```

### MongoDB Driverをimportしたソース(main.go)を作成
```go:main.go 
package main

import 	"go.mongodb.org/mongo-driver/mongo"

func main() {
}
```

### Go言語 Driverのインストール
上記の**main.go**を作成してからインストールしましょう

```bash
dep ensure -add "go.mongodb.org/mongo-driver/mongo@~1.0.0"
```
https://github.com/mongodb/mongo-go-driver  のInstallationを参照

# Insertするドキュメントの作成方法

### ドキュメントの表現方法を３つ紹介します

- bson.D
- bson.M
- 構造体(struct) 自分で定義します

なお、**bson.D,bson.M**において配列には**bson.A**を使います

上記の方法で表すJSONは次の通りとします

```json
{"str1": "abc", "num1":1, "str2": "xyz", "num2": [2,3,4], "subdoc": {"str": "subdoc", "num": 987},"date": 現在の日付}
```

#### bson.D

```go
	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

```go
	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**を使います。

```go
	bsonM := bson.M{
		
		"subdoc": bson.D{{"str", "subdoc"}, {"num", 987}},
		
	}
```

#### 構造体(struct)

```go
type myType struct {
	Str1 string
	Num1 int
	Str2 string
	Num2 []int
	Subdoc struct {
		Str string
		Num int
	}
	Date time.Time
}
```

```go
	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された項目の順序が保証されないことが確かめられると思います
- トランザクション処理はしていません

```go:main.go
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**を含むものと含まないものを２つ用意すれば可能ですが、１つの構造体で行う方法を紹介します。


```go
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

```go
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{}**として定義します。


```go
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
}
```

```go
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**との３タイプで定義されたドキュメントを一度にInsertする例です.

```go:main.go
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)**を有効にしてみてください。１件もInsertされないはずです。

**（注意）　通常、コレクションが存在しない状態でinsertするとコレクションが作成されます。しかし、トランザクション処理内でコレクションを作成することは出来ませんので、あらかじめコレクションを作成してください。よって、何度も繰り返しトランザクション処理のテストするときはdb.col.drop()ではなくdb.col.remove({})としてコレクションを削除しないでください。**


```go:main.go
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()**のみ記載します。

```go
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処理は（２）として次回の投稿予定です。