Go 连接 MongoDB

MongoDB

MongoDB是一种高性能、开源、文档型的NoSQL数据库,被广泛应用于Web应用、大数据以及云计算领域。MongoDB属于非关系型数据库,它是由C++编写的分布式文档数据库。内部使用类似于Json的bson二进制格式。

中文手册

https://www.w3cschool.cn/mongodb/

驱动

驱动 https://www.mongodb.com/docs/drivers/

Go驱动 https://www.mongodb.com/docs/drivers/go/current/

驱动安装

1
go get go.mongodb.org/mongo-driver/mongo

连接字符串

https://www.mongodb.com/docs/manual/reference/connection-string/#examples

1
2
mongodb://[username:password@]host1[:port1][,...hostN[:portN]] [/[defaultauthdb][?options]]
mongodb://wayne:wayne@mongodb0.example.com:27017

连接例子 https://www.mongodb.com/docs/drivers/go/current/fundamentals/connection/#connection-example

快速入门 https://www.mongodb.com/docs/drivers/go/current/quick-start/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import (
"context"
"fmt"
"log"
"time"

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

var client *mongo.Client
var db *mongo.Database
var users *mongo.Collection

func init() {
url := "mongodb://root:123456@127.0.0.1:27017//"
opts := options.Client()
opts.ApplyURI(url).SetConnectTimeout(5 * time.Second).
SetAuth(options.Credential{Username: "root", Password: "123456", AuthSource: "admin"})

var err error
client, err = mongo.Connect(context.TODO(), opts) // context.TODO() 空上下文
if err != nil {
log.Fatalln(err)
}

err = client.Ping(context.TODO(), nil)
if err != nil {
log.Fatalln(err)
}
fmt.Println("----------------------------")

// 不使用:=
db = client.Database("test") // 连接test库
users = db.Collection("users") // 集合,相当于表

}

// 断开连接放到其它函数里
defer func() {
if err := c.Disconnect(context.TODO()); err != nil {
log.Fatalln(err)
}
}()

基本概念

MongoDB中可以创建使用多个库,但有一些数据库名是保留的,可以直接访问这些有特殊作用的数据库。

  • admin: 从权限的角度来看,这是”root”数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。
  • local: 这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合。
  • config: 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。
RDBMSMongoDB
DatabaseDatabase
TableCollection
RowDocument
ColumnField
JoinEmbedded Document嵌入文档或Reference引用
Primary Key主键(MongoDB提供了key为_id)

Go Driver使用,官方博客 https://www.mongodb.com/blog/post/mongodb-go-driver-tutorial

数据封装

1
2
3
4
5
6
7
8
9
type User struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
Name string
Age int
}

func (u User) String() string {
return fmt.Sprintf("<%s: %s,%d", u.ID, u.Name, u.Age)
}

Tag参考 https://www.mongodb.com/docs/drivers/go/upcoming/fundamentals/bson/#struct-tags

User结构体中ID一定要使用omitempty,新增时结构体ID不设置则为零值,提交时不会提交ID,数据库自动生成_id

ObjectId有12字节组成,参考 bson/primitive/objectid.go/NewObjectID()函数

  • 4字节时间戳
  • 5字节进程唯一值
  • 3字节随机数,每次加1

插入数据

操作参考 https://www.mongodb.com/docs/drivers/go/current/usage-examples/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 插入一条数据
func insertOne() {
tom := User{Name: "tom", Age: 35}
insertResult, err := users.InsertOne(context.TODO(), tom)
if err != nil {
log.Fatalln(err)
}
fmt.Println(insertResult.InsertedID)
}

// 插入多条数据
func insertMany() {
jerry := User{Name: "jerry", Age: 23}
ben := User{Name: "ben", Age: 19}
insertManyResult, err := users.InsertMany(context.TODO(), []interface{}{jerry, ben})
if err != nil {
log.Fatalln(err)
}
fmt.Println(insertManyResult.InsertedIDs...)
}

BSON

https://www.mongodb.com/docs/drivers/go/upcoming/fundamentals/bson/

MOngoDB的Go库提供的构建BSON的数据类型分为4种

  • D : An ordered representation of a BSON document (slice),表示有序的,切片且元素是二元的
  • M : An unordered representation of a BSON document (map),表示无序的,map且元素是kv对
  • A : An ordered representation of a BSON array
  • E : A single element inside a D type

具体使用看以下的例子

查询

单条查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 查询一条数据
func findOne() {
// 条件
// filter := bson.D{{"name": "tom"}} // slice
// filter := bson.D{{"name", bson.D{{"$eq", "tom"}}}}
filter := bson.M{"name": "tom"} // map
// filter := bson.M{"name": bson.M{"$ne": "jerry"}}
// filter := bson.D{{}} // 没有条件全部都符合

var u User
err := users.FindOne(context.TODO(), filter).Decode(&u)
if err != nil {
if err == mongo.ErrNoDocuments {
// 说明没有任务匹配文档
log.Println("没有匹配的文档")
return
}
log.Fatalln(err)
}
fmt.Println(u)
}

多条查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 查询多条数据
func findMany1() {
filter := bson.M{} // 无条件,全部符合
cursor, err := users.Find(context.TODO(), filter)
if err != nil {
log.Fatalln(err)
}
var results []*User
for cursor.Next(context.TODO()) {
var u User
err = cursor.Decode(&u)
if err != nil {
log.Fatalln(err)
}
results = append(results, &u) // 装入容器
}
if err := cursor.Err(); err != nil {
log.Fatal(err)
}
cursor.Close(context.TODO()) // 关闭游标
fmt.Println(results)
}

// 查询多条数据,成批装入容器
func findMany2() {
filter := bson.M{} // 无条件,全部符合
var results []*User
cursor, err := users.Find(context.TODO(), filter)
if err != nil {
log.Fatalln(err)
}
err = cursor.All(context.TODO(), &results)
if err != nil {
log.Fatalln(err)
}
cursor.Close(context.TODO()) // 关闭游标
for i, j := range results {
fmt.Println(i, j)
}
}

条件查询

改造上面的findMany2函数,可以使用下面表格中不同filter

https://www.mongodb.com/docs/manual/reference/operator/query/and/

比较符号含义filter示例
$lt小于bson.M{"age": bson.M{"$lt": 20}}
$gt大于bson.M{"age": bson.M{"$gt": 20}}
$lte小于等于bson.M{"age": bson.M{"$lte": 20}}
$gte大于等于bson.M{"age": bson.M{"$gte": 20}}
$ne不等于bson.M{"age": bson.M{"$ne": 20}}
$eq等于,可以不用这个符号bson.M{"age": bson.M{"$eq": 20}} bson.M{"age": 20}
$in在范围内bson.M{"age": bson.M{"$in": []int{16, 33}}}
$nin不在范围内bson.M{"age": bson.M{"$nin": []int{16, 33}}}
逻辑符号含义filter 示例
$andbson.M{"$and": []bson.M{{"name": "tom"}, {"age": 33}}}
bson.M{"$and": []bson.M{{"name": "tom"}, {"age": bson.M{"$gt":40}}}}
$orbson.M{"$or": []bson.M{{"name": "tom"}, {"age": bson.M{"$lt":20}}}}
$notbson.M{"age": bson.M{"$not": bson.M{"$gte": 20}}}
元素含义示例
$exists文档中是否有这个字段bson.M{"Name": bson.M{"$exists": true}}
$type字段是否是指定的类型bson.M{"age": bson.M{"$type": 16}}

常用类型,参考 https://docs.mongodb.com/manual/reference/operator/query/type/#op._S_type

  • 字符串类型编码为2,别名为string
  • 整型编码为16,别名为int
  • 长整型编码为18,别名为long

改造函数findByFilterfindAll,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
func findAll(filter interface{}, opt *options.FindOptions) {
var results []*User
cursor, err := users.Find(context.TODO(), filter, opt)
if err != nil {
log.Fatalln(err)
}
err = cursor.All(context.TODO(), &results)
if err != nil {
log.Fatalln(err)
}
cursor.Close(context.TODO()) // 关闭游标
fmt.Println(results)
}

投影

1
2
3
4
5
6
7
filter := bson.M{"age": bson.M{"$gt": 20}}
opt := options.Find()
opt.SetProjection(bson.M{"name": false, "age": false}) // name、age字段不投影, 都显示为零值
findAll(filter, opt)

opt.SetProjection(bson.M{"name": true}) // name投影,age字段零值显示 1
findAll(filter, opt)

排序

1
2
opt.SetSort(bson.M{"age": 1})  // 升序
opt.SetSort(bson.M{"age": -1}) // 降序

分页

1
2
3
opt.SetSkip(1)  // offset
opt.SetLimit(1) // limit
findAll(filter, opt)

更新

更新操作符含义示例
$inc对给定字段数字值增减bson.M{"$inc": bson.M{"age": -5}}
$set设置字段值,如果字段不存在则创建bson.M{"$set": bson.M{"gender": "M"}}
$unset移除字段{'$unset':{'Name':""}}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 更新一条
func updateOne() {
filter := bson.M{"age": bson.M{"$exists": true}} // 所有有age字段的文档
update := bson.M{"$inc": bson.M{"age": -5}} // age字段减5
ur, err := users.UpdateOne(context.TODO(), filter, update)
if err != nil {
log.Fatal(err)
}
fmt.Println(ur.MatchedCount, ur.ModifiedCount)
}

// 更新多条
func updateMany() {
filter := bson.M{"age": bson.M{"$exists": true}} // 所有有age字段的文档
update := bson.M{"$set": bson.M{"gender": "M"}} // 为符合条件的文档设置gender字段
// update := bson.M{"$unset": bson.M{"gender": ""}} // 为符合条件的文档移除gender字段
users.UpdateMany(context.TODO(), filter, update)
}

删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 删除一条
func deleteOne() {
filter := bson.M{} // 没有条件,匹配所有文档
dr, err := users.DeleteOne(context.TODO(), filter)
if err != nil {
log.Fatal(err)
}
fmt.Println(dr.DeletedCount)
}

// 删除多条
func deleteMany() {
// filter := bson.M{} // 没有条件,匹配所有文档
filter := bson.M{"name": "tom"}
dr, err := users.DeleteMany(context.TODO(), filter)
if err != nil {
log.Fatal(err)
}
fmt.Println(dr.DeletedCount)
}
-------------本文结束感谢您的阅读-------------