持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情
标题:golang对mongoDB的封装处理,完全版;
1、mongo-driver
mongoDB官方提供了golang语言开发的驱动包:mongo-driver,但是用起来比较繁琐,操作上有代码叠加场景,这里简单的对其二次封装处理,使单表的增删改查,批量处理,分页……多表的聚合查询,用起来更简单。
2、原理篇
2.1、封装思路
- 抽出一个操作数据库的接口,用来做抽象处理;
- mongo的每一个库的每一张集合,都会对应一个golang的结构体对象,来做唯一映射,即满足单表所有场景下的增删改查操作;
- 提供初始化函数和创建集合对象的函数,用于项目启动时的加载项;
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……