MongoDB
MongoDB 是一個文檔數據庫,它的基本數據單位就是文檔,存儲格式是 BSON(Binary JSON)一種類似 JSON 的結構,松散的結構可以存儲不同類型的數據,相較於關系數據庫更為靈活,並且使用 js 作為腳本語言,可以通過腳本來完成組合操作。本文主要介紹使用在 Go 中使用官方的 mongo 驅動操作 mongodb 數據庫,並不是 mongodb 教程,如果你沒有 mongo 基礎,請先自行了解和學習。
mongodb 文檔:Introduction to MongoDB — MongoDB Manual
驅動
mongodb 在 go 這方面的庫比較少,早期有些社區維護的庫,後面都停止維護了,不過官方的 mongo 驅動庫已經完全足夠使用了。
開源倉庫:mongodb/mongo-go-driver: The Official Golang driver for MongoDB (github.com)
文檔地址:mongodb/mongo-go-driver: The Official Golang driver for MongoDB (github.com)
安裝
下載依賴的話使用下面的地址就行了。
$ 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,也可以用來映射數據。
TIP
驅動 examples 目錄下有著相當多的使用示例,官方非常詳細的演示了如何使用上述四種類型。
查詢文檔
首先創建 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:}]創建文檔
下面是創建一個文檔的例子
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.insertOne和db.users.insertMany。
更新文檔
下面是更新單個文檔的示例,將名為 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 還提供了FindOneAndUpdate和FindOneAndReplace來獲取文檔和更新文檔。如下
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}此操作會先查詢文檔再進行修改文檔。
刪除文檔
下面是刪除一個文檔的例子
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.Pipeline類型,它的本質是[]bson.D
type Pipeline []bson.Dpipline := 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 的用戶,然後按照年齡排序。
