1. MongoDB 简介
1.1 MongoDB 介绍
由于我们时常需要存储一些大文本数据(比如文章内容),存储到一些关系型数据库可能不是最好的选择,这个时候就需要引入一些 NoSQL(Not Only SQL),比如 MongoDB 等数据库,NoSQL 主要用于解决大规模数据集合、多种数据格式等大数据相关问题,在实践中可以理解为用关系型数据库不好解决的就可以考虑使用 NoSQL
⭐ MongoDB 官方文档:https://www.mongodb.com/zh-cn/docs/
1.2 MongoDB 基本特性
MongoDB 主要有以下几点特性:
- 面向集合存储:一个集合可以近似理解为 MySQL 当中的表;集合中存储了很多文档,可以近似理解为 MySQL 当中的数据行
- 模式自由:MongoDB 采用无模式结构存储数据,意味着可以不用像 MySQL 那样预先定义表结构
- 扩展性强:MongoDB 内部支持分片功能,便于进行横向扩展
总结下来,为什么我们在技术选型的时候考虑使用 MongoDB,主要是以下两方面原因:
- 灵活的文档模型:不需要预先定义文档结构,即可插入数据
- 便于横向扩展:可以通过增加 MongoDB 实例来应对数据增长
2. MongoDB 安装
本次我们通过使用 Docker 的方式来启动 MongoDB,在项目中编写如下docker-compose.yaml
文件
yaml
services:
mongo:
image: mongo:6.0
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: example
ports:
- 27017:27017
然后在命令行中输入docker compose up
即可启动

看到如上图所示内容证明 MongoDB 启动成功!
3. MongoDB 快速入门
3.1 初始化客户端
初始化客户端主要分为以下步骤:
- 创建配置对象:使用
options.Client().ApplyURI()
并传入连接地址字符串 - 创建客户端连接:使用
mongo.Connect
方法并传入配置对象
go
package main
import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/v2/event"
"go.mongodb.org/mongo-driver/v2/mongo"
"go.mongodb.org/mongo-driver/v2/mongo/options"
)
func main() {
// 1. 准备命令监视器
monitor := &event.CommandMonitor{
Started: func(ctx context.Context, startedEvent *event.CommandStartedEvent) {
// 输出查询命令
fmt.Println(startedEvent.Command)
},
Succeeded: func(ctx context.Context, succeededEvent *event.CommandSucceededEvent) {},
Failed: func(ctx context.Context, errEvent *event.CommandFailedEvent) {},
}
// 2. 设置连接地址
opts := options.Client().ApplyURI("mongodb://root:[email protected]:27017").SetMonitor(monitor)
client, err := mongo.Connect(opts)
if err != nil {
panic(err)
}
// 3. 创建database和collection
col := client.Database("webook").Collection("articles")
fmt.Println(col)
}
3.2 插入文档
在上一小节初始化客户端的基础上,我们就可以使用得到的客户端client对象来操作 MongoDB 了:
插入文档的 API 为:col.InsertOne()
go
func main() {
// 1. 准备命令监视器
monitor := &event.CommandMonitor{
Started: func(ctx context.Context, startedEvent *event.CommandStartedEvent) {
// 输出查询命令
fmt.Println(startedEvent.Command)
},
Succeeded: func(ctx context.Context, succeededEvent *event.CommandSucceededEvent) {},
Failed: func(ctx context.Context, errEvent *event.CommandFailedEvent) {},
}
// 2. 设置连接地址
opts := options.Client().ApplyURI("mongodb://root:[email protected]:27017").SetMonitor(monitor)
client, err := mongo.Connect(opts)
if err != nil {
panic(err)
}
// 3. 创建database和collection
col := client.Database("webook").Collection("articles")
// 4. 插入文档
ctx := context.Background()
result, err := col.InsertOne(ctx, Article{
Id: 1,
Title: "我的标题",
Content: "我的内容",
AuthorId: 1,
Status: 1,
Ctime: 123,
Utime: 456,
})
if err != nil {
panic(err)
}
// 这里的InsertedID为文档ID
fmt.Println(result.InsertedID)
}
// Article 文章结构体
type Article struct {
Id int64
Title string
Content string
AuthorId int64
Status uint8
Ctime int64
Utime int64
}
程序运行结果如下:

💡 温馨提示:但是 MongoDB 没有自增主键的概念,因此 InsertedID 是文档ID对应的字符串
3.3 查询文档
插入文档的 API 有如下两种方式:
- 通过
bson
结构来构造查询条件: - 通过结构体对象进行查询(需要小心零值问题)
go
func main() {
// ... 省略前面代码
// 5.1 通过bson查询文档
filter := bson.D{bson.E{Key: "id", Value: 1}}
var art Article
err = col.FindOne(ctx, filter).Decode(&art)
if err != nil {
panic(err)
}
fmt.Println(art)
// 5.2 通过结构体查询文档
var art2 Article
err = col.FindOne(ctx, Article{
Id: 1,
}).Decode(&art2)
if err == mongo.ErrNoDocuments {
fmt.Errorf("文档未找到! %w", err)
}
fmt.Println(art2)
}
// Article 文章结构体
type Article struct {
Id int64 `bson:"id,omitempty"`
Title string `bson:"title,omitempty"`
Content string `bson:"content,omitempty"`
AuthorId int64 `bson:"author_id,omitempty"`
Status uint8 `bson:"status,omitempty"`
Ctime int64 `bson:"ctime,omitempty"`
Utime int64 `bson:"utime,omitempty"`
}
程序运行结果如下:

💡 温馨提示:如果使用 结构体对象作为查询条件,则需要在结构体标签处加上
omitempty
参数,表示如果为零值不参与到过滤条件中
3.4 修改文档
修改文档的 API 也跟查询一样有如下两种方式:
case1:构造过滤查询条件
case2:构造更新对象
1. 通过`bson`结构来构造修改条件:
2. 通过结构体对象进行修改(需要小心零值问题)
go
func main() {
// 省略上述代码
// 6. 修改文档
upDoc := bson.D{bson.E{Key: "$set", Value: Article{
Title: "新的标题",
Content: "新的内容",
Utime: 789,
}}}
result, err := col.UpdateOne(ctx, filter, upDoc)
if err != nil {
panic(err)
}
fmt.Println(result.ModifiedCount) // 修改数量
}
💡 温馨提示:这里的"$set"是 MongoDB 独有的操作符
3.5 删除文档
删除文档的方式和查找是一样的,只需要定义过滤条件即可:
这里直接贴代码:
go
func main() {
// 省略上述代码
// 7. 删除文档
delFilter := bson.D{bson.E{Key: "id", Value: 1}}
delResult, err := col.DeleteOne(ctx, delFilter)
if err != nil {
panic(err)
}
fmt.Println(delResult.DeletedCount)
}
程序运行结果:

4. MongoDB 进阶查询
4.1 构造or查询条件
首先我个人的一个结论就是:在使用 MongoDB 构造查询结构体时,最外层一定是 bson.D 的结构
下面为使用 or 查询的方式(对于初学者而言不需要关心如何构造,跟着抄就行)
go
func main() {
// 省略上述代码
ctx := context.Background()
// 插入id=1,2,3的记录
col.InsertOne(ctx, Article{
Id: 1,
})
col.InsertOne(ctx, Article{
Id: 2,
})
col.InsertOne(ctx, Article{
Id: 3,
})
// 构造or查询条件
filter := bson.A{
bson.D{bson.E{Key: "id", Value: 1}},
bson.D{bson.E{Key: "id", Value: 2}},
}
// 查询id为1或者2的记录
find, err := col.Find(ctx, bson.D{bson.E{Key: "$or", Value: filter}})
var articles []Article
err = find.All(ctx, &articles)
if err != nil {
panic(err)
}
fmt.Println(articles)
}
程序运行结果:

4.2 构造and查询条件
下面为使用 and 查询的方式(对于初学者而言不需要关心如何构造,跟着抄就行)
go
func main() {
// 构造and查询条件
andFilter := bson.A{
bson.D{bson.E{Key: "id", Value: 1}},
bson.D{bson.E{Key: "title", Value: "我的标题"}},
}
res, err := col.Find(ctx, bson.D{bson.E{Key: "$and", Value: andFilter}})
if err != nil {
panic(err)
}
err = res.All(ctx, &articles)
if err != nil {
panic(err)
}
fmt.Println(articles)
}
程序运行结果:

4.3 构造in查询条件
下面为使用 in 查询的方式(对于初学者而言不需要关心如何构造,跟着抄就行)
go
func main() {
// 省略上述代码
var articles []Article
// 构造in查询条件
inFilter := bson.D{
bson.E{
Key: "id",
Value: bson.D{bson.E{Key: "$in", Value: []int{1, 2, 3}}}},
}
res, err := col.Find(ctx, inFilter)
if err != nil {
panic(err)
}
err = res.All(ctx, &articles)
if err != nil {
panic(err)
}
fmt.Println(articles)
}
程序运行结果:
