写点什么

[go]mongo 工具类

作者:林逸民
  • 2022 年 5 月 29 日
  • 本文字数:5483 字

    阅读完需:约 18 分钟

前言

在日常工作当中开发人员接触的最多的非数据库莫属了, 那么就需要一套统一数据库工具类, 不管使用什么样的语言, 一致的接口定义可以大大简化开发人员的学习成本, 也能更容易在整合框架及扩展架构的时候减少对业务代码的侵入式修改.

虽然此篇文章是面向go的数据库工具类, 但是接口与[ts]后台管理数据权限控制实现(无业务修改)_typescript_林逸民_InfoQ写作社区内的数据库接口一致, 使用的库为https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo.

文章中主键固定为ID且类型为string, 其余的大家可以自行扩展.

文章中使用另外一个库为: ahl5esoft/golang-underscore: Helpfully Functional Go like underscore.js (github.com), 该库提供了类似C# Linq的功能.

接口

首先来看一下go内的接口定义, 代码如下:

// 数据工厂type IDbFactory interface {  // 创建数据仓储  Db(entry IDbModel, extra ...interface{}) IDbRepository  // 创建工作单元  Uow() IUnitOfWork}
// 数据仓储type IDbRepository interface { // 删除 Delete(entry interface{}) error // 新增 Insert(entry interface{}) error // 查询 Query() IDbQuery // 更新 Update(entry interface{}) error}
// 数据表查询type IDbQuery interface { // 查询数量 Count() (int64, error) // 正序 Order(fields ...string) IDbQuery // 倒序 OrderByDesc(fields ...string) IDbQuery // 跳过 Skip(v int) IDbQuery // 限制查询行数 Take(v int) IDbQuery // 查询结果(数组) ToArray(dst interface{}) error // 条件 Where(args ...interface{}) IDbQuery}
// 工作单元(事务)type IUnitOfWork interface { // 提交 Commit() error}
// 工作单元仓储type IUnitOfWorkRepository interface { IUnitOfWork
// 注册删除 RegisterDelete(entry IDbModel) // 注册新增 RegisterInsert(entry IDbModel) // 注册更新 RegisterUpdate(entry IDbModel)}
// 数据库模型type IDbModel interface { GetID() string}
复制代码
驱动工厂

由于CRUD中都需要频繁使用到mongo.Client mongo.DataBase mongo.Collection, 因此需要一个驱动工厂来创建这几个对象, 创建对象的方式大致代码如下:

// 驱动工厂type driverFactory struct {	Ctx context.Context
name string client *mongo.Client db *mongo.Database option *options.ClientOptions}
// 创建mongo.Clientfunc (m *driverFactory) BuildClient() (*mongo.Client, error) { if m.client == nil { var err error if m.client, err = mongo.Connect(m.Ctx, m.option); err != nil { return nil, err } }
return m.client, nil}
// 创建mongo.Collectionfunc (m *driverFactory) BuildCollection(model string) (*mongo.Collection, error) { db, err := m.BuildDb() if err != nil { return nil, err }
return db.Collection(model), nil}
// 创建mongo.Databasefunc (m *driverFactory) BuildDb() (*mongo.Database, error) { if m.db == nil { client, err := m.BuildClient() if err != nil { return nil, err }
m.db = client.Database(m.name) }
return m.db, nil}
// 创建驱动工厂func newDriverFactory(name, uri string) *driverFactory { return &driverFactory{ Ctx: context.Background(), name: name, option: options.Client().ApplyURI(uri), }}
复制代码
模型 -> bson.M

mongo是文档数据库, 需要将模型转化成文档对象, 这需要使用到go.mongodb.org/mongo-driver/bson, 至于bson内的几个不同的结构, 可以网上查询相关的资料去了解.


  1. 新增: bson.M{ "$set": { 字段1名: 字段1值, 字段2名: 字段2值, ..., 字段n名: 字段n值, } }

  2. 删除: bson.M{ 主键字段名: 主键字段值 }

  3. 更新: 条件结构bson.M{ 主键字段名: 主键字段值 },更新结构bson.M{ "$set": { 字段1名: 字段1值, 字段2名: 字段2值, ..., 字段n名: 字段n值, } }


如果想要覆盖mongo_id, 则需要额外处理主键字段即可, 接下来的文章中将不提供模型转bson.M的代码.

实现工作单元

使用工作单元实现事务可以保证使用统一的上下文, 隐藏不同数据库的实现细节, 其内部的原理为将注册的增删改分别用相应的队列存储起来, 当事务提交的时候, 在mongo的会话内调用相应的操作即可, 大致代码如下:

// 工作单元type unitOfWorkRepository struct {  driverFacory *driverFactory  deleteQueue  []IDbModel  insertQueue  []IDbModel  updateQueue  []IDbModel}
// 提交func (m *unitOfWorkRepository) Commit() error { if len(m.deleteQueue) == 0 && len(m.insertQueue) == 0 && len(m.updateQueue) == 0 { return nil }
client, err := m.driverFacory.BuildClient() if err != nil { return err }
return client.UseSession(m.driverFacory.Ctx, func(ctx mongo.SessionContext) (err error) { if err = m.commitInsert(ctx); err != nil { return err }
if err = m.commitDelete(ctx); err != nil { return err }
return m.commitUpdate(ctx) })}
// 注册删除func (m *unitOfWorkRepository) RegisterDelete(entry IDbModel) { m.deleteQueue = append(m.deleteQueue, entry)}
// 注册新增func (m *unitOfWorkRepository) RegisterInsert(entry IDbModel) { m.insertQueue = append(m.insertQueue, entry)}
// 注册更新func (m *unitOfWorkRepository) RegisterUpdate(entry IDbModel) { m.updateQueue = append(m.updateQueue, entry)}
// 提交删除func (m *unitOfWorkRepository) commitDelete(ctx mongo.SessionContext) (err error) { underscore.Chain(m.deleteQueue).Map(func(r IDbModel, _ int) error { _, cErr := col.DeleteOne(ctx, bson.M{ "_id": r.GetID(), // 根据ID删除 }) return cErr }).Find(func(r error, _ int) bool { return r != nil }).Value(&err) m.deleteQueue = make([]IDbModel, 0) return}
// 提交新增func (m *unitOfWorkRepository) commitInsert(ctx mongo.SessionContext) (err error) { underscore.Chain(m.insertQueue).Map(func(r IDbModel, _ int) error { // 模型数据转化成bson.M, 略 doc := nil _, rErr := col.InsertOne(ctx, doc) return rErr }).Find(func(r error, _ int) bool { return r != nil }).Value(&err) m.insertQueue = make([]IDbModel, 0) return}
// 提交更新func (m *unitOfWorkRepository) commitUpdate(ctx mongo.SessionContext) (err error) { underscore.Chain(m.updateQueue).Map(func(r IDbModel, _ int) error { // 模型数据转化成bson.M, 略 doc := nil _, rErr := col.UpdateOne(ctx, filer, bson.M{ "$set": doc, }) return rErr }).Find(func(r error, _ int) bool { return r != nil }).Value(&err) m.updateQueue = make([]IDbModel, 0) return}
// 创建工作单元func newUnitOfWorkRepository(driverFactory *driverFactory) *unitOfWork { return &unitOfWorkRepository{ driverFacory: driverFactory, deleteQueue: make([]IDbModel, 0), insertQueue: make([]IDbModel, 0), updateQueue: make([]IDbModel, 0), }}
复制代码
数据表查询

IDbQuery的实现是比较简单的, 直接将各方法参数转换成对应的bson对象, 然后获取游标之后, 将游标内的doc转码成模型对象即可(反射), 代码如下:

// 数据查询type dbQuery struct {  filter        bson.M  sorts         bson.D  modelType     reflect.Type  driverFactory *driverFactory  findOption    *options.FindOptions}
// 查询数量func (m dbQuery) Count() (int64, error) { defer m.reset()
c, err := m.driverFactory.BuildCollection(m.model) if err != nil { return 0, err }
return c.CountDocuments(m.driverFactory.Ctx, m.filter)}
// 排序(正序)func (m *dbQuery) Order(fields ...string) IDbQuery { m.sort(1, fields) return m}
// 排序(倒序)func (m *dbQuery) OrderByDesc(fields ...string) IDbQuery { m.sort(-1, fields) return m}
// 跳过行数func (m *dbQuery) Skip(v int) IDbQuery { m.findOption = m.findOption.SetSkip( int64(v), ) return m}
// 限制行数func (m *dbQuery) Take(v int) IDbQuery { m.findOption = m.findOption.SetLimit( int64(v), ) return m}
// 查询行func (m *dbQuery) ToArray(dst interface{}) error { defer m.reset()
c, err := m.driverFactory.BuildCollection(m.modelType.Name()) if err != nil { return err }
if len(m.sorts) > 0 { m.findOption = m.findOption.SetSort(m.sorts) }
cur, err := c.Find(m.driverFactory.Ctx, m.filter, m.findOption) if err != nil { return err }
sliceType := reflect.SliceOf(m.modelType) sliceValue := reflect.MakeSlice(sliceType, 0, 0) for cur.Next(m.driverFactory.Ctx) { rowValue := reflect.New(m.modelType) row := value.Interface() cur.Decode(row) sliceValue = reflect.Append( sliceValue, value.Elem(), ) } var dstValue reflect.Value var ok bool if dstValue, ok = dst.(reflect.Value); !ok { dstValue = reflect.ValueOf(dst) } dstValue.Elem().Set(sliceValue) return nil}
// 设置过滤条件func (m *dbQuery) Where(args ...interface{}) IDbQuery { if len(args) == 0 { return m }
if f, ok := args[0].(bson.M); ok { m.filter = f } return m}
// 重置查询func (m *dbQuery) reset() { m.filter = make(bson.M) m.findOption = options.Find() m.sorts = bson.D{}}
// 排序(复用)func (m *dbQuery) sort(flag int, fields []string) { underscore.Chain(fields).Each(func(r string, _ int) { m.sorts = append(m.sorts, bson.E{ Key: r, Value: flag, }) })}
// 创建数据查询func newDbQuery(modelType reflect.Type, driverFactory *driverFactory) IDbQuery { q := &dbQuery{ driverFactory: driverFactory, modelType: modelType, } q.reset() return q}
复制代码
数据仓库

数据仓储作为增删改查的代理, 将增删改交由IUnitOfWork处理, 而将查询交由IDbQuery处理, 大致代码如下:

// 数据仓储type dbRepository struct {  uow           contract.IUnitOfWorkRepository  modelType     reflect.Type  isTx          bool  driverFactory *driverFactory}
// 删除func (m dbRepository) Delete(entry contract.IDbModel) error { m.uow.RegisterDelete(entry) if m.isTx { return nil }
return m.uow.Commit()}
// 新增func (m dbRepository) Insert(entry contract.IDbModel) error { m.uow.RegisterInsert(entry) if m.isTx { return nil }
return m.uow.Commit()}
// 查询func (m dbRepository) Query() contract.IDbQuery { return newDbQuery(m.modelType, m.driverFactory)}
// 更新func (m dbRepository) Update(entry contract.IDbModel) error { m.uow.RegisterUpdate(entry) if m.isTx { return nil } return m.uow.Commit()}
// 创建数据仓储func newDbRepository( uow contract.IUnitOfWorkRepository, modelType reflect.Type, isTx bool, driverFactory *driverFactory,) contract.IDbRepository { return &dbRepository{ driverFactory: driverFactory, isTx: isTx, modelType: modelType, uow: uow, }}
复制代码
数据库工厂

工厂负责创建IDbRepositoryIUnitOfWork, 而创建IDbRepository的方法内传入的IUnitOfWork是可选项, 大致代码如下:

// 数据库工厂type dbFactory struct {  driverFactory *driverFactory}
// 创建数据仓储func (m *dbFactory) Db(entry contract.IDbModel, extra ...interface{}) contract.IDbRepository { var uow *unitOfWorkRepository isTx := true underscore.Chain(extra).Each(func(r interface{}, _ int) { if v, ok := r.(*unitOfWorkRepository); ok { uow = v } })
if uow == nil { isTx = false uow = m.Uow().(*unitOfWorkRepository) }
modelType := reflect.TypeOf(entry) return newDbRepository(m.driverFactory, isTx, modelType, uow)}
// 创建工作单元func (m *dbFactory) Uow() contract.IUnitOfWork { return newUnitOfWorkRepository(m.driverFactory)}
// 创建数据库工厂func NewDbFactory(name string, uri string) contract.IDbFactory { return &dbFactory{ driverFactory: newDriverFactory(name, uri), }}
复制代码
结尾

文章到此结束了, 如果有任何问题和疑问, 欢迎留言, 谢谢.

发布于: 刚刚阅读数: 4
用户头像

林逸民

关注

痛苦丢给身体 舒适留给灵魂 2018.11.13 加入

编程爱好者

评论

发布
暂无评论
[go]mongo工具类_Go_林逸民_InfoQ写作社区