1. Visão geral
Neste tutorial, vamos entender como usar o Morphia, um Object Document Mapper (ODM) para MongoDB em Java.
No processo, também entenderemos o que é um ODM e como ele facilita o trabalho com o MongoDB.
2. O que é um ODM ?
Para os não iniciados nesta área, MongoDB é um banco de dados orientado a documentos construído para ser distribuído por natureza . Bancos de dados orientados a documentos, em termos simples, gerenciam documentos, que nada mais são do que uma maneira sem esquema de organizar dados semiestruturados . Eles se enquadram em um guarda-chuva mais amplo e vagamente definido de bancos de dados NoSQL, nomeados após seu aparente afastamento da organização tradicional dos bancos de dados SQL.
O MongoDB fornece drivers para quase todas as linguagens de programação populares, como Java . Esses drivers oferecem uma camada de abstração para trabalhar com o MongoDB para que não estejamos trabalhando diretamente com o Wire Protocol. Pense nisso como a Oracle fornecendo uma implementação do driver JDBC para seu banco de dados relacional.
No entanto, se lembrarmos de nossos dias trabalhando com JDBC diretamente, podemos apreciar o quão confuso ele pode ficar - especialmente em um paradigma orientado a objetos. Felizmente, temos frameworks de Mapeamento Relacional de Objetos (ORM) como o Hibernate para nos resgatar. Não é muito diferente para o MongoDB.
Embora certamente possamos trabalhar com o driver de baixo nível, ele requer muito mais clichê para realizar a tarefa. Aqui, temos um conceito semelhante ao ORM chamado Object Document Mapper (ODM) . O Morphia preenche exatamente esse espaço para a linguagem de programação Java e funciona em cima do driver Java para MongoDB.
3. Configurando dependências
Já vimos teoria suficiente para nos colocar em algum código. Para nossos exemplos, vamos modelar uma biblioteca de livros e ver como podemos gerenciá-la no MongoDB usando o Morphia.
Mas antes de começarmos, precisaremos configurar algumas das dependências.
3.1. MongoDB
Precisamos ter uma instância em execução do MongoDB para trabalhar. Existem várias maneiras de obter isso, e a mais simples é baixar e instalar a edição da comunidade em nossa máquina local.
Devemos deixar todas as configurações padrão como estão, incluindo a porta na qual o MongoDB é executado.
3.2. Morfia
Podemos baixar os JARs pré-construídos para Morphia do Maven Central e usá-los em nosso projeto Java.
No entanto, a maneira mais simples é usar uma ferramenta de gerenciamento de dependências como o Maven:
<dependency>
<groupId>dev.morphia.morphia</groupId>
<artifactId>core</artifactId>
<version>1.5.3</version>
</dependency>
4. Como se conectar usando o Morphia?
Agora que temos o MongoDB instalado e em execução e configuramos o Morphia em nosso projeto Java, estamos prontos para nos conectar ao MongoDB usando o Morphia.
Vamos ver como podemos fazer isso:
Morphia morphia = new Morphia();
morphia.mapPackage("com.baeldung.morphia");
Datastore datastore = morphia.createDatastore(new MongoClient(), "library");
datastore.ensureIndexes();
É mais ou menos isso! Vamos entender isso melhor. Precisamos de duas coisas para que nossas operações de mapeamento funcionem:
- Um mapeador:é responsável por mapear nossos POJOs Java para coleções do MongoDB . Em nosso snippet de código acima, Morphia é a classe responsável por isso. Observe como estamos configurando o pacote onde ele deve procurar nossos POJOs.
- Uma conexão:Esta é a conexão com um banco de dados MongoDB no qual o mapeador pode executar diferentes operações. A classe Armazenamento de dados toma como parâmetro uma instância de MongoClient (do driver Java MongoDB) e o nome do banco de dados MongoDB, retornando uma conexão ativa para trabalhar .
Então, estamos prontos para usar este Datastore e trabalhar com nossas entidades.
5. Como trabalhar com entidades?
Antes de podermos usar nosso Datastore recém-criado , precisamos definir algumas entidades de domínio com as quais trabalhar.
5.1. Entidade Simples
Vamos começar definindo um Livro simples entidade com alguns atributos:
@Entity("Books")
public class Book {
@Id
private String isbn;
private String title;
private String author;
@Property("price")
private double cost;
// constructors, getters, setters and hashCode, equals, toString implementations
}
Há algumas coisas interessantes a serem observadas aqui:
- Observe a anotação @Entidade que qualifica este POJO para mapeamento ODM por Morphia
- Morphia, por padrão, mapeia uma entidade para uma coleção no MongoDB pelo nome de sua classe, mas podemos substituir isso explicitamente (como fizemos para a entidade Book aqui)
- Morphia, por padrão, mapeia as variáveis em uma entidade para as chaves em uma coleção do MongoDB pelo nome da variável, mas novamente podemos substituir isso (como fizemos para a variável cost aqui)
- Por último, precisamos marcar uma variável na entidade para atuar como chave primária pela anotação @Id (como se estivéssemos usando ISBN para nosso livro aqui)
5.2. Entidades com Relacionamentos
No mundo real, porém, as entidades dificilmente são tão simples quanto parecem e têm relacionamentos complexos entre si. Por exemplo, nossa entidade simples Book pode ter um Editor e pode fazer referência a outros livros complementares. Como os modelamos?
O MongoDB oferece dois mecanismos para construir relacionamentos — Referenciamento e Incorporação . Como o nome sugere, com referência, o MongoDB armazena dados relacionados como um documento separado na mesma coleção ou em uma coleção diferente e apenas os referencia usando seu id.
Pelo contrário, com a incorporação, o MongoDB armazena, ou melhor, incorpora a relação dentro do próprio documento pai.
Vamos ver como podemos usá-los. Vamos começar incorporando Editor em nosso Livro :
@Embedded
private Publisher publisher;
Simples o suficiente. Agora vamos em frente e adicionar referências a outros livros:
@Reference
private List<Book> companionBooks;
É isso — Morphia fornece anotações convenientes para modelar relacionamentos conforme suportado pelo MongoDB. A escolha de referência versus incorporação, no entanto, deve ser baseada na complexidade, redundância e consistência do modelo de dados entre outras considerações.
O exercício é semelhante à normalização em bancos de dados relacionais.
Agora, estamos prontos para realizar algumas operações em Book usando Datastore .
6. Algumas Operações Básicas
Vamos ver como trabalhar com algumas das operações básicas usando o Morphia.
6.1. Salvar
Vamos começar com a mais simples das operações, criando uma instância de Book em nosso banco de dados MongoDB biblioteca :
Publisher publisher = new Publisher(new ObjectId(), "Awsome Publisher");
Book book = new Book("9781565927186", "Learning Java", "Tom Kirkman", 3.95, publisher);
Book companionBook = new Book("9789332575103", "Java Performance Companion",
"Tom Kirkman", 1.95, publisher);
book.addCompanionBooks(companionBook);
datastore.save(companionBook);
datastore.save(book);
Isso é suficiente para permitir que o Morphia crie uma coleção em nosso banco de dados MongoDB, caso ela não exista, e execute uma operação de upsert.
6.2. Consulta
Vamos ver se conseguimos consultar o livro que acabamos de criar no MongoDB:
List<Book> books = datastore.createQuery(Book.class)
.field("title")
.contains("Learning Java")
.find()
.toList();
assertEquals(1, books.size());
assertEquals(book, books.get(0));
A consulta de um documento no Morphia começa com a criação de uma consulta usando Datastore e depois adicionando filtros declarativamente, para o deleite dos apaixonados por programação funcional!
O Morphia suporta uma construção de consulta muito mais complexa com filtros e operadores. Além disso, o Morphia permite limitar, pular e ordenar os resultados na consulta.
Além disso, o Morphia nos permite usar consultas brutas escritas com o driver Java para MongoDB para maior controle, caso seja necessário.
6.3. Atualizar
Embora uma operação de salvamento possa lidar com atualizações se a chave primária corresponder, o Morphia fornece maneiras de atualizar documentos seletivamente:
Query<Book> query = datastore.createQuery(Book.class)
.field("title")
.contains("Learning Java");
UpdateOperations<Book> updates = datastore.createUpdateOperations(Book.class)
.inc("price", 1);
datastore.update(query, updates);
List<Book> books = datastore.createQuery(Book.class)
.field("title")
.contains("Learning Java")
.find()
.toList();
assertEquals(4.95, books.get(0).getCost());
Aqui, estamos construindo uma consulta e uma operação de atualização para aumentar em um o preço de todos os livros retornados pela consulta.
6.4. Excluir
Finalmente, o que foi criado deve ser excluído! Novamente, com Morphia, é bastante intuitivo:
Query<Book> query = datastore.createQuery(Book.class)
.field("title")
.contains("Learning Java");
datastore.delete(query);
List<Book> books = datastore.createQuery(Book.class)
.field("title")
.contains("Learning Java")
.find()
.toList();
assertEquals(0, books.size());
Criamos a consulta da mesma forma que antes e executamos a operação de exclusão no Datastore .
7. Uso avançado
O MongoDB tem algumas operações avançadas como agregação, indexação e muitas outras . Embora não seja possível realizar tudo isso usando o Morphia, certamente é possível conseguir um pouco disso. Para outros, infelizmente, teremos que recorrer ao driver Java para MongoDB.
Vamos nos concentrar em algumas dessas operações avançadas que podemos realizar por meio do Morphia.
7.1. Agregação
A agregação no MongoDB nos permite definir uma série de operações em um pipeline que pode operar em um conjunto de documentos e produzir saída agregada .
O Morphia tem uma API para dar suporte a esse pipeline de agregação.
Vamos supor que desejamos agregar os dados de nossa biblioteca de forma que tenhamos todos os livros agrupados por autor:
Iterator<Author> iterator = datastore.createAggregation(Book.class)
.group("author", grouping("books", push("title")))
.out(Author.class);
Então, como isso funciona? Começamos criando um pipeline de agregação usando o mesmo antigo Datastore . Temos que fornecer a entidade na qual desejamos realizar as operações de agregação, por exemplo, Livro aqui.
Em seguida, queremos agrupar documentos por “autor” e agregar seu “título” em uma chave chamada “livros”. Finalmente, estamos trabalhando com um ODM aqui. Então, temos que definir uma entidade para coletar nossos dados agregados — no nosso caso, é Autor .
Claro, temos que definir uma entidade chamada Autor com uma variável chamada books:
@Entity
public class Author {
@Id
private String name;
private List<String> books;
// other necessary getters and setters
}
Isso, é claro, apenas arranha a superfície de uma construção muito poderosa fornecida pelo MongoDB e pode ser explorada mais detalhadamente.
7.2. Projeção
A projeção no MongoDB nos permite selecionar apenas os campos que queremos buscar de documentos em nossas consultas . Caso a estrutura do documento seja complexa e pesada, isso pode ser muito útil quando precisamos de apenas alguns campos.
Vamos supor que só precisamos buscar livros com seu título em nossa consulta:
List<Book> books = datastore.createQuery(Book.class)
.field("title")
.contains("Learning Java")
.project("title", true)
.find()
.toList();
assertEquals("Learning Java", books.get(0).getTitle());
assertNull(books.get(0).getAuthor());
Aqui, como podemos ver, apenas retornamos o título em nosso resultado e não o autor e outros campos. Devemos, no entanto, ter cuidado ao usar a saída projetada ao salvar de volta no MongoDB. Isso pode resultar em perda de dados!
7.3. Indexação
Os índices desempenham um papel muito importante na otimização de consultas com bancos de dados - relacionais e muitos não relacionais.
MongoDB define índices no nível da coleção com um índice exclusivo criado na chave primária por padrão . Além disso, o MongoDB permite que índices sejam criados em qualquer campo ou subcampo dentro de um documento. Devemos optar por criar um índice em uma chave dependendo da consulta que desejamos criar.
Por exemplo, em nosso exemplo, podemos desejar criar um índice no campo “título” de Livro como muitas vezes acabamos consultando:
@Indexes({
@Index(
fields = @Field("title"),
options = @IndexOptions(name = "book_title")
)
})
public class Book {
// ...
@Property
private String title;
// ...
}
Claro, podemos passar opções de indexação adicionais para personalizar as nuances do índice que é criado. Observe que o campo deve ser anotado por @Propriedade para ser usado em um índice.
Além disso, além do índice de nível de classe, o Morphia também possui uma anotação para definir um índice de nível de campo.
7.4. Validação de esquema
Temos a opção de fornecer regras de validação de dados para uma coleção que o MongoDB pode usar ao executar uma operação de atualização ou inserção . Morphia suporta isso por meio de suas APIs.
Digamos que não queremos inserir um livro sem um preço válido. Podemos aproveitar a validação do esquema para conseguir isso:
@Validation("{ price : { $gt : 0 } }")
public class Book {
// ...
@Property("price")
private double cost;
// ...
}
Existe um rico conjunto de validações fornecido pelo MongoDB que pode ser empregado aqui.
8. ODMs alternativos do MongoDB
O Morphia não é o único ODM do MongoDB para Java disponível. Existem vários outros que podemos considerar usar em nossas aplicações. Uma discussão sobre comparação com Morphia não é possível aqui, mas é sempre útil conhecer nossas opções:
- Spring Data:fornece um modelo de programação baseado em Spring para trabalhar com o MongoDB
- MongoJack:fornece mapeamento direto de objetos JSON para MongoDB
Esta não é uma lista completa de ODMs do MongoDB para Java, mas existem algumas alternativas interessantes disponíveis!