Java para Web

Aula 10 : MongoDB e Mongoose

Aula 10 - MongoDB e Mongoose

Exemplos de aula

Tecnologias

MongoDB
MongoDB

O MongoDB é software multi-plataforma de base de dados orientado a documentos e ao modelo JSON. Sua primeira versão foi públicada em 2009.

Suas características permitem a criação de dados aninhados em hierarquias complexas e flexíveis, mas que ainda permitem a indexação e busca eficiente.

O MongoDB pode ser instalado em sua versão Comunity ou utilizado diretamente através da nuvem da Atlas.

Site do MongoDB Community Edition

Site da Atlas

Mongoose
Mongoose

A biblioteca Mongoose fornece uma interface para validação, casting e lógica de negócio com o MongoDB.

Apesar de não ser estritamente necessário, o mongoose facilita a maioria das operações habituais para implementação de um CRUD.

Para instalar o mongoose, utilize o comando:

npm install mongoose --save

Em seguida, vamos implementar a interação com o banco de dados seguindo os seguintes passos:

  • Criar uma conexão
  • Criar um esquema
  • Realizar operações

Quick Start na Documentação

Usando o Mongoose

Criando Conexão no Mongoose

O primeiro para estabelecer uma conexão entre a aplicação e o servidor mongodb, é utilizar a seguinte linha de código:

js index.js
const mongoose = require('mongoose')
mongoose.connect('mongodb://username:password@host:port/database?options...', {useNewUrlParser: true})

Onde:

  • mongodb - protocolo
  • username:password - (opcional) define usuário e senha
  • host - define endereço do servidor
  • port - define a porta do servidor (padrão: 27017)
  • database - nome da base de dados a ser utilizada (será criada caso não exista)

A opção {useNewUrlParser: true} é utilizada atualmente para efeitos de compatibilidade com versões anteriores da biblioteca.

Eventos no Mongoose

O Mongoose permite a implementação de eventos utilizando a função ``on()``:

js index.js
const mongoose = require('mongoose')
mongoose.connection.on('connected', function(){
console.log("MongoDB conectado")

Os tipos de eventos incluem:

  • connecting - Emitido quando o mongoose tenta iniciar uma conexão
  • connected - Emitido quando o mongoose realiza a conexão com sucesso
  • disconnected - Emitido quando o mongoose perde a conexão com o servidor
  • error - Emitido em caso de erro com a conexão

Mais eventos na Documentação

Schemas no Mongoose

Com a conexão estabelecida, podemos definir esquemas que serão utilizados como modelos para os dados que iremos trabalhar na aplicação:

js index.js
var mongoose = require('mongoose')

var blogSchema = new mongoose.Schema({
    title:  String,
    author: String,
    body:   String,
    comments: [{ body: String, date: Date, default: [] }],
    date: { type: Date, default: Date.now },
})

var Blog = mongoose.model('Blog', blogSchema)

Repare que um esquema geralmente segue as mesmas características do exemplo acima:

  • Deve ser criado instanciando um objeto do tipo Schema
  • Deve ser atribuído um nome de classe, como no caso Blog
  • Deve ser especificado o tipo de dado de cada atributo, atribuindo diretamente o tipo de dado (Ex.: title: String, ou usando o formato de objeto (Ex.: title: { type: String } ).

Outro comportamento padrão da biblioteca mongoose é atribuir automaticamente o nome da coleção no formato plural, no MongoDB. Caso este comportamento não seja desejado, podemos definir um nome de coleção fixo usando um parâmetro a mais no mongoose.model():

js index.js
var Blog = mongoose.model('Blog', blogSchema, 'blog')
Tipos de Dados do Schema no Mongoose

Os tipos de dados primitivos utilizados pelo mongoose são:

  • String
  • Number
  • Date
  • Buffer
  • Boolean
  • Mixed
  • ObjectId
  • Array
  • Decimal128
  • Map

Além disso, também é possivel definir dentro de um schema algumas propriedades para cada dado:

  • required - booleano ou função, define se o atributo é obrigatório
  • default - define o valor padrão para o atributo
  • validate - define uma função de validação
  • lowercase e uppercase - booleano, retorna letras modificadas do valor
  • min e max - valida valor máximo e mínimo
  • indexes : { unique : true} - define um atributo único no indice

Mais na Documentação

Inserindo com Mongoose

Para inserir um novo documento na base de dados, primeiro precisamos iniciar um novo objeto a partir do modelo:

js index.js
var Blog = mongoose.model('Blog', blogSchema)
var post = new Blog({
    title:  "Olá mundo!",
    author: "Ramon Venson",
    body:   "Seja bem vindo ao mongoose",
})

Em seguida, o objeto post pode ser salvo facilmente usando a função save():

js index.js
post.save(function(err){
    if(!err){
        console.log("Objeto salvo com sucesso!")
    }
})

Apos ser salvo no banco de dados, o objeto criado recebe o _id localmente e pode ser alterado dentro do contexto local. Ao utilizar a função save() novamente, por exemplo, é possível atualizar o objeto já salvo

js index.js
//Após realizar o primeiro save() do objeto
console.log(post._id) // Retorna seu _id
post.author = "Rogerinho do Ingá"
post.save() // Atualiza o objeto no banco
Consultando com Mongoose

Para realizar uma consulta em objetos (documentos), podemos usar:

js index.js
Blog.find({author: "Ramon Venson"}, function(err, doc){
    //Imprime todos os posts encontrados na pesquisa
    console.log(doc)
})

Para retornar todas as ocorrencias possíveis, também é possível utilizar a função find apenas com o callback:

js index.js
Blog.find(function(err, doc){
    //Imprime todos os posts 
    console.log(doc)
})
Atualizando com Mongoose

Podemos realizar a atualização de um documento usando seu _id através da função findByIdAndUpdate(). Essa função aceita atualização parcial de um documento:

js index.js
Blog.findByIdAndUpdate("5f929234943405679494e465", {title: "Novo Mundo!"}, function(err, doc){
    if(!err){
        console.log("Objeto Atualizado: " + doc)
    }
})
Deletando com Mongoose

Podemos realizar a deleção de um documento usando seu _id através da função findByIdAndDelete()

js index.js
Blog.findByIdAndDelete("5f929234943405679494e465", function(err, doc){
    if(!err){
        console.log("Objeto deletado: " + doc)
    }
})
Limit e Skip do Mongoose

Quando retornamos muitos resultados do banco, é comum realizar paginação para evitar um intenso tráfego de dados na rede e consumo de tempo computacional sem necessidade. Para essa tarefa, podemos atrelar as funções limit(n) e skip(n), que filtra o resultado automaticamente antes que seja enviado ao cliente, como neste exemplo:

js index.js
Blog.find(function(err, doc){
    //Imprime apenas 5 resultados, pulando os 2 primeiros
    console.log(doc)
}).limit(5).skip(2)
Relacionamentos no Mongoose

O mongoose permite atribuir alguns relacionamentos entre coleções. Levamos em consideração os esquemas a seguir:

js index.js
const JogoSchema = new mongoose.Schema({
    nome: String,
    desenvolvedora: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'desenvolvedoras'
    }
})

const DesenvolvedoraSchema = new mongoose.Schema({
    nome: String,
    pais: String
})

const Jogo = mongoose.model('jogos', JogoSchema)
const Desenvolvedora = mongoose.model('desenvolvedoras', DesenvolvedoraSchema)

Ao utilizar o find() para buscar um jogo, retornariamos o atributo desenvolvedora com o _id único atribuído.

js index.js
Jogo.find(function (err, result) {
    res.json(result)
})

Retorna:

js index.js
{
    "_id": "5d955978dab55639505396d6",
    "nome": "Super Mario World",
    "desenvolvedora": "5d955cc41f512317f8e3b1d6"
}

No entanto, neste caso, podemos utilizar a função populate() para trazer, automaticamente, os dados da desenvolvedora junto ao jogo.

js index.js
Jogo.find().populate('desenvolvedora').then(function(doc){
    res.json(doc)
})

Retorna:

js index.js
{
    "_id": "5d955978dab55639505396d6",
    "nome": "Super Mario World",
    "desenvolvedora": {
        "nome": "Nintendo",
        "pais": "Japão"
    }
}
Substituindo ID no Mongoose

É possível alterar o identificador único gerado pelo MongoDB na criação de um novo documento. Para isso, basta inserir no esquema o atributo _id, que pode ser de qualquer um dos tipos aceitos pelo Mongoose.

Dessa forma, ao usar a função populate(), o mongoose usará o novo identificador, que deve obedecer as seguintes restrições:

  • Precisa ser um valor único
  • Não pode ser modificado durante o ciclo de vida do documento

Exercícios Complementares

Exercício: Salvar Música

Implemente uma pequena API capaz de manipular os dados de albuns de música. A aplicação deve possuir três endpoints /album realizando as seguintes funções:

  • Um método POST que recebe um objeto de album através do corpo da mensagem (body) e salva no banco, retornando sucesso ou o erro ao usuário
  • Um método GET retorna um vetor contendo todos os albuns cadastrados
  • Um método GET que retorna apenas um álbum específico, de acordo com seu id

O schema do álbum deve conter pelo menos os seguintes elementos:

js Album.js
new mongoose.Schema({
    nome: String,
    artista: String,
    ano: Number,
    generos: Array,
    faixas: Array,
    lancamento: Date ou Number
})