概述
本教程将介绍如何使用官方的 Golang 驱动 Mongo Go Driver 连接 MongoDB,操作 MongoDB 以及索引的一些操作。更多的是一些使用方法,当然,还有一些注意点也是有提到,其实也是踩过的一些坑了。
API 使用
导入依赖包
package main
import (
"context"
"fmt"
"os"
"time"
// 官方的 "mongo-go-driver "包
"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"
)
连接 DB
使用 options.Client()
创建一个 MongoDB 连接的参数选项,你可以添加各种你需要选项,同时 MongoDB 的 URI 信息也是通过这里加入:
[[email protected]]# cat main.go
// 声明要传递给 Connect() 方法的主机和端口选项
var opts = options.Client().
ApplyURI(addr).
SetConnectTimeout(time.Second * 5).
SetSocketTimeout(time.Second * 5).
SetWriteConcern(writeconcern.New(writeconcern.WMajority()))
这里我使用了很多参数:
ApplyURI
:指定 MongoDB 的地址信息SetConnectTimeout
和SetSocketTimeout
:连接控制的一些选项SetWriteConcern
:读写分离的设置
然后执行以下命令,将 clientOptions
实例传递给 mongo.Connect()
方法,并确保同时需要传递一个 context
对象,这个 context
你可以做很多事情,例如控制连接超时超时时间之类的:
[[email protected]]# cat main.go
// 连接到MongoDB并返回客户端实例
客户端,err := mongo.Connect(context.TODO(), clientOptions)
if err !=nil {
fmt.Println("mongo.Connect() ERROR:", err)
os.Exit(1)
}
操作 Collection
有了 Client 之后,还需要指定 DB 和 Collection 才能操作具体的 Collection,例如:
[[email protected]]# cat main.go
// 通过数据库访问 MongoDB 集合
col := client.Database("db").Collection("collection")
CRUD 就太简单了,直接看例子吧。
CRUD
Get
Get 方法的要点其实就是 FindOne 返回的值是 SingleResult,然后一些常见的错误可以处理一下:
[[email protected]]# 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.SingleResult
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 就比较复杂了,主要有几个要点:
- 排序需要考虑排序的字段是有顺序的
- 查询返回的是 Cursor,是带连接状态的,需要关注连接的关闭
[[email protected]]# 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.Cursor
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))
if cursor, err = coll.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
}
这里我使用的是 cursor.All
来解压数据,还有一种比较常见的用法是这样的:
[[email protected]]# 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)
}
这里是采用迭代器的形式来进行解压数据的,使用这种方法切记一定要主动关闭 Cursor,否则可能造成连接泄漏的情况。
Delete
删除的话比较简单,唯一一点值得一提的就是删除的元素不存在怎么判断,这里我是通过判断删除结果中的:DeleteCount
来判断是否真的删除了元素:
[[email protected]]# 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
更新也是比较简单的操作,一个需要注意的点就是是否允许 upsert
,这里是设置了这个选项:
[[email protected]]# 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
创建元素的话有一个点就是插入元素的 ID 是什么,在 Mongo Go Driver 中是通过一个返回值来确定的,但是返回的是一个 interface{}
,需要进行转换:
[[email protected]]# 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.InsertOneResult
if insertOneResult, err = coll.InsertOne(ctx, post); err != nil {
return nil, errors.Wrap(err, "save doc")
}
post.Id = insertOneResult.InsertedID.(primitive.ObjectID)
return post, nil
}
索引
创建单个索引
无论使用哪种语言,使用最新的驱动创建索引的方法调用都是 createIndex()
、createOne()
、create_index()
方法,如果是 Go 的官方驱动 API,则是 Indexes().CreateOne
方法。这个方法调用需要传递一个 key,或者是用来索引数据的字段,以及一个排序顺序的整数,可以是升序的 1
,也可以是降序的 -1
。第二个参数的选项也可能是必需的。下面是一个例子。
[[email protected]]# db.coll.createIndex( |CONTEXT|, { |KEY| : |SORT_ORDER|, |OPTIONS| } )
这个是 Github 上 mongo-go-driver
中 index_view.go
, 这个方法的方法原型:
// CreateOne 在模型指定的集合中创建一个索引。
func (iv IndexView) CreateOne(ctx context.Context, model IndexModel, opts, *options.CreateIndexesOptions) (string, error)
可选择将 options.Index()
方法调用传递给IndexModel的 Options
参数。下面的例子就是为博客的 id 设置一个唯一索引。
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
}
同时创建多个索引
创建多个索引和创建单个索引的区别就两个:
- 创建的函数从
CreateOne
换成CreateMany
- 创建的参数从
IndexModel
换成[]IndexModel
其他地方都是类似的。
[[email protected]]# 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
}