Esfinge Query Builder - Persistência simples e rápida


Funcionalidades Básicas do Query Builder


O Esfinge Query Builder oferece uma solução para a criação de uma camada de persistência de forma simples e rápida. Através da filosofia “para um bom framework, o nome do método basta”, o Query Builder utiliza os nomes dos métodos de uma interface para inferir as consultas que precisam ser executadas na base de dados.

 

Query Builder em 1 minuto

Essa seção apresenta uma visão geral rápida de como o Esfinge Query Builder é utilizado na prática.
O primeiro passo é a definição de uma interface com os nomes do métodos referentes as consultas que precisam ser feitas. Opcionalmente, quando se deseja que sejam disponibilizadas operações CRUD, pode-se estender a interface Repository. Veja o exemplo da interface abaixo criada para a classe Person.
public interface PersonDAO extends Repository<Person>{
    public List<Person> getPersonByLastName(String lastname);
    public List<Person> getPersonByAddressCity(String city);
    public List<Person> getPersonByAgeGreater(Integer age);
}
O segundo passo é criar a interface e utilizar. Só isso? É só isso mesmo! A idéia do Query Builder é realmente deixar a criação da interface de persistência muito simples. Veja abaixo como criar a instância da classe que realmente implementa essa interface:
PersonDAO dao = QueryBuilder.create(PersonDAO.class);

Como funciona?

Os nomes dos métodos são interpretados pelo Query Builder que gera em tempo de execução uma consulta que é executada no banco de dados e tem seu resultado retornado. O método getPersonByLastName(), por exemplo, retorna instâncias da classe Person filtrando pela propriedade lastName. O método getPersonByAddressCity() faz a consulta filtrando pela propriedade city da propriedade address, que é também uma classe persistente. Finalmente, o método getPersonByAgeGreater() irá retornar pessoas com idade maior do que a recebida como parâmetro.

Achou fácil? Saiba mais sobre os detalhes e as funcionalidades do QueryBuilder nas seções seguintes.

Configurando o Query Builder

O Query Builder foi feito para poder funcionar independente do mecanismo de persistência utilizado pela aplicação. Por enquanto, está disponível apenas uma versão para JPA 1, que trabalha em cima da geração de consultas em JPAQL. As classes principais do framework são divididas em 2 arquivos JAR: esfinge_querybuilder_core_1_X.jar e esfinge_querybuilder_jpa1_1_X.jar. O primeiro contém as funcionalidades gerais do framework e o segundo as classes específicas para o JPA. O arquivo esfinge_querybuilder_jpalocal1_1_X.jar só deve ser incluído caso as transações do entity manager do JPA sejam gerenciadas localmente. Caso as transações sejam controladas por um container EJB atarvés do JTA ou por um framework como o Spring, esse arquivo não deve ser incluído.

Além disso ainda são necessárias outros JAR básicos para o acesso ao banco de dados, como o driver de conexão e a implementação JPA.

Além disso, é necessário criar uma classe, que implementa a interface org.esfinge.querybuilder.jpa1.EntityManagerProvider, responsável por retornar as instâncias de EntityManager e EntityManagerFactory utilizadas pela aplicação. No caso de uma aplicação que utilizasse o JPA independente de um servidor de aplicações, essa classe seria como o exemplo exibido abaixo. No caso do uso de um container, por exemplo, a implementação deveria recuperar as instâncias do contexto JNDI.
public class TestEntityManagerProvider implements EntityManagerProvider {
    @Override
    public EntityManager getEntityManager() {
        return getEntityManagerFactory().createEntityManager();
    }

    @Override
    public EntityManagerFactory getEntityManagerFactory() {
        return Persistence.createEntityManagerFactory("database_test");
    }
}

Para configurar essa nova classe criada, é preciso criar um arquivo chamado org.esfinge.querybuilder.jpa1.EntityManagerProvider no diretório META-INF/services de algum dos arquivos JAR da aplicação. Esse arquivo deve conter apenas o nome completo da classe criada.

Repositório

Apesar de não ser uma de suas funcionalidades principais, o Query Builder implementa as funcionalidades básicas de CRUD. Para que isso possa ser utilizado basta que a interface criada implemente a interface org.esfinge.querybuilder.Repository. É importante que ao implementar essa interface seja definido qual a classe persistente que está sendo utilizada, como por exemplo Repository<Person>.

Abaixo seguem os métodos disponibilizados por essa interface com as respectivas descrições:

Método Descrição
E save(E obj) Grava no banco de dados o objeto passado como parêmetro. O objeto é inserido caso não exista ou atualizado caso já exista.
void delete(Object id) O método exclui da base de dados a entidade cujo id foi passado como parâmetro.
List<E> list() Retorna uma lista com todas as entidades do banco de dados.
E getById(Object id) Retorna uma instância de acordo com o id passado como parâmetro.
List<E> queryByExample(E obj) Faz uma query que faz a busca de acordo com as propriedades populadas do objeto.

 

Nomeandos os Métodos

Os nomes dos métodos da interface são utilizados para a geração das consultas e por isso é muito importante saber nomea-los corretamente. Abaixo seguem algumas regras para a criação dos métodos:
  • Os métodos devem começar com get e serem seguidos do nome da entidade. O nome utilizado deve ser o mesmo utilizado pelo JPA. Exemplo: List<Person> getPerson()
  • Para passar parâmetros para a consulta, o nome da entidade deve ser seguido por by e por nomes de propriedades da classe. O parâmetro deve ser do mesmo tipo da propriedade. Exemplo: Person getPersonByName(String name) e Person getPersonByLastName(String name)
  • Os métodos podem retornar uma instância da entidade ou uma lista de instâncias da entidade , como os dois exemplos anteriores apresentados.
  • O parâmetro passado pode navegar entre as dependências da classe e acessar propriedades delas. Exemplo: List<Person> getPersonByAddressCity(String city) , que filtraria a propriedade city na propriedade address.
  • Para passar mais de um parâmetro pode-se utilizar and ou or entre as propriedades. Os parâmetros serão considerados na mesma ordem definida no nome. Exemplo: Person getPersonByNameAndLastName(String name, String lastname) e List<Person> getPersonByNameOrLastName(String name, String lastname)

Outros Tipos de Comparação

Além das comparações do tipo “igual” utilizadas nos exemplos apresentados até aqui, também são possíveis outros tipos de comparação. Para que outros tipos de comparação sejam utilizadas existem duas abordagens possíveis. A primeira abordagem consiste em colocar anotações nos parâmetros registrando o tipo de comparação que se deseja fazer. Veja os exemplos:
List<Person> getPersonByAge(@Greater Integer age);

List<Person> getPersonByName(@Starts String name);
Com a abordagem baseada em anotações, não é possível ter dois métodos que executam uma consulta baseada nos mesmos parâmetros mas com tipos de comparação. Por esse motivo, também é suportada a sintaxe onde o tipo de comparação é colocado logo após o nome da propriedade. Veja os exemplos:
public List<Person> getPersonByAgeLesser(Integer age);

public List<Person> getPersonByLastNameNotEquals(String name);

public List<Person> getPersonByNameStartsAndAgeGreater(String name, Integer age);
As anotações possuem extamente o mesmo nome da string que precisa ser adicionada no nome do método. No momento os operadores suportados são: Lesser, Greater, LesserOrEquals, GreaterOrEquals, NotEquals, Contains, Starts, Ends.

 

Ordenação de Consultas

Também é possí­vel compor o nome dos métodos de forma que as consultas sejam ordenadas. Para isso é necessário no final do nome do método colocar OrderBy seguido pelo nome da propriedade. Caso seja necessária a ordenação por dois campos, pode-se separar o nome deles com and. Também é possí­vel depois do nome da propriedade colcoar Asc ou Desc para definir o sentido da ordenação (ascendente ou descendente), sendo ASC o valor default. Veja os exemplos:
public List<Person> getPersonOrderByName();

public List<Person> getPersonByAgeOrderByNameDesc(@Greater Integer age);

public List<Person> getPersonOrderByNameAndLastName();


Query Objects

Quando a quantidade de parâmetros de uma consulta é muito grande, isso inviabiliza a criação de uma consulta definida pelo nome do método. Para esses casos, o Esfinge QueryBuilder possibilita que propriedades de uma classe sejam utilizadas como parâmetro para a consulta. Essa classe deve ser definida como um Java Bean comum. As propriedades devem possuir o mesmo nome da propriedade da entidade que será filtrada. Existem três possibilidade para se definir os tipos de comparação: anotações no atributo, anotações no método getter ou adicionar o tipo de comparação no final do nome da propriedade.
Abaixo segue um exemplo de uma classe desse tipo. Observe que as propriedade ageGreater e ageLesser exemplificam o uso do tipo de comparação no nome da propriedade e as propriedades name e lastName exemplificam respectivamente o uso de anotações no atributo e no método getter.
public class ExampleQueryObject {

    private Integer ageGreater;
    private Integer ageLesser;

    @Contains
    private String name;
    private String lastName;

    public Integer getAgeGreater() {
        return ageGreater;
    }
    public void setAgeGreater(Integer ageGreater) {
        this.ageGreater = ageGreater;
    }
    public Integer getAgeLesser() {
        return ageLesser;
    }
    public void setAgeLesser(Integer ageLesser) {
        this.ageLesser = ageLesser;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Contains
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

}
Depois de definida a classe, o método da interface deve receber apenas essa classe como parâmetro e anotar o parâmetro com a anotação @QueryObject. Além disso não se deve utilizar como parte do nome do método o “By” com os respectivos parâmetros, porém o OrderBy e termos de domínio (apresentados a seguir) podem ser utilizados normalmente.

Abaixo segue um exemplo de um método que utiliza a classe definida acima:

public List getPerson(@QueryObject ExampleQueryObject obj);
Note que uma classe que definir um query object pode inicializar as variáveis com valores default para os parâmetros da consulta, o que não é possível quando parâmetros são utilizados.

Termos de Domínio com Query Builder


O Esfinge Query Builder define uma DSL que pode ser utilizada para definir nomes de métodos de forma que os mesmos sejam utilizados para a geração de consultas. Muitas vezes existem termos que fazem sentido dentro do domínio e que precisam ser utilizados para compor os nomes do métodos. Esse tutorial ensina a incluir termos de domínio para uma interface e usar esses termos nos nomes dos métodos.

Definindo Termos de Domínio

Os termos de domínio são definidos através de anotações na interface que define os métodos com as consultas. Os termos de domínio podem ser utilizados para definir conjuntos de entidades que fazem sentido dentro do contexto de negócios da aplicação. Esses conjuntos são muitas vezes definidos por restrições que são incluídas nas consultas. O termo maior de idade denota, por exemplo, pessoas cuja a idade é maior que 18 anos e o termo paulista denota pessoas nascidas no estado de São Paulo. A idéia é esses termos pdoerem ser definidos e utilizados para compor os nomes dos métodos.
Quando apenas um termo de domínio é necessário, a anotação @DomainTerm pode ser utilizada para defini-lo. Ela deve configurar o nome do termo de domínio e as condições que devem ser obedecidas. O nome do termo de domínio pode ser composto por mais de uma palavra. Veja abaixo a definição de um termo de domínio com uma condição:
@DomainTerm(term="major", conditions=@Condition(property="age",comparison=ComparisonType.GREATER_OR_EQUALS,value="18"))
public interface PersonQuery{
//methods ommited
}
O atributo conditions pode receber uma lista de anotações do tipo @Condition. Cada uma delas deve definir uma propriedade (que pode acessar propriedades de propriedades, como address.city), o tipo de comparação e o valor. O tipo de comparação é o único valor que pode ser omitido, possuindo como default a comparação EQUALS. Veja abaixo um exemplo que define mais de uma condição:
@DomainTerm(term="teenager",
conditions={@Condition(property="age",comparison=ComparisonType.GREATER_OR_EQUALS,value="13"),
@Condition(property="age",comparison=ComparisonType.LESSER_OR_EQUALS,value="19")})
public interface PersonQuery{
//methods ommited
}
Caso mais de um termo de domínio precise ser definido para a mesma classe, pode-se usar a anotação @DomainTerms, que possui uma lista de termos de domínio. Veja abaixo um exemplo que define mais de um termo de domínio na mesma classe:
@DomainTerms({
@DomainTerm(term="elder", conditions=@Condition(property="age",comparison=ComparisonType.GREATER_OR_EQUALS,value="65")),
@DomainTerm(term="paulista", conditions=@Condition(property="address.state",value="SP"))
})
public interface PersonQuery{
//methods ommited
}

Usando os Termos de Domínio nos Métodos

Usar os termos de domínio no nome dos métodos é bem fácil, bastando incluí-los depois do nome da entidade. Nesse caso, a condição associada ao termo de domínio será incorporada na consulta. Mais de um termo de domínio pode ser utilizado na mesma consulta e eles também podem ser utilizados sem restrição junto com parâmetros. Veja abaixo um exemplo completo de uma interface que utiliza termos de domínio no nome dos métodos:
@DomainTerms({
@DomainTerm(term="old guys", conditions=@Condition(property="age",comparison=ComparisonType.GREATER_OR_EQUALS,value="65")),
@DomainTerm(term="paulista", conditions=@Condition(property="address.state",value="SP")),
@DomainTerm(term="teenager",conditions={@Condition(property="age",comparison=ComparisonType.GREATER_OR_EQUALS,value="13"),
@Condition(property="age",comparison=ComparisonType.LESSER_OR_EQUALS,value="19")})
})
public interface PersonQueries{
public List<Person> getPersonTeenager();
public List<Person> getPersonPaulista();
public List<Person> getPersonTeenagerPaulista();
public List<Person> getPersonOldGuys();
public List<Person> getPersonPaulistaByAge(@Greater Integer age);
}

Trabalhando com Parâmetros NULL


As funcionalidades normais do Esfinge QueryBuilder não permitem que parâmetros nulos sejam passados para os métodos. O resultado é inesperado dependendo do provider utilizado para o JPA. Com a configuração de anotações, é possível definir como um valor nulo deve ser interpretado pelo framework: deve realmente ser feita a comparação com nulo ou o parâmetro deve ser ignorado.

 

Comparando com NULL

Para que seja realizada a comparação com NULL quando um valor nulo for passado como parâmetro, basta incluir a anotação @CompareToNull. Note que nesse caso, será substituída a comparação que seria feita (sendo ela maior, igual, menor e etc…) pela cláusula IS NULL na consulta. Observe o uso da anotação nos métodos abaixo:
public List<Person> getPersonByCompany(@CompareToNull String company);

public List<Person> getPersonByNameAndLastName(@Starts String name, @CompareToNull String lastname);
No método getPersonByCompany(), caso seja passado o parâmetro NULL serão buscadas todas as pessoas que não possuem uma empresa associada. No segundo exemplo, observe que apenas o segundo parâmetro possui a anotação, então será válido passar o valor nulo apenas para ele.

 

Ignorando Parâmetros que Receberem NULL

Outra opção interessante para se lidar valores nulos é ignorar o parâmetro quando o valor nulo é recebido. Nesse caso, o parâmetro não será incluído na consulta caso seu valor seja nulo. Isso pode ser muito interessante para a criação de consultas que permitam vários parâmetros opcionais. Para que isso seja feito, basta incluir a anotação @IgnoreWhenNull no parâmetro. Veja o exemplo abaixo:
public List<Person> getPersonByNameStartsAndLastNameStarts(@IgnoreWhenNull String name, @IgnoreWhenNull String lastname);
O exemplo apresenta uma consulta que busca pelo nome e pelo sobrenome começados com o que for passado como parâmetro. Como ambos os parâmetros possuem a anotação @IgnoreWhenNull, ambos podem ser ignorados. Observe que existem na verdade quatro consultas possíveis: não ignorando nenhum aprâmetro, ignorando o primeiro parâmetro, ignorando o segundo parâmetro e ignorando os dois parâmetros. O uso desse recurso pode permitir a combinação de consultas em apenas um método, eliminando a necessidade de criação de vários outros
Caso as propriedades sejam valores primitivos, para utilizar essa funcionalidade, recomenda-se que sejam utilizados as respectivas classes wrapper. Por exemplo, utilize Integer no lugar de int. Isso permitirá que sejam passados valores nulos e essa funcionalidade possa ser utilizada.

 

Lidando com NULL em Query Objects

Tanto a anotação @IgnoreWhenNull quanto a @CompareToNull podem ser utilizadas em nas propriedades da classe quando são utilizados query objects para as consultas. As anotações podem ser colocadas nas propriedades ou nos métodos getter.

Métodos Customizados


Quando se cria uma interface no QueryBuilder, a invocação dos métodos é direcionada para um proxy dinâmico que interpreta o nome e executa a consulta adequada. Dessa forma, os métodos da interface não possuem de fato uma implementação. Um caso a parte ocorre quando a interface estende a interface Repository, onde a invocação dos métodos dessa interface são direcionados para uma implementação específica. Essa seção ensina a incluir métodos na interface que serão direcionados para uma implementação específica.

 

Adicionando um método customizado

O primeiro passo para criar um método customizado é definir uma interface apenas com esse método:

public interface CustomMethodInterface {
    public void customMethod();
}

Em seguida, define-se uma classe que implementa essa interface:

public class CustomMethodImpl implements CustomMethodInterface{
    @Override
    public void customMethod() {
        //faz alguma coisa
    }
}
Em seguida é preciso configurar essa implementação como a que deve ser utilizada para essa interface. Isso pode ser feito criando um arquivo com o nome completo da interface, no caso org.esfinge.querybuilder.jpa1.custommethods.CustomMethodInterface, no caminho META-INF/services dentro de algum jar do classpath, contendo apenas o nome da implementação, no caso “org.esfinge.querybuilder.jpa1.custommethods.CustomMethodImpl”.
Depois disso, qualquer interface do QueryBuilder que estender essa interface que foi definida irá conter seus métodos e consequentemente a implementação definida será executada quando eles forem invocados.

 

Parametrizando a interface

Da forma como foi apresentado na seção anterior, o método possui uma implementação fixa e normalmente será adicionado em apenas uma interface. Um recurso poderoso é permitir que a implementação receba a classe da entidade que está sendo utilizada na interface. Dessa forma, a implementação pode usar recursos de reflexão para personalizar a execução dependendo da entidade.
Para que isso seja possível, a interface que irá definir o método customizado deve possuir um tipo genérico e estender a interface NeedClassConfiguration, conforme o exemplo abaixo.
public interface GenericMethodInterface extends NeedClassConfiguration {
    public E createNewInstance();
}
A implementação precisará implementar um método adicional com a seguinte assinatura “public void configureClass(Classc)”. Esse método irá receber a classe que será configurada como parâmetro do tipo genérico no momento da implementação da interface. Segue abaixo o exemplo da implementação. Note que a implementação utiliza a classe para retornar uma nova instância da mesma.
public class GenericMethodImpl implements GenericMethodInterface {

    private Class clazz;

    @Override
    public void configureClass(Class c) {
        clazz = c;
    }

    @Override
    public E createNewInstance() {
        try {
            return clazz.newInstance();
        } catch (Exception e) {
            throw new RuntimeException("Not possible to instantiate "+clazz.getName(),e);
        }
    }

}
A configuração do arquivo que faz a ligação entre interface e configuração é feita mesma forma. Quando a interface for implementada, é preciso especificar o tipo genérico que será utilizado, para a implementação poder receber a classe. Segue o exemplo:
public interface GenericInterface extends GenericMethodInterface{
   //outros métodos
}

Recuperando implementações

As implementações configuradas dessa forma podem ser recuperadas através do método getServiceImplementation() da classe org.esfinge.querybuilder.utils.ServiceLocator . Isso pode ser muito útil nas implementações para recuperar os serviços já oferecidos pelo framework. Abaixo segue um exemplo de como recuperar a classe que provê o EntityManager para o framework.
EntityManagerProvider emp = ServiceLocator.getServiceImplementation(EntityManagerProvider.class)

Sobrepondo implementações

Qualquer implementação do framework que é recuperada dessa forma pode ser sobreposta por uma outra, desde que a mesma possua maior prioridade. Para definir uma nova implementação, a classe deve implementar a interface desejada e configurar o arquivo que liga a implementação a interface da forma como foi apresentada anteriormente. A anotação @ServicePriority deve ser utilizada na classe passando como parâmetro um número maior que a prioridade do serviço a ser sobreposto (que normalmente é zero pois essa é a prioridade default quando a anotação não é configurada).
Um exemplo é a interface Repository, que é utilidada para implementar as operações do tipo CRUD. Caso seja adequado, pode-se sobrepor a implementação existente, criando-se uma específica da aplicação.

Integrando o Query Builder com Spring


Agradecimento a contribuição de Leonardo Machado Moreira que escreveu boa parte e desenvolveu o exemplo desse tutorial.

O Esfinge Query Builder pode ser facilmente integrado a outros frameworks. Esse tutorial mostra como ele pode ser integrado ao Spring Framework, utilizando o EntityManager gerenciado por ele.

Configuração do Spring

Abaixo segue o arquivo que mostra como configurar os beans relativos ao acesso ao banco de dados no Spring.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd


http://www.springframework.org/schema/tx

http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"
default-autowire="byName">

<bean id="dataSource">
<property name="driverClass"        value="com.mysql.jdbc.Driver"/>

<property name="jdbcUrl"            value="jdbc:mysql://localhost:3306/erh"/>
<property name="user"               value="root"/>
<property name="password"           value="303016"/>
<property name="initialPoolSize"    value="5"/>
<property name="minPoolSize"        value="5"/>

<property name="maxPoolSize"        value="50"/>
<property name="autoCommitOnClose"  value="false"/>
<property name="checkoutTimeout"    value="30000"/>
<property name="maxStatements"      value="50"/>
</bean>

<bean id="entityManagerFactory">
<property name="dataSource" ref="dataSource" />
<property name="persistenceUnitName" value="persistence-unit" />
</bean>

<bean id="entityManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<bean id="transactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

<tx:annotation-driven transaction-manager="transactionManager" />

<bean id="springApplicationContext"/>

</beans>
O bean dataSource é responsável por efetuar a conexão com o banco de dados, neste caso, um banco MySql. A configuração da conexão efetuada é simples, com exceção do pool de conexões, que neste caso foi utilizado o c3p0. Este pool de conexões específico foi utilizado pois este tutorial não tem ambiente determinado, então ao invés de indicar um pool de qualquer container ou web-server a utilização do c3po se mostrou mais adequada.
O bean entityManagerFactory é a implementação da classe LocalContainerEntityManagerFactoryBean, responsável por implementar uma factory de Entity Manager. Foi escolhida a classe LocalContainerEntityManagerFactoryBean, pois a mesma permite controle absoluto na configuração da forma que os EntityManagers serão criados. Atente para a propriedade persistenceUnitName, onde informamos o nome da unidade de persistencia que a factory deverá procurar. Naturalmente o Spring procurará o arquivo persistence.xml na pasta resources/META-INF.
O bean entityManager do arquivo database.xml é o mais importante na configuração do Spring e o Esfinge Query Builder. Nas configurações de Spring e JPA encontradas na internet este bean raramente é declarado de forma explicita no contexto, porém o Spring sempre cria este Entity Manager de modo a compartilha-lo por todo o contexto. Caso você recupere uma Entity Manager utilizando a anotação @PersistenceContext com certeza receberá uma classe do tipo SharedEntityManagerBean. Esta classe é apenas mais uma implementação de Entity Manager como qualquer outra, com uma única difereça, a mesma é Thread-Safe, pois será complartilhada por todo contexto.
Também foi declarado o bean transactionManager apenas para indicar o gerenciador de transações a ser utilizado.
O bean springApplicationContext foicriado para facilitar a integração do Spring com o Esfinge Query Builder, permitindo que o mesmo recupere o EntityManager do contexto do Spring. Abaixo segue a implementação dessa classe que serve para a recuperação do contexto do Spring.
public class SpringApplicationContext implements ApplicationContextAware {

private static ApplicationContext CONTEXT;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
CONTEXT = applicationContext;
}

public static Object getBean(String beanName) {
return CONTEXT.getBean(beanName);
}

public static Object getBean(Class<?>classType) {
return CONTEXT.getBean(classType);
}
}

Configuração do Query Builder

Neste momento toda a configuração necessária para o Spring foi descrita, agora a configuração do Esfinge Query Builder será abordad. O arquivo resources/META-INF/services/org.esfinge.querybuilder.jpa1.EntityManagerProvider será responsável por indicar ao Query Builder a classe onde uma implementação de EntityManager será pega, neste caso a classe org.esfinge.exemplo.jpaprovider.JPAEntityManagerProvider apresentada abaixo:
public class JPAEntityManagerProvider implements EntityManagerProvider {

@Override
public EntityManager getEntityManager() {
return (EntityManager) SpringApplicationContext.getBean("entityManager");
}

@Override
public EntityManagerFactory getEntityManagerFactory() {
return (EntityManagerFactory) SpringApplicationContext.getBean("entityManagerFactory");
}
}
Esta classe contém os métodos indicados pela classe EntityManagerProvider, do querybuilder, onde o retorno de um EntityManager e uma EntityManagerFactory se faz necessária. Nesta aplicação as instâncias de EntityManager e a EntityManagerFactory foram recuperadas do contexto do Spring utilizando uma outra classe a SpringApplicationContext.
Esta é uma forma do próprio Spring para a recuperação de beans presentes no contexto do Spring por código vindo de fora do contexto. Neste momento que o bean springApplicationContext é importante, pois ele permitirá que a classe EntityManagerProvider recupere os beans de EntityManager e EntityManagerFactory do Spring mesmo sem pertencer a qualquer tipo de contexto.

Agora é só criar as instâncias a partir do QueryBuilder baseadas nas suas interfaces!

Atenção: é importante lembrar que quando o EntityManager do Spring é usado, o arquivo esfinge_querybuilder_jpalocal1_1_X.jar não deve ser incluído no classpath!

Plugin Para Eclipse


O Esfinge Query Builder também oferece um plugin para Eclipse que busca facilitar ainda mais o desenvolvimento, encontrando erros no uso da DSL do framework e apontando-os ao usuário em tempo de compilação que caso contrário, apenas seriam encontrados em tempo de execução. O plugin busca tirar o máximo de proveito dos recursos de validação do Query Builder, uma vez que faz uso do próprio framework para validar a DSL empregada nos métodos de consulta. Além disso, o plugin também é responsável por refatorar automaticamente qualquer ocorrência dos atributos de uma entidade nos métodos de consulta sempre que os getters dessa entidade forem refatorados.

 

Instalação

O plugin pode ser instalado manualmente seguindo os passos abaixo:

 

  1. Baixe o Esfinge Query Builder Plugin (EsfingePlugin_1.0.0.jar)
  2. Copie o plugin para o diretório plugins dentro da raiz do Eclipse
  3. Reinicie o Eclipse

 

Uso

O plugin é ativado automaticamente assim que é instalado, mas é possível desabilitar ou habilitar o mesmo sempre que necessário clicando com o botão direito do mouse sobre o projeto e em seguida escolher a opção Remove Esfinge Builder ou Add Esfinge Builder.

 

 

Uma vez ativado, o plugin é capaz de indicar erros de nomenclatura presentes na assinatura dos métodos de consulta bem como nos tipos e números de parâmetros dos mesmos. Os erros de nomenclatura ficam sublinhados em vermelho no código, como pode ser observado na imagem abaixo:

 

 

Os marcadores são atualizados a cada novo evento de construção e, caso a configuração de construção automática esteja ativada (como indicado na figura abaixo), a validação é feita sempre que um arquivo é salvo.

 

 

Refatoração

Sempre que uma entidade ou os getters de seus atributos forem renomeados, o plugin também irá renomear qualquer ocorrência alusiva a essas entidades ou suas propriedades nos métodos de consulta presentes em cada DAO.

 

 

É importante lembrar que a refatoração de uma atributo não desencadeia a refatoração automática nas interfaces, por esse motivo é necessário refatorar os getters das entidades para que o plugin realize a refatoração automática dos métodos nas interfaces de consulta.

Query Builder com MongoDB


O MongoDB é um banco de dados NoSQL, opensource, de alta performance, escrito em C++ e orientado a documentos, sendo que seus documentos seguem o formato JSON. Este módulo tem seu desenvolvimento com o apoio do projeto Morphia, pois providencia a tradução de objetos Java para o banco.

Para utilizá-lo com o Query Builder siga os seguintes passos:

Primeiramente crie um novo projeto Java e adicione os seguintes JARs nele:

  • mongo-2.7.3.jar
  • morphia-0.99.1-SNAPSHOT.jar
  • QueryBuilder_jar.jar
  • QueryBuilder_MongoDB_jar.jar
  • QueryBuilderParser_jar.jar

Os JARs podem ser encontrados no seguinte link https://github.com/rmmariano/jars_for_query_builder_mongodb ou caso queira o código-fonte, estão disponíveis em https://github.com/EsfingeFramework/querybuilder

Crie um arquivo com o nome "org.esfinge.querybuilder.mongodb.DatastoreProvider" em uma pasta META-INF/services no código-fonte do projeto. Neste arquivo coloque somente uma linha com o nome da classe que herda da DatastoreProvider.

org.esfinge.querybuilder.mongodb.MongoDBDatastoreProvider

Crie uma classe para ser persistida no Java/MongoDB. Utilize a anotação @Id para indicar quem é o indicador único do objeto/documento (ex.: classe Cliente)

Caso essa classe contenha referências, crie-as também (ex.: classe Cachorro e Pagamento



Crie uma classe que herde a DatastoreProvider, colocando a conexão do MongoDB em seu construtor. Lembrando de utilizar o IP/porta do teu banco, que por padrão é 127.0.0.1/27017. Sobreescreva o método getDatastore(), criando um Datastore passando a conexão do banco com o nome do DB utilizado. Utilize o método getMorphia().map() passando como parâmetro quais as classes que devem ser persistidas. A anotação @ServicePriority(1) serve para dar prioridade ao serviço, no caso maior prioridade (ex.: classe MongoDBDatastoreProvider).

 

Salvando um objeto

Crie um objeto da classe que herda da DatastoreProvider e a partir dele pegue o Datastore, com este objeto obtido, utilize o método save para salvar os objetos no banco.

 

Consultando dados

Crie um objeto da classe que herda da DatastoreProvider e a partir dele pegue o Datastore, com este objeto obtido, utilize o método find para retornar os objetos no banco, converta-o para uma List e itere-o.

 

 

 

Apoio

Todos os direitos reservados