Avançar para a área de conteúdo

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Todas as informações enviadas são seguras.

  • Fechar [x]

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Todas as informações enviadas são seguras.

  • Fechar [x]

Persistência de modelo de domínio com Morphia e MongoDB

Use Morphia para tornar persistente, carregar, excluir e consultar um modelo de domínio Java mapeado ao MongoDB

John D'Emic, Senior Software Engineer, IBM
ohn D'Emic
John D'Emic é Engenheiro de Software Senior em IBM Global Services e usou MongoDB em diversos contextos de desenvolvimento no ano passado. Ele é coautor ( com David Dossot) de Mule in Action (Manning Publications, 2009).

Resumo:  Morphia é uma biblioteca de mapeamento de objetos com segurança de tipos para MongoDB, um banco de dados de software livre orientado a documentos. Este artigo mostra as vantagens de mapear documentos de e para objetos e mostra como usar Morphia para esse fim. Em seguida, mostra como tornar persistente, carregar, excluir e consultar um modelo de domínio Java™ mapeado ao MongoDB.

Data:  12/Abr/2011
Nível:  Intermediário Também disponível em :   Inglês
Atividade:  1763 visualizações
Comentários:  


MongoDB é um banco de dados orientado a documentos para armazenar e recuperar documentos no estilo JavaScript Object Notation (JSON). Ampliado com recursos de indexação, replicação e sharding, MongoDB estabeleceu-se como um competidor NoSQL robusto e escalável (consulte Recursos).

Um driver Java oficial está disponível para interagir com MongoDB. O driver fornece uma implementação de Map, BasicDBObject, para representar documentos no armazenamento de dados. Embora a representação Map seja conveniente, especialmente ao serializar a partir de JSON, poder representar os documentos como uma hierarquia de classes Java também tem suas vantagens. Mapear documentos a partir de um modelo de domínio Java, por exemplo, permite forçar segurança de tipo na camada Java e ao mesmo tempo aproveitar os benefícios do desenvolvimento sem esquemas com MongoDB. E muitas estruturas Java pressupõem o uso de POJOs (plain old Java objects) ou tem maior capacidade de lidar com eles.

Morphia é um projeto Google Code com licença Apache que permite tornar persistente, recuperar, excluir e consultar POJOs armazenados como documentos no MongoDB. Morphia consegue isso fornecendo um conjunto de anotações e um wrapper em torno do driver Java do Mongo. Morphia tem conceito semelhante ao de mapeadores relacionais de objetos, tais como implementações Java Persistence API (JPA) ou Java Data Objects (JDO). Neste artigo, mostrarei como usar Morphia com um modelo de domínio Java mapeado ao MongoDB. Consulte Download para obter o código de amostra completo.

Definindo o modelo de domínio

Usarei um modelo de domínio simplificado para demonstrar a funcionalidade do Morphia. BandManager, um aplicativo da Web hipotético, fornece dados sobre bandas musicais: seus membros, distribuidor, catálogo, gênero e assim por diante. Definirei as classes Band, Song, Distributor e ContactInfo para representar este modelo de domínio, como mostra a Figura 1:


Figura 1. Classes do BandManager

O diagrama de Unified Modeling Language (UML) na Figura 1 mostra a hierarquia de classes do modelo de domínio. O retângulo à esquerda representa a classe Band. Empilhados à direita estão retângulos representando as classes ContactInfo, Distributor e Song, respectivamente. Uma seta indo de Band para ContactInfo está marcada com 1 no lado de ContactInfo, indicando uma relação de um a um entre as duas classes. Uma linha conectando Band a Distributor está marcada no lado de Band com 0..* e com 1 no lado de Distributor, indicando que Band tem apenas um Distributor e que um Distributor representa várias Bands. Por fim, uma seta indo de Band para Song está marcada no lado de Song com catalog 0..1, indicando que Band tem uma relação de um para muitos com Song e que esta relação é chamada de catalog.

Anotarei essas classes e usarei a interface Datastore do Morphia para salvá-las como documentos no MongoDB.

Anotando o modelo de domínio

A Listagem 1 mostra como a classe Band é anotada:


Listagem 1. Band.java

@Entity("bands")
public class Band {

    @Id
    ObjectId id;

    String name;

    String genre;

    @Reference
    Distributor distributor;

    @Reference("catalog")
    List<Song> songs = new ArrayList<Song>();

    @Embedded
    List<String> members = new ArrayList<String>();

    @Embedded("info")
    ContactInfo info;

A anotação @Entity é necessária. Ela declara que a classe deve ser persistente como um documento em uma coleção dedicada. O valor fornecido à anotação @Entity, bands, define o nome da coleção. Morphia, por padrão, usa o nome da classe para nomear a coleção. Se eu não incluísse o valor de bands, por exemplo, a coleção seria chamada Band no banco de dados.

Tipos de dados

MongoDB oferece suporte a um conjunto menor de tipos de dados que a linguagem Java, a saber integer, long, double e string. Morphia converte tipos de dados Java básicos (tais como float) automaticamente.

A anotação @Id instrui o Morphia sobre qual campo usar como ID do documento. Se você tentar tornar persistente um objeto cujo campo anotado @Id está vazio, o Morphia gera automaticamente um valor de ID.

Morphia tenta tornar persistente qualquer campo sem anotação que encontrar, a menos que estejam marcados com a anotação @Transient. As propriedades name e genre, por exemplo, serão salvas como cadeias de caractere no documento, com chaves de name e genre.

As propriedades distributor, songs, members e info fazem referência a outros objetos. Um objeto membro, a menos que seja anotado com @Reference, como você verá adiante, é considerado integrado. Aparecerá como um filho no documento pai na coleção. Por exemplo, o members List ficaria assim ao persistir:

"members" : [ "Jim", "Joe", "Frank", "Tom"]

A propriedade info é outro objeto integrado. Neste caso, estou configurando a anotação @Embedded explicitamente com um valor de info. Isso substitui o nome padrão do filho no documento, que de outra forma seria chamado contactInfo. Por exemplo:

"info" : { "city" : "Brooklyn", "phoneNumber" : "718-555-5555" }

@Reference e DBRefs

Por trás das cortinas, o Morphia usa um DBRef do Mongo para referenciar objetos em uma coleção diferente.

Usar a anotação @Reference indica que o objeto é uma referência a um documento em outra coleção. Quando o objeto é carregado da coleção do Mongo, o Morphia segue essas referências para desenvolver o gráfico de objeto. Por exemplo, a propriedade distributor tem este aspecto no documento persistido:

"distributor" : { "$ref" : "distributors", "$id" : ObjectId("4cf7ba6fd8d6daa68a510e8b") }

Assim como a anotação @Embedded, @Reference pode receber um valor para substituir o nome padrão. Neste caso, estou chamando a Lista de songs um catalog no documento.

Agora observe as definições de classe de Song, Distributor e ContactInfo. A Listagem 2 mostra a definição de Song:


Listagem 2. Song.java


@Entity("songs")
public class Song {

    @Id
    ObjectId id;

    String name;

A Listagem 3 mostra a definição de Distributor:


Listagem 3. Distributor.java


@Entity("distributors")
public class Distributor {

    @Id
    ObjectId id;

    String name;

    @Reference
    List<Band> bands = new ArrayList<Band>();

A Listagem 4 mostra a definição de ContactInfo:


Listagem 4. ContactInfo.java
public class ContactInfo {


    public ContactInfo() {
    }

    String city;

    String phoneNumber;

A classe ContactInfo não tem uma anotação @Entity. Isso é proposital, pois não preciso de uma coleção dedicada para ContactInfo. Instâncias sempre serão integradas em documento band.

Agora que eu defini e anotei o modelo de domínio, vou mostrar como usar o Datastore do Morphia para salvar, carregar e excluir entidades.


Usando o Datastore

Injeção de dependência (DI)

Datastore e Mongo são ambos compatíveis com DI. Você não deve ter problemas ao conectá-los a Spring ou Guice, por exemplo. Se possível, você deve configurar cada um como um singleton e compartilhá-los entre beans em colaboração.

A interface Datastore — ,um wrapper em torno do driver Java do Mongo, — é usada para gerenciar entidades no MongoDB. Como Datastore requer uma instância do Mongo para instanciação, qualquer instância do Mongo existente pode ser reutilizada, ou é possível configurar uma de maneira apropriada para o seu ambiente. Aqui está um exemplo da instanciação de um Datastore que se conecta a uma instância do MongoDB local:


Mongo mongo = new Mongo("localhost");
Datastore datastore = new Morphia().createDatastore(mongo, "bandmanager");

Em seguida, criarei uma instância de Band:

Band band = new Band();
band.setName("Love Burger");
band.getMembers().add("Jim");
band.getMembers().add("Joe");
band.getMembers().add("Frank");
band.getMembers().add("Tom");
band.setGenre("Rock");

Agora que tenho uma instância de Band, posso usar datastore para torná-la persistente:

datastore.save(band);

A band deve agora ser salva em uma coleção chamada bands no banco de dados bandmanager. Usando o cliente da interface de linha de comando do Mongo, eu posso examinar para ter certeza (as linhas são partidas neste e em outros exemplos, devido à largura de página deste artigo):

> db.bands.find();
{ "_id" : ObjectId("4cf7cbf9e4b3ae2526d72587"), "className" : 
"com.bandmanager.model.Band", "name" : "Love Burger", "genre" : "Rock", 
"members" : [ "Jim", "Joe", "Frank", "Tom" ] }

Ótimo! Está lá. Tudo está como deveria para o campo className. Morphia cria automaticamente este campo para registrar o tipo de objeto no MongoDB. É usado primariamente para determinar os tipos de objetos que não são necessariamente conhecidos no tempo de compilação (ao carregar objetos de uma coleção com tipos mistos, por exemplo). Se isso lhe incomoda, e você tem certeza de que não precisará desta funcionalidade, é possível fazer com que className não seja persistida adicionando o valornoClassnameStored à anotação @Entity:

@Entity(value="bands",noClassnameStored=true)

Agora eu carregarei Band e indicarei que é igual ao band que eu tornei persistente:

assert(band.equals(datastore.get(Band.class, band.getId())));

O método get() de Datastore permite carregar uma entidade usando seu ID. Não é preciso especificar a coleção ou definir uma cadeia de caractere de consulta para carregar o objeto. Basta dizer a Datastore qual classe você deseja carregar e qual é o seu ID. Morphia faz o resto.

Agora é hora de examinar os objetos em colaboração em um Band. Começarei definindo alguns Songs, e em seguida irei adicioná-los à instância Band que acabo de criar:

Song song1 = new Song("Stairway");
Song song2 = new Song("Free Bird");

datastore.save(song1);
datastore.save(song2);

Ao verificar a coleção de songs no Mongo, devo ver algo assim:

> db.songs.find();
{ "_id" : ObjectId("4cf7d249c25eae25028ae5be"), "className" : 
"com.bandmanager.model.Song", "name" : "Stairway" }
{ "_id" : ObjectId("4cf7d249c25eae25038ae5be"), "className" :
"com. bandmanager.model.Song", "name" : "Free Bird" }

Observe que Songs não são referenciadas em band ainda. Irei adicioná-las a band e verei o que acontece:

band.getSongs().add(song1);
band.getSongs().add(song2);

datastore.save(band);

Agora, quando consulto a coleção bands, devo ver algo assim:

{ "_id" : ObjectId("4cf7d249c25eae25018ae5be"), "name" : "Love Burger", "genre" : "Rock", 
   "catalog" : [
   {
      "$ref" : "songs",
      "$id" : ObjectId("4cf7d249c25eae25028ae5be")
   },
   {
      "$ref" : "songs",
      "$id" : ObjectId("4cf7d249c25eae25038ae5be")
   }
], "members" : [ "Jim", "Joe", "Frank", "Tom"] }

Transações

É importante lembrar que o MongoDB não oferece suporte a transações da mesma maneira que a maioria dos sistemas de gerenciamento de banco de dados relacionais. Se o aplicativo precisa coordenar vários encadeamentos gravando ou lendo em uma coleção, é preciso confiar nos recursos de serialização e simultaneidade da linguagem Java.

Observe como a coleção songs é salva em um array chamado catalog como duas DBRefs.

Uma limitação atual é que objetos referenciados precisam ser salvos antes que outros objetos possam referenciá-los. É por isso que salvei song1 e song2 antes de adicionar a band.

Agora irei excluir song2:

datastore.delete(song2);

Ao consultar a coleção songs, deve ser indicado que song2 está ausente. Mas se você examinar band, verá que a música ainda está lá. O pior é que tentar carregar a entidade band causa uma exceção:

Caused by: com.google.code.morphia.mapping.MappingException: The 
reference({ "$ref" : "songs", "$id" : "4cf7d249c25eae25038ae5be" }) could not be 
fetched for com.bandmanager.model.Band.songs

Por enquanto, para evitar este erro, é necessário remover referências a uma música antes de excluí-la manualmente.


Consultando

Carregar entidades por seus IDs não irá me levar muito longe. No fim, eu quero poder consultar o Mongo para obter as entidades que desejo.

Em vez de carregar um band por seu ID, irei consultar pelo seu nome. Faço isso criando um objeto Query e especificando um filtro para obter os resultados que eu desejo:

Query query = datastore.createQuery(Band.class).filter("name = ","Love Burger");

Eu especifico a classe que desejo consultar, Band, e um filtro para o método createQuery(). Após definir a consulta, eu posso usar o método asList() para acessar os resultados:

Band band = (Band) query.asList().get(0);

Os operadores de filtro do Morphia são mapeados intimamente aos operadores de consulta usados em consultas do MongoDB. O operador = que eu usei na consulta acima, por exemplo, é análogo ao operador $eq no MongoDB. Detalhes completos sobre os operadores de filtro estão disponíveis na documentação online do Morphia (consulte Recursos).

Como alternativa para filtrar consultas, o Morphia oferece uma interface fluente para desenvolver consultas. A consulta de interface fluente a seguir, por exemplo, é idêntica à consulta de filtro anterior:

Query query = datastore.createQuery(Band.class).field("name").equal("Love Burger");

É possível usar "notação de ponto" para consultar objetos integrados. Aqui está uma consulta que usa notação de ponto e a interface fluente para selecionar todas as bandas sediadas no Brooklyn:

Query query = datastore.createQuery(Band.class).field("info.city").equal("Brooklyn");

É possível definir mais ainda o conjunto de resultados da consulta. Eu irei modificar a consulta anterior para classificar as bandas por nome e limitar os resultados a 100:

Query query = 
datastore.createQuery(Band.class).field("info.city").equal
("Brooklyn").order("name").limit(100);


Indexação

Você notará que, conforme sua coleção crescer, o desempenho das consultas irá diminuir. Coleções do Mongo, assim como tabelas de bancos de dados relacionais, precisam ser indexadas adequadamente para garantir desempenho de consulta razoável.

Anotar uma propriedade com a anotação @Indexed aplica um índice a um campo. Aqui eu crio um índice de ascensão chamado genreName na propriedade genre de um Band:

@Indexed(value = IndexDirection.ASC, name = "genreName")
String genre;

Para aplicar os índices, o Morphia precisa saber quais classes serão mapeadas. É preciso instanciar o Morphia de maneira um pouco diferente para assegurar que os índices sejam aplicados. Isso pode ser feito da seguinte maneira:

Morphia morphia = new Morphia();
morphia.mapPackage("com.bandmanager.model");
datastore = morphia.createDatastore(mongo, "bandmanager");
datastore.ensureIndexes();

A chamada ensureIndexes() final instrui o datastore a criar os índices exigidos se não já existirem.

Índices também podem ser usados para evitar que duplicatas sejam inseridas em uma coleção. Ao configurar a propriedade unique na anotação @Indexed para o nome de um band, por exemplo, eu posso assegurar que apenas um band com o nome dado estará na coleção:

@Indexed(value = IndexDirection.ASC, name = "bandName", unique = true)
String name;

bands posteriores com o mesmo nome seriam excluídos.


Conclusão

Morphia é uma ferramenta poderosa para interagir com o MongoDB. Permite acesso idiomático com segurança de tipo a documentos do MongoDB. Este artigo cobriu os aspectos primários ao trabalhar com o Morphia, mas excluiu alguns recursos. Eu sugiro que você confira o projeto do Google Code do Morphia para obter informações sobre seus recursos de suporte, validação e mapeamento manual de data access object (DAO).



Download

DescriçãoNomeTamanhoMétodo de download
Sample code for this articlej-morphia.zip17.2KBHTTP

Informações sobre métodos de download


Recursos

Aprender

Obter produtos e tecnologias

  • Morphia: Faça o download do Morphia.

  • MongoDB: Faça o download do MongoDB.

Discutir

  • Morphia Google Group: Obtenha ajuda de outros usuários do Morphia.

  • Participe da comunidade do developerWorks. Entre em contato com outros usuários do developerWorks e explore os blogs, fóruns, grupos e wikis voltados para desenvolvedores.

Sobre o autor

ohn D'Emic

John D'Emic é Engenheiro de Software Senior em IBM Global Services e usou MongoDB em diversos contextos de desenvolvimento no ano passado. Ele é coautor ( com David Dossot) de Mule in Action (Manning Publications, 2009).

Ajuda para Relatar Abuso

Relatar abuso

Obrigado. Esta entrada foi sinalizada para atenção do moderador.


Ajuda para Relatar Abuso

Relatar abuso

Falha no envio do Relatório de abuso. Tente novamente mais tarde.


developerWorks: Registre-se


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Selecione seu nome de exibição

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

(Deve possuir de 3 a 31 caracteres.)


Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Classificar este artigo

Comentários

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java, Software livre
ArticleID=646868
ArticleTitle=Persistência de modelo de domínio com Morphia e MongoDB
publish-date=04122011
author1-email=john.demic@gmail.com
author1-email-cc=jaloi@us.ibm.com

Conheça a IBM da sua cidade

Virtual Branch Office Brasil

A IBM está mais perto do que você imagina!


Tags

Help
Use o campo de pesquisa para encontrar todos os tipos de conteúdo no My developerWorks com essa tag.

Use a barra de rolagem para ver mais ou menos tags.

Tags populares mostra as principais tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Minhas tags mostra suas tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Use o campo de pesquisa para localizar todos os tipos de conteúdo no Meu developerWorks com essa tag. Tags populares mostra as tags principais para essa zona de conteúdo particular (por exemplo, tecnologia Java, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere). Minhas tags mostra as suas tags para essa zona de conteúdo em particular (por exemplo, tecnologia Java, Linux, WebSphere).