[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.Client
func (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.Collection
func (m *driverFactory) BuildCollection(model string) (*mongo.Collection, error) {
db, err := m.BuildDb()
if err != nil {
return nil, err
}
return db.Collection(model), nil
}
// 创建mongo.Database
func (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
内的几个不同的结构, 可以网上查询相关的资料去了解.
新增:
bson.M{ "$set": { 字段1名: 字段1值, 字段2名: 字段2值, ..., 字段n名: 字段n值, } }
删除:
bson.M{ 主键字段名: 主键字段值 }
更新: 条件结构
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,
}
}
数据库工厂
工厂负责创建IDbRepository
和IUnitOfWork
, 而创建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),
}
}
结尾
文章到此结束了, 如果有任何问题和疑问, 欢迎留言, 谢谢.
版权声明: 本文为 InfoQ 作者【林逸民】的原创文章。
原文链接:【http://xie.infoq.cn/article/b43890d827afec84541cc50d9】。未经作者许可,禁止转载。
林逸民
痛苦丢给身体 舒适留给灵魂 2018.11.13 加入
编程爱好者
评论