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
}
相关推荐
数据库小组1 天前
2026 年,MySQL 到 SelectDB 同步为何更关注实时、可观测与可校验?
数据库·mysql·数据库管理工具·数据同步·ninedata·selectdb·迁移工具
华科易迅1 天前
MybatisPlus增删改查操作
android·java·数据库
Kethy__1 天前
计算机中级-数据库系统工程师-计算机体系结构与存储系统
大数据·数据库·数据库系统工程师·计算机中级
SHoM SSER1 天前
MySQL 数据库连接池爆满问题排查与解决
android·数据库·mysql
熬夜的咕噜猫1 天前
MySQL备份与恢复
数据库·oracle
jnrjian1 天前
recover database using backup controlfile until cancel 假recover,真一致
数据库·oracle
lifewange1 天前
java连接Mysql数据库
java·数据库·mysql
大妮哟1 天前
postgresql数据库日志量异常原因排查
数据库·postgresql·oracle
还是做不到嘛\.1 天前
Dvwa靶场-SQL Injection (Blind)-基于sqlmap
数据库·sql·web安全
人间打气筒(Ada)1 天前
go实战案例:如何通过 Service Meh 实现熔断和限流
java·开发语言·golang·web·istio·service mesh·熔断限流