golang对mongoDB的封装处理,完全版

1,926 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

标题:golang对mongoDB的封装处理,完全版;

1、mongo-driver

mongoDB官方提供了golang语言开发的驱动包:mongo-driver,但是用起来比较繁琐,操作上有代码叠加场景,这里简单的对其二次封装处理,使单表的增删改查,批量处理,分页……多表的聚合查询,用起来更简单。

2、原理篇

2.1、封装思路

  1. 抽出一个操作数据库的接口,用来做抽象处理;
  2. mongo的每一个库的每一张集合,都会对应一个golang的结构体对象,来做唯一映射,即满足单表所有场景下的增删改查操作;
  3. 提供初始化函数和创建集合对象的函数,用于项目启动时的加载项;

2.2、实现方式

1、提供一个BaseCollection接口;

package collection

import "context"

//
//  BaseCollection
//  @Description: 定义操作的接口
//
type BaseCollection interface {

	//
	//  SelectPage
	//  @Description: 分页查询
	//  @param ctx
	//  @param filter
	//  @param sort
	//  @param skip
	//  @param limit
	//  @return int64
	//  @return []interface{}
	//  @return error
	//  @author: ikejcwang
	//  @create: 2022-10-04 16:21:08
	SelectPage(ctx context.Context, filter interface{}, sort interface{}, skip, limit int64) (int64, []interface{}, error)

	//
    //  SelectList
    //  @Description: 查询列表
    //  @param ctx
    //  @param filter
    //  @param sort
    //  @return []interface{}
    //  @return error
    //  @author: ikejcwang
    //  @create: 2022-10-04 16:21:51
	SelectList(ctx context.Context, filter interface{}, sort interface{}) ([]interface{}, error)

	//
    //  SelectOne
    //  @Description: 查询单条
    //  @param ctx
    //  @param filter
    //  @return interface{}
    //  @return error
    //  @author: ikejcwang
    //  @create: 2022-10-04 16:21:56
	SelectOne(ctx context.Context, filter interface{}) (interface{}, error)

	//
	//  SelectCount
	//  @Description: 查询统计
	//  @param ctx
	//  @param filter
	//  @return int64
	//  @return error
	//  @author: ikejcwang
	//  @create: 2022-10-04 14:36:45
	SelectCount(ctx context.Context, filter interface{}) (int64, error)

	//
	//  UpdateOne
	//  @Description: 更新单条
	//  @param ctx
	//  @param filter
	//  @param update
	//  @return int64
	//  @return error
	//  @author: ikejcwang
	//  @create: 2022-10-04 14:37:58
	UpdateOne(ctx context.Context, filter, update interface{}) (int64, error)

	//
	//  UpdateMany
	//  @Description: 更新多条
	//  @param ctx
	//  @param filter
	//  @param update
	//  @return int64
	//  @return error
	//  @author: ikejcwang
	//  @create: 2022-10-04 14:38:26
	UpdateMany(ctx context.Context, filter, update interface{}) (int64, error)

	//
	//  Delete
	//  @Description: 根据条件删除
	//  @param ctx
	//  @param filter
	//  @return int64
	//  @return error
	//  @author: ikejcwang
	//  @create: 2022-10-04 14:38:53
	Delete(ctx context.Context, filter interface{}) (int64, error)

	//
	//  InsetOne
	//  @Description: 插入单条
	//  @param ctx
	//  @param model
	//  @return interface{}
	//  @return error
	//  @author: ikejcwang
	//  @create: 2022-10-04 14:39:38
	InsetOne(ctx context.Context, model interface{}) (interface{}, error)

	//
	//  InsertMany
	//  @Description: 插入多条
	//  @param ctx
	//  @param models
	//  @return []interface{}
	//  @return error
	//  @author: ikejcwang
	//  @create: 2022-10-04 14:40:25
	InsertMany(ctx context.Context, models []interface{}) ([]interface{}, error)

	//
	//  Aggregate
	//  @Description: 聚合查询
	//  @param ctx
	//  @param pipeline
	//  @param result
	//  @return error
	//  @author: ikejcwang
	//  @create: 2022-10-04 14:46:54
	Aggregate(ctx context.Context, pipeline interface{}, result interface{}) error
  
	//
  //  CreateIndexes
  //  @Description: 创建索引,用于初始化时调用
  //  @param ctx
  //  @param indexes
  //  @return error
  //  @author: ikejcwang
  //  @create: 2022-10-06 13:23:20
	CreateIndexes(ctx context.Context, indexes []mongo.IndexModel) error

	//
  //  GetCollection
  //  @Description: 获取当前的*mongo.Collection对象
  //  @return *mongo.Collection
  //  @author: ikejcwang
  //  @create: 2022-10-04 17:04:45
	GetCollection() *mongo.Collection
}

2、创建BaseCollection的结构体,且实现上述接口;

package collection

import (
	"context"
	"fmt"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

//
//  BaseCollectionImpl
//  @Description: collection 的实现
//
type BaseCollectionImpl struct {
	DbName     string
	ColName    string
	DataBase   *mongo.Database
	Collection *mongo.Collection
}

func (b *BaseCollectionImpl) SelectPage(ctx context.Context, filter interface{}, sort interface{}, skip, limit int64) (int64, []interface{}, error) {
	var err error

	resultCount, err := b.Collection.CountDocuments(ctx, filter)
	if err != nil {
		return 0, nil, err
	}

	opts := options.Find().SetSort(sort).SetSkip(skip).SetLimit(limit)
	finder, err := b.Collection.Find(ctx, filter, opts)
	if err != nil {
		return resultCount, nil, err
	}

	result := make([]interface{}, 0)
	if err := finder.All(ctx, &result); err != nil {
		return resultCount, nil, err
	}
	return resultCount, result, nil
}

func (b *BaseCollectionImpl) SelectList(ctx context.Context, filter interface{}, sort interface{}) ([]interface{}, error) {
	var err error

	opts := options.Find().SetSort(sort)
	finder, err := b.Collection.Find(ctx, filter, opts)
	if err != nil {
		return nil, err
	}

	result := make([]interface{}, 0)
	if err := finder.All(ctx, &result); err != nil {
		return nil, err
	}
	return result, err
}

func (b *BaseCollectionImpl) SelectOne(ctx context.Context, filter interface{}) (interface{}, error) {
	result := new(interface{})
	err := b.Collection.FindOne(ctx, filter, options.FindOne()).Decode(result)
	if err != nil {
		return nil, err
	}
	return result, nil
}

func (b *BaseCollectionImpl) SelectCount(ctx context.Context, filter interface{}) (int64, error) {
	return b.Collection.CountDocuments(ctx, filter)
}

func (b *BaseCollectionImpl) UpdateOne(ctx context.Context, filter, update interface{}) (int64, error) {
	result, err := b.Collection.UpdateOne(ctx, filter, update, options.Update())
	if err != nil {
		return 0, err
	}
	if result.MatchedCount == 0 {
		return 0, fmt.Errorf("Update result: %s ", "document not found")
	}
	return result.MatchedCount, nil
}

func (b *BaseCollectionImpl) UpdateMany(ctx context.Context, filter, update interface{}) (int64, error) {
	result, err := b.Collection.UpdateMany(ctx, filter, update, options.Update())
	if err != nil {
		return 0, err
	}
	if result.MatchedCount == 0 {
		return 0, fmt.Errorf("Update result: %s ", "document not found")
	}
	return result.MatchedCount, nil
}

func (b *BaseCollectionImpl) Delete(ctx context.Context, filter interface{}) (int64, error) {
	result, err := b.Collection.DeleteMany(ctx, filter, options.Delete())
	if err != nil {
		return 0, err
	}
	if result.DeletedCount == 0 {
		return 0, fmt.Errorf("DeleteOne result: %s ", "document not found")
	}
	return result.DeletedCount, nil
}

func (b *BaseCollectionImpl) InsetOne(ctx context.Context, model interface{}) (interface{}, error) {
	result, err := b.Collection.InsertOne(ctx, model, options.InsertOne())
	if err != nil {
		return nil, err
	}
	return result.InsertedID, err
}

func (b *BaseCollectionImpl) InsertMany(ctx context.Context, models []interface{}) ([]interface{}, error) {
	result, err := b.Collection.InsertMany(ctx, models, options.InsertMany())
	if err != nil {
		return nil, err
	}
	return result.InsertedIDs, err
}

func (b *BaseCollectionImpl) Aggregate(ctx context.Context, pipeline interface{}, result interface{}) error {
	finder, err := b.Collection.Aggregate(ctx, pipeline, options.Aggregate())
	if err != nil {
		return err
	}
	if err := finder.All(ctx, &result); err != nil {
		return err
	}
	return nil
}

func (b *BaseCollectionImpl) CreateIndexes(ctx context.Context, indexes []mongo.IndexModel) error {
	_, err := b.Collection.Indexes().CreateMany(ctx, indexes, options.CreateIndexes())
	return err
}

func (b *BaseCollectionImpl) GetCollection() *mongo.Collection {
	return b.Collection
}

3、提供初始化函数,创建collection对象函数;

package collection

import (
	"context"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/mongo/readpref"
	"time"
)

var (
	MongoClient *mongo.Client
)

const (
	defaultTimeout = 50 * time.Second
	maxPoolSize    = 10
)

//
//  InitMongo
//  @Description: 初始化mongo
//  @param mongoUrl
//  @return error
//  @author: ikejcwang
//  @create: 2022-10-04 15:27:55
func InitMongo(mongoUrl string) error {
	var err error

	ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
	defer cancel()

	MongoClient, err = mongo.Connect(ctx, options.Client().ApplyURI(mongoUrl).SetMaxPoolSize(maxPoolSize))
	if err != nil {
		return err
	}

	if err := MongoClient.Ping(ctx, readpref.Primary()); err != nil {
		return err
	}
	return nil
}

//
//  CreateMongoCollection
//  @Description: 创建mongo集合的服务
//  @param dbName
//  @param colName
//  @return BaseCollection
//  @return error
//  @author: ikejcwang
//  @create: 2022-10-04 15:15:14
func CreateMongoCollection(dbName, colName string) BaseCollection {
	dataBase := MongoClient.Database(dbName)
	return &BaseCollectionImpl{
		DbName:     dbName,
		ColName:    colName,
		DataBase:   dataBase,
		Collection: dataBase.Collection(colName),
	}
}

3、场景篇

为了演示简单处理,其实mongoDB的配置应该用.conf文件的方式引入,且可以丰富选项;

1、假设目前有一个库,一张表,那就创建一个baseCollection对象,有多个就创建多个,放在程序初始化的入口那里;

2、一个baseCollection对象,包含着该集合的绝大多数数据操作(丰富的增删改查);

3、同时也提供初始化索引的API,因为mongoDB的数据库,集合无需主动创建,引用到了,就是创建了。故把索引结构初始化的逻辑也放到应用程序这一侧

2、如果有特殊的业务处理baseCollection里面封装的函数没有覆盖到,需要原始API,例如初始化时创建各种类型的索引,它也提供着原始的*mongo.Collection的属性,可以直接调用它来操作,类似于:appCollection.GetCollection()


var (
	appCollection  *mongo.Collection
	userCollection *mongo.Collection
	// ……多个
)

//  
//  Init
//  @Description: 初始化mongo
//  @param mongoUrl
//  @author: ikejcwang
//  @create: 2022-10-06 13:40:05
func Init(mongoUrl string) error {

	if err := collection.InitMongo(mongoUrl); err != nil {
		panic(err)
	}
	ctx := context.TODO()

	//  逐一初始化集合对象
	userCollection := collection.CreateMongoCollection("test", "user")
	appCollection := collection.CreateMongoCollection("test", "user")

	// 依次给集合初始化索引
	indexes := []mongo.IndexModel{
		{
			Keys:    bson.D{{"name", 1}, {"age", -1}},
			Options: options.Index().SetUnique(true),
		},
	}
	if err := userCollection.CreateIndexes(ctx, indexes); err != nil {
		fmt.Printf("init indexes error: %v", err.Error())
		return err
	}
	//  ………………多个
	
	return nil
}

4、测试

1、创建一个集合的结构体,属性映射到所有集合字段上,同时提供bson格式化转换的函数;

//
//  UserDto
//  @Description: app集合的字段映射
//
type UserDto struct {
	Name string `json:"name" bson:"name"`
	Age  int64  `json:"age" bson:"age"`
}

func (a *UserDto) BsonByte(item interface{}) {
	bytes, err := bson.Marshal(item)
	if err != nil {
		panic(err)
	}
	bson.Unmarshal(bytes, a)
}

2、测试,初始化Mongo客户端连接,创建bsonCollection对象,写查询条件,调用对应函数,格式化结果集,输出结果集;

func main() {

	if err := collection.InitMongo("mongodb://user:password@9.13.28.88:27017/?authSource=admin"); err != nil {
		panic(err)
	}
	ctx := context.TODO()

	//  初始化user的集合对象
	userCollection := collection.CreateMongoCollection("test", "user")

	// 初始化索引
	indexes := []mongo.IndexModel{
		{
			Keys:    bson.D{{"name", 1}, {"age", -1}},
			Options: options.Index().SetUnique(true),
		},
	}
	if err := userCollection.CreateIndexes(ctx, indexes); err != nil {
		fmt.Printf("init indexes error: %v", err.Error())
	}

	filter := bson.M{
		"name": bson.M{
			"$regex":   "ike",
			"$options": "i",
		},
	}
	list, err := userCollection.SelectList(ctx, filter, nil)
	if err != nil {
		panic(err)
	}

	userList := make([]*UserDto, 0)
	for _, item := range list {
		app := new(UserDto)
		app.BsonByte(item)
		userList = append(userList, app)
	}

	fmt.Printf("userList: length:%v\n", len(userList))
	bytes, _ := json.Marshal(userList)
	fmt.Printf("userList: data:%v\n", string(bytes))
}

3、执行:go run xxx.go

wangjinchao@IKEJCWANG-MB0 14_ike_goMongo % go run test/test.go
userList: length:6
userList: data:[{"name":"ikejcwang","age":35},{"name":"ikejcwang","age":34},{"name":"ikejcwang","age":33},{"name":"ikejcwang","age":32},{"name":"ikejcwang","age":31},{"name":"ikejcwang","age":0}]

5、应用

类似java web框架开发那种分层处理,这里封装的是orm层(dao层),直接对接数据库,前面应该还有controller,service……