MongoDB go快速操控

简介

qmgo 是七牛云基于官方 mongo-go-driver 封装的 Go 操作 MongoDB 库,相比原生驱动更简洁、易用,贴合 Go 开发者的使用习惯。

mod 复制代码
go 1.25.0

require (
	github.com/bytedance/sonic v1.11.6
	github.com/qiniu/qmgo v1.1.10
	github.com/robfig/cron/v3 v3.0.1
)

客户端 初始化

go 复制代码
package mgdb

import (
	"context"
	"log"

	"github.com/qiniu/qmgo"
)

const (
	Uri           = "mongodb://127.0.0.1:27017"
	Database      = "wtt"
	Username      = "wtt"
	Password      = "PWY123123123"
	AuthMechanism = "SCRAM-SHA-256"
	AuthSource    = "wtt"
)

var (
	ctx        = context.Background()
	client     *qmgo.Client
	database   *qmgo.Database
	collection *qmgo.Collection
)

func init() {
	InitMongoDbClient()
}

func InitMongoDbClient() {
	c, err := qmgo.NewClient(ctx, &qmgo.Config{
		Uri:      Uri,
		Database: Database,
		Auth: &qmgo.Credential{
			Username:      Username,
			Password:      Password,
			AuthMechanism: AuthMechanism,
			AuthSource:    AuthSource, // 认证数据库,通常是用户所在的数据库名
		},
	})
	if err != nil {
		log.Fatal("mongodb 初始化数据库失败:", err)
		panic(err)

	} else {
		log.Println("mongodb 数据库初始化 成功")
	}

	client = c
	database = client.Database("wtt")
	collection = client.Database("wtt").Collection("whero")
}

func GetCollection() *qmgo.Collection {
	if collection != nil {
		return collection
	} else {
		InitMongoDbClient()
		return collection
	}
}

共用结构体

go 复制代码
import (
	"context"

	"github.com/qiniu/qmgo"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
)

var ctx = context.Background()
var userColl = mgdb.GetCollection()

type User struct {
	ID    primitive.ObjectID `bson:"_id,omitempty" json:"id"`
	Name  string             `bson:"name"  json:"name"`
	Age   uint8              `bson:"age"  json:"age"`
	Email string             `bson:"email,omitempty"  json:"email"` // omitempty 表示如果email为空则不存入数据库
}

qmgo 支持单条插入和批量插入,核心方法是 InsertOne 和 InsertMany。

go 复制代码
func insertOne(ctx context.Context, collection *qmgo.Collection) error {
	user := &User{Name: "Alice", Age: 25, Email: "alice@example.com"}
	res, err := collection.InsertOne(ctx, user)
	fmt.Printf("%T---%+v\n", res, res)
	return err
}


// 插入多条数据
func insertMany(ctx context.Context, collection *qmgo.Collection) error {
	users := make([]*User, 0, 120)

	for i := 0; i < 50; i++ {
		var tmp = User{
			Name:  "whero" + GenerateRandomString(6),
			Age:   uint8(i),
			Email: GenerateRandomString(10) + "@126.com",
		}

		users = append(users, &tmp)
	}

	_, err := collection.InsertMany(ctx, users)
	return err
}

qmgo 支持单条删除、批量删除,核心方法是 DeleteOne/DeleteMany。

go 复制代码
// 单条删除(根据姓名删除)
func deleteOneUser() {
	// 构造删除条件
	filter := bson.M{"name": "李四"}

	// 执行删除
	err := userColl.Remove(ctx, filter)
	if err != nil {
		log.Fatalf("单条删除失败: %v", err)
	}
}


// 批量删除(年龄<30 的用户)
func deleteManyUsers() {
	// 构造删除条件
	filter := bson.M{"age": bson.M{"$gt": 20}}

	// 执行批量删除
	result, err := userColl.RemoveAll(ctx, filter)
	if err != nil {
		log.Fatalf("批量删除失败: %v", err)
	}
	fmt.Printf("批量删除了 %d 条文档\n", result.DeletedCount)
}

qmgo 支持单条更新、批量更新、替换文档、增量更新,核心方法是 UpdateOne/UpdateMany,更新语法使用 MongoDB 的更新操作符(set/set/set/inc 等)。

go 复制代码
// 单条更新(更新张三的年龄和邮箱)
func updateOneUser() {
	// 构造查询条件(找到要更新的文档)
	filter := bson.M{"name": "张三"}
	// 构造更新内容($set 表示设置字段,$inc 表示自增,$unset 表示删除字段等)
	update := bson.M{
		"$set": bson.M{
			"age":   26,                         // 年龄改为26
			"email": "zhangsan_new@example.com", // 邮箱更新
		},
	}

	// 执行更新
	err := userColl.UpdateOne(ctx, filter, update)
	if err != nil {
		log.Fatalf("单条更新失败: %v", err)
	}
}



// 批量更新(年龄>25 的用户,年龄+1)
func updateManyUsers() {
	// 构造查询条件
	filter := qmgo.M{"age": qmgo.M{"$gt": 25, "$lt": 30}}
	// 构造更新内容($inc 自增)
	update := qmgo.M{
		"$inc": qmgo.M{"age": 1}, // 年龄 分别 各自 +1
		"$set": qmgo.M{
			"email": "立正", // 邮箱 均设置为 立正
		},
	}

	// 执行批量更新
	result, err := userColl.UpdateAll(ctx, filter, update)
	if err != nil {
		log.Fatalf("批量更新失败: %v", err)
	}
	fmt.Printf("匹配到 %d 条文档,更新了 %d 条文档\n", result.MatchedCount, result.ModifiedCount)
}


// 替换文档(覆盖整个文档,除了 _id)
func replaceUser() {
	// 构造查询条件
	filter := bson.M{"name": "李四"}
	// 构造新的文档(替换原有内容)
	newUser := User{
		Name:  "李四",
		Age:   29,
		Email: "lisi_new@example.com",
	}

	// 执行替换
	err := userColl.ReplaceOne(ctx, filter, newUser)
	if err != nil {
		log.Fatalf("替换文档失败: %v", err)
	}
}

qmgo 的查询功能非常灵活,支持单条查询、批量查询、条件查询、分页、排序、字段过滤等,核心方法是 Find + One/All。

go 复制代码
// 根据 ID 查询单条数据
func findUserByID(id string) {
	// 将字符串 ID 转为 ObjectID
	objectID, err := primitive.ObjectIDFromHex(id)
	if err != nil {
		log.Fatalf("ID 格式错误: %v", err)
	}

	var user User
	// 执行查询(Find 构造条件,One 取单条)
	err = userColl.Find(ctx, bson.M{"_id": objectID}).One(&user)
	if err != nil {
		if err == qmgo.ErrNoSuchDocuments {
			log.Println("未找到该用户")
			return
		}
		log.Fatalf("查询失败: %v", err)
	}
	fmt.Printf("查询结果: %+v\n", user)
}


// 条件查询(年龄>25 且 姓名包含"李")
func findUsersByCondition() {
	// 构造查询条件(bson.M 是原生 MongoDB 查询语法)
	filter := bson.M{
		"age":  bson.M{"$gt": 25},     // 年龄 > 25
		"name": bson.M{"$regex": "a"}, // 姓名包含"a"(正则匹配)
	}

	var users []User
	// 执行查询(All 取多条结果)
	err := userColl.Find(ctx, filter).All(&users)
	if err != nil {
		log.Fatalf("条件查询失败: %v", err)
	}
	fmt.Printf("查询到 %d 条数据:\n", len(users))
	for _, u := range users {
		fmt.Printf("%+v\n", u)
	}
}


// 分页 + 排序查询(按创建时间降序,第1页,每页2条)
func findUsersWithPagination() {
	page := 1     // 当前页
	pageSize := 5 // 每页条数
	skip := (page - 1) * pageSize

	var users []User
	// Find(条件) -> Sort(排序) -> Skip(跳过) -> Limit(限制条数) -> All(取结果)
	err := userColl.Find(ctx, bson.M{"age": bson.M{"$gt": 25}}).
		Sort("-age"). // 降序(- 表示降序,无则升序)
		Skip(int64(skip)).
		Limit(int64(pageSize)).
		All(&users)
	if err != nil {
		log.Fatalf("分页查询失败: %v", err)
	}
	fmt.Printf("分页查询结果:\n")
	for _, u := range users {
		fmt.Printf("%+v\n", u)
	}
}


// 字段过滤(只返回 name 和 age,不返回 _id)
func findUsersWithProjection() {
	var users []User
	// 使用 qmgo 的 Find 方法,Projection 定义返回的字段(1 表示返回,0 表示不返回)
	err := userColl.Find(ctx, qmgo.M{}).Select(qmgo.M{
		"name": 1,
		"age":  1,
		"_id":  0,
	}).All(&users)
	if err != nil {
		log.Fatalf("查询失败: %v", err)
	}

	fmt.Printf("字段过滤查询结果:\n")
	for _, u := range users {
		fmt.Printf("姓名: %s, 年龄: %d\n", u.Name, u.Age)
	}
}

事务

go 复制代码
// 事务示例(插入+更新原子执行)
func transactionDemo() {

	tx, err := client.Session()
	if err != nil {
		log.Fatalf("获取 Session 失败: %v", err)
	}

	// 务必在函数结束时关闭 Session
	defer tx.EndSession(ctx)

	// 2. 执行事务
	// WithTransaction 会自动处理事务的提交和回滚

	res, err := tx.StartTransaction(ctx, func(ctx context.Context) (any, error) {
		// --- 事务开始 ---

		// 事务内操作1:插入用户
		user := User{Name: "赵六", Age: 32, Email: "zhaoliu@example.com"}
		_, err := userColl.InsertOne(ctx, user)
		if err != nil {
			return "", err

		}

		// 事务内操作2:更新张三的年龄
		filter := bson.M{"name": "张三"}
		update := bson.M{"$set": bson.M{"age": 11, "email": "3333333333"}}
		err = userColl.UpdateOne(ctx, filter, update) // 注意:UpdateOne 返回 (UpdateResult, error)
		if err != nil {
			return "", err
		}

		return "123", nil

	})

	if err != nil {
		log.Fatalf("事务执行失败: %v", err)
	}

	fmt.Println(res)
	fmt.Println("事务执行成功")
}

上面的代码执行 如果报错: (IllegalOperation) Transaction numbers are only allowed on a replica set member or mongos

它不是代码写错了,而是你的 MongoDB 数据库环境配置不支持事务。

MongoDB 的事务功能(Transactions)有严格的环境要求:

  • 单机模式:默认安装的 MongoDB 通常是单机模式,不支持事务。
  • 支持模式:只有 副本集 或 分片集群 才支持事务。

你当前的数据库是以单机模式运行的,所以当你尝试开启事务时,数据库直接拒绝了请求。

聚合查询

go 复制代码
package main

import (
	"context"
	"fmt"
	"gotests/mgdb"

	"go.mongodb.org/mongo-driver/bson"
)

var ctx = context.Background()
var userColl = mgdb.GetCollection("users")

type User struct {
	ID    string `bson:"_id,omitempty" json:"id"`
	Name  string `bson:"name" json:"name"`
	Age   uint8  `bson:"age" json:"age"`
	Email string `bson:"email,omitempty" json:"email"`
}
type Vips struct {
	ID    string `bson:"_id,omitempty" json:"id"`
	Name  string `bson:"name" json:"name"`
	Age   uint8  `bson:"age" json:"age"`
	Email string `bson:"email,omitempty" json:"email"`
}

func main() {

	err := aggregateLeftJoinUserOrders()
	fmt.Println(err)

}

// 聚合左连接:用户表 LEFT JOIN 订单表
func aggregateLeftJoinUserOrders() error {
	// 定义聚合管道(Pipeline)
	pipeline := []bson.M{
		// 第一步:关联订单表($lookup 是联表核心操作)
		{
			"$lookup": bson.M{
				"from":         "vips",        // 要关联的集合名
				"localField":   "age",         // 当前集合(users)的关联字段
				"foreignField": "age",         // 关联集合(vips)的关联字段
				"as":           "matchedVips", // 关联结果存储的字段名
			},
		},
		// 第二步:可选 - 过滤掉没有订单的用户(如果需要 INNER JOIN 效果)
		{
			"$match": bson.M{
				"matchedVips": bson.M{"$ne": []any{}}, // 排除订单为空的用户
			},
		},
		// 第三步:unwind - 展开数组
		{
			"$unwind": "$matchedVips",
		},
	}

	// 执行聚合查询
	var users []User
	err := userColl.Aggregate(ctx, pipeline).All(&users)
	if err != nil {
		return err
	}
	fmt.Println(len(users))

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

	return nil
}

聚合查询 总数统计

go 复制代码
func aggregateLeftJoinUserOrders() error {
	// 定义聚合管道(Pipeline)
	pipeline := []bson.M{
		// 第一步:关联订单表($lookup 是联表核心操作)
		{
			"$lookup": bson.M{
				"from":         "vips",        // 要关联的集合名
				"localField":   "age",         // 当前集合(users)的关联字段
				"foreignField": "age",         // 关联集合(vips)的关联字段
				"as":           "matchedVips", // 关联结果存储的字段名
			},
		},
		// 第二步:可选 - 过滤掉没有订单的用户(如果需要 INNER JOIN 效果)
		{
			"$match": bson.M{
				"matchedVips": bson.M{"$ne": []any{}}, // 排除订单为空的用户
			},
		},
		// 第三步:unwind - 展开数组
		{
			"$unwind": "$matchedVips",
		},

		// 第四步:count - 统计总数
		{
			"$count": "totalit",
		},
	}

	// 执行聚合查询
	var results []struct {
		Totalit int `bson:"totalit"`
	}
	err := userColl.Aggregate(ctx, pipeline).All(&results)
	if err != nil {
		return err
	}

	fmt.Println(results[0].Totalit)

	return nil
}

位置范围查询

go 复制代码
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/qiniu/qmgo"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
)
var collection = 初始化的文档变量
func main() {

	res, err := queryNearbyUsers(ctx, collection, "用户A", 15)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(res)
}

// User 用户模型(包含地理坐标)
type User struct {
	ID       primitive.ObjectID `bson:"_id,omitempty" json:"id"` // 主键
	Name     string             `bson:"name" json:"name"`        // 用户名
	Location struct {           // 地理坐标(2dsphere 索引核心字段)
		Type        string    `bson:"type" json:"type"`               // 固定值:Point
		Coordinates []float64 `bson:"coordinates" json:"coordinates"` // 格式:[经度, 纬度]
	} `bson:"location" json:"location"`
}

// queryNearbyUsers 查询指定用户附近 N 公里的所有用户
// 参数:
//
//	ctx: 上下文
//	col: 集合对象
//	userName: 目标用户名(如用户A)
//	radiusKm: 搜索半径(公里)
func queryNearbyUsers(ctx context.Context, col *qmgo.Collection, userName string, radiusKm float64) ([]User, error) {
	// 第一步:先查询用户A的经纬度
	var targetUser User
	err := col.Find(ctx, bson.M{"name": userName}).One(&targetUser)
	if err != nil {
		return nil, fmt.Errorf("查询目标用户失败:%v", err)
	}

	// 第二步:构造地理空间查询条件
	// 注意:$maxDistance 单位是米,所以需要将公里转换为米
	maxDistance := radiusKm * 1000
	filter := bson.M{
		"name": bson.M{"$ne": userName}, // 排除用户A自己
		"location": bson.M{
			"$near": bson.M{
				"$geometry": bson.M{
					"type":        targetUser.Location.Type,
					"coordinates": targetUser.Location.Coordinates,
				},
				"$maxDistance": maxDistance, // 单位:米
			},
		},
	}

	// 第三步:执行查询
	var nearbyUsers []User
	err = col.Find(ctx, filter).All(&nearbyUsers)
	if err != nil {
		return nil, fmt.Errorf("查询附近用户失败:%v", err)
	}

	return nearbyUsers, nil
}
相关推荐
小年糕是糕手2 小时前
【35天从0开始备战蓝桥杯 -- Day2】
开发语言·jvm·数据库·c++·程序人生·考研·蓝桥杯
数据知道2 小时前
MongoDB:如何构建“数据回收站“,防止人为误删数据(延迟节点)
数据库·mongodb
云边有个稻草人2 小时前
KingbaseES融合数据库:内核级一体化架构,实现一库多能全域支撑
数据库·国产数据库·kingbasees·金仓数据库
零基础的修炼2 小时前
Linux网络---多路转接
数据库
Gold Steps.2 小时前
Go 语言核心:函数、结构体与接口深度解析
开发语言·后端·golang
cookqq2 小时前
MongoDB $in查询参数上限是多少个?
数据库·mongodb
西门吹雪分身2 小时前
Mongodb存储大文件
数据库·mongodb·文件存储·gridfs
IvorySQL6 小时前
PostgreSQL 技术日报 (3月11日)|4库合一性能提升350倍与内核新讨论
数据库·postgresql·开源
IvorySQL6 小时前
谁动了我的查询结果?PostgreSQL 联表加锁的隐藏陷阱
数据库·postgresql·开源