LoginSignup
25
15

More than 5 years have passed since last update.

MongoDBの Offical Go言語 Driverを使ってみる(1)準備・Insert

Posted at

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)を作成

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.Dprimitive.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.Mprimitive.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された項目の順序が保証されないことが確かめられると思います
  • トランザクション処理はしていません
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を含むものと含まないものを2つ用意すれば可能ですが、1つの構造体で行う方法を紹介します。

Str2 *string ",omitempty" 
または
Str2 *string `bson:",omitempty"` 

str2stringのポインタにして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を使ったものです。

例えばstr2string, 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する例です.

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を確定するか取り消すかをします。またInsertOnecontext.ContextにはUseSessionで渡されたmongo.SessionContextを使います。
ctx.CommitTransaction(ctx)をコメントにして、代わりにコメントになっているctx.AbortTransaction(ctx)を有効にしてみてください。1件もInsertされないはずです。

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

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()のみ記載します。

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)として次回の投稿予定です。

25
15
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
25
15