Codemountain, Paulo Suzart's Blog

Posts Tagged ‘spring

Adeus GenericAbstractBaseWhateverDAO!

with 3 comments

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. 🙂

Written by paulosuzart

outubro 8, 2009 at 3:49 pm

Publicado em Java, jpa, spring

Tagged with , , ,