Posts Tagged ‘spring’
Adeus GenericAbstractBaseWhateverDAO!
Update (09/10/09): Conversando com Daniel Guggi (Desenvolvedor Grepo), ele me alertou sobre a possibilidade de uma configuração mais enxuta no applicationContext.xml do Spring. Eu mantive essa configuração no post por ser a utilizada no projeto em questão. Daniel também falou sobre a versão estável do framework que será lançada até o final da semana que segue.
Outra vez longe do blog! Passei o último mês até o pescoço em um projeto um pouco apertado. Algo que acontece só as vezes não é mesmo?
Despois de quase um ano focado apenas em SOA e todo seu “pequeno” universo, precisei fazer o setup e os primeiros códigos/camadas deste projeto. Logo, juntando o pouco tempo para entrega com minha ausência na construção de aplicações standalone no último ano, temos como resultado: Desespero! 🙂 Mas um desespero divertido. ;p
A aplicação possui bastante CRUD, e na equipe começaram os rumores dos famosos AbstractDAO. Wow! Realmente não dá pra fazer o mesmo código sempre pra operações básicas. Mas construir seu próprio AbstractDAO tem lá seus riscos e desvantagens, a saber: não atingir um nível de abstração satisfatório que permita a construção realmente rápida de telas básicas, perda de tempo fazendo a abstração se não existir uma, discussões sobre quem não obedeceu a abstração corretamente, e coisas do gênero.
E é aí que entra o GREPO, um framework da Codehaus que se entitula como simples, flexível e consistênte. E é verdade. A simplicidade pode ser atribuída ao fato de nenhum implementação ser necessária para o seus DAOs. Isso sim é ganhar tempo!
O Grepo, ou Generic Repository permite iterações via JPA, Hibernate e ainda execução de procedures. Meu foco aqui é JPA. O Grepo trabalha em conjunto com o spring, então os exemplos aqui foram construídos numa aplicação com Spring. Chega de conversa e vamos ao primeiro DAO:
//Entidade @Entity public class Administrador { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @Basic private String email; @Temporal(TemporalType.TIMESTAMP) private Date cadastro; //get/set etc ... } //Código do DAO public interface AdministradorDAO extends ReadWriteJpaRepository<Administrador, Integer> {}
Supondo uma entidade Administrador de Primary Key do tipo Integer, criamos a nossa interface AdministradorDAO que extende ReadWriteJpaRepository passando a entidade e o tipo da sua chave como parâmetro de tipos (generics).
ReadWriteJpaRepository, como o nome já diz, fornece operações de leitura e escrita em uma entidade, ao contrário de ReadOnlyJpaRepository. E é só! Imagine agora o código de uma classe Service:
@Autowired private AdministradorDAO dao; public Administrador findById(Integer id) { return dao.find(id); } public void removeAdministrador(Administrador admin) { dao.remove(dao.merge(admin)); }
Na verdade, a interface herdada fornece todos os métodos do EntityManager JPA: find, remove, refresh, merge, lock e persist. E isso sem sem escrever código algum para o DAO. Mas sim, há necessidade de escrevermos consultas que permitam retornar entidades que não seja pelo seu id.
Mantendo a abordagem de código zero, definimos um método na interface do dao assim:
public interface AdministradorDAO extends ReadWriteJpaRepository<Administrador, Integer> { @GenericQuery(query = "from Administrador where email = :email") public Administrador getByEmail(@Param("email") String email); }
Quase dispensa explicações! adicionando este método, definimos via anotação a query que deve ser executada. O parâmetro nomeado JPA é mapeada para o argumento email passado ao método via anotação @Param(“email”). É possível usar named queries, passando em @GenericQuery o valor de queryName. O Grepo fornece algumas possibilidades para um bind de um método na interface para uma named query definida, por exemplo, na entidade.
Todo tipo de query jpa, incluindo o uso de QueryHints é possível. Operações de deleção, update, etc, tudo pode ser usado normalmente sem limitações.
Outra sitaução comum é a filtragem de entidades a partir de parâmetros preenchidos em um formulário de filtro html, o que exige uma query gerada dinamicamente a partir dos dados pesquisados. Numa tela de filtro de administradores onde a busca por e-mail e/ou data de cadastro usamos o AbstractJpaQueryGenerator para gerar a consulta:
public interface AdministradorDAO extends ReadWriteJpaRepository<Administrador, Integer> { @GenericQuery(query = "from Administrador where email = :email") public Administrador getByEmail(@Param("email") String email); @GenericQuery(queryGenerator=AdministradorFiltro.class) public List<Administrador> findByFilter(AdministradorFiltroBean filtro) public static class AdministradorFiltro extends AbstractJpaQueryGenerator { @Override public String generate(QueryMethodParameterInfo info) { AdministradorFiltroBean bean = info.getParameter(0, AdministradorFiltroBean.class); StringBuilder query = new StringBuilder(); query.append(" from Administrador where 1=1 "); if (bean.getEmail() != null){ query.append(" and email = :email "); this.addDynamicNamedParam(new DynamicNamedJpaParam("email", bean.getEmail())); } if (filtro.getDe() != null) { query.append(" and cadastro >= :de"); this.addDynamicNamedParam(new DynamicNamedJpaParam("de", bean.getDe())); } if (filtro.getAte() != null) { query.append(" and cadastro <= :ate"); this.addDynamicNamedParam(new DynamicNamedJpaParam("ate", bean.getAte())); } return query.toString(); } } }
Preenchendo o Bean de pesquisa que guarda o e-mail e data de início e fim de cadastro a pesquisar, passamos como argumento para o método findByFilter de AdministradorDAO e automagicamente o Grepo invoca o AdministradorFiltro, que por sua vez implementa o método generate.
QueryMethodParameterInfo armazena os parâmetros passados para a inferface de pesquisa, no nosso caso o único parâmetro passado é o Bean de Filtro. Note tambémm as classes DybamicNamedJpaParam, que é responsável por fazer o mapeamento do valor do Bean e um parâmetro nomeado.
Pronto, temos um filtro conciso!
Outro ponto que vale a pena observar é que os nomes de métodos no DAO estão todos em inglês. Isto por que o Grepo usa convention over configuration para decidir pela invocação de getSingleResult() ou getResultList() por exemplo. As convenções seguidas são a partir do prefixo dos métodos e podem ser: is|has|find|get|load|delete|updat.
Mas o Grepo não força o uso destes prefixos, voce pode escolher um nome qualquer e construir seu próprio QueryExecutor, ganhando flexibilidade como prometido. Confesso que foi ótimo usar estas convenções e a equie absorveu muito rápido o uso delas.
Você deve estar se perguntando onde configurar isso, ta tudo muito bonito e muito simples até agora. E continuará simples. Mas afinal de onde vem o código para a interface do DAO? Qual a instância injetada classe service acima via @Autowired?
Com pequenas configurações no seu Spring applicationContext.xml, você define seu repositório abstrato de onde todos os seus repositórios (leia-se DAO) irão herdar, passando pra ele o EntityManagerFactory, configurações de transação (opcional) e outras configurações do Grepo:
<!-- Configuracao repositorios --> <import resource="classpath:META-INF/spring/grepo-query-jpa-default.cfg.xml" /> <bean id="abstractRepositoryTarget" abstract="true"> <property name="entityManagerFactory" ref="entityManagerFactory" /> <property name="executorFindingStrategy" ref="grepo.queryExecutorFindingStrategy" /> <property name="resultConversionService" ref="grepo.resultConversionService" /> <property name="executorFactory" ref="grepo.defaultQueryExecutorFactory" /> </bean> <!-- Todo repositorio sera um filho do repositorio abstrato --> <bean id="abstractRepository" abstract="true"> <property name="interceptorNames" value="grepo.genericQueryIntroductionInterceptor" /> </bean> <!-- nossa interface --> <bean id="administradorDAO" parent="abstractRepository"> <property name="proxyInterfaces"> <value>com.codemountain.dao.AdministradorDAO</value> </property> <property name="target"> <bean parent="abstractRepositoryTarget"> <constructor-arg value="com.codemountain.Administrador" /> </bean> </property> </bean>
A interface é registrada no Spring, e o Grepo fornece a implementação que por traz utiliza Spring JPA Template através de um proxy dessa interface. A EntityManager Factory utilizada foi previamente configurada.
Um DAO Grepo pode ser transacional se a propriedade transactionTemplate for configurada com um transactionManager para o bean abstractRepositoryTarget.
A intenção do post é apresentar o básico deste framework que realmente conseguiu me chamar atenção. O projeto que citei no início do post está entrando em produção e os desenvolvedores envolvidos ficarm muito satisfeitos com o uso desta ferramenta.
De fato um DAO capaz de fornecer operações básicas é frequentemente necessário, o que não precisamos é gastar tempo construindo ou utilizando implementações não capazes de satisfazer as necessidades do projeto. Experimente, vale a pena. Até a próxima. 🙂