Подробное руководство по MongoDB, Mongoose

Nov 5, 2018

Введение

MongoDB состоит из БД, которые состоят из коллекций. Коллекции, в свою очередь, состоят из документов. Каждый документ состоит из полей.
www.mongodb.com

MongoDB клиенты

Конфиг mongodb для windows

В папке bin создайте файл и назовите его mongodb.config.

Указываем в этом файле путь, где будем хранить БД, например, для windows:

dbpath=c:\mongodb\data

Запуск

  • mongod - это сервер баз данных MongoDB. Он обрабатывает запросы, управляет форматом данных и выполняет различные операции в фоновом режиме по управлению БД. Командная строка отобразит нам ряд служебной информации, например, что сервер запускается на localhost на порту 27017.
  • mongo - клиентская консоль для взаимодействия с базами данных.
  • mongodump - утилита создания бэкапа БД.
  • mongorestore - позволяет записывать данные из дампа, созданного mongodump, в новую или существующую БД.

Стартуем mongod (сервер):

mongod --config c:\mongodb\bin\mongodb.config
// не забудьте заранее создать папку data

Подключаемся к запущенному серверу

Команда mongo позволяет подключиться к запущенному серверу (стартуем mongo оболочку/shell).

mongo

Работаем с БД

Выводим все БД в mongo:

show dbs

Переходим (и одновременно создаем) к нужной БД

use name_bd

Команды db возвращает имя БД, внутри которой мы сейчас находимся:

db
//test

Коллекции

Показать все коллекции в БД

show collections

Метод find()

Показать весь контент нужной коллекции:

db.collection_name.find()

Пример:

db.band.find()
db.band.find().pretty()

Метод pretty выводит результат в удобном для чтения виде.

Метод count()

Метод count выводит количество документов в коллекции:

db.band.count()

Метод remove()

Метод remove используется, чтобы удалить документ из коллекции (или всю коллекцию).

db.unicorns.remove({name: "Leto"})

Метод insert()

Заносим данные в коллекцию band (создаем тем самым коллекцию band, если ее нет):

db.band.insert({name: 'Queen', bid: '3'})

Добавим составы груп в коллекцию band:

db.band.update({bid: '1'}, {$set: {members: [
    {name: "Jimmy Page", id: "1"},
    {name: "robert Plant", id: "2"},
    {name: "John Bonham", id: "3"},
    {name: "John Paul Jones", id: "4"}]}})

db.band.update({bid: '2'}, {$set: {members: [
    {name: "Syd Barret", id: "5"},
    {name: "Roger Waters", id: "6"},
    {name: "Nick Mason", id: "7"},
    {name: "Richard Wright", id: "8"},
    {name: "David Gilmour", id: "9"}]}})

Мы можем добавлять данные, не декларируя их предварительно: свойство members. Отсутствует схема: легко добавили массив объектов.

Мы не обязаны создавать коллекции явно. Мы просто можем вставить документ в новую коллекцию. Чтобы это сделать, используйте команду insert, передав ей вставляемый документ:

db.unicorns.insert({name: 'Aurora', gender: 'f', weight: 450})

Модификация данных

Оператор $set

Оператор $set заставляет команду update модифицировать лишь те ключи, которые ему переданы (см. пример выше).

Оператор $unset

Оператор $unset удаляет указанный ключ

db.collection.update({id: 2}, {$unset: {myKey: 1}});
db.example.update({}, {$unset: {words:1}}, false, true);

Оператор $inc

Оператор $inc увеличивает значение поля на указанную величину

db.collection.update({id: 2}, {$inc: {myCounter: 111}});
db.collection.update( {"players.playerName":"Joe"}, { $inc : { "players.$.playerScore" : 1 } }

Оператор $rename

Оператор $rename позволяет переименовать поля

db.collection.update({id: 2}, {$rename: {"old_name": "new_name"}});
db.band.update({bid: "1"},{$rename:{"members":"members_new"}});

Индексы

Индексация поддерживает эффективное выполнение запросов. Без индексов MongoDB необходимо сканировать каждый документ коллекции для выбора тех документов, которые соответствуют запросу. Данный процесс крайне неэффективен и требует обработки большого количества данных.

Индексы MongoDB – это специальные структуры данных, которые хранят небольшие части данных в форме, которая легко распознаётся. Они хранят значение определённого поля или набора полей, упорядоченных по значению поля, указанному в индексе.

Построим индекс по ключу bid:

db.band.ensureIndex({bid: 1}) // deprecated
 db.users.createIndex({"name" : 1}) // actual

ensureIndex устарел, начиная с версии 3.0, в данный момент является псевдонимом для db.collection.createIndex().

Полезные материалы: на metanit.com/nosql/mongodb - Работа с индексами,
proselyte.net/tutorials/mongodb/indexing - Индексация в MongoDB

Схемы и модели

Схемы определяют структуру документов внутри коллекции, а модели используются для создания копий данных, хранящихся в документах.

Основы MongoDB

  • 1    Внутри MongoDB может быть ноль или более баз данных.
  • 2    База данных может иметь ноль или более «коллекций» (коллекция практически тоже что и таблица).
  • 3    Коллекции состоят из нуля или более «документов». Опять же, документ можно рассматривать как «строку».
  • 4    Документ состоит из одного или более «полей», которые — как можно догадаться — подобны «колонкам».
  • 5    «Индексы» в MongoDB почти идентичны таковым в реляционных базах данных.
  • 6    Важно понимать, что когда мы запрашиваем у MongoDB какие-либо данные, то она возвращает курсор, с которыми мы можем делать все что угодно.

Отличия MongoDB от реляционных БД

Основное различие в том, что реляционные базы данных определяют «колонки» на уровне «таблицы», в то время как документ-ориентированные базы данных определяют «поля» (в реляционных "колонки") на уровне «документа» (в релационных "запись").

В конечном счёте дело в том, что коллекция не содержит информации о структуре содержащихся в ней данных. Информацию о полях содержит каждый отдельный документ.

Селекторы запросов

Селектор запросов MongoDB (это JSON-объект) аналогичен предложению where SQL-запроса. Как таковой он используется для поиска, подсчёта, обновления и удаления документов из коллекций.

Селектор — это JSON-объект, в простейшем случае это может быть даже {}, что означает выборку всех документов (аналогичным образом работает null). Если нам нужно выбрать всех единорогов (англ. «unicorns») женского рода, можно воспользоваться селектором {gender:'f'}.

{поле: значение} используется для поиска всех документов, у которых есть 'поле' и у него есть 'значение'.

{поле1: значение1, поле2: значение2} работает как логическое И.

Оператор $lt, $lte, $gt, $gte, $ne

Специальные операторы $lt, $lte, $gt, $gte и $ne используются для выражения операций «меньше», «меньше или равно», «больше», «больше или равно», и «не равно».

Пример использовани селекторов с командой find (но также селекторы могут быть использованы с remove, count, update):

Например, чтобы получить всех самцов единорога, весящих более 700 фунтов, мы можем написать:

db.unicorns.find({gender: 'm', weight: {$gt: 700}})

Оператор $exists

Оператор $exists используется для проверки наличия или отсутствия поля, например:

db.unicorns.find({vampires: {$exists: false}})

Оператор $or

Оператор $or используется как ИЛИ

db.unicorns.find({gender: 'f', $or: [{loves: 'apple'}, {loves: 'orange'}, {weight: {$lt: 500}}]})
//... или любят яблоки, или любят апельсины, или весят менее 500 фунтов

Отметьте

db.unicorns.insert({name: 'Leia', dob: new Date(2001, 9, 8, 14, 53), loves: ['apple', 'watermelon'],
    weight: 601, gender: 'f', vampires: 33});

Поле loves это массив. MongoDB поддерживает массивы как объекты первого класса. Самое интересное это та простота, с которой делается выборка по значению массива: {loves: 'watermelon'} вернёт нам все документы, у которых watermelon является одним из значений поля loves.

Оператор $where

Оператор $where (в след. разделах)

Самый гибкий оператор — $where, позволяющий нам передавать JavaScript для его выполнения на сервере.

Оператор ObjectId

ObjectId, сгенерированный MongoDB для поля _id, подставляется в селектор следующим образом:

db.unicorns.find({_id: ObjectId("TheObjectId")})

update

В простейшей форме, update принимает 2 аргумента: селектор для выборки и то, чем обновить соответствующее поле. Второй параметр используется для полной замены оригинала:

db.unicorns.update({name: 'Roooooodles'}, {weight: 590})

По умолчанию, update обновляет лишь первый найденный документ

Модификатор $set

Модификатор $set обновляет конкретные поля, а не весь документ.

Если вам нужно всего лишь изменить пару полей, лучше всего использовать модификатор $set:

db.unicorns.update({weight: 590},
    {$set: {name: 'Roooooodles', dob: new Date(1979, 7, 18, 18, 44), loves: ['apple'], gender: 'm', vampires: 99}})

db.unicorns.update({name: 'Roooooodles'}, {$set: {weight: 590}})

Не забывайте использовать модификатор $set, если вам нужно обновить лишь некоторые поля.

модификатор $inc

Модификатор $inc - увеличить или уменьшить значение поля. Модификатор воздействуют непосредственно на поля, а не на весь документ.

db.unicorns.update({name: 'Pilot'}, {$inc: {vampires: -2}})

Модификатор $push

Модификатор $push - позволяет добавить данные в массив. Модификатор воздействуют непосредственно на поля, а не на весь документ.

db.unicorns.update({name: 'Aurora'}, {$push: {loves: 'sugar'}})

Разрешаем вставку при обновлении (3-й параметр)

Обновление/вставка: обновляет документ, если он найден, или создаёт новый — если не найден. Чтобы разрешить вставку при обновлении (если элемент не будет найден), установите третий параметр в true.

db.hits.update({page: 'unicorns'}, {$inc: {hits: 1}});
db.hits.find();

Вставки и обновления не будет, так как 3-й параметр опущен, а документа с {page: 'unicorns'} отсутствует в коллекции.

db.hits.update({page: 'unicorns'}, {$inc: {hits: 1}}, true);
db.hits.find();

Поскольку документы с полем page, равным unicorns, не существуют, то будет создан новый документ. Если выполнить это вторично, существующий документ будет обновлён, и поле hits увеличится до 2.

Одновременно создастся коллекция hits, если она отсутствует.

Множественные обновления (4-й параметр)

Чтобы обновить множество документов нужно установить четвертый параметр в true:

db.unicorns.update({}, {$set: {vaccinated: true }}, false, true);

Этим мы обновили все поля добавив везде поле vaccinated со значением true

find (курсор)

Курсор базы данных - это объект БД, который позволяет приложениям работать с записями "по-одной", а не с множеством сразу. То есть курсор (как мы помним это объект), который позволяет передвигаться по выборке (назад на одно, вперед на одну, в конец/начало) при помощи своих методов.

Как уже упоминалось, результатом find является курсор. Второй необязательный параметр у find это список полей, которые мы хотим получить.

db.unicorns.find(null, {name: 1});
cursor = db.unicorns.find(null, {name: 1});

_id по умолчанию возвращается всегда. Но мы можем исключить_id следующим образом: {name:1, _id: 0}.

Получаем все поля, кроме поля name:

db.unicorns.find({}, {name: 0})

Как уже упоминалось, результатом find является курсор. Поэтому мы можем присоединить к нему ряд методов:

Сортировка (метод sort)

Синтаксис метода sort: мы указываем поля, по которым надо сортировать, используя 1 для сортировки по возрастанию и -1 для сортировки по убыванию. Например:

db.unicorns.find().sort({weight: -1})
// по убыванию 999,998,997 ...
db.unicorns.find({}, {name: true}).sort({name: -1})

Но для сортировки большого объема данных в MongoDB необходимо использовать индексы.

Метод limit()

db.unicorns.find({}, {name: true}).sort({name: -1}).limit(3)

Метод skip()

Метод skip() позволяет пропустить определенное количество записей.

db.unicorns.find({}, {name: true}).sort({name: -1}).limit(3).skip(1)

Обратите внимание как мы соединяем методы в цепочки.

Моделирование данных

MongoDB не поддерживает JOIN. По существу мы должны делать второй запрос, чтобы найти связанные данные.

p.s: Для создания нового ObjectID используется следующий код: NewObjectId = ObjectId()

Моделируем 'один-ко-многим' или 'многие-ко-многим'

Когда требуется смоделировать отношения «один-ко-многим» или «многие-ко-многим» можно использовать массивы ( в MongoDB массивы это объекты первого класса).

db.employees.insert({_id: ObjectId("4d85c7039ab0fd70a117d733"), name: 'Siona',
manager: [ObjectId("4d85c7039ab0fd70a117d730"), ObjectId("4d85c7039ab0fd70a117d732")] })

При этом следующий find сработает:

db.employees.find({manager: ObjectId("4d85c7039ab0fd70a117d730")})

Массивы значений намного удобнее в использовании, нежели таблицы связи «многие-ко-многим»

Вложенные документы

MongoDB поддерживает вложенные документы:

db.employees.insert({_id: ObjectId("4d85c7039ab0fd70a117d734"),
name: 'Ghanima', family: {mother: 'Chani', father: 'Paul', brother: ObjectId("4d85c7039ab0fd70a117d730")}})

Вложенные документы можно запрашивать с помощью точечной нотации:

db.employees.find({'family.mother': 'Chani'})

Денормализация

Традиционный путь ассоциировать пользователя с его постом — это колонка userid в таблице posts. С такой моделью нельзя отобразить список постов без дополнительного извлечения данных (JOIN) из таблицы пользователей. Возможное решение — хранить имя пользователя (name) вместе с userid для каждого поста.

Команды (выжимка)

db.version()
//показывает номер версии сервера
db.getCollectionNames()
//получить список коллекций внутри нашей БД
db.unicorns.find()
//вернет список документов (записей)
db.unicorns.remove()
//поскольку мы не передали селектора, произойдёт удаление всех документов
//Например, чтобы получить всех самцов единорога, весящих более 700 фунтов, мы можем написать:
db.unicorns.find({gender: 'm', weight: {$gt: 700}})
//Оператор $exists используется для проверки наличия или отсутствия поля, например:
db.unicorns.find({vampires: {$exists: false}})
//Оператор $or используется как ИЛИ
db.unicorns.find({gender: 'f', $or: [{loves: 'apple'}, {loves: 'orange'}, {weight: {$lt: 500}}]})
//update принимает 2 аргумента: селектор (where) для выборки и то, чем обновить соответствующее поле.
//Второй параметр используется для полной замены оригинала
db.unicorns.update({name: 'Roooooodles'}, {weight: 590})
//Модификатор $set обновляет конкретные поля, а не весь документ
db.unicorns.update({name: 'Roooooodles'}, {$set: {weight: 590}})
//модификатор $inc - увеличить или уменьшить значение поля
db.unicorns.update({name: 'Pilot'}, {$inc: {vampires: -2}})
//модификатор $push - позволяет добавить данные в массив
db.unicorns.update({name: 'Aurora'}, {$push: {loves: 'sugar'}})
//Обновление/вставка обновляет документ, если он найден, или создаёт новый — если не найден.
//Чтобы разрешить вставку при обновлении, установите третий параметр в true (ниже мы создаем коллекцию hits, если ее нет)
db.hits.update({page: 'unicorns'}, {$inc: {hits: 1}}, true);
//если установить 4-й параметр в true, то обновятся все документы
db.unicorns.update({}, {$set: {vaccinated: true }}, false, true);
//Второй необязательный параметр у find указывает на список полей, которые мы хотим получить.
db.unicorns.find(null, { name:true })
//получаем все поля, кроме поля name:
db.unicorns.find({}, {name: 0})
//сортировка по убыванию
db.unicorns.find().sort({weight: -1})
// сортируем по весу, но получаем 2 и 3 по весу единорога, пропуская 1-го
db.unicorns.find().sort({weight: -1}).limit(2).skip(1)
//подсчитать кол-во единорогов на счету которых более 60 вампиров
db.unicorns.count({vampires: {$gt: 50}})
//Когда требуется смоделировать отношения «один-ко-многим» или «многие-ко-многим» можно использовать массивы
db.employees.insert({_id: ObjectId("4d85c7039ab0fd70a117d733"), name: 'Siona',
manager: [ObjectId("4d85c7039ab0fd70a117d730"), ObjectId("4d85c7039ab0fd70a117d732")] })
// Ищем значение в массиве manager
db.employees.find({manager: ObjectId("4d85c7039ab0fd70a117d730")})
//MongoDB поддерживает вложенные документы:
db.employees.insert({_id: ObjectId("4d85c7039ab0fd70a117d734"), name: 'Ghanima',
family: {mother: 'Chani', father: 'Paul', brother: ObjectId("4d85c7039ab0fd70a117d730")}})
//Вложенные документы можно запрашивать с помощью точечной нотации:
db.employees.find({'family.mother': 'Chani'})
// версия > 3.2
db.employees.updateOne(…);
db.employees.updateMany (…);

operator update (документация)

// другие команды для работы с коллекциями:

insertOne
insertMany

    // получаем кол-во документов в коллекции
db.cats.count()

// модификаторы:
    // длина массива равен 3
{$size: 3}
    // выборка по типу
{$type: number}

Подключение MongoDB в Node.js

// Retrieve
var MongoClient = require('mongodb').MongoClient;

// Connect to the db
MongoClient.connect("mongodb://localhost:27017/exampleDb", function(err, db) {
  if(!err) {
    console.log("We are connected");
  }
});

соединение с БД mongo DB

Mongoose

ODM – Object-Document Mapper (объектно-документное отображение). У MongoDB нет жесткой структуры, а вот Mongoose позволяет нам ввести понятие схемы.

Установка и подключение (работаем через mongoose)

//install
$ npm install mongoose
//use
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');

var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
    // connect
});

Установка и подключение (работаем через нативный клиент - MongoClient)

var MongoClient = require("mongodb").MongoClient;
mongoClient.connect("mongodb://localhost:27017/test", function(err, db){
    if(err){
        return console.log(err);
    }

    // работаем с БД

    db.close();
});

Схема

Схема в Mongoose определяет метаданные модели - ее свойства, типы данных и ряд другой информации. mongoosejs.com/docs/guide.html

var mongoose = require('mongoose');
var Schema = mongoose.Schema;


// внутри перечисляем наши поля
var blogSchema = new Schema({
    title:  String,
    author: String,
    body:   String,
    comments: [{ body: String, date: Date }],
    date: {
        type: Date,
        default: Date.now,
        required: [true, 'Укажите дату']
    },
    hidden: Boolean,
    meta: {
        votes: Number,
        favs:  Number
    }
});


var Blog = mongoose.model('Blog', blogSchema);
// ready to go!

Типы схем

mongoosejs.com/docs/schematypes.html

Модель

Модели (http://mongoosejs.com/docs/models.html) - это конструкторы, составленные из определения нашей схемы. Экземпляры модели представляют собой документы, которые могут быть сохранены и извлечены из нашей БД.

var schema = new mongoose.Schema({ name: 'string', size: 'string' });
var Tank = mongoose.model('Tank', schema);

Первый параметр в методе mongoose.model указывает на название модели, а второй параметр - схема.

Сохраняем объект в БД

Кроме метода save() также можно использовать метод Person.create() (см. код ниже). Первый параметр метода - сохраняемый объект.

var Person = mongoose.model('Person', yourSchema);

var subject = new Person({name: 'John'});

subject.save(function(err) {
    if (err) return handkeError(err);
})

// или

Person.create({name: 'John'}, function(err, subject) {
    if (err) return handkeError(err);
})

Поиск (find, findById, findOne)

http://mongoosejs.com/docs/queries.html

Методы Для получения данных:

find - возвращает все объекты, которые соответствуют условию фильтрации. find() в качестве первого параметра принимает условие фильтрации; второй параметр метода find() - функция обратного вызова, в которую передаются полученные из БД документы. Если в качестве условия фильтрации передаются пустые фигурные скобки ({}), то возвращаются все объекты.

findById - возвращает один объект по значению поля _id. Метод возвращает документ с определенным идентификатором.

findOne - возвращает один объект, который соответствует критерию фильтрации. В отличие от метода find, метод findOne() возвращает один объект.

var Person = mongoose.model('Person', yourSchema);

//  { 'name.last': 'Ghost' }       - условие
//  'name occupation'              - выбираем нужные поля
//  function (err, person) { ...   - обрабатываем данные в callback'е

// find each person with a last name matching 'Ghost', selecting the `name` and `occupation` fields
Person.findOne({ 'name.last': 'Ghost' }, 'name occupation', function (err, person) {
  if (err) return handleError(err);
  console.log('%s %s is a %s.', person.name.first, person.name.last, person.occupation)
  // Space Ghost is a talk show host.
})

// находим все
Person.find({ 'name.last': 'Ghost' }, 'name occupation', function (err, docs) {})
// альтернатива callback

Person.find({ 'name.last': 'Ghost' }, 'name occupation').exec(function (err, docs) {})

Редактирование (update, findByIdAndUpdate)

Каждая модель имеет метод update(), который позволяет обновить документы в БД. Первый параметр метода — условие фильтрации. Второй параметр описывает, что и как надо изменить. В функцию обратного вызова передается результат операции.

Нередко для обновления используется фильтрация по _id. И на этот случай мы можем использовать метод findByIdAndUpdate().

Первый параметр метода findByIdAndUpdate() - значения для поля _id у обновляемого документа, а второй — набор новых значений для полей объекта. В функцию обратного вызова передается обновленный документ.

// меняем Karl на Johny
var query = {name: 'Karl'};

Model.update(query, {name: 'Johny'}, options, callback); // все заменит на  {name: 'Johny'}

Model.update(query, {$set: {name: 'Johny'}}, options, callback); // меняем только поле name

Person.findOne({name: 'Johny'}, function (err, person) {
    if (err) return handleError(err);
    person.name = 'Johny';
    person.save();
})

Удаление (remove, findOneAndRemove)

Для удаления применяется метод remove(). В метод remove() передается критерий фильтрации документов на удаление. Объект, который передается в функцию обратного вызова, содержит информацию об операции удаления.

Метод findOneAndRemove() позволяет удалить один документ. В функцию обратного вызова метод findOneAndRemove() передается удаленный документ.

И частная разновидность этого метода - удаление по полю _id в виде метода findByIdAndRemove().

// удаление:
Model.remove({name: 'Johny'}, function (err, person) {
    if (err) return handleError(err);
})

Валидация в Mongoose

Mongoose имеет ряд встроенных правил валидации, которые следует указывать в схеме:

  • required - обязательно наличие значения для свойства.
  • min и max - задают минимальное и максимальное значения для числовых данных.
  • minlength и maxlength - задают минимальную и максимальную длину для строк.
  • enum - строка должна представлять одно из значений в указанном массиве строк.
  • match - строка должна соответствовать регулярному выражению.

Если мы попытаемся добавить некорректные данные в БД, то запрос на добавление вернет ошибку.

Пример:

const personScheme = new Schema({
    name: {
        type: String,
        required: true,
        minlength:3,
        maxlength:15
    },
    age: {
        type: Number,
        required: true,
        min: 1,
        max:125
    }
});

Promise и Mongoose

С MongoDB можно использовать Promise.

С помощью метода then мы можем получить данные, которые возвратил нам сервер и выполнить обработку результата.

user.save()
.then(function(data){
    console.log("Сохранен объект", data);
    mongoose.disconnect();  // отключение от базы данных
})
.catch(function (err){
    console.log(err);
    mongoose.disconnect();
});

mlab.com

mlab.com - это облачный сервис по предоставлению БД mongoDB.

  • Создаем БД
  • Добавляем пользователя
  • И подключаемся в своем приложении:
mongoose
    .connect(`mongodb://user_name:pass@ds147072.mlab.com:47072/name_bd`);

CRUD

CRUD — (create, read, update, delete — «создание, чтение,обновление, удаление») - 4 основные функции, используемые при работе базами данных.

Послесловие

Node.js, а значит и Mongo, применяется для высоконагруженных проектов, там, где необходимо передавать много информации, чаты, игры.

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