MongoDB数据读写查


mongodb和其他sql不同的地方:

  • mongodb的表单叫做Collection(集),
  • 它没有严格的字段类型限制,甚至没有字段限制,可以容纳不同字段名,类型,结构,的数据在一个集
  • 它没有关系映射,任何数据之间的联系操作都要手动编写代码去完成
  • 它的唯一键值ID为对象,如果要记录对象需转为字符串
  • 任何要操作某条数据时,如果知道键值ID,需要先转为对象

示范

model.go

go
package model

import (
    "go.mongodb.org/mongo-driver/mongo"
)

// 定义全局表单变量,也就是集的对象
var (
    TicketModel *mongo.Collection
    FqaModel *mongo.Collection
    NewsModel *mongo.Collection
)

//初始化连接程序
func Init(client *mongo.Client) {
    db := client.Database("btcinfo") //连接btcinfo数据库
	TicketModel = db.Collection("ticket") //把集(表)对象赋予变量
    FqaModel = db.Collection("fqa")
    NewsModel = db.Collection("news")
}

main.go

go
package main

import (
	model "btcinfo/models"  //导入model包
	"go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
	"context"
	"time"
	"log"
)

func main() {
	 // 运行初始化 MongoDB 客户端连接的函数
	 client, err := initMongoClient()
	 if err != nil {
		 log.Fatal("初始化数据库客户端失败:", err)
	 }
	 defer client.Disconnect(context.Background())

	 //执行模型初始化
	 model.Init(client)
 
	r := router.Router()
	r.Run(":80") // 启动HTTP服务器,监听端口80
}

//初始化 MongoDB 客户端连接的函数
func initMongoClient() (*mongo.Client, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))
    if err != nil {
        return nil, err
    }

	// 尝试 Ping MongoDB 以确保连接成功
    err = client.Ping(ctx, nil)
    if err != nil {
		log.Fatal("数据库ping失败", err)
        return nil, err
    }

    return client, err
}

数据库的操作

注意,官方教程是使用bson.D{{"id", "123"}}的格式,但是这样不会被编辑器识别其格式,会出现警告,所以我采用bson.D{{Key: "id", Value: "123"}}这种格式可避免警告。

controller.go

go
package controller

import (
	model "btcinfo/models"
	"fmt"
	"log"
	"context"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive" //转换对象和字符串需要
)
func(){
	
	//写入一条数据(也叫写入文档)
	
	// 构造要插入的数据
		data := bson.D{
			{Key: "email", Value: "13322@gmail.com"},
			{Key: "subject", Value: "主题"},
			{Key: "description", Value: "内容"},
		}
	
		// 向 TicketModel集插入数据
		result, err2 := model.TicketModel.InsertOne(context.Background(), data)
		if err2 != nil {
			log.Print("写入文档出错", err2)
			return
		}
	
	
		// 取刚才插入数据的字符串ID,
		insertedID := result.InsertedID.(primitive.ObjectID).Hex()
		//.(primitive.ObjectID).Hex()用于将对象转为字符串ID
	
	// 将 user.ID 转换为字符串
	//userIDStr := user.ID.Hex()
	
	
	//修改数据,或者追加字段
	
	//指定被目标条件,这里的result.InsertedID是之前写入返回的对象
		filter := bson.D{{Key: "_id", Value:result.InsertedID}}

		// 构造更新操作,和内容,$set为插入数据,或者更新
		update := bson.D{
			{Key: "$set", Value: bson.D{
				// 设置要更新的字段和对应的值,这里假设追加了一个is_file字段,值为布尔型
				{Key: "is_file", Value: true},
			}},
		}
		// 执行更新操作,注意_id条件必须是对象,而不是字符串id
		result2, err3 := model.TicketModel.UpdateOne(context.Background(),filter, update)
		if err3 != nil {
			log.Fatal("写入附件字段失败:", err3)
			return
		}
		// 检查是否有文档被更新,用来判断是否更新成功
		if result2.ModifiedCount == 0 {
			log.Println("未更新任何数据,可能是指定的 _id 不存在于数据库中")
			return
		}
	
	//删除字段
	//假设我知道这条数据的id,将字符串ID转为数据对象ID
		objectID, err := primitive.ObjectIDFromHex("65edb47e1858a8d104843ef7")
		if err != nil {
			log.Println("转换ID出错:", err)
			return
		}

		// 构造过滤条件,指定要更新的文档
		filter := bson.D{{Key: "_id", Value: objectID}}

		// 构造更新文档,使用 $unset 操作符指定要删除的字段,$unset就是删除
		update := bson.D{
			{Key:"$unset", Value:bson.D{{Key: "is_file", Value: ""}}},
		}

		// 执行更新操作,删除字段
		_, err5 := model.TicketModel.UpdateOne(context.Background(), filter, update)
		if err5 != nil {
			log.Println("更新文档出错:", err5)
			return
		}

}

对象ID和字符串ID的互转

go
//将对象ID转字符串ID,假设result是返回的对象
insertedID := result.InsertedID.(primitive.ObjectID).Hex()
//或者可以试试直接result.Hex()

//将字符串ID转对象ID
objectID, err := primitive.ObjectIDFromHex("65edb47e1858a8d104843ef7")
		if err != nil {
			log.Println("转换ID出错:", err)
			return
		}

查询

查询操作,查询一个表的所有文档:

go
import (
	model "btcinfo/models"
	"fmt"
	"context"
	"go.mongodb.org/mongo-driver/bson"
)

// 定义一个文章据库模型
type Article_model struct {
	Subject     string    //主题
	Description string    //描述
	Dalei       string    //类型
	Xiaolei       string    //类型
	CreatedAt   time.Time //创建时间
}

//下面的要写在某个函数里
// 定义一个空的切片来存储查询结果
var results []Article_model

// 执行查询操作
cursor, err := FaqModel.Find(context.TODO(), bson.M{})
if err != nil {
	c.JSON(500, gin.H{"db_error": err})
	return
}

if err := cursor.All(context.TODO(), &results); err != nil {
			// 处理解码错误
			c.JSON(500, gin.H{"db_error": err})
			return
}

//遍历单个结果
for _, result := range results {
		fmt.Printf("主题:", result.Subject)
	}

按条件查询结果

go
//查询xiaolei字段值为gift的结果
filter := bson.D{{Key: "xiaolei", Value: "gift"}}

// 执行查询操作
cursor, err := FaqModel.Find(context.TODO(), filter)
if err != nil {
	c.JSON(500, gin.H{"db_error": err})
	return
}
//其他的就和上面一样解码处理就行

更多查询方法

go
//查一条数据
// 定义一个空的切片来存储查询结果
var result Article_model
err = coll.FindOne(context.TODO(), bson.D{{Key: "xiaolei", Value: "gift"}}).Decode(&result)//结果保存在result中

//查多条数据,其中$gte 是大于等于的意思,也就是查找大于或等于46的结果
cursor, err := coll.Find(context.TODO(), bson.D{{Key: "age", Value: bson.D{{Key: "$gte", Value:46}}}})

查询结果排除某条结果:

go
//设置查询结果为3条
findOptions := options.Find().SetLimit(3)
//构建查询条件排除某条
filter = bson.D{{Key:"_id", Value:bson.D{{Key:"$ne", Value:objectID}}}}

排序

go
//默认是按_id正序排,也就是越晚插入越靠前
options := options.Find().SetSort(bson.D{{"_id", -1}}) //按id倒序

分页查询

go
page, err := strconv.Atoi(c.Param("page")) //页码需要先转整数
    if err != nil {
        // 处理转换错误
        c.JSON(400, gin.H{"page_error": err})
        return
    }
	pageSize := 6 // 每页显示的文档数量
	// 计算要跳过的文档数量
    skip := (page - 1) * pageSize
	// 设置查询选项,包括分页参数
    options := options.Find()
    options.SetSkip(int64(skip))
    options.SetLimit(int64(pageSize))

查询总共有多少条数据

go
// 使用 estimatedDocumentCount 方法,这样更快,可能不准确
count, _ := model.ArticleModel.EstimatedDocumentCount(context.TODO())

// 使用 countDocuments 方法,准确但是慢
count, err := model.ArticleModel.CountDocuments(context.TODO())


//按条件查询返回数量
//查询条件
filter = bson.D{{Key: "xiaolei", Value: xiaolei}}
//取当前总页数
zongtiaoshu, _ := model.ArticleModel.CountDocuments(context.TODO(), filter)
fmt.Print("总条数",zongtiaoshu)

聚合管道查询的示范

go
//指定查询某个小类
filter = bson.D{{Key: "xiaolei", Value: xiaolei}}

// 构造聚合管道
		pipeline := bson.A{
			bson.M{
				"$match": filter,
			},
			bson.M{
				"$project": bson.M{
					"description": bson.M{ //指定要截断的字段description
						"$substr": bson.A{"$description", 0, 50},
					},
					// 其他字段投影
					// ...
				},
			},
		}

		// 执行聚合查询操作
		cursor, err := model.ArticleModel.Aggregate(context.TODO(), pipeline)
		if err != nil {
			c.JSON(500, gin.H{"db_error": err})
			return
		}

		//解码
		if err := cursor.All(context.TODO(), &results); err != nil {
			// 处理解码错误
			c.JSON(500, gin.H{"db_error": err})
			return
		}


// 这是另一个示范
// 使用聚合管道查询回复及其用户信息
    pipeline := mongo.Pipeline{
		// 匹配指定的主题 ID
        {{Key: "$match", Value: bson.D{{Key: "info_id", Value: objectID}}}},
		// 通过 user_id 字段在 user 集合中查找对应的用户信息,结果临时存储在$lookup
        {{Key: "$lookup", Value: bson.D{
            {Key: "from", Value: "user"}, // 指定要查找的集合为 "user"
            {Key: "localField", Value: "user_id"}, // 当前集合中的字段 "user_id"
            {Key: "foreignField", Value: "_id"}, // "user" 集合中的字段 "_id"
            {Key: "as", Value: "user"}, // 查找到的用户信息存储在 "user" 字段中
        }}},
		// 展开查找到的用户信息数组(因为 $lookup 结果是数组)
        {{Key: "$unwind", Value: "$user"}},
		// 投影结果字段,选择需要的字段并重命名
        {{Key: "$project", Value: bson.D{
            {Key: "_id", Value: 1},  // 保留 "_id" 字段
            {Key: "user_id", Value: 1},  // 保留 "user_id" 字段
            {Key: "content", Value: 1}, // 保留 "content" 字段
            {Key: "created_at", Value: 1}, // 保留 "created_at" 字段
            {Key: "name", Value: "$user.name"}, // 将 "user" 集合中的 "name" 字段重命名为 "username"
        }}},
    }


// 查询所有贴子并附带用户信息
		pipeline := mongo.Pipeline{
			// 匹配所有的主题
			{{Key: "$match", Value: bson.D{}}},
			// 通过 user_id 字段在 user 集合中查找对应的用户信息,结果临时存储在$lookup
			{{Key: "$lookup", Value: bson.D{
				{Key: "from", Value: "user"},          // 指定要查找的集合为 "user"
				{Key: "localField", Value: "user_id"}, // 当前集合中的字段 "user_id"
				{Key: "foreignField", Value: "_id"},   // "user" 集合中的字段 "_id"
				{Key: "as", Value: "user"},            // 查找到的用户信息存储在 "user" 字段中
			}}},
			// 展开查找到的用户信息数组(因为 $lookup 结果是数组)
			{{Key: "$unwind", Value: "$user"}},
			// 投影结果字段,这里是InfoDB集合中需要保留的字段和$user.name插入字段并重命名为name
			{{Key: "$project", Value: bson.D{
				{Key: "_id", Value: 1},             // 保留 "_id" 字段
				{Key: "user_id", Value: 1},         // 保留 "user_id" 字段
				{Key: "gpu_model", Value: 1},         // 保留 "user_id" 字段
				{Key: "card_num", Value: 1},         // 保留 "user_id" 字段
				{Key: "number", Value: 1},         // 保留 "user_id" 字段
				{Key: "date", Value: 1},         // 保留 "user_id" 字段
				{Key: "price", Value: 1},         // 保留 "user_id" 字段
				{Key: "auth", Value: 1},         // 保留 "user_id" 字段
				{Key: "created_at", Value: -1},      // 保留 "created_at" 字段
				{Key: "name", Value: "$user.name"}, // 将 "user" 集合中的 "name" 字段重命名为 "username"

			}}},
			// 按 "created_at" 字段进行降序排序
			{{Key: "$sort", Value: bson.D{
				{Key: "created_at", Value: -1}, // -1 表示降序排序
			}}},
		}

		cursor, err := model.InfoDB.Aggregate(context.TODO(), pipeline)
		if err != nil {
			log.Print("查询回复时聚合查询失败:", err)
			c.JSON(500, gin.H{"error": err.Error()})
			return
		}
		defer cursor.Close(context.TODO())

		var listcha []model.ListRentCha
		if err = cursor.All(context.TODO(), &listcha); err != nil {
			log.Print("查询回复时聚合查询失败:", err)
			c.JSON(500, gin.H{"error": err.Error()})
			return
		}

删除一条数据

go
// 构建删除的过滤条件
filter := bson.M{"_id": objectID}

// 执行删除操作
_, err := model.FaqModel.DeleteOne(context.TODO(), filter)
if err != nil {
	c.JSON(400, gin.H{"删除失败": err})
	return
}

数据库迁移

备份
mongodump --host 127.0.0.1:27017 --db btcinfo --out btcinfo
//也可以指定路径--out /path/to/backup/directory

恢复
mongorestore --host 127.0.0.1:27017 --db btcinfo --dir /home/btcinfo/

比较运算符

在 MongoDB 中,可以使用多种比较运算符来设置查询条件。以下是一些常见的比较运算符及其用途:

常见的比较运算符

  1. $eq:等于

    • 用于匹配等于指定值的文档。
    go
    filter := bson.M{"field": bson.M{"$eq": value}}
    
  2. $ne:不等于

    • 用于匹配不等于指定值的文档。
    go
    filter := bson.M{"field": bson.M{"$ne": value}}
    
  3. $gt:大于

    • 用于匹配大于指定值的文档。
    go
    filter := bson.M{"field": bson.M{"$gt": value}}
    
  4. $gte:大于或等于

    • 用于匹配大于或等于指定值的文档。
    go
    filter := bson.M{"field": bson.M{"$gte": value}}
    
  5. $lt:小于

    • 用于匹配小于指定值的文档。
    go
    filter := bson.M{"field": bson.M{"$lt": value}}
    
  6. $lte:小于或等于

    • 用于匹配小于或等于指定值的文档。
    go
    filter := bson.M{"field": bson.M{"$lte": value}}
    
  7. $in:在指定数组中

    • 用于匹配字段值在指定数组中的文档。
    go
    filter := bson.M{"field": bson.M{"$in": bson.A{value1, value2, value3}}}
    
  8. $nin:不在指定数组中

    • 用于匹配字段值不在指定数组中的文档。
    go
    filter := bson.M{"field": bson.M{"$nin": bson.A{value1, value2, value3}}}
    

MongoDB Shell管理命令

删除info集合中的所有img字段

css
db.getCollection("info").updateMany(
    {},
    { $unset: { img: "" } }
);