为什么要这么做?
虽然mgo 十分好用且稳定, 但是由于mgo不再维护 不支持事务, 并且golang 推荐使用官方驱动 mongo driver. 所以更换成mongo driver.
GitHub: https://github.com/mongodb/mo...
Doc: https://godoc.org/go.mongodb....
对比 mgo和 mongo driver使用上的区别
因为偷懒 所有代码都忽略了错误处理。
连接
mgo:
globalSession, err := := mgo.Dial(mongdbUrl)
session := globalSession.Copy()
defer session.Close()
mongo driver
client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongodbUrl))
session, err := globalClient.StartSession()
defer session.EndSession(context.TODO())
有一个小坑,在本地的时候mgo 的mongodburl 可以写成127.0.0.1,但是mongo driver 必须写成 mongodb://127.0.0.1
可以看到他们的区别:
mgo:
连接后返回一个 session. 可以通过.Copy()来复制session,然后通过.Close()来关闭连接。
mongo driver:
返回一个 client 通过.StartSession()和.EndSession(context.TODO())**来开启和关闭session
从mgo的文档上看 .Copy()新建会话保留身份验证信息。
Copy works just like New, but preserves the exact authentication information from the original session.
而在mongo driver的client 里维护了一个 session.Pool。每次.StartSession() 都会从这个pool 里面拿,pool size默认值是100 可以通过clientOptions.SetMaxPoolSize来设置.*
基本操作:查找,插入,更新,删除,聚合
mgo:
// s = session
collection := s.DB(db).C(collectionName)
var err error
// find all
testResult := []*model.TestModel{}
err = collection.Find(collection, bson.M{}).All(&testResult)
// find one
tmFindOne := &model.TestModel{}
err = collection.Find(collection, bson.M{}).One(&tmFindOne)
//insert one
tmInsertOne := &model.TestModel{}
err = collection.Insert(tmInsertOne)
//insert many
tmInsertMany := []*model.TestModel{}
err = collection.Insert(tmInsertMany...)
// update by id
update := bson.M{"$set":bson.m{"name":"update name"}}
err := collection.UpdateId(id, update)
// replace by id
tmReplace := &model.TestModel{}
err := collection.UpdateId(id, tmReplace)
// remove by id
info, err := collection.RemoveId(id)
// remove by query
info, err := collection.RemoveAll(selector)
mongo driver:
// s = session
ctx := context.TODO()
collection := s.Client().Database(db).Collection(collectionName)
// find all
testResult := []*model.TestModel{}
cur , err := collection.Find(ctx, bson.M{}, options.Find())
err = cur.All(s.c, &testResult)
// find one
tmFindOne := &model.TestModel{}
err = collection.FindOne(ctx, bson.M{"_id": id}).Decode(tmFindOne)
// insert one
tmInsertOne := &model.TestModel{}
insertOneResult, err := collection.InsertOne(ctx, tmInsertOne, options.InsertOne())
// insert many
tmInsertMany := []*model.TestModel{}
insertManyResult, err := collection.InsertMany(ctx, tmInsertMany, options.InsertMany())
// update by id
update := bson.M{"$set":bson.m{"name":"update name"}}
updateResult , err := collection.UpdateOne(ctx, bson.D{{"_id", id}}, update, options.Update())
// replace by id
tmReplace := &model.TestModel{}
replaceResult , err := collection.ReplaceOne(ctx, bson.D{{"_id", id}}, tmReplace, options.Replace())
// remove by id
deleteResult, error := collection.DeleteOne(ctx, bson.M{"_id": id}, options.Delete())
// remove by query
deleteManyResult, err := collection.DeleteMany(ctx, bson.M{}, options.Delete())
(mongo driver 简称 md, mgo 简称 mg)
结构上的区别:
md 需要传递一个contxt 和 options.xxx() 的参数, 就我目前使用来看 md 在开启事务的时候 会在老的context写一个session来创建新的context。
newCtx := mongo.NewSessionContext(ctx, session)
md 通过传递这个newCtx 来标示哪些是事务操作 (下面会写一个md的事务例子 )。 options.xxx()可以设置相应操作的可选项。比如 upsert, AllowDiskUse , Limit , sort ....
基础操作的区别:
1.md 的find 返回一个 cur 游标, 我们可以通过
cur , err := collection.Find(ctx, bson.M{}, options.Find())
tms := []*model.TestModel{}
defer cur.Close(s.c)
for cur.Next(s.c) {
tm := &model.TestModel{}
err := cur.Decode(tm)
tms = append(tms, tm)
}
这种方式来取值,也可以通过cur.All() 一把拿到,适用不同的场景。
2.md的insert one 和 insert many 是分开的,这个关系不大 我们可以自己封一下。
Insert(docs ...interface{}) (*mongo.InsertManyResult, error) {
return collection.InsertMany(ctx, docs, options.InsertMany())
}
3.mg 的updateId 支持update 的bson 操作语句,比如: update := bson.M{"$set":bson.m{"name":"update name"}}, 也可以丢入doc 直接replace。 但是md 需要分别使用 UpdateOne 和 ReplaceOne。 这个问题我们也可以简单的封装一下, 比如:
UpdateId(collectionName string, id interface{}, update interface{}) error {
switch update.(type) {
case primitive.D, primitive.M, primitive.A:
_, err := collection.UpdateOne(s.c, bson.D{{"_id", id}}, update)
return err
default:
_, err := collection.ReplaceOne(s.c, bson.D{{"_id", id}}, update)
return err
}
}
有些简陋,大家可以想想 其他的方法。
4.md 使用DeleteOne 和 DeleteMany, mg 用 RemoveId 和 RemoveAll,用法差不多。
聚合
用法是一样的只是名字不一样且md返回游标。
md使用:
cursor, err := c.Aggregate(ctx, []bson.M{})
mg使用:
pipe := c.Pipe([]bson.M{
project,
unwind,
group,
})
事务
需要使用 replica 才能开启事务。
Because transaction numbers are only allowed on a replica set member or mongos.So you need to install replica set locally*
参考: http://thecodebarbarian.com/i...
有两种开启方式:
1.
ctx := context.TODO()
// 开启事务
err := session.StartTransaction()
// 注意: 想要进行事务的操作,必须传递这个sCtx,否则不包含在事务内.
sCtx = mongo.NewSessionContext(ctx, session)
collection := session.Client().Database(db).Collection(collectionName)
tmInsertOne := &model.TestModel{}
// 必须要用 sCtx
insertOneResult, err := collection.InsertOne(sCtx, tmInsertOne, options.InsertOne())
if err != nil {
// 出现err 回滚事务
session.AbortTransaction(ctx)
}
// 提交事务
session.CommitTransaction(ctx)
2.
ctx := context.TODO()
collection := session.Client().Database(db).Collection(collectionName)
err = mongo.WithSession(ctx, sess,
func(sessCtx mongo.SessionContext) error {
insertOneResult, err := collection.InsertOne(sCtx, tmInsertOne, options.InsertOne())
if err != nil {
return err
}
})
用第一种方式需要自己手动开启, 回滚或提交事。第二种方式WithSession会自动帮你开启事务,出现 error 会自动AbortTransaction, 成功之后CommitTransaction. 适用于不同的场景。
自定义主键
在代码中我们想使用 string 类型或者其他类型的主键 id,但是在mongo db 还是以ObjectId()的方式存贮,我们可以这么做。
在代码我们定义自己的主键 id , 并且实现 EncodeValue 和 DecodeValue两个接口.( mg 是实现 GetBson 和 SetBson 这两个接口)
type Id string
func (e *Id) EncodeValue(ectx bsoncodec.EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
return IdEncodeObjectId(ectx, vw, val)
}
func (e *Id) DecodeValue(ectx bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
return ObjectIdDecodeId(ectx, vr, val)
}
var IdEncodeObjectId = func(ectx bsoncodec.EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if val.Type() != ToId {
return bsoncodec.ValueDecoderError{Name: "IdEncodeValue", Types: []reflect.Type{ToId}, Received: val}
}
if val.String() == "" {
return vw.WriteObjectID(primitive.NewObjectID())
}
objectId, err := primitive.ObjectIDFromHex(val.String())
if err != nil {
return err
}
return vw.WriteObjectID(objectId)
}
var ObjectIdDecodeId = func(ectx bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if !val.CanSet() || val.Type() != ToId {
return bsoncodec.ValueDecoderError{Name: "IdDecodeValue", Types: []reflect.Type{ToId}, Received: val}
}
var id string
switch vrType := vr.Type(); vrType {
case bsontype.ObjectID:
objectId, err := vr.ReadObjectID()
if err != nil {
return err
}
id = objectId.Hex()
case bsontype.String:
str, err := vr.ReadString()
if err != nil {
return err
}
if len(str) != 12 {
return fmt.Errorf("an ObjectID string must be exactly 12 bytes long (got %v)", len(str))
}
id = str
case bsontype.Null:
if err := vr.ReadNull(); err != nil {
return err
}
case bsontype.Undefined:
if err := vr.ReadUndefined(); err != nil {
return err
}
default:
return fmt.Errorf("cannot decode %v into an ObjectID", vrType)
}
val.SetString(id)
return nil
}
然后在连接数据库的时候注册
registry := bson.NewRegistryBuilder()
id := m.Id("")
registry.RegisterCodec(reflect.TypeOf(Id("")), &id)
clientOptions := options.Client().SetRegistry(registry().Build())
mongo.Connect(ctx, clientOptions)
这样在关于 Id 操作的时候,我们代码自定义类型 Id 会Encode成 md 的ObjectId即 primitive.ObjectID{}, 我们也可以用 Id 接收 mongo ObjectId.我们结构可以这么定义
type TestModel struct {
Id Id `bson:"_id"`
}
我遇到的一些问题和解决的办法
mongo-go-driver “server selection error”
问题链接: https://stackoverflow.com/que...
md 通过连接语句 mongdbUrl 找到设置的集群主机 rs0,然后获取rs0的config 里面配置的其他子机的host,再通过这些host来访问子机,所以需要把这些config的子机host写到我们server的机器上去。
对replica 不太了解的可以看这个 replica-set