Bootstrap

GoWeb——访问MongoDB

Go范文MongoDB

  1. 连接数据库

获取驱动,命令行输入:

go get go.mongodb.org/mongo-driver/mongo

通过ApplyURI()方法连接数据库:

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

var mgoCli *mongo.Client

func initDb() {
	var err error 
	clientOptions := options.Client().ApplyURI("mongodb://localhost:27017")
	
	//连接MongoDB
	mgoCli, err = mongo.Connect(context.TODO(), clientOptions)
	if err != nil {
		log.Fatal(err)
	}
	//检查连接
	err = mgoCli.Ping(context.TODO(), nil)
	if err != nil {
		log.Fatal(err)
	}
}

func MgoCli() *mongo.Client {
	if mgoCli == nil {
		initDb()
	}
	return mgoCli
}

在连接数据库后,调用MgoCli()函数获取MongoDB客户端实例,用Database()方法指定数据库,用Collection()方法指定数据集合:

func main() {
	var (
		client     = MgoCli()
		db         *mongo.Database
		collection *mongo.Collection
	)
	//选择数据库
	db = client.Database("test")
	//选择表
	collection = db.Collection("book")
	fmt.Println(collection)
}
  1. 插入一条数据

首先编写模型文件,构建结构体ExecTime、LogRecord:

type ExecTime struct {
	StartTime int64 `bson:"startTime"` //开始时间
	EndTime int64 `bson:"endTime"` //结束时间
}

type LogRecord struct {
	JobName string `bson:"JobName"`//任务名
	Command string `bson:"command"`//shell命令
	Err string `bson:"err"`//脚本错误
	Content string `bson:"content"`//脚本输出
	Tp ExecTime //执行时间
}
import (
	"./model"
	"./util"
	"context"
	"fmt"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"time"
)

func main() {
	var (
		client     = util.MgoCli()
		err        error
		collection *mongo.Collection
		iResult    *mongo.InsertOneResult
		id         primitive.ObjectID
	)

	//选择数据库和表
	collection = client.Database("test").Collection("logRecord")
	//插入某一条数据
	logRecord := model.LogRecord{
		JobName: "job1",
		Command: "echo 1",
		Err:     "",
		Content: "1",
		Tp: model.ExecTime{
			StartTime: time.Now().Unix(),
			EndTime:   time.Now().Unix() + 10,
		},
	}
	if iResult, err = collection.InsertOne(context.TODO(), logRecord); err != nil {
		fmt.Println(err)
		return
	}
	//_id:默认生成一个全局唯一ID
	id = iResult.InsertedID.(primitive.ObjectID)
	fmt.Println("自增ID", id.Hex())
}
  1. 批量插入数据

批量插入数据时,只需要调用InsertMany()方法:

func main() {
	var (
		client     = util.MgoCli()
		err        error
		collection *mongo.Collection
		result     *mongo.InsertManyResult
		id         primitive.ObjectID
	)

	//选择数据库和表
	collection = client.Database("test").Collection("logRecord")
	//批量插入
	result, err = collection.InsertMany(context.TODO(), []interface{}{
		model.LogRecord{
			JobName: "job multi1",
			Command: "echo multi1",
			Err:     "",
			Content: "1",
			Tp: model.ExecTime{
				StartTime: time.Now().Unix(),
				EndTime:   time.Now().Unix() + 10,
			},
		},
		model.LogRecord{
			JobName: "job multi2",
			Command: "echo multi2",
			Err:     "",
			Content: "2",
			Tp: model.ExecTime{
				StartTime: time.Now().Unix(),
				EndTime:   time.Now().Unix() + 10,
			},
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	if result == nil {
		log.Fatal("result nil")
	}
	for _, v := range result.InsertedIDs {
		id = v.(primitive.ObjectID)
		fmt.Println("自增ID:", id.Hex())
	}
}
  1. 查询数据

首先,在model文件中添加一个查询结构体:

//查询结构体
type FindJobName struct {
	JobName string `bson:"jobName"` //任务名
}

然后,通过Find()函数按照条件进行查找:

func find() {
	var (
		client     = util.MgoCli()
		err        error
		collection *mongo.Collection
		cursor     *mongo.Cursor
	)
	collection = client.Database("test").Collection("logRecord")
	//查询条件
	cond := model.FindJobName{
		JobName: "job multi1",
	}
	if cursor, err = collection.Find(
		context.TODO(),
		cond,
		options.Find().SetSkip(0),
		options.Find().SetLimit(2)); err != nil {
		fmt.Println(err)
		return
	}
	defer func() {
		if err = cursor.Close(context.TODO()); err != nil {
			log.Fatal(err)
		}
	}()

	//遍历邮编获取结果数据
	for cursor.Next(context.TODO()) {
		var lr model.LogRecord
		//反序列化Bson到对象
		if cursor.Decode(&lr) != nil {
			fmt.Println(err)
			return
		}
		fmt.Println(lr)
	}

	var results []model.LogRecord
	if err = cursor.All(context.TODO(), &results); err != nil {
		log.Fatal(err)
	}
	for _, result := range results {
		fmt.Println(result)
	}
}
  1. 用BSON进行复合查询

复合查询会使用到BSON包。MongoDB中的JSON文档存储在名为BSON(二进制编码的JSON)的二进制表示中。与其他编码将JSON数据存储为简单字符串和数字的数据库不同,BSON编码扩展了JSON表示,使其包含额外的类型,如int、long、date、decimal128等。这使得应用程序更容易可靠地处理、排序和比较数据。

在连接MongoDB的Go驱动程序中,有两大类型表示BSON数据:D类型和Raw类型。

1. D类型

D类型被用来简洁地构建使用本地Go类型的BSON对象。这对于构造传递给MongoDB的命令特别有用。D类型包括以下4个子类。

  • D:一个BSON文档。这种类型应该在顺序重要的情况下使用,比如MongoDB命令。
  • M:一张无序的map。它和D类似,只是它不保持顺序。
  • A:一个BSON数组。
  • E:D中的一个元素。

使用BSON可以更方便地用Go完成对数据库的CURD操作。要使用BSON,需要先导入下
面的包:

import "go.mongodb.org/mongo-driver/bson"

下面是一个使用D类型构建的过滤器文档的例子,它可以用来查找name字段与“Jim”或“Jack”匹配的文档:

bson.D{
	"name",
	bson.D{
		"$in",
		bson.A{"Jim", "Jack"}
	}
}
2. Raw类型

Raw类型用于验证字节切片。Raw类型还可以将BSON反序列化成另一种类型。

import "fmt"
import "go.mongodb.org/mongo-driver/bson"

func main() {
	//声明一个BSON类型
	testM := bson.M{
		"jobName": "job multi1",
	}
	//定义一个Raw类型
	var raw bson.Raw
	tmp, _ := bson.Marshal(testM)
	bson.Unmarshal(tmp, &raw)

	fmt.Println(testM) //map[jobName:job multi1]
	fmt.Println(raw)//{"jobName": "job multi1"}
}

对于复合查询来说,D类型更加强大。下面介绍如何使用D类型进行常用的复合查询。

3. 聚合查询

如果需要对数据进行聚合查询,则要用到group()等聚合方法。示例代码如下:

import (
	"./util"
	"context"
	"fmt"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"log"
)

func main() {
	var (
		client     = util.MgoCli()
		collection *mongo.Collection
		err        error
		cursor     *mongo.Cursor
	)
	collection = client.Database("test").Collection("logRecord")
	//按照jobName分组,统计countJob中每组的数目
	groupStage := mongo.Pipeline{
		bson.D{
			{"$group", bson.D{
				{"_id", "$JobName"},
				{"countJob", bson.D{
					{"$sum", 1},
				}},
			}},
		},
	}
	if cursor, err = collection.Aggregate(context.TODO(), groupStage); err != nil {
		log.Fatal(err)
	}
	defer func() {
		if err = cursor.Close(context.TODO()); err != nil {
			log.Fatal(err)
		}
	}()
	var results []bson.M
	if err = cursor.All(context.TODO(), &results); err != nil {
		log.Fatal(err)
	}
	for _, result := range results {
		fmt.Println(result) 
	}
	//map[_id:job multi1 countJob:1]
	//map[_id:job1 countJob:1]
	//map[_id:job multi2 countJob:1]
}
4. 更新数据

同样的,更新数据也需要建立专门用于更新的结构体。结构体有Command、Content两个字段。

更新时需要同时对这两个字段进行赋值,否则未被赋值的字段会被更新为G0的数据类型初始值。为更新更方便些,可采用bson.M{"$set":bson.M{"command":"ByBsonM",}}来进行更新。

创建UpdateByJobName结构体:

//更新结构体
type UpdateByJobName struct {
	Command string `bson:"command"`//shell命令
	Content string `bson:"content"`//脚本输出
}

根据结构体进行更新:

import (
	"./util"
	"context"
	"fmt"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"log"
)

func main() {
	var (
		client     = util.MgoCli()
		collection *mongo.Collection
		err        error
		uResult    *mongo.UpdateResult
	)
	collection = client.Database("test").Collection("logRecord")
	filter := bson.M{"JobName": "job multi1"}
	//update := bson.M{
	//	"$set": model.UpdateByJobName{Command: "byModel", Content: "model"},
	//}
	//使用bson方式
	update := bson.M{
		"$set": bson.M{
			"command": "xxxxxxx",
			"content": "yyyyyyy",
		},
	}
	if uResult, err = collection.UpdateMany(context.TODO(), filter, update); err != nil {
		log.Fatal(err)
	}
	fmt.Println("更新了:", uResult.ModifiedCount, "条记录")
}

使用$inc可以对字段的值进行增减,例如:

bson.M("$inc": bson.M{"age": -1})

使用$push给该字段增加1个元素,例如:

bson.M{"$push": bson.M{"interests": "Golang"}}

使用$pull可以对该字段删除一个元素,例如:

bson.M{"$pull":bson.M{"interests": "Golang"}}
5. 删除数据

可以用DeleteMany()方法来删除数据,示例如下:

import (
	"./util"
	"context"
	"fmt"
	"go.mongodb.org/mongo-driver/mongo"
	"log"
	"time"
)

type DeleteCond struct {
	BeforeCond TimeBeforeCond `bson:"tp.startTime"`
}

// startTime小于某个时间,用这种方式提前定义要进行的操作($set、$group等)
type TimeBeforeCond struct {
	BeforeTime int64 `bson:"$lt"`
}

func main() {
	var (
		client     = util.MgoCli()
		collection *mongo.Collection
		err        error
		uResult    *mongo.DeleteResult
	)
	collection = client.Database("test").Collection("logRecord")

	//使用结构体构建条件,删除jobName中名为job0的数据
	delCond := &DeleteCond{
		BeforeCond: TimeBeforeCond{
			BeforeTime: time.Now().Unix(),
		},
	}
	//使用BSON构建条件
	//filter := bson.M{
	//	"tp.startTime": bson.M{
	//		"$lt": time.Now().Unix(),
	//	},
	//}
	if uResult, err = collection.DeleteMany(context.TODO(), delCond); err != nil {
		log.Fatal(err)
	}
	fmt.Println(uResult.DeletedCount)
}

如果要忽略被初始化的值,则可以直接在结构体中增加omitempty属性:

type ExecTimeFilter struct {
	StartTime interface{} `bson:"tp.startTime,omitempty"` //开始时间
	EndTime   interface{} `bson:"tp.endTime,omitempty"`   //结束时间
}

type LogRecordFilter struct {
	ID interface{} `bson:"_id,omitempty"`
	JobName interface{}   `bson:"JobName,omitempty" json:"jobName"` //任务名
	Command interface{}   `bson:"command,omitempty"` //shell命令
	Err     interface{}   `bson:"err,omitempty"`     //脚本错误
	Content interface{}   `bson:"content,omitempty"` //脚本输出
	Tp      interface{} `bson:"tp,omitempty"`//执行时间
}

另外,可以在结构体中添加$lt、$group、$sum等表示逻辑关系的属性:

//小于示例
type Lt struct {
	Lt int64 `bson:"$lt"`
}
//分组示例
type Group struct {
	Group interface{} `bson:"$group"`
}
//求和示例
type Sum struct {
	Sum interface{} `bson:"$sum"`
}

$group进行分组求和的示例:

import (
	"./model"
	"./util"
	"context"
	"fmt"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"log"
)

func main() {
	var (
		client     = util.MgoCli()
		collection *mongo.Collection
		err        error
		cursor     *mongo.Cursor
	)
	collection = client.Database("test").Collection("logRecord")

	groupStage := []model.Group{}
	groupStage = append(groupStage, model.Group{
		Group: bson.D{
			{"_id", "$JobName"},
			{"countJob", model.Sum{Sum: 1}},
		},
	})

	if cursor, err = collection.Aggregate(context.TODO(), groupStage); err != nil {
		log.Fatal(err)
	}
	defer func() {
		if err = cursor.Close(context.TODO()); err != nil {
			log.Fatal(err)
		}
	}()
	var results []bson.M
	if err = cursor.All(context.TODO(), &results); err != nil {
		log.Fatal(err)
	}
	for _, result := range results {
		fmt.Println(result)
	}
}

;