Overview

This tutorial will introduce how to use the official Golang driver Mongo Go Driver to connect to MongoDB, manipulate MongoDB and some operations of indexes. More on how to use it, of course, there are some points of note are also mentioned, in fact, it is also stepped in some pits.

API usage

import dependency package

package main

import (
    "context"
    "fmt"
    "os"
    "time"

    // The official "mongo-go-driver" package
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    "go.mongodb.org/mongo-driver/x/bsonx"
)

Connecting to the DB

Use options.Client() to create a parameter option for the MongoDB connection, you can add all the options you need, and the MongoDB URI information is also added here:

[root@liqiang.io]# cat main.go
// Declare the host and port options to be passed to the Connect() method
var opts = options.Client().
        ApplyURI(addr).
        SetConnectTimeout(time.Second * 5).
        SetSocketTimeout(time.Second * 5).
        SetWriteConcern(writeconcern.New(writeconcern.WMajority()))

Here I use a number of parameters.

Then execute the following command, passing the clientOptions instance to the mongo.Connect()` method, and make sure you also pass acontext`` object, which you can do a lot of things with, such as controlling the connection timeout timeout and such.

[root@liqiang.io]# cat main.go
// Connect to MongoDB and return the client instance
client, err := mongo.Connect(context.TODO(), clientOptions)
if err ! =nil {
    fmt.Println("mongo.Connect() ERROR:", err)
    os.Exit(1)
}

Operation Collection

After you have a Client, you still need to specify the DB and the Collection to operate on a specific Collection, e.g.

[root@liqiang.io]# cat main.go
// Accessing a MongoDB collection via the database
col := client.Database("db").Collection("collection")

CRUD would be too simple, just look at the example.

CRUD

Get

The point of the Get method is actually that the value returned by FindOne is SingleResult, and then some common errors can be dealt with.

[root@liqiang.io]# cat main.go
func (r *postRepository) Get(ctx context.Context, id string) (post *postDoc, err error) {
    var coll = r.client.Database(r.dbName).Collection(r.collName)

    var singleResult *mongo.
    if singleResult = coll.FindOne(ctx, bson.M{"post_id": id}); singleResult.Err() ! = nil {
        if singleResult.Err() == mongo.ErrNoDocuments {
            return nil, utils.ErrNotFound
        }
        return nil, errors.Wrap(singleResult.Err(), "query doc")
    }

    var doc postDoc
    if err = singleResult.Decode(&doc); err ! = nil {
        return nil, errors.Wrap(err, "decode doc")
    }

    return &doc, nil
}

List

List is a bit more complicated and has several main points.

[root@liqiang.io]# cat main.go
func (r *postRepository) List(
    ctx context.Context,
    selector map[string]interface{},
    sorter []string,
    page, pageSize int,
) (posts []postDoc, err error) {
    var coll = r.client.Database(r.dbName).Collection(r.collName)

    var cursor *mongo.
    var sortOpts = bsonx.Doc{}
    for _, s := range sorter {
        var order = int32(1)
        if strings.HasPrefix(s, "-") {
            order = int32(-1)
            s = s[1:]
        }
        sortOpts = append(sortOpts, bsonx.Elem{
            Key: s,
            Value: bsonx.Int32(order),
        })
    }
    var opts = options.Find().
        SetSort(sortOpts).
        SetSkip(int64((page - 1) * pageSize)).
        SetLimit(int64(pageSize))
    Find(ctx, selector, opts); err ! = nil {
        return nil, errors.Wrap(err, "query docs")
    }

    var postDocs []postDoc
    if err = cursor.All(ctx, &postDocs); err ! = nil {
        return nil, errors.Wrap(err, "decode docs")
    }
    return postDocs, nil
}

Here I’m using cursor.All to decode the data, and another more common usage is this

[root@liqiang.io]# cat main.go
    var postDocs []postDoc
    defer cursor.Close(ctx)
    for cursor.Next(ctx) {
        var postDoc postDoc
        if err = cursor.Decode(&postDoc); err ! = nil {
            return nil, errors.Wrap(err, "decode doc")
        }
        postDocs = append(postDocs, postDoc)
    }

Here is the form of iterator to decode data, using this method remember to be sure to actively close the Cursor, otherwise it may cause a connection leak.

Delete

Delete is relatively simple, the only thing worth mentioning is how to determine if the deleted element does not exist, here I am judging the result of the deletion: DeleteCount to determine if the element is really deleted:

[root@liqiang.io]# cat main.go
func (r *postRepository) Delete(ctx context.Context, id string) (err error) {
    var coll = r.client.Database(r.dbName).Collection(r.collName)

    var delRst *mongo.DeleteResult
    if delRst, err = coll.DeleteOne(ctx, bson.M{"post_id": id}); err ! = nil {
        return errors.Wrap(err, "delete doc")
    }

    if delRst.DeletedCount == 0 {
        return utils.ErrNotFound
    }
    return nil
}

Update

Update is also a relatively simple operation, a point to note is whether to allow upsert, here is set this option: ``` go

[root@liqiang.io]# cat main.go
func (r *postRepository) Update(ctx context.Context, post *postDoc) (rtn *postDoc, err error) {
    var coll = r.client.Database(r.dbName).Collection(r.collName)

    var opts = options.Update().SetUpsert(true)
    _, err = coll.UpdateOne(ctx, bson.M{"post_id": post.PostId}, bson.M{"$set": post}, opts)
    if err ! = nil {
        return nil, errors.Wrap(err, "upsert doc")
    }

    return post, nil
}

Create

One point in creating an element is what the ID of the inserted element is, which is determined by a return value in the Mongo Go Driver, but the return is an interface{}, which needs to be converted to

[root@liqiang.io]# cat main.go
func (r *postRepository) Save(ctx context.Context, post *postDoc) (rtn *postDoc, err error) {
    var coll = r.client.Database(r.dbName).Collection(r.collName)

    var insertOneResult *mongo.
    InsertOne(ctx, post); err ! = nil {
        return nil, errors.Wrap(err, "save doc")
    }
    post.Id = insertOneResult.InsertedID.(primitive.ObjectID)

    return post, nil
}

Indexes

Creating a single index

Regardless of the language used, the method calls to create an index using the latest driver are the createIndex(), createOne(), create_index() methods, or in the case of Go’s official driver API, the Indexes().CreateOne method. This method call requires passing a key, or a field to index the data, and an integer in sort order, either 1 in ascending order or -1 in descending order. The option for the second parameter may also be required. Here is an example.

[root@liqiang.io]# db.coll.createIndex( |CONTEXT|, { |KEY| : |SORT_ORDER|, |OPTIONS| } )

This is index_view.go in mongo-go-driver on Github, the method has the following method prototype.

// CreateOne creates an index in the collection specified by the model.
func (iv IndexView) CreateOne(ctx context.Context, model IndexModel, opts, *options.CreateIndexesOptions) (string, error)

Optionally, the options.Index() method call can be passed to the Options parameter of the IndexModel. The following example sets a unique index for the id of the blog.

func (r *postRepository) createIndex(ctx context.Context) (err error) {
    var coll = r.client.Database(r.dbName).Collection(r.collName)
    var indexModel = mongo.IndexModel{
        Keys: bsonx.Doc{
            {
                Key: "post_id",
            },
        },
        Options: options.Index().SetUnique(true),
    }
    var createOpts = options.CreateIndexes().SetMaxTime(time.Second * 10)
    if _, err = coll.Indexes().CreateOne(ctx, indexModel, createOpts); err != nil {
        return errors.Wrap(err, "create unique post_id index")
    }
    return nil
}

Creating multiple indexes at the same time

The difference between creating multiple indexes and creating a single index is twofold.

Everything else is similar:

[root@liqiang.io]# cat main.go
func (r *postRepository) createIndexes(ctx context.Context) (err error) {
    var coll = r.client.Database(r.dbName).Collection(r.collName)
    var indexModels = []mongo.IndexModel{
        {
            Keys: bsonx.Doc{
                {
                    Key: "post_id",
                },
            },
            Options: options.Index().SetUnique(true),
        },
        {
            Keys: bsonx.Doc{
                {
                    Key:   "type",
                    Value: bsonx.Int32(1),
                },
            },
        },
        {
            Keys: bsonx.Doc{
                {
                    Key:   "status",
                    Value: bsonx.Int32(1),
                },
            },
        },
    }
    var createOpts = options.CreateIndexes().SetMaxTime(time.Second * 10)
    if _, err = coll.Indexes().CreateMany(ctx, indexModels, createOpts); err != nil {
        return errors.Wrap(err, "create indexes")
    }
    return nil
}