MongoDB [GoLang]

Introduction

MongoDB — нереляционная база данных, хранящая данные в бинарном виде называемым Binary JSON (BSON). Структуры GO легко сериализуются в BSON.
Данные в MongoDB хранятся в коллекциях состоящих из документов, которые в свою очередь состоят из полей вида key:value. Если проводить аналогию с MySQL,то коллекции MongoDB это аналог таблиц в MySQL, документы аналог строк, а поля документов это имена колонок таблиц MySQL.

Run MongoDB server in docker

docker mongo
Install MongoDB Enterprise with Docker

Создаём том для баз mongo

docker volume  create mongodb_vol

Запускаем контейнер

docker container run \
-d \
--restart=always \
--name mongodb \
-p 27017:27017 \
-e MONGO_INITDB_ROOT_USERNAME="admin" \
-e MONGO_INITDB_ROOT_PASSWORD="adminpass" \
-v mongodb_vol:/data/db \
mongo

Проверяем доступ к mongodb серверу с помощью mongo shell

mongo -u 'admin' -p 'adminpass' --authenticationDatabase admin

Connect to MongoDB with GO

package mgo
package bson
Для работы с MongoDB в GO используется пакет mgo.
Устанавливаем mgo:

go get gopkg.in/mgo.v2

Для использования mgo необходимо импортировать два пакеты:

import (
    "gopkg.in/mgo.v2"
    "gopkg.in/mgo.v2/bson"
)

Для подключения к базе необходимо создать сессию с помощью фун-ии Dial или DialWithInfo (предварительно указав с помощью DialInfo необходимые параметры подключения).
Пример с Dial

session, err := mgo.Dial("127.0.0.1")

Если необходимо подключится к кластеру то

session, err := mgo.Dial("server1.mongodb.srv,server2.mongodb.srv,server3.mongodb.srv")

Пример с DialWithInfo

//предварительно указываем необходимую информацию для подулюченя, такую как логин, пароль, базу авторизации
mongoDialInfo := &mgo.DialInfo{
    Addrs: []string{"localhost"},
    Timeout: 5 * time.Second,
    Database: "admin",
    Username: "admin",
    Password: "adminpass",
    Mechanism: "SCRAM-SHA-1",
}
//создаём сессию
session, err := mgo.DialWithInfo(mongoDialInfo)
if err != nil {
    log.Fatalln("Create sessio error: ",err)
}
defer session.Close()

Для того чтоб приступить к операциям CRUD предварительно необходимо создать объект коллекции с помощью метода С (возвращает объект типа mgo.Collection). Данный метод в свою очередь может быть вызван объектом базы, который создается методом DB(возвращает объект типа mgo.Database). В свою очередь данный метод доступен объекту session созданного с помощь метода Dial или DialWithInfo.

//подключаемся к базе  usersdb, коллекции  users
c := session.DB("usersdb").C("users")

CRUD with GO and MongoDB

В GO можно сохранять стурктуры, слайсы, мапы как BSON документы, т. е. mgo автоматически сериализует данные типы данных в BSON.

Insert

Для добавления документа используется метод Insert(docs ...interface{}), данный метод доступен объекту коллекции.

Insert Struct

Рассмотрим пример добавления документов в базу. В примере ниже документы будет получаться путем сериализации GO структур.

package main

import (
	"log"
	"time"

	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

//данный тип используется для анонимнго поля структуры user
type info struct {
	Age    int
	Gender string
}
//объявляем тип user являющийся структурой
//в дальнейшем объекты данного типа будут использоваться как доументы базы
type user struct {
	ID    bson.ObjectId `bson:"_id,omitempty"`
	UName string        `bson:"login"`
	FName string        `bson:"firstName"`
	LName string        `bson:"lastName"`
	info  `bson:",inline"` //анонимное поле,inline -важный ключ для работы 
                               //с аномнимными полями https://godoc.org/gopkg.in/mgo.v2/bson#Marshal
}

//создаём данный тип,в качестве примера будем итерироваться по объекту данного типа 
//и добалять их как документы в базу
type users []user

func main() {
        //создам сессию
	mongoDialInfo := &mgo.DialInfo{
		Addrs:     []string{"localhost"},
		Timeout:   5 * time.Second,
		Database:  "admin",
		Username:  "admin",
		Password:  "adminpass",
		Mechanism: "SCRAM-SHA-1",
	}

	session, err := mgo.DialWithInfo(mongoDialInfo)
	if err != nil {
		log.Fatalln("Create sessio error: ", err)
	}
	defer session.Close()

        //создаём объект коллекции
	c := session.DB("usersdb").C("users")

        //далее в качестве примера создадим несколько объектов типа user различными способами
	//короткий вариант
	u1 := user{
		ID:    bson.NewObjectId(),
		UName: "admin",
		FName: "Tony",
		LName: "Hawk",
		info: info{
			23,
			"male",
		},
	}
	//с помощью оператора "new"
	u2 := new(user)
	u2.ID = bson.NewObjectId()
	u2.UName = "billy"
	u2.FName = "Billy"
	u2.LName = "Bones"
	u2.Age = 27
	u2.Gender = "male"

	//с помощью указателя
	u3 := &user{
		ID:    bson.NewObjectId(),
		UName: "artur",
		FName: "Artur",
		LName: "Pirozhkov",
		info: info{
			Age:    25,
			Gender: "male",
		},
	}
	//анонимная стуктура
	u4 := struct {
		ID    bson.ObjectId `bson:"_id,omitempty"`
		UName string        `bson:"login"`
		FName string        `bson:"firstName"`
		LName string        `bson:"lastName"`
		info  `bson:",inline"`
	}{
		ID:    bson.NewObjectId(),
		UName: "dima",
		FName: "Dima",
		LName: "Shvetcov",
		info: info{
			Age:    21,
			Gender: "male",
		},
	}
        
        //традиционный вариант объявления переменной без указания полей структуры
	var u5 = user{
		bson.NewObjectId(),
		"alex",
		"Alexander",
		"Sirop",
		info{
			23,
			"male",
		},
	}

        //традиционный вариант объявления переменной с указаниеи полей структуры
	var u6 = user{
		ID:    bson.NewObjectId(),
		UName: "lena",
		FName: "Elena",
		LName: "Grachevskaya",
		info: info{
			19,
			"female",
		},
	}

        //далее будем итерироваться по обеъктам данного слайса
	var us users
	us = []user{u5, u6}

	//Ниже приведены несколько примеров добавления документов в базу
        //добаляем один документ
	err = c.Insert(u1)
	if err != nil {
		log.Fatalln("Insert u1 error:", err)
	}
        //добаляем один документ
	err = c.Insert(u2)
	if err != nil {
		log.Fatalln("Insert u2 error:", err)
	}
        //добаляем сразу два документа
	err = c.Insert(u3, u4)
	if err != nil {
		log.Fatalln("Insert u3,u4 error:", err)
	}
        //итерируемся и добаляем документ в базу
	for i := range us {
		err := c.Insert(us[i])
		if err != nil {
			log.Fatalf("Insert %s error:%s\n", us[i].UName, err)
		}
	}
}

Проверить результат можно через mongo shell,выполнив каманды

use usersdb
db.users.find().pretty()

или дописав код, в конце фун-ии main() добавить

count, err := c.Count()
if err != nil {
    log.Fatalln("Count error:", err)
}
fmt.Println(count)

c.Count() — данный метод вернёт кол-во документов содержащихся в коллекции
Надо отметить, что каждый документ базы содержит поле _id. Это так называемый objectid, это не случайная уникальная последовательность.
Рассмотрим пример такой последовательности 5b4edf36bd575d1b29145aaf, она состоит из:
4 bytes 5b4edf36 — timestamp, кол-во секунд с полуночи (00:00:00 UTC) 1 января 1970 года (см. Unix Epoch)
3 bytes bd575d — mid идентификатор машины
2 bytes 1b29 — pid идентификаатор процесса
3 bytes 145aaf — просто счетчик, перое число выбирается случайно
Если данное поле не выставлять самим, то mongodb сгенерирует его автоматически.
В нашем примере в структуре user для поля ID указали тип bson.ObjectId и для него указали bson тэг _id. Для генерации значения данного типа используется метод bson.NewObjectId()

Insert Map

Рассмотрим пример добавления GO map, как документ mongoDB.

package main

import (
	"log"
	"time"

	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

func main() {
	mongoDialInfo := &mgo.DialInfo{
		Addrs:     []string{"localhost"},
		Timeout:   5 * time.Second,
		Database:  "admin",
		Username:  "admin",
		Password:  "adminpass",
		Mechanism: "SCRAM-SHA-1",
	}

	session, err := mgo.DialWithInfo(mongoDialInfo)
	if err != nil {
		log.Fatalln("Create sessio error: ", err)
	}
	defer session.Close()

	c := session.DB("usersdb").C("users")
        //классическая инициализация map в GO
	//map[string]interface{}
	m := make(map[string]interface{})
	m["_id"] = bson.NewObjectId()
	m["fname"] = "Viktor"
	m["lname"] = "Cherkov"
	m["age"] = 29
	m["phone"] = "0000000000"
	
        //bson.M аналог map[string]interface{}
	p1 := bson.M{
		"_id":   bson.NewObjectId(),
		"fname": "Tony",
		"lname": "Hawk",
		"age":   21,
		"phone": "1111111111",
	}

	p2 := bson.M{
		"_id":   bson.NewObjectId(),
		"fname": "Tony",
		"lname": "Alva",
		"age":   20,
		"phone": "2222222222",
	}

	p3 := bson.M{
		"_id":   bson.NewObjectId(),
		"fname": "Kris",
		"lname": "Karter",
		"age":   24,
		"phone": "3333333333",
	}
	//bson.D см. https://godoc.org/gopkg.in/mgo.v2/bson#D
	p4 := bson.D{
		{"_id", bson.NewObjectId()},
		{"fname", "Boris"},
		{"lname", "Britva"},
		{"age", 27},
		{"phone", "4444444444"},
	}
	p5 := bson.D{
		{"_id", bson.NewObjectId()},
		{"fname", "Tyler"},
		{"lname", "Durden"},
		{"age", 23},
		{"phone", "5555555555"},
	}
	p6 := bson.D{
		{"_id", bson.NewObjectId()},
		{"fname", "Evgen"},
		{"lname", "Pergen"},
		{"age", 23},
		{"phone", "6666666666"},
	}

	c.Insert(m, p1, p2, p3, p4, p5, p6)
	if err != nil {
		log.Fatalln("bsonM insert error: ", err)
	}

}

Delete

Удаление одного документа осуществляется методом Remove(selector interface{}), данный метод доступен объекту коллекции . Где селектор передается, как тип bson.M{}.

err = c.Remove(bson.M{"fname": "Evgen"})
if err != nil {
    log.Fatalln("bsonM insert error: ", err)
}

Удаление нескольких документов осуществляется методом RemoveAll. Данный метод помимо ошибки возвращает данные типа ChangeInfo, включающие в себя такую полезную информацию как — сколько найдено совпадений, сколько документов удалено.

info,err := c.RemoveAll(bson.M{"fname": "Tony"})
if err != nil {
    log.Fatalln("bsonM insert error: ", err)
}
fmt.Println(info)

Если необходимо очистить всю коллекцию, то в RemoveAll передаём nil:

info,err := c.RemoveAll(nil)
if err != nil {
    log.Fatalln("bsonM insert error: ", err)
}
fmt.Println(info)

Read

Поиск/чтение документов осуществляется методом Find(query interface{}). Данный метод возвращает объект типа Query. Этот тип обладает такими методами как One,Iter,Sort, которые позволяют полученный результат сериализовать в данные GO map или struct типа.

Find(nil)

Пример получения всех документов в коллекции

iter := c.Find(nil).Iter()
res := make(bson.M)
for iter.Next(&res) {
    fmt.Printf("fname:%s,lname:%s,age:%d,phone:%s,\n", res["fname"], res["lname"], res["age"], res["phone"])
}
if err = iter.Close(); err != nil {
    log.Fatal(err)
}

Find(nil).Sort()

Если необходимо вернуть отсортированный результат по определенному полю, то используется метод Sort

iter := c.Find(nil).Sort("fname").Iter()
res := make(bson.M)
for iter.Next(&res) {
    fmt.Printf("fname:%s,lname:%s,age:%d,phone:%s,\n", res["fname"], res["lname"], res["age"], res["phone"])
}
if err = iter.Close(); err != nil {
    log.Fatal(err)
}

Find(nil).Sort() reverse

Сортируем в обратном порядке

iter := c.Find(nil).Sort("-fname").Iter()
res := make(bson.M)
for iter.Next(&res) {
    fmt.Printf("fname:%s,lname:%s,age:%d,phone:%s,\n", res["fname"], res["lname"], res["age"], res["phone"])
}
if err = iter.Close(); err != nil {
    log.Fatal(err)
}

Find(nil).One()

Возвращаем только один результат

res := make(bson.M)
err = c.Find(nil).One(res)
if err != nil {
    log.Fatalln(err)
}
fmt.Printf("fname:%s,lname:%s,age:%d,phone:%s,\n", res["fname"], res["lname"], res["age"], res["phone"])

Find({selector})

В качестве селектора метод find принимает значение типа bson.M{}

iter := c.Find(bson.M{"fname": "Tony"}).Iter()
res := make(bson.M)
for iter.Next(&res) {
    fmt.Printf("fname:%s,lname:%s,age:%d,phone:%s,\n", res["fname"], res["lname"], res["age"], res["phone"])
}
if err = iter.Close(); err != nil {
    log.Fatal(err)
}

Find({selector1,selector2,…})

Селектор по двум полям

iter := c.Find(bson.M{"fname": "Tony"},{"lname":"Hawk"}).Iter()
res := make(bson.M)
for iter.Next(&res) {
    fmt.Printf("fname:%s,lname:%s,age:%d,phone:%s,\n", res["fname"], res["lname"], res["age"], res["phone"])
}
if err = iter.Close(); err != nil {
    log.Fatal(err)
}

Find({selector},{fields})

Поиск документов и вывод результата с определенными полями, аналог find({selector1,selector2,…},{fields}) в mongodb

iter := c.Find(bson.M{"fname": "Tony"}).Select(bson.M{"_id": 0, "fname": 1, "lname": 1, "age": 1}).Iter()
res := make(bson.M)
for iter.Next(&res) {
    fmt.Println(&res)
}
if err = iter.Close(); err != nil {
    log.Fatal(err)
}

Find with Comparison Operators — $lt,$gt,$lte,$gte

При задании селектора могут быть использованы различные логические операторы, операторы сравнения и т.д.

Неполный список операторов сравнения:
$ne не равно
$lt < 
$gt >
$lte <= 
$gte >=

Пример селектора с использование оператора сравнения

iter := c.Find(bson.M{"age": bson.M{"$gte": 23}}).Iter()
res := make(bson.M)
for iter.Next(&res) {
    fmt.Println(&res)
}
if err = iter.Close(); err != nil {
    log.Fatal(err)
}

Find with Logical Operators — $or,$and,$nor,$not

Пример селектора с использование лигических операторов

iter := c.Find(bson.M{"$or": []bson.M{bson.M{"phone": "1111111111"}, bson.M{"phone": "2222222222"}}}).Select(bson.M{"_id": 0}).Iter()
res := make(bson.M)
for iter.Next(&res) {
    fmt.Println(&res)
}
if err = iter.Close(); err != nil {
    log.Fatal(err)
}

Find with operator $exists

Если необходимо найти документы содержащии или не содержащии определенное поле,
то используется оператор $exists

iter := c.Find(bson.M{"phone": bson.M{"$exists": true}}).Select(bson.M{"_id": 0}).Iter()
res := make(bson.M)
for iter.Next(&res) {
    fmt.Println(&res)
}
if err = iter.Close(); err != nil {
    log.Fatal(err)
}

Find with operator $type

Если необходимо найти документы содержащии поле с определенным типом данных,
то используется оператор $type

iter := c.Find(bson.M{"age": bson.M{"$type": 16}}).Select(bson.M{"_id": 0}).Iter()
res := make(bson.M)
for iter.Next(&res) {
    fmt.Println(&res)
}
if err = iter.Close(); err != nil {
    log.Fatal(err)
}

Update

Обновление документов осуществляется методом Update(selector interface{}, update interface{}) и UpdateAll(selector interface{}, update interface{}), где первый метод обновит первый документ попавший под селектор, а второй все документы подходящие под селектор.
Если обновлять документ без каких-либо дополнительных операторов таких как $set,$unset и т.д., то перезаписывается весь документ.

Было
{ "_id" : ObjectId("5beb717cd582872c5a4312f2"), "fname" : "Tony", "lname" : "Hawk", "age" : 21, "phone" : "1111111111" }

После выполнения данного кода
err = c.Update(bson.M{"lname": "Hawk"}, bson.M{"fname": "TONY"})
if err != nil {
    log.Fatal(err)
}

Результат будет такой
{ "_id" : ObjectId("5beb717cd582872c5a4312f2"), "fname" : "TONY" }
т.е. все не указанные поля удалились и перезаписалось только одно поле.

Метод update() сожержит различные операторы, которые помогают модифицировать отдельные поля, массивы данных

$set,$unset

$set — изменить/добавить поле(если поля в документе не существует, то оно добавляется)
$unset — удалить поле
Обновим документ используя $set, в качестве примера добавим новое поле "testfield": "testfield"

err = c.Update(bson.M{"fname": "TONY"}, bson.M{"$set": bson.M{"fname": "Tony", "lname": "Hawk", "age": 22, "phone": "1111111111", "testfield": "testfield"}})
if err != nil {
    log.Fatal(err)
}

Теперь удалим поле "testfield": "testfield" с помощью оператора $unset

err = c.Update(bson.M{"lname": "Hawk"}, bson.M{"$unset": bson.M{"testfield": "testfield"}})
if err != nil {
    log.Fatal(err)
}

$rename

Данный оператор позволяет переименовывать поля документов.
Переименуем поле lname в lastname всех документов.

info, err = c.UpdateAll(bson.M{}, bson.M{"$rename": bson.M{"lname": "lastname"}})
if err != nil {
    log.Fatal(err)
}
fmt.Println(info)

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *