LoginSignup
11
5

More than 3 years have passed since last update.

mongoDB公式のGoのDriverを使ってみた

Last updated at Posted at 2019-11-12

概要

mongoDB公式のGo用のDriver触ったので、そのまとめ

環境

go 1.12.5
go.mongodb.org/mongo-driver v1.1.2
mongoDB 3.1

用語

わかりやすくまとめてくれてる方がいた

  • table → collection
  • row → document
  • column → field

接続

公式ドキュメントのUsage見た方が早い気がしやすが一応

import (
    "context"
    "log"

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

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://username:password@localhost:27017"))
    if err != nil {
        log.Fatalln(err)
        return
    }
}

ApplyURI("mongodb://username:password@localhost:27017")ここで接続先指定してる感じ

FindOne

1ドキュメントの取得
検索条件を構造体で作成した場合

import (
    "go.mongodb.org/mongo-driver/bson/primitive"
    // NOTE: 他に必要なパッケージは省略
)

type FindOneRequest struct {
    // NOTE: 検索したいfieldとvalueを適宜定義する
    TargetField string `json:"targetField" bson:"targetField"`
}

type FindOneResponse struct {
    ID              primitive.ObjectID `json:"id" bson:"_id"`
    Hoge            string             `json:"hoge" bson:"hoge"`
    Fuga            string             `json:"fuga" bson:"fuga"`
}

func main() {
    // NOTE: 接続処理は省きやす 上の見てくだされ

    collection := client.Database("db_name").Collection("collection_name")

    request := FindOneRequest{
        TargetField: "検索内容",
    }

    var response FindOneResponse
    err = collection.FindOne(context.Background(), request).Decode(&response)
    if err == mongo.ErrNoDocuments {
        log.Println("Documents not found")
    } else if err != nil {
        log.Fatalln(err)
    }

    log.Println(res)
}

構造体使う場合はbsonタグ使ってドキュメントで使用している項目名を指定すればいい感じにマッピングしてくれる
ドキュメント作ると勝手に生成してくれるObjectIDはprimitive.ObjectID型を使えばよき

Find

複数ドキュメントの取得
今回は検索条件をbson.Dで定義し、レスポンスにはbson.Mを使用

import (
    "go.mongodb.org/mongo-driver/bson"
    // NOTE: 他に必要なパッケージは省略
)

func main() {
     // NOTE: 接続処理は省きやす

    collection := client.Database("db_name").Collection("collection_name")

    // NOTE: 検索したいfield名とvalueを定義
    cur, err := collection.Find(ctx, bson.D{{Key: "field_name", Value: "value"}})
    if err != nil {
        log.Fatal(err)
    }
    defer cur.Close(ctx)
    for cur.Next(ctx) {

        // NOTE: 1ドキュメントずつdecode
        //       mapが返ってくる
        var result bson.M
        err := cur.Decode(&result)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println(result)
    }
    if err := cur.Err(); err != nil {
        log.Fatal(err)
    }
}

Find使った時に返り値には単純な配列とかが返ってくるわけでなく、
*Cursor型が返ってくるのでここらへんの扱いが独特だと感じた次第

InsertOne

1ドキュメントの追加

import (
    "go.mongodb.org/mongo-driver/bson/primitive"
    // NOTE: 他に必要なパッケージは省略
)

type InsertOneRequest struct {
    ID              primitive.ObjectID `json:"id" bson:"_id"`
    Hoge            string             `json:"hoge" bson:"hoge"`
    Fuga            string             `json:"fuga" bson:"fuga"`
}

func main() {
    // NOTE: 接続処理は省きやす

    collection := client.Database("db_name").Collection("collection_name")

    request := InsertOneRequest{
        ID: primitive.NewObjectID(),
        Hoge: "hoge",
        Fuga: "fuga",
    }

    // NOTE: InsertOneの返り値には作成したドキュメントのObjectIDが返ってくる
    response, err := collection.InsertOne(context.Background(), request)
    if err != nil {
        log.Fatalln(err)
    }
}

作成したドキュメント使って何かしたいときはrequest.ID使ってFindOneした構造体を使う感じになりますかね

InsertMany

複数ドキュメントの登録

import (
    "go.mongodb.org/mongo-driver/bson/primitive"
    // NOTE: 他に必要なパッケージは省略
)

type Request struct {
    ID              primitive.ObjectID `json:"id" bson:"_id"`
    Hoge            string             `json:"hoge" bson:"hoge"`
    Fuga            string             `json:"fuga" bson:"fuga"`
}

func main() {
    // NOTE: 接続処理は省きやす

    collection := client.Database("db_name").Collection("collection_name")

    var insertManyRequest []interface{}

    for index := 0; index < 10; index++ {
        indexString := strconv.Itoa(index)

        request := Request{
            ID:   primitive.NewObjectID(),
            Hoge: "hoge",
            Fuga: "fuga",
        }

        insertManyRequest = append(insertManyRequest, question)
    }

    // NOTE: 今回の場合、一括で10件登録してくれる
    //       返り値はinsertしたドキュメントのObjectIDの配列
    response, err := collection.InsertMany(context.Background(), insertManyRequest)
    if err != nil {
        log.Fatalln(err)
    }
}

InsertManyメソッドの第二引数は[]interface{}

Aggregate

集計はAggregateってやつでできるらしい
mongoDBにはOperatorsってのがあってCollectionに対して、このOperatorsを使ってく感じですね

Operatorsの中でもAggregateで使うのがPipeline
公式ドキュメントに比較的Exampleが書いてるのでそれとにらめっこするといつの間にか実装できる

import (
    "go.mongodb.org/mongo-driver/bson"
    // NOTE: 他に必要なパッケージは省略
)

func main() {
    // NOTE: 接続処理は省きやす

    collection := client.Database("db_name").Collection("collection_name")

    pipeline := []bson.M{
        bson.M{
            "$match": bson.M{
                "targetField": "value",
            },
        },
        bson.M{
            "$group": bson.M{
                "_id": "$targetFieldID",
                "count": bson.M{
                    "$sum": 1,
                },
            },
        },
    }

    // NOTE: AggregateはFindと同様に`*Cursor`型を返すので、Cursor型用の扱い方が必要
    hogeAggre, err := collection.Aggregate(ctx, pipeline)
    if err != nil {
        log.Fatalln(err)
    }

    defer hogeAggre.Close(ctx)

    for hogeAggre.Next(ctx) {
        var result bson.M
        err := answerAggre.Decode(&result)
        if err != nil {
            log.Fatal(err)
        }

        fmt.Println(result)
    }
    if err := answerAggre.Err(); err != nil {
        log.Fatal(err)
    }
}

出力結果は下記のような感じのを返してくれるはず
厳密には違うかもしれないので悪しからず

[
    {
        "_id": "hogehoge",
        "count": 15,
    },
    {
        "_id": "fugafuga",
        "count": 13,
    },
]

$がついてるのがOperatorですね
今回Pipelineで指定したのはcollection_nameってコレクションのtargetFieldフィールドの値がvalueのドキュメントたちをtargetFieldIDフィールドでGroupByしてtargetFieldIDごとのドキュメント数をcountフィールドに算出させた感じです
多分・・・

OperatorとかPiplineのStageとかはもっとドキュメント読み込む必要がありやす

所感

InsertやFindするときの引数や返り値は構造体でしっかり型定義してたけども、結局ドキュメントの構造は同一のコレクションでも異なる可能性が出てくるのでbson.Mとかみたくしっかり型定義しないほうがいい気もした

11
5
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
11
5