MongoDB数据读写查

mongodb和其他sql不同的地方:

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

示范

model.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

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

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.Fatal("写入文档出错", err2)
            return
        }
    
    
        // 取刚才插入数据的字符串ID,
        insertedID := result.InsertedID.(primitive.ObjectID).Hex()
        //.(primitive.ObjectID).Hex()用于将对象转为字符串ID
    
    
    //修改数据,或者追加字段
    
    //指定被目标条件,这里的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的互转

//将对象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
        }

查询

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

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)
    }

按条件查询结果

//查询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
}
//其他的就和上面一样解码处理就行

更多查询方法

//查一条数据
// 定义一个空的切片来存储查询结果
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}}}})

查询结果排除某条结果:

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

排序

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

分页查询

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))

查询总共有多少条数据

// 使用 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)

聚合管道查询的示范

//指定查询某个小类
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
        }

删除一条数据

// 构建删除的过滤条件
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/