为什么要这么做?

尽管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 alltestResult := []*model.TestModel{}err = collection.Find(collection, bson.M{}).All(&testResult)// find onetmFindOne := &model.TestModel{}err = collection.Find(collection, bson.M{}).One(&tmFindOne)//insert onetmInsertOne := &model.TestModel{}err = collection.Insert(tmInsertOne)//insert manytmInsertMany := []*model.TestModel{}err = collection.Insert(tmInsertMany...)// update by idupdate := bson.M{"$set":bson.m{"name":"update name"}}err := collection.UpdateId(id, update)// replace by idtmReplace := &model.TestModel{}err := collection.UpdateId(id, tmReplace)// remove by idinfo, err := collection.RemoveId(id)// remove by queryinfo, err :=  collection.RemoveAll(selector)

mongo driver:

// s = session ctx := context.TODO()collection := s.Client().Database(db).Collection(collectionName)// find alltestResult := []*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 onetmInsertOne := &model.TestModel{}insertOneResult, err := collection.InsertOne(ctx, tmInsertOne, options.InsertOne())// insert manytmInsertMany := []*model.TestModel{}insertManyResult, err := collection.InsertMany(ctx, tmInsertMany, options.InsertMany())// update by idupdate := bson.M{"$set":bson.m{"name":"update name"}}updateResult , err := collection.UpdateOne(ctx, bson.D{{"_id", id}}, update, options.Update())// replace by idtmReplace := &model.TestModel{}replaceResult , err := collection.ReplaceOne(ctx, bson.D{{"_id", id}}, tmReplace, options.Replace())// remove by iddeleteResult, error := collection.DeleteOne(ctx, bson.M{"_id": id}, options.Delete())// remove by querydeleteManyResult, 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 须要别离应用 UpdateOneReplaceOne。 这个问题咱们也能够简略的封装一下, 比方:

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{}// 必须要用 sCtxinsertOneResult, 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 , 并且实现 EncodeValueDecodeValue两个接口.( 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 的ObjectIdprimitive.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

语言组织不到位,写的很乱。如果有不对的中央心愿大家多多指教,谢谢!