MongoDB

寒江蓑笠翁大约 11 分钟

MongoDB

MongoDB是一个文档数据库,它的基本数据单位就是文档,存储格式是BSON(Binary JSON)一种类似JSON的结构,松散的结构可以存储不同类型的数据,相较于关系数据库更为灵活,并且使用js作为脚本语言,可以通过脚本来完成组合操作。本文主要介绍使用在Go中使用官方的mongo驱动操作mongodb数据库,并不是mongodb教程,如果你没有mongo基础,请先自行了解和学习。

mongodb文档:Introduction to MongoDB — MongoDB Manualopen in new window

驱动

mongodb在go这方面的库比较少,早期有些社区维护的库,后面都停止维护了,不过官方的mongo驱动库已经完全足够使用了。

开源仓库:mongodb/mongo-go-driver: The Official Golang driver for MongoDB (github.com)open in new window

文档地址:mongodb/mongo-go-driver: The Official Golang driver for MongoDB (github.com)open in new window

安装

下载依赖的话使用下面的地址就行了。

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

连接

下面是一个简单的mongo客户端与服务端建立连接的例子。

package main

import (
   "context"
   "fmt"
   "go.mongodb.org/mongo-driver/mongo"
   "go.mongodb.org/mongo-driver/mongo/options"
   "go.mongodb.org/mongo-driver/mongo/readpref"
   "log"
)

func main() {
   ctx := context.Background()
   // 使用URI建立连接
   client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://admin:123456@192.168.48.138:27017/"))
   if err != nil {
      log.Panicln(err)
   }
   // 关闭连接
   defer client.Disconnect(ctx)
   // ping测试连接是否可用
   fmt.Println(client.Ping(ctx, readpref.Primary()))
}

bson

mongodb在go里面使用了以下几种类型来映射数据库中的文档,位于bson/bson.go

// BSON文档的有序表示
type D = primitive.D

// 一对键值,BSON文档的有序表示的基本单位
type E = primitive.E

// BSON文档的无序表示
type M = primitive.M

// BSON数据的有序表示
type A = primitive.A

它们的实际类型如下

// BSON文档的有序表示
type D []E

// 一对键值,BSON文档的有序表示的基本单位
type E struct {
	Key   string
	Value interface{}
}

// BSON文档的无序表示
type M map[string]interface{}

// BSON数据的有序表示
type A []interface{}

通过以上几种类型,即可以构造查询SQL,也可以用来映射数据。

提示

驱动examples目录下有着相当多的使用示例,官方非常详细的演示了如何使用上述四种类型。

地址:mongo-go-driver/examples/documentation_examples/examples.go at master · mongodb/mongo-go-driver (github.com)open in new window

查询文档

官方查询示例:mongo-go-driver/examples/documentation_examples/examples.go at master · mongodb/mongo-go-driver (github.com)open in new window

首先创建user数据库,向集合users插入如下数据

> use user
> db.users.insertMany([
    {
        name: "mike",
        age: 12,
        
    },
    {
        name: "jenny",
        age: 14,
        
    },
    {
        name: "jack",
        age: 18,
        address: "usa"
    }
])

查询单个

type User struct {
    Name    string `bson:"name"`
    Age     int    `bson:"age"`
    Address string `bson:"address"`
}

var user User

result := client.Database("user"). // 选中数据库
                    Collection("users").                     // 选中集合
                    FindOne(ctx, bson.D{{"address", "usa"}}) // 过滤条件

// 反序列化
if err := result.Decode(&user); err != nil {
    log.Panicln(err)
}

fmt.Printf("%+v\n", user)

上面那段查询代码等价于

db.users.findOne({
    address: "usa"
})

输出结果

{Name:jack Age:18 Address:usa}

查询多个

type User struct {
   Name    string `bson:"name"`
   Age     int    `bson:"age"`
   Address string `bson:"address"`
}

var users []User

cursor, err := client.Database("user"). // 选中数据库
               Collection("users"). // 选中集合
               Find(ctx, bson.D{})  // 过滤条件

if err != nil {
   log.Panicln(err)
}

if err := cursor.All(ctx, &users); err != nil {
   log.Panicln(err)
}

fmt.Printf("%+v\n", users)

等价于

db.users.find({})

输出

[{Name:jack Age:18 Address:usa} {Name:mike Age:12 Address:} {Name:jenny Age:14 Address:}]

在构造查询条件的时候,也可以使用options

type User struct {
    Name    string `bson:"name"`
    Age     int    `bson:"age"`
    Address string `bson:"address"`
}

var users []User

find := options.Find()
find.SetSort(bson.M{"age": 1})
find.SetLimit(1)

cursor, err := client.Database("user"). // 选中数据库
                    Collection("users").      // 选中集合
                    Find(ctx, bson.D{}, find) // 过滤条件

if err != nil {
    log.Panicln(err)
}

if err := cursor.All(ctx, &users); err != nil {
    log.Panicln(err)
}

fmt.Printf("%+v\n", users)

等价于

db.users.find({}).sort({age:1}).limit(1)

输出

[{Name:mike Age:12 Address:}]

创建文档

官方创建实例:mongo-go-driver/examples/documentation_examples/examples.go at master · mongodb/mongo-go-driver (github.com)open in new window

下面是创建一个文档的例子

one, err := client.Database("user").Collection("users").InsertOne(ctx, User{
    Name:    "lili",
    Age:     20,
    Address: "china",
})
if err != nil {
    log.Panicln(err)
}
fmt.Println(one.InsertedID)

创建成功后会返回文档的ObjectID

ObjectID("64c60fa01e2548d9e4de6cf4")

下面是创建多个文档的例子

users := []any{User{
    Name:    "john",
    Age:     10,
    Address: "usa",
}, User{
    Name:    "pop",
    Age:     30,
    Address: "uk",
}}

one, err := client.Database("user").Collection("users").InsertMany(ctx, users)
if err != nil {
    log.Panicln(err)
}
fmt.Println(one.InsertedIDs)

创建成功后返回返回一组ObjectID

[ObjectID("64c610d5aec2618d6ca0b515") ObjectID("64c610d5aec2618d6ca0b516")]

上面两段代码就等价于db.users.insertOnedb.users.insertMany

更新文档

官方更新示例:mongo-go-driver/examples/documentation_examples/examples.go at master · mongodb/mongo-go-driver (github.com)open in new window

下面是更新单个文档的示例,将名为lili人更名为mark

upres, err := client.Database("user").Collection("users").UpdateOne(ctx, bson.D{
    {"name", "mark"},
},
    bson.D{
       {"$set", bson.D{
          {"name", "lili"},
       }},
    })
if err != nil {
    log.Panicln(err)
}
fmt.Printf("%+v", upres)

等价于

db.users.updateOne({
    name: "lili"
}, {
    $set: {
        name: "mark"
    },
})

输出

&{MatchedCount:1 ModifiedCount:1 UpsertedCount:0 UpsertedID:<nil>}

下面是更新多个文档的示例,将年龄为10的人地址更新为cn

upres, err := client.Database("user").Collection("users").UpdateMany(ctx, bson.D{
    {"age", 10},
},
    bson.D{
        {"$set", bson.D{
            {"address", "cn"},
        }},
    })
if err != nil {
    log.Panicln(err)
}
fmt.Printf("%+v", upres)

除了使用Update,mongo还提供了Replace,两者的区别在于前者是更新文档字段,后者是直接替换文档。例如下面的代码,就不再需要操作符了。

upres, err := client.Database("user").Collection("users").ReplaceOne(ctx, bson.D{
    {"age", 10},
},
    bson.D{
       {"address", "cn"},
    })
if err != nil {
    log.Panicln(err)
}
fmt.Printf("%+v", upres)

同时mongo还提供了FindOneAndUpdateFindOneAndReplace来获取文档和更新文档。如下

res := client.Database("user").Collection("users").FindOneAndReplace(ctx, bson.D{
    {"address", "cn"},
},
    bson.D{
       {"address", "uk"},
    })
if err := res.Err(); err != nil {
    log.Panicln(err)
}

var user User

res.Decode(&user)

fmt.Printf("%+v", user)

输出

Name: Age:0 Address:cn}

此操作会先查询文档再进行修改文档。

删除文档

官方删除示例:mongo-go-driver/examples/documentation_examples/examples.go at master · mongodb/mongo-go-driver (github.com)open in new window

下面是删除一个文档的例子

result, err := client.Database("user").Collection("users").DeleteOne(ctx, bson.D{
    {"name", "jack"},
})
if err != nil {
    log.Panicln(err)
}
fmt.Println(result.DeletedCount)

下面是删除多个文档的例子

result, err := client.Database("user").Collection("users").DeleteMany(ctx, bson.D{
    {"age", "10"},
})
if err != nil {
    log.Panicln(err)
}
fmt.Println(result.DeletedCount)

聚合

官方聚合示例:mongo-go-driver/examples/documentation_examples/examples.go at master · mongodb/mongo-go-driver (github.com)open in new window

聚合操作会用到mongo.Pipeline类型,它的本质是[]bson.D

type Pipeline []bson.D
pipline := mongo.Pipeline{
    {
       {"$match", bson.D{
          {"address", "uk"},
       }},
    },
    {
       {"$sort", bson.D{
          {"age", 1},
       }},
    },
}
aggregate, err := client.Database("user").Collection("users").Aggregate(ctx, pipline)
if err != nil {
    log.Panicln(err)
}
var users []User
if err := aggregate.All(ctx, &users); err != nil {
    log.Panicln(err)
}
log.Println(users)

输出

[{lili 20 uk} {kak 30 uk}]

这段聚合操作就是匹配所有address为uk的用户,然后按照年龄排序。