Как использовать MongoDB в Go
В этой статье я расскажу вам об использовании Golang с MongoDB. Сначала я покажу вам, как установить MongoDB и важные пакеты Golang.
Затем мы подключимся к серверу MongoDB, пропингуем его и выведем список существующих баз данных. После этого я рассмотрю пример, в котором я использую различные функции для выполнения операций CRUD.
Установка пакетов MongoDB и Golang
Установить MongoDB для вашей платформы довольно просто. Вы можете получить версию для сообщества отсюда.
Учитывая, что я работаю в Windows, у меня была возможность установить MongoDB как службу. Я решил не делать этого, потому что использую MongoDB в качестве тестового сервера. Однако в результате мне нужно запустить MongoDB вручную.
Ручной запуск MongoDB в Windows
Чтобы запустить сервер MongoDB вручную, вы можете сделать следующее:
- Во-первых, убедитесь, что в вашем пути есть каталог данных, из которого вы запускаете команду — в приведенном ниже примере это
./data
. - Затем запустите
"C:\Program Files\MongoDB\Server\5.0\bin\mongod" — dbpath ./data
в своем терминале. Ваш путь может немного отличаться. В этом случае вы можете найтиmongod
и посмотреть, какой путь может быть в вашей системе.
Настольное приложение MongoDB Compass
Вы также можете установить MongoDB Compass. Получите его можно здесь.
Это настольное приложение может помочь вам управлять данными, которые вы вводите на сервер MongoDB, и проверять их.
Это очень удобно для целей тестирования, поскольку это приложение позволяет вам видеть, какие базы данных, коллекции и документы были созданы.
Адрес сервера и пароль
До конца этой статьи мой сервер базы данных MongoDB будет работать по адресу mongodb://localhost:27017
.
Поскольку база данных предназначена только для изучения и тестирования идей в Golang, я не указал имя пользователя и пароль на сервере базы данных.
Если вы используете его для производственного кода, вам необходимо защитить свой сервер MongoDB.
Поэтому при использовании сервера MongoDB для производства адрес будет выглядеть иначе, включая имя пользователя и пароль. Смотрите документацию здесь.
Установка пакетов Golang для MongoDB
Чтобы использовать MongoDB с Golang, вам, вероятно, потребуется выполнить следующие команды в своем терминале:
go mod init
go get go.mongodb.org/mongo-driver/mongo
go get go.mongodb.org/mongo-driver/bson
BSON используется для сериализации данных.
В приведенных выше командах вы можете видеть, что мы также устанавливаем BSON. BSON — это формат сериализации, аналогичный JSON.
Из спецификации BSON:
BSON, сокращение от Binary JSON, представляет собой двоично-кодированную сериализацию JSON-подобных документов. Как и JSON, BSON поддерживает встраивание документов и массивов в другие документы и массивы. BSON также содержит расширения, позволяющие представлять типы данных, не входящие в спецификацию JSON.
Остальные характеристики можно прочитать здесь.
Мы будем использовать его с MongoDB для сохранения данных документов и создания фильтров при обращении к документам в запросах и т.п.
Подключение и отключение сервера MongoDB
Начнем с подключения к серверу MongoDB.
Мы можем создавать множество различных форм контекстов. Для этого примера я создам максимально простой контекст, а именно context.TODO()
. Подробнее о контекстах можно прочитать здесь.
Как упоминалось выше, мой сервер базы данных расположен по адресу: mongodb://localhost:27017
.
package main
import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func main() {
ctx := context.TODO()
opts := options.Client().ApplyURI("mongodb://localhost:27017")
client, err := mongo.Connect(ctx, opts)
if err != nil {
panic(err)
}
defer client.Disconnect(ctx)
fmt.Printf("%T\n", client)
}
Обратите внимание, что мы откладываем отключение клиента от базы данных в строке 17. Это означает, что отключение произойдет после последнего оператора внутри функции, в которую он помещен — в данном случае это main()
.
Go все равно выполнит отложенный вызов функции Disconnect()
, если после оператора возникнут какие-либо ошибки.
Проверка связи с сервером MongoDB
Если я изменю порт MongoDB, например 20000
, на неправильный номер порта, клиент MongoDB не выдаст ошибки.
Поэтому, чтобы убедиться, что у нас есть правильное соединение, мы также можем использовать функцию Ping()
.
package main
import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
)
func main() {
ctx := context.TODO()
opts := options.Client().ApplyURI("mongodb://localhost:20000")
client, err := mongo.Connect(ctx, opts)
if err != nil {
panic(err)
}
defer client.Disconnect(ctx)
fmt.Printf("%T\n", client)
if err := client.Ping(ctx, readpref.Primary()); err != nil {
panic(err)
}
}
Приведенный выше код выдаст ошибку, так как мой сервер MongoDB работает не на 20000
порту, а на 27017
.
Если я изменю порт на правильный номер порта, оператор Ping()
не выдаст ошибку, и программа завершится успешно.
Список текущих баз данных на сервере MongoDB
Можно перечислить базы данных, присутствующие на вашем сервере MongoDB. Если вы только что установили сервер, вероятно, будет только несколько «административных» баз данных.
package main
import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func main() {
ctx := context.TODO()
opts := options.Client().ApplyURI("mongodb://localhost:27017")
client, err := mongo.Connect(ctx, opts)
if err != nil {
panic(err)
}
defer client.Disconnect(ctx)
fmt.Printf("%T\n", client)
dbNames, err := client.ListDatabaseNames(ctx, bson.M{})
if err != nil {
panic(err)
}
fmt.Println(dbNames)
}
Как только вы добавите больше баз данных, вы увидите их в списке, напечатанном оператором в строке 25.
Создать коллекцию базы данных
Небольшое примечание: коллекция чаще называется таблицей базы данных на других серверах баз данных.
Давайте сначала создадим базу данных на нашем сервере MongoDB с именем test
, а затем добавим в эту базу коллекцию с именем example
.
package main
import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func main() {
ctx := context.TODO()
opts := options.Client().ApplyURI("mongodb://localhost:27017")
client, err := mongo.Connect(ctx, opts)
if err != nil {
panic(err)
}
defer client.Disconnect(ctx)
fmt.Printf("%T\n", client)
testDB := client.Database("test")
fmt.Printf("%T\n", testDB)
exampleCollection := testDB.Collection("example")
defer exampleCollection.Drop(ctx)
fmt.Printf("%T\n", exampleCollection)
}
Поскольку это всего лишь пример, и мы не хотим сохранять базу данных после запуска программы, мы также отбросим (удалим) коллекцию. Итак, в строке 25 мы отложим вызов функции Drop()
, чтобы он выполнялся после всех остальных операторов функции main()
.
Очевидно, что если вы хотите сохранить коллекцию базы данных после запуска программы, вы должны удалить этот оператор Drop()
.
Добавить документы в коллекцию
Здесь мы добавляем документы в только что созданную коллекцию.
Каждый вставляемый документ содержит три поля: someString
, someInteger
, и someStringSlice
.
example := bson.D{
{"someString", "Example String"},
{"someInteger", 12},
{"someStringSlice", []string{"Example 1", "Example 2", "Example 3"}},
}
r, err := exampleCollection.InsertOne(ctx, example)
if err != nil {
panic(err)
}
fmt.Println(r.InsertedID)
examples := []interface{}{
bson.D{
{"someString", "Second Example String"},
{"someInteger", 253},
{"someStringSlice", []string{"Example 15", "Example 42", "Example 83", "Example 5"}},
},
bson.D{
{"someString", "Another Example String"},
{"someInteger", 54},
{"someStringSlice", []string{"Example 21", "Example 53"}},
},
}
rs, err := exampleCollection.InsertMany(ctx, examples)
if err != nil {
panic(err)
}
fmt.Println(rs.InsertedIDs)
time.Sleep(20 * time.Second)
В строках 1–5 мы создаем документ с тремя полями, как было сказано выше.
В строке 6 мы сначала вставляем этот элемент с помощью функции InsertOne()
.
В строке 10 мы можем вывести идентификатор элемента в коллекции. Для этого мы можем использовать свойство InsertedID
, возвращаемого функцией InsertOne()
.
В строках 11–22 создаются еще два документа.
Затем в строках 23 и 27 мы вставим эти два документа с помощью InsertMany()
и перечислим эти идентификаторы вместе со свойством InsertedIDs
.
В строке 29 мы также включим оператор time.Sleep()
, чтобы мы могли использовать MongoDB Compass для проверки того, что элемент был правильно вставлен в коллекцию, прежде чем коллекция будет удалена. Если вы решите сохранить коллекцию после завершения программы, нет необходимости сохранять оператор sleep.
Запрос документов из коллекции
Мы можем запросить коллекцию теперь, когда мы вставили документы в нашу коллекцию MongoDB.
c := exampleCollection.FindOne(ctx, bson.M{"_id": r.InsertedID})
var exampleResult bson.M
c.Decode(&exampleResult)
fmt.Printf("\nItem with ID: %v contains the following:\n", exampleResult["_id"])
fmt.Println("someString:", exampleResult["someString"])
fmt.Println("someInteger:", exampleResult["someInteger"])
fmt.Println("someStringSlice:", exampleResult["someStringSlice"])
filter := bson.D{{"someInteger", bson.D{{"$lt", 60}}}}
examplesGT50, err := exampleCollection.Find(ctx, filter)
if err != nil {
panic(err)
}
var examplesResult []bson.M
if err = examplesGT50.All(ctx, &examplesResult); err != nil {
panic(err)
}
for _, e := range examplesResult {
fmt.Printf("\nItem with ID: %v contains the following:\n", e["_id"])
fmt.Println("someString:", e["someString"])
fmt.Println("someInteger:", e["someInteger"])
fmt.Println("someStringSlice:", e["someStringSlice"])
}
time.Sleep(20 * time.Second)
В строке 1 мы будем использовать FindOne()
для поиска одного документа в коллекции. В этом примере идентификатор этого документа будет соответствовать идентификатору первого документа, который мы вставили в коллекцию.
В строке 2 мы создаем карту BSON exampleResult
, предназначенную для хранения декодированного результата запроса. Этот декодированный результат является документом. Используя карту BSON, будет легко получить значения полей.
В строке 3 результат запроса декодируется с использованием Decode()
в exampleResult
.
В строках 5–8 выводим идентификатор и три поля документа.
Затем в строке 10 создается фильтр, фильтрующий документы по их полю someInteger
. Это поле должно быть больше 60.
В строке 11 мы найдем несколько документов с этим фильтром.
В строке 15 создается срез вызванных карт BSON examplesResult
для хранения результирующих документов.
В строке 16 функция All()
используется вместо Decode()
для извлечения документов в examplesResult
.
Впоследствии в строках 20–25 будут распечатаны поля документов, содержащихся в examplesResult
.
Обновление документов коллекции
Здесь я расскажу о трех способах обновления ваших документов.
rUpdt, err := exampleCollection.UpdateByID(
ctx,
r.InsertedID,
bson.D{
{"$set", bson.M{"someInteger": 201}},
},
)
if err != nil {
panic(err)
}
fmt.Println("Number of items updated:", rUpdt.ModifiedCount)
rUpdt, err = exampleCollection.UpdateOne(
ctx,
bson.M{"_id": r.InsertedID},
bson.D{
{"$set", bson.M{"someString": "The Updated String"}},
},
)
if err != nil {
panic(err)
}
fmt.Println("Number of items updated:", rUpdt.ModifiedCount)
rUpdt2, err := exampleCollection.UpdateMany(
ctx,
bson.D{{"someInteger", bson.D{{"$gt", 60}}}},
bson.D{
{"$set", bson.M{"someInteger": 60}},
},
)
if err != nil {
panic(err)
}
fmt.Println("Number of items updated:", rUpdt2.ModifiedCount)
В строках 1–7 мы обновляем один документ с идентификатором r.InsertedID
(это идентификатор первого документа, который мы вставили в коллекцию). В этом документе поле someInteger
установлено в 201
. Для обновления документа по идентификатору мы используем функцию с именем UpdateByID()
.
В строке 11 мы печатаем, сколько документов было обновлено. Количество обновленных документов содержится в результате и извлекается с помощью ModifiedCount
. В данном случае должно быть 1
.
В строках 14–20 мы обновляем один документ. Для обновления одного документа мы используем функцию с именем UpdateOne()
. И снова этот документ имеет идентификатор r.InsertedID
, но он адресован иначе, чем раньше. На этот раз поле someString
установлено в "The Updated String"
.
На самом деле, вместо использования идентификатора документа вы можете использовать другой способ поиска документа для обновления. Это может быть документ, который выполняет определенное свойство. Например, это может быть документ, someInteger
равный 201
.
В строках 27–33 мы видим эту идею. Здесь мы обновляем все документы, которые выполняют определенное свойство. Это свойство someInteger
должно быть выше 60
. Все документы, удовлетворяющие этому требованию, обновляются таким образом, что для их поля someInteger
устанавливается значение 60
.
Как вы можете видеть в приведенном выше примере, при обновлении вы обычно обновляете только одно или несколько полей документов.
С другой стороны, если вы хотите обновить весь документ, вам, вероятно, будет лучше использовать эту функцию ReplaceOne()
. Подробнее об этой функции можно прочитать здесь.
Удаление документов коллекции
И последнее, но не менее важное: вот способ удалить документ.
rDel, err := exampleCollection.DeleteOne(ctx, bson.M{"_id": r.InsertedID})
if err != nil {
panic(err)
}
fmt.Println("Number of items deleted:", rDel.DeletedCount)
time.Sleep(20 * time.Second)
Здесь, в строке 1, мы удалим первый документ, вставленный в нашу коллекцию с помощью функции DeleteOne()
.
В строке 6 мы выведем, сколько документов было удалено. Это значение извлекается из результата DeleteOne()
использования DeletedCount
. Это аналогично свойству ModifiedCount
при обновлении документов.
Пакет MongoDB также содержит функцию DeleteMany()
, вызываемую для одновременного удаления множества документов. Подробнее об этом можно прочитать здесь.
Полный код…
Ниже вы можете найти весь код для этой статьи. Все приведенные выше фрагменты кода включены.
package main
import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
"time"
)
func main() {
ctx := context.TODO()
opts := options.Client().ApplyURI("mongodb://localhost:27017")
client, err := mongo.Connect(ctx, opts)
if err != nil {
panic(err)
}
defer client.Disconnect(ctx)
fmt.Printf("%T\n", client)
if err := client.Ping(ctx, readpref.Primary()); err != nil {
panic(err)
}
dbNames, err := client.ListDatabaseNames(ctx, bson.M{})
if err != nil {
panic(err)
}
fmt.Println(dbNames)
testDB := client.Database("test")
fmt.Printf("%T\n", testDB)
exampleCollection := testDB.Collection("example")
defer exampleCollection.Drop(ctx)
fmt.Printf("%T\n", exampleCollection)
example := bson.D{
{"someString", "Example String"},
{"someInteger", 12},
{"someStringSlice", []string{"Example 1", "Example 2", "Example 3"}},
}
r, err := exampleCollection.InsertOne(ctx, example)
if err != nil {
panic(err)
}
fmt.Println(r.InsertedID)
examples := []interface{}{
bson.D{
{"someString", "Second Example String"},
{"someInteger", 253},
{"someStringSlice", []string{"Example 15", "Example 42", "Example 83", "Example 5"}},
},
bson.D{
{"someString", "Another Example String"},
{"someInteger", 54},
{"someStringSlice", []string{"Example 21", "Example 53"}},
},
}
rs, err := exampleCollection.InsertMany(ctx, examples)
if err != nil {
panic(err)
}
fmt.Println(rs.InsertedIDs)
c := exampleCollection.FindOne(ctx, bson.M{"_id": r.InsertedID})
var exampleResult bson.M
c.Decode(&exampleResult)
fmt.Printf("\nItem with ID: %v contains the following:\n", exampleResult["_id"])
fmt.Println("someString:", exampleResult["someString"])
fmt.Println("someInteger:", exampleResult["someInteger"])
fmt.Println("someStringSlice:", exampleResult["someStringSlice"])
filter := bson.D{{"someInteger", bson.D{{"$lt", 60}}}}
examplesGT50, err := exampleCollection.Find(ctx, filter)
if err != nil {
panic(err)
}
var examplesResult []bson.M
if err = examplesGT50.All(ctx, &examplesResult); err != nil {
panic(err)
}
for _, e := range examplesResult {
fmt.Printf("\nItem with ID: %v contains the following:\n", e["_id"])
fmt.Println("someString:", e["someString"])
fmt.Println("someInteger:", e["someInteger"])
fmt.Println("someStringSlice:", e["someStringSlice"])
}
rUpdt, err := exampleCollection.UpdateByID(
ctx,
r.InsertedID,
bson.D{
{"$set", bson.M{"someInteger": 201}},
},
)
if err != nil {
panic(err)
}
fmt.Println("Number of items updated:", rUpdt.ModifiedCount)
c2 := exampleCollection.FindOne(ctx, bson.M{"_id": r.InsertedID})
var exampleResult2 bson.M
c2.Decode(&exampleResult2)
fmt.Printf("\nItem with ID: %v contains the following:\n", exampleResult2["_id"])
fmt.Println("someString:", exampleResult2["someString"])
fmt.Println("someInteger:", exampleResult2["someInteger"])
fmt.Println("someStringSlice:", exampleResult2["someStringSlice"])
rUpdt, err = exampleCollection.UpdateOne(
ctx,
bson.M{"_id": r.InsertedID},
bson.D{
{"$set", bson.M{"someString": "The Updated String"}},
},
)
if err != nil {
panic(err)
}
fmt.Println("Number of items updated:", rUpdt.ModifiedCount)
c3 := exampleCollection.FindOne(ctx, bson.M{"_id": r.InsertedID})
var exampleResult3 bson.M
c3.Decode(&exampleResult3)
fmt.Printf("\nItem with ID: %v contains the following:\n", exampleResult3["_id"])
fmt.Println("someString:", exampleResult3["someString"])
fmt.Println("someInteger:", exampleResult3["someInteger"])
fmt.Println("someStringSlice:", exampleResult3["someStringSlice"])
rUpdt2, err := exampleCollection.UpdateMany(
ctx,
bson.D{{"someInteger", bson.D{{"$gt", 60}}}},
bson.D{
{"$set", bson.M{"someInteger": 60}},
},
)
if err != nil {
panic(err)
}
fmt.Println("Number of items updated:", rUpdt2.ModifiedCount)
examplesAll, err := exampleCollection.Find(ctx, bson.M{})
if err != nil {
panic(err)
}
var examplesResult2 []bson.M
if err = examplesAll.All(ctx, &examplesResult2); err != nil {
panic(err)
}
for _, e := range examplesResult2 {
fmt.Printf("\nItem with ID: %v contains the following:\n", e["_id"])
fmt.Println("someString:", e["someString"])
fmt.Println("someInteger:", e["someInteger"])
fmt.Println("someStringSlice:", e["someStringSlice"])
}
rDel, err := exampleCollection.DeleteOne(ctx, bson.M{"_id": r.InsertedID})
if err != nil {
panic(err)
}
fmt.Println("Number of items deleted:", rDel.DeletedCount)
time.Sleep(20 * time.Second)
}