Consultas com QueryBuilder MongoDB
O driver-mongo-java do banco de dados MongoDB, fornece uma classe muito interessante para montar as consultas a banco. Esta classe é a QueryBuilder. Para quem já trabalhou com Hibernate, conhece a diferença entre uma consulta em HQL e uma consulta usando a API Criteria. A QueryBuilder do MongoDB é semelhante a API Criteria, porque ela possui métodos direcionados a consulta. Por exemplo, se você deseja fazer uma consulta usando o operador $and, basta chamar o método and(), se pretende usar o operador $gt, basta então usar o método greaterThan() e assim por diante. Neste tutorial será demonstrado como usar a classe QueryBuilder e alguns de seus métodos.
1. Preparando o projeto
Para este tutorial vamos usar a versão 2.10.1 do driver MongoDB para Java:
- Mongo-driver-java – Versão 2.10.1: https://github.com/mongodb/mongo-java-driver/downloads.
2. Iniciando o projeto
Os exemplos de consultas serão executados na coleção contatos, que será disponibilizada para download junto com o projeto. Seus documentos serão semelhantes ao seguinte documento:
{
'_id' : '03',
'nome' : 'Joao Carlos Rios Ruzinsky',
'idade' : 28,
'tarefas' : [ 'Futebol', 'Basquete' ]
}
Veja na Listagem 1 a classe Contato que representará o documento no projeto Java.
package com.wp.mb.tutorial.entity;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* http://www.mballem.com/
*/
public class Contato implements Serializable {
private String id;
private String nome;
private Integer idade;
private List tarefas;
//gere os métodos get/set
@Override
public String toString() {
return "Contato{" +
"id='" + id + ''' +
", nome='" + nome + ''' +
", idade='" + idade + ''' +
", tarefas=" + tarefas +
'}';
}
}
Em seguida teremos a classe ContatoConverter, responsável em converter o resultado da consulta em um objeto Java do tipo Contato, veja na Listagem 2.
package com.wp.mb.tutorial.converter;
import com.mongodb.DBObject;
import com.wp.mb.tutorial.entity.Contato;
import java.util.List;
/**
* http://www.mballem.com/
*/
public class ContatoConverter {
public static Contato converterToContato(DBObject dbo) {
Contato contato = new Contato();
contato.setId((String) dbo.get("_id"));
contato.setNome((String) dbo.get("nome"));
contato.setIdade((Integer) dbo.get("idade"));
contato.setTarefas((List) dbo.get("tarefas"));
return contato;
}
}
Na Listagem 3 temos a interface IContatoDao, que possui apenas um método de consulta. Este método tem como retorno uma lista de contatos.
package com.wp.mb.tutorial.dao; import com.mongodb.DBObject; import java.io.Serializable; import java.util.List; /** * http://www.mballem.com/ */ public interface IContatoDao{ List findContatos(DBObject dbo); }
A implementação da interface IContatoDao fica por conta da classe ContatoDao na Listagem 4. O método findContatos() recebe um objeto do tipo DBObject (nativo do mongo-driver) como argumento, o qual conterá a consulta montada através da QueryBuilder.
package com.wp.mb.tutorial.dao; import com.mongodb.DBCollection; import com.mongodb.DBCursor; import com.mongodb.DBObject; import com.wp.mb.tutorial.converter.ContatoConverter; import com.wp.mb.tutorial.entity.Contato; import java.util.ArrayList; import java.util.List; /** * http://www.mballem.com/ */ public class ContatoDao implements IContatoDao{ private DBCollection db; public ContatoDao() { this.db = MongoConnection.getInstance().getDB().getCollection("contatos"); } public List findContatos(DBObject dbo) { List contatos = new ArrayList (); DBCursor cursor = db.find(dbo); while (cursor.hasNext()) { contatos.add(ContatoConverter.converterToContato(cursor.next())); } return contatos; } }
Na Listagem 5 é possível conferir a classe de conexão com o MongoDB, chamada MongoConnection. Esta classe foi construída com base no padrão Singleton, o qual tem como objetivo fornecer uma única instancia da classe durante todo o ciclo de vida da aplicação. O acesso a esta instancia fica por conta de um método estático chamado getInstance(). O nome do banco de dados será: db-contato.
package com.wp.mb.tutorial.dao;
import com.mongodb.DB;
import com.mongodb.MongoClient;
import java.net.UnknownHostException;
/**
* http://www.mballem.com/
*/
public class MongoConnection {
private static final String HOST = "localhost";
private static final int PORT = 27017;
private static final String DB_NAME = "db-contato";
private static MongoConnection uniqInstance;
private static int mongoInstance = 1;
private MongoClient mongo;
private DB db;
private MongoConnection() {
//construtor privado
}
//garante sempre uma unica instancia da classe
public static synchronized MongoConnection getInstance() {
if (uniqInstance == null) {
uniqInstance = new MongoConnection();
}
return uniqInstance;
}
//garante um unico objeto mongo
public DB getDB() {
if (mongo == null) {
try {
mongo = new MongoClient(HOST, PORT);
db = mongo.getDB(DB_NAME);
System.out.println("Mongo instance equals :> " + mongoInstance++);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
return db;
}
}
3. Implementando as consultas com QueryBuilder
A primeira consulta que vamos testar será baseada em uma busca por nome, veja na Listagem 6. Devemos criar um objeto do tipo DBObject que receberá a consulta QueryBuilder. O primeiro método a ser usado é o start() que indica por qual chave será a consulta. O método is() recebe o parametro da consulta, no caso o nome do contato. Este método seria equivalente ao like do SQL. Já método get() transforma a consulta em um objeto DBObject. Em seguida fazemos uma chamada ao método findContatos(), da interface IUserDao, e passamos como par metro a variável query, que contem a consulta.
public class ContatoTeste {
private IContatoDao dao = new ContatoDao();
public static void main(String[] args) {
ContatoTeste t = new ContatoTeste();
t.findByName();
//t.findByNameAndIdade();
//t.findByTarefas();
}
private void findByName() {
DBObject query = QueryBuilder
.start("nome")
.is("Bruna de Melo Pereira")
.get();
List contatos = dao.findContatos(query);
for (Contato contato : contatos) {
System.out.println(contato.toString());
}
}
}
Contato{id='44', nome='Bruna de Melo Pereira', idade='25', tarefas=[ "Musica"]}
A próxima consulta (Listagem 7) a ser analisada usará os métodos greaterThanEquals() – maior igual que – o método lessThanEquals() – menor igual que – e o método and(). Veja que o método and() é seguido por um método regex(), que possui a função de expressão regular. Esta consulta então irá selecionar todos os contatos entre ‘25’ e ‘30’ de idade, e que contenham como parte do nome o sobrenome ‘Souza’.
private void findByNomeAndIdade() {
DBObject query = QueryBuilder.start("idade")
.greaterThanEquals(25)
.lessThanEquals(30)
.and("nome").regex(Pattern.compile("Souza"))
.get();
List contatos = dao.findContatos(query);
for (Contato contato : contatos) {
System.out.println(contato.toString());
}
}
Contato{id='04', nome='Ana Carla de Souza', idade='25', tarefas=[ "Culinaria" , "Filmes"]}
Contato{id='07', nome='Joao Antonio de Souza', idade='25', tarefas=[ "Veterinaria" , "Voo Livre"]}
Contato{id='18', nome='Marus Daniel Souza e Castro', idade='26', tarefas=[ "Cinema" , "Teatro"]}
Contato{id='23', nome='Miguel Antunes de Souza', idade='29', tarefas=[ "Poker" , "Bilhar"]}
Contato{id='32', nome='Milton Onorio de Souza', idade='27', tarefas=[ "Pescaria" , "Surf"]}
Na Listagem 8 vamos trabalhar com o campo tarefas do documento, que é um campo do tipo array. No MongoDB temos três possibilidades de consulta sobre um campo do tipo array, veremos a seguir a diferença entre as três.
Na primeira consulta, referente ao objeto is, foi informado como parametros um array contendo duas tarefas. O retorno dessa consulta será documento(s) que tenham no campo tarefas a seqüência exata de tarefas passadas como parametro. Ou seja, “Teatro e Cinema”. Se existir um documento que contenha “Cinema, Teatro” ou “Teatro” ou “Cinema” ou “Teatro, Novela, Cinema”, estes não serão retornar, porque não existe a seqüência informada como parametro.
A próxima consulta, do objeto in, irá retornar todos os documentos que contenham as tarefas “Teatro” E/OU “Cinema”.
A última consulta, do objeto all, irá retornar apenas os documentos que contenham “Teatro” e “Cinema”, independentemente da ordem que eles se encontrem nos documentos.
private void findByTarefas() {
// Busca Apenas quando houver a sequencia Teatro e Cinema
DBObject is = QueryBuilder.start("tarefas")
.is( new String[] {"Teatro", "Cinema"} ).get();
List contatos = dao.findContatos(is);
for (Contato contato : contatos) {
System.out.println("is : " + contato.toString());
}
// Busca Teatro E/Ou Cinema
DBObject in = QueryBuilder.start("tarefas")
.in( new String[] {"Teatro", "Cinema"} ).get();
contatos = dao.findContatos(in);
for (Contato contato : contatos) {
System.out.println("in : " + contato.toString());
}
// Busca Teatro E Cinema
DBObject all = QueryBuilder.start("tarefas")
.all( new String[] {"Teatro", "Cinema"} ).get();
contatos = dao.findContatos(all);
for (Contato contato : contatos) {
System.out.println("all : " + contato.toString());
}
}
Saída no console:
is : Contato{id='35', nome='Marta Fill Nunes', idade='23', tarefas=[ "Teatro" , "Cinema"]}
in : Contato{id='08', nome='Mario Nelson Nunes', idade='26', tarefas=[ "Teatro"]}
in : Contato{id='12', nome='Nicole Rios da Silva', idade='22', tarefas=[ "Voleibol" , "Cinema"]}
in : Contato{id='14', nome='Bruna Roque Santos', idade='24', tarefas=[ "Cinema" , "Novelas" , "Teatro"]}
in : Contato{id='18', nome='Marus Daniel Souza e Castro', idade='26', tarefas=[ "Cinema" , "Teatro"]}
in : Contato{id='27', nome='Crisitina Oliveira', idade='33', tarefas=[ "Medicina" , "Cinema"]}
in : Contato{id='30', nome='Antonia Cristina Mel', idade='34', tarefas=[ "Cinema" , "Teatro"]}
in : Contato{id='35', nome='Marta Fill Nunes', idade='23', tarefas=[ "Teatro" , "Cinema"]}
in : Contato{id='40', nome='Lisiane de Almeida', idade='21', tarefas=[ "Artes" , "Teatro"]}
all : Contato{id='14', nome='Bruna Roque Santos', idade='24', tarefas=[ "Cinema" , "Novelas" , "Teatro"]}
all : Contato{id='18', nome='Marus Daniel Souza e Castro', idade='26', tarefas=[ "Cinema" , "Teatro"]}
all : Contato{id='30', nome='Antonia Cristina Mel', idade='34', tarefas=[ "Cinema" , "Teatro"]}
all : Contato{id='35', nome='Marta Fill Nunes', idade='23', tarefas=[ "Teatro" , "Cinema"]}
4. Importando a coleção contatos
A coleção contatos está disponível dentro da pasta file, que se encontra com o projeto. O nome do arquivo a ser importado é contatos.json. Para importar esta coleção inicialize o MongoDB e em seguida, em uma nova janela do console use o seguinte comando:
C:mongo>mongoimport -d db-contato -c contatos < contatos.json
O resultado deverá informar que foram importados 46 documentos, como este:
connected to: 127.0.0.1 Mon Jan 07 18:21:59 imported 46 objects
Saiba mais em:


