golang基于官方驱动的MongoDB ODM插件库mgm的使用

Golang基于官方驱动的MongoDB ODM插件库mgm的使用

Mongo Go Models Logo

Mongo Go Models (mgm)是Go语言的MongoDB ODM库,基于官方MongoDB Go驱动开发。

特性

  • 定义模型并执行CRUD操作,每个操作前后都有钩子
  • 简化MongoDB搜索和聚合操作
  • 一次性配置后可在任何地方获取集合
  • 预定义了所有Mongo操作符和键,无需硬编码
  • 封装了官方MongoDB Go驱动

要求

  • Go 1.17或更高版本
  • MongoDB 3.6或更高版本

安装

go get github.com/kamva/mgm/v3

使用

基本配置

import (
   "github.com/kamva/mgm/v3"
   "go.mongodb.org/mongo-driver/mongo/options"
)

func init() {
   // 设置mgm默认配置
   err := mgm.SetDefaultConfig(nil, "mgm_lab", options.Client().ApplyURI("mongodb://root:12345@localhost:27017"))
}

定义模型

type Book struct {
   // DefaultModel添加_id, created_at和updated_at字段
   mgm.DefaultModel `bson:",inline"`
   Name             string `json:"name" bson:"name"`
   Pages            int    `json:"pages" bson:"pages"`
}

func NewBook(name string, pages int) *Book {
   return &Book{
      Name:  name,
      Pages: pages,
   }
}

CRUD操作示例

插入文档

book := NewBook("Pride and Prejudice", 345)

// 确保传递模型的引用(以便mgm更新模型的"updated_at"、"created_at"和"id"字段)
err := mgm.Coll(book).Create(book)

查找文档

// 获取文档的集合
book := &Book{}
coll := mgm.Coll(book)

// 查找并将文档解码到book模型
_ = coll.FindByID("5e0518aa8f1a52b0b9410ee3", book)

// 获取集合的第一个文档
_ = coll.First(bson.M{}, book)

// 使用过滤器获取集合的第一个文档
_ = coll.First(bson.M{"pages":400}, book)

更新文档

// 找到你的书
book := findMyFavoriteBook()

// 并更新它
book.Name = "Moulin Rouge!"
err := mgm.Coll(book).Update(book)

删除文档

// 只需找到并删除你的文档
err := mgm.Coll(book).Delete(book)

查找并解码结果

result := []Book{}

err := mgm.Coll(&Book{}).SimpleFind(&result, bson.M{"pages": bson.M{operator.Gt: 24}})

模型的默认字段

每个模型默认(通过使用DefaultModel结构体)有以下字段:

  • _id: 文档ID
  • created_at: 文档创建日期。保存新文档时,由Creating钩子自动填充
  • updated_at: 文档最后更新日期。保存文档时,由Saving钩子自动填充

你可以实现自己的默认模型来自定义其字段。

模型的钩子

每个模型有以下钩子:

  • Creating: 创建新模型时调用
  • Created: 新模型创建后调用
  • Updating: 更新模型时调用
  • Updated: 模型更新后调用
  • Saving: 创建或更新模型时调用
  • Saved: 模型创建或更新后调用
  • Deleting: 删除模型时调用
  • Deleted: 模型删除后调用

关于钩子的注意事项:

  • 每个模型默认使用CreatingSaving钩子,如果你想自己定义这些钩子,记得从你自己的钩子中调用DefaultModel的钩子
  • 调用这些钩子的集合方法:
    • Create & CreateWithCtx
    • Update & UpdateWithCtx
    • Delete & DeleteWithCtx

示例:

func (model *Book) Creating(ctx context.Context) error {
   // 调用DefaultModel的Creating钩子
   if err := model.DefaultModel.Creating(ctx); err!=nil {
      return err
   }

   // 我们可以验证模型的字段并返回错误以防止文档插入
   if model.Pages < 1 {
      return errors.New("book must have at least one page")
   }

   return nil
}

配置

mgm默认配置有一个上下文超时:

func init() {
   _ = mgm.SetDefaultConfig(&mgm.Config{CtxTimeout:12 * time.Second}, "mgm_lab", options.Client().ApplyURI("mongodb://root:12345@localhost:27017"))
}

// 获取上下文,只需调用Ctx()方法,将其赋值给变量
ctx := mgm.Ctx()

// 并使用它
coll := mgm.Coll(&Book{})
coll.FindOne(ctx, bson.M{})

// 或者直接调用Ctx()并使用
coll.FindOne(mgm.Ctx(), bson.M{})

集合

获取模型的集合:

coll := mgm.Coll(&Book{})

// 对集合进行操作

mgm自动检测模型集合的名称:

book := Book{}

// 打印模型的集合名称
collName := mgm.CollName(&book)
fmt.Println(collName) // 输出: books

你也可以通过实现CollectionNameGetter接口为模型设置自定义集合名称:

func (model *Book) CollectionName() string {
   return "my_books"
}

// mgm返回"my_books"集合
coll := mgm.Coll(&Book{})

或者通过名称获取集合(无需为其定义模型):

coll := mgm.CollectionByName("my_coll")
   
// 对集合进行聚合等操作

聚合

虽然我们可以使用Mongo Go驱动的聚合功能,但mgm也提供了更简单的方法来执行聚合:

运行聚合并解码结果:

authorCollName := mgm.Coll(&Author{}).Name()
result := []Book{}

// 只需一行代码即可完成查找
_ := mgm.Coll(&Book{}).SimpleAggregate(&result, builder.Lookup(authorCollName, "auth_id", "_id", "author"))

// 多阶段(混合mgm构建器和原始阶段)
_ := mgm.Coll(&Book{}).SimpleAggregate(&result,
    builder.Lookup(authorCollName, "auth_id", "_id", "author"),
    M{operator.Project: M{"pages": 0}},
)

使用mongo的Aggregate方法进行聚合:

import (
   "github.com/kamva/mgm/v3"
   "github.com/kamva/mgm/v3/builder"
   "github.com/kamva/mgm/v3/field"
   . "go.mongodb.org/mongo-driver/bson"
   "go.mongodb.org/mongo-driver/bson/primitive"
)

// Author模型集合
authorColl := mgm.Coll(&Author{})

cur, err := mgm.Coll(&Book{}).Aggregate(mgm.Ctx(), A{
    // S函数接受操作符并返回bson.M类型
    builder.S(builder.Lookup(authorColl.Name(), "author_id", field.Id, "author")),
})

更复杂的示例,与mongo原始管道混合:

import (
   "github.com/kamva/mgm/v3"
   "github.com/kamva/mgm/v3/builder"
   "github.com/kamva/mgm/v3/field"
   "github.com/kamva/mgm/v3/operator"
   . "go.mongodb.org/mongo-driver/bson"
   "go.mongodb.org/mongo-driver/bson/primitive"
)

// Author模型集合
authorColl := mgm.Coll(&Author{})

_, err := mgm.Coll(&Book{}).Aggregate(mgm.Ctx(), A{
    // S函数获取操作符并返回bson.M类型
    builder.S(builder.Lookup(authorColl.Name(), "author_id", field.Id, "author")),
    builder.S(builder.Group("pages", M{"books": M{operator.Push: M{"name": "$name", "author": "$author"}}})),
    M{operator.Unwind: "$books"},
})

if err != nil {
    panic(err)
}

事务

  • 要在默认连接上运行事务,使用mgm.Transaction()函数,例如:
d := &Doc{Name: "Mehran", Age: 10}

err := mgm.Transaction(func(session mongo.Session, sc mongo.SessionContext) error {

   // 不要忘记将session的上下文传递给集合方法
   err := mgm.Coll(d).CreateWithCtx(sc, d)

   if err != nil {
      return err
   }

   return session.CommitTransaction(sc)
})
  • 要使用自己的上下文运行事务,使用mgm.TransactionWithCtx()方法
  • 要在另一个连接上运行事务,使用mgm.TransactionWithClient()方法

其他Mongo Go Models包

我们实现了这些包来简化mongo中的查询和聚合

builder: 简化mongo查询和聚合

operator: 包含作为预定义变量的mongo操作符 (例如 Eq = "$eq", Gt = "$gt")

field: 包含聚合等中使用的mongo字段作为预定义变量 (例如 LocalField = "localField", ForeignField = "foreignField")

示例:

import (
  "github.com/kamva/mgm/v3"
  f "github.com/kamva/mgm/v3/field"
  o "github.com/kamva/mgm/v3/operator"
  "go.mongodb.org/mongo-driver/bson"
)

// 代替硬编码mongo操作符和字段
_, _ = mgm.Coll(&Book{}).Aggregate(mgm.Ctx(), bson.A{
   bson.M{"$count": ""},
   bson.M{"$project": bson.M{"_id": 0}},
})

// 使用预定义的操作符和管道字段
_, _ = mgm.Coll(&Book{}).Aggregate(mgm.Ctx(), bson.A{
   bson.M{o.Count: ""},
   bson.M{o.Project: bson.M{f.Id: 0}},
})

更多关于golang基于官方驱动的MongoDB ODM插件库mgm的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang基于官方驱动的MongoDB ODM插件库mgm的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang中使用mgm进行MongoDB对象文档映射(ODM)

mgm是一个基于官方MongoDB Go驱动(mongo-go-driver)的轻量级ODM库,提供了类似Mongoose的API风格,简化了MongoDB操作。下面我将详细介绍mgm的使用方法。

安装

首先安装mgm和官方驱动:

go get -u github.com/kamva/mgm/v3
go get go.mongodb.org/mongo-driver/mongo

基本配置

import (
	"context"
	"log"
	"time"

	"github.com/kamva/mgm/v3"
	"go.mongodb.org/mongo-driver/mongo/options"
)

func init() {
	// 设置MongoDB连接
	err := mgm.SetDefaultConfig(nil, "mgm_lab", options.Client().ApplyURI("mongodb://localhost:27017"))
	if err != nil {
		log.Fatal(err)
	}
}

定义模型

type Book struct {
	mgm.DefaultModel `bson:",inline"`
	Name             string `json:"name" bson:"name"`
	Pages            int    `json:"pages" bson:"pages"`
}

// 自定义集合名称
func (model *Book) CollectionName() string {
	return "books"
}

CRUD操作示例

创建文档

func CreateBook() {
	book := &Book{
		Name:  "Golang编程",
		Pages: 300,
	}

	// 插入文档
	err := mgm.Coll(book).Create(book)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("创建成功,ID: %v\n", book.ID)
}

查询文档

func FindBooks() {
	var books []Book

	// 查询所有文档
	err := mgm.Coll(&Book{}).SimpleFind(&books, bson.M{})
	if err != nil {
		log.Fatal(err)
	}

	// 带条件查询
	var filteredBooks []Book
	err = mgm.Coll(&Book{}).SimpleFind(&filteredBooks, bson.M{"pages": bson.M{"$gt": 200}})
	if err != nil {
		log.Fatal(err)
	}

	// 查询单个文档
	book := &Book{}
	err = mgm.Coll(book).FindByID("5f8d8a7f9d6b2b2b2b2b2b2b", book)
	if err != nil {
		log.Fatal(err)
	}
}

更新文档

func UpdateBook() {
	book := &Book{}
	err := mgm.Coll(book).FindByID("5f8d8a7f9d6b2b2b2b2b2b2b", book)
	if err != nil {
		log.Fatal(err)
	}

	book.Name = "Go语言高级编程"
	book.Pages = 400

	err = mgm.Coll(book).Update(book)
	if err != nil {
		log.Fatal(err)
	}
}

删除文档

func DeleteBook() {
	book := &Book{}
	err := mgm.Coll(book).FindByID("5f8d8a7f9d6b2b2b2b2b2b2b", book)
	if err != nil {
		log.Fatal(err)
	}

	err = mgm.Coll(book).Delete(book)
	if err != nil {
		log.Fatal(err)
	}
}

高级功能

事务支持

func TransactionExample() {
	err := mgm.Transaction(func(session mongo.Session, sc mongo.SessionContext) error {
		book1 := &Book{Name: "Book1", Pages: 100}
		if err := mgm.Coll(book1).CreateWithCtx(sc, book1); err != nil {
			return err
		}

		book2 := &Book{Name: "Book2", Pages: 200}
		if err := mgm.Coll(book2).CreateWithCtx(sc, book2); err != nil {
			return err
		}

		return nil
	})

	if err != nil {
		log.Fatal(err)
	}
}

钩子函数

mgm支持多种钩子函数:

type User struct {
	mgm.DefaultModel `bson:",inline"`
	Name            string `json:"name" bson:"name"`
	Email           string `json:"email" bson:"email"`
}

// 创建前钩子
func (u *User) Creating() error {
	u.Email = strings.ToLower(u.Email)
	return nil
}

// 更新前钩子
func (u *User) Updating() error {
	if u.Name == "" {
		return errors.New("name cannot be empty")
	}
	return nil
}

// 保存后钩子
func (u *User) Saved() error {
	log.Printf("User %s saved", u.ID.Hex())
	return nil
}

分页查询

func PaginationExample() {
	var books []Book
	page := 1
	perPage := 10

	findOptions := options.Find()
	findOptions.SetSkip(int64((page - 1) * perPage))
	findOptions.SetLimit(int64(perPage))
	findOptions.SetSort(bson.M{"pages": -1}) // 按页数降序

	cursor, err := mgm.Coll(&Book{}).Find(context.Background(), bson.M{}, findOptions)
	if err != nil {
		log.Fatal(err)
	}

	if err = cursor.All(context.Background(), &books); err != nil {
		log.Fatal(err)
	}

	fmt.Printf("第%d页数据: %v\n", page, books)
}

性能优化建议

  1. 合理使用索引
  2. 批量操作时使用BulkWrite
  3. 对于大量数据查询使用游标
  4. 合理设置连接池参数

总结

mgm提供了简洁的API来操作MongoDB,同时保留了官方驱动的灵活性。它特别适合需要简单ODM功能但不想引入复杂框架的项目。通过模型定义、钩子函数和事务支持,可以构建结构良好的MongoDB应用程序。

注意:在实际项目中,建议将数据库操作封装在repository层,并使用依赖注入来管理数据库连接。

回到顶部