Tuesday, May 26, 2015

Java e NoSQL: usando o MongoDB com Spring Data e QueryDSL

Interessante como algumas tecnologias conseguem ser úteis e flexíveis. Esse é um dos pontos que eu mais admiro no Spring Data. É possível usar seus recursos, conceito e o modelo de componentes em diferentes mecanismos de persistência. Nesse post eu volto a escrever sobre o Spring Data e o QueryDSL, mas em outro contexto, minha idéia é demonstrar como usar essas tecnologias para persistir dados no MongoDB.

Como referência vou utilizar um projeto que compartilhei no Github. Trata-se de uma aplicacação simples com uma página de listagem e pesquisa com filtros variados. A simulação de um cadastro de Mercadoria, um pojo marcado com @Entity. Anotação do Morphia, tecnologia para mapeamento objeto em documento, fazendo o meio campo entre objetos Java e coleções do MongoDB. Na verdade o QueryDSL utiliza o Morphia para gerar a estrutura de consultas para o MongoDB. O plugin do QueryDSL para gerar a estrutura de consultas, por entidade, utiliza a API do Morphia. Veja as configurações do plugin APT do QueryDSL no pom.xml ou build.gradle.

O papel do QueryDSL nesse projeto é pontual, criar o Predicate com as condições variadas para a consulta das coleções no MongoDB. A consulta varia de acordo com os filtros preenchidos pelo formulário de pesquisa (lista de mercadorias). O código a seguir, da classe MercadoriaQuery, demonstra como construir o Predicate:
  ...
  public static Predicate whereByCriterio(FiltrosPesquisaMercadoria filtros) {
    QMercadoria mercadoria = QMercadoria.mercadoria;
    BooleanBuilder builder = new BooleanBuilder();
    if (!Strings.isNullOrEmpty(filtros.getDescricaoMercadoria())) {
      builder.and(mercadoria.descricao.startsWithIgnoreCase(
        filtros.getDescricaoMercadoria()));
    }
    if (!Strings.isNullOrEmpty(filtros.getNomeMercadoria())) {
      builder.and(mercadoria.nome.startsWithIgnoreCase(
        filtros.getNomeMercadoria()));
    }
    if (filtros.getPrecoDe() != null && filtros.getPrecoDe() > 0) {
      builder.and(mercadoria.preco.goe(filtros.getPrecoDe()));
    }
    if (filtros.getPrecoAte() != null && filtros.getPrecoAte() > 0) {
      builder.and(mercadoria.preco.loe(filtros.getPrecoAte()));
    }
    if (!Strings.isNullOrEmpty(filtros.getCategoria())) {
      builder.and(mercadoria.categoria.descricao.startsWithIgnoreCase(
        filtros.getCategoria()));
    }
    return builder;
  } 
  ...

Além das condições para construir a consulta, é importante notar outra característica sobre front-end: a lista de mercadorias apresenta os registros organizados por página. O Spring Data é o responsável por resolver a paginação da consulta com MongoDB. Na classe CriteriaUtil, definos o método para criar o Pageable, com informações da página, quantidade de registros e campo de ordenação. Além disso, essa classe também define um método para montar uma expressão like p/ o MongoDB via QueryDSL.
public final class CriteriaUtil {

  public static Pageable buildPageRequest(int page, int rows, 
      String sortBy) {
    Sort sort = new Sort(Sort.Direction.ASC, sortBy);
    return new PageRequest(page, rows, sort);
  }

  public static BooleanExpression likeWithLowerCase(
      StringExpression field, String expression) {
    String filterValue = String.format("%s%s",expression.toLowerCase(), "%");
    return field.toLowerCase().like(filterValue);
  }
}

Olhando para o principal componente do Spring Data, temos o repositório MercadoriaRepository. Um contrato que define as operações de persistência sobre a coleção Mercadoria. Parecido com o que acontece com o JPA o repositório extende MongoRepository, interface com as operações de persistência (CRUD) de uma determinada coleção. Já outra extensão, de QueryDSLPredicateExecutor, permite que repositório tenha a capacidade de realizar consultas aplicando Predicates do QueryDSL. Veja o código do repositório:
@Repository
public interface MercadoriaRepository
  extends MongoRepository<Mercadoria, Long>, 
    QueryDslPredicateExecutor<Mercadoria> {

  List<Mercadoria> findByDescricaoLike(String descricao);

  @Query(value="{ 'quantidade': { $gte: ?0 } }")
  List<Mercadoria> findByQuantidadeEqualOrGreather(
    Integer quantidade);

  default List<CategoriaGroup> groupByCategorias(
      MongoTemplate template) {
    Aggregation agg = newAggregation(
      group("categoria.descricao").count().as("qtdMercadorias"),
      project("qtdMercadorias").and("categoria.descricao").previousOperation(),
      sort(Sort.Direction.DESC, "qtdMercadorias"));
    AggregationResults<CategoriaGroup> groupResults = 
      template.aggregate(agg, Mercadoria.class, CategoriaGroup.class);
    return groupResults.getMappedResults();
  }
}

O Spring Data constrói o proxy que implementa o contrato repostiório, de forma transparente para o restante da aplicação.

Além dos métodos padrões, das duas interfaces citadas, defino três consultas customizadas:

  • findByDescricaoLike(String): o Spring Data gera o código da consulta, respeitando o nome do método. Nesse caso usando como filtro o campo descricao e operador de consulta like, aplicando o argumento informado. Esse um recurso do Spring Data, independente do MongoDB, funciona tanto em banco de dados NoSQL quanto em relacionais.
  • findByQuantidadeEqualOrGreather(Integer): nesse caso o Spring Data implementa o método usando a consulta definida via anotação @Query.
  • groupByCategorias: método utiliza API do Spring Data para gerar consultas com funções agregadoras do MongoDB (link). Um contador simples de Mercadorias, agrupados por Categoria, um campo (embedded) do documento.


Na controller MercadoriaController, defino diversos métodos que operam sobre o repositório. O destaque fica para o método list, que é acionado para consultar as Mercadorias de acordo com os filtros do formulário de pesquisa.
  ...
  @RequestMapping(method = RequestMethod.GET)
  public PesquisaMercadorias list(FiltrosPesquisaMercadoria filtros) {
    Pageable page = buildPageRequest(filtros.getPagina(), 
      filtros.getLinhas(), filtros.getOrdem());

    Predicate predicate = whereByCriterio(filtros);
    long total = repository.count(predicate);
    List<Mercadoria> mercadorias = 
      Lists.newArrayList(repository.findAll(predicate, page));
    return new PesquisaMercadorias(total, mercadorias);
  }
  ...

No startup da aplicação realizo o setup dos dados, acesse e analise o código da classe Application.

Outros detalhes sobre o projeto:

  • O build e as dependências do projeto são controladas pelo Maven e/ou Gradle;
  • Spring MVC atua como framework web;
  • JQuery e Foundation no front-end da aplicação;


www.yaw.com.br

Tuesday, May 12, 2015

Combinando Spring Data, QueryDSL e JPA / Hibernate para persistência em Java

Mais um post falando sobre persistência em Java com QueryDSL, mas agora explorando também o Spring Data. O Spring Data é uma tecnologia criada para facilitar a criação de componentes de acesso a dados via: bancos relacionais, NoSQL,  
map-reduce e serviços baseados em cloud.

Olhando para base de dados relacionais, o Spring Data oferece componentes para JPA e JDBC. Em outro post escrevi sobre o QueryDSL, uma ferramenta para produzir consultas flexíveis encima da JPA (e outros mecanismos de persistência) com uma DSL bem interessante. Evoluindo aquela abordagem, meu objetivo é apresentar outra estratégia para persistência unindo o QueryDSL e Spring Data para JPA / Hibernate.

Compartilhei no Github um projeto que pode ser referência para esse post. Outro detalhe importante é que o projeto foi construído com o Spring Boot, que disponibiliza um Tomcat embutido. A classe Application é o ponto de entrada da aplicação, aonde ficam contidas as configurações do projeto. É o caso da anotação EnableJpaRepositories, que indica o pacote base dos componentes Repositories. O código a seguir demonstra o componente MercadoriaRepository, uma interface anotada como Repository responsável pela operações de persistência da entidade Mercadoria.
@Repository
public interface MercadoriaRepository
  extends JpaRepository<Mercadoria, Long>,
    QueryDslPredicateExecutor<Mercadoria> {
  …
}

JpaRepository é uma interface do Spring que define operações básicas de persistência sobre uma determinada entidade. Operações para CRUD, paginação e ordenação. Já a interface QueryDslPredicateExecutor atua como uma bridge entre o Spring Data e QueryDSL. Através dela é possível usar Predicates construídos via QueryDSL em consultas via Spring Data.

É possível e válido definir uma classe concreta como Repository. Mas prefiro definir esses componentes como contratos, sendo o Spring o responsável por escrever código que implementa (proxy) as interfaces. Mesmo em interface, o Spring Data permite a definição de métodos para consultas customizados.

No controller, o proxy de MercadoriaRepository é injetado pelo Spring, via anotação Autowired. A seguir trecho de código do componente MercadoriaController, com destaque para a definição do Repository e o método list, responsável por realizar a consulta de Mercadorias de acordo com os filtros informados:
@RestController
@RequestMapping(value="/")
public class MercadoriaController {
  
  @Autowired
  private MercadoriaRepository repository;
   
  @RequestMapping(method = RequestMethod.GET)
  public PesquisaMercadorias list(FiltrosPesquisaMercadoria filtros) {
    Pageable page = buildPageRequest(filtros.getPagina(), 
      filtros.getLinhas(), filtros.getOrdem());
    
    Predicate predicate = whereByCriterio(filtros);
    long total = repository.count(predicate);
    List<Mercadoria> mercadorias =
      Lists.newArrayList(repository.findAll(predicate, page));
    return new PesquisaMercadorias(total, mercadorias);
  }
  ...
}

O primeiro passo é construir Pageable com as configurações para paginação e ordenação via buildPageRequest. Depois construir o Predicate com as condições da consulta, via whereByCriterio, que será utilizado no contador de registros (informação para paginação) e para processar a consulta das mercadorias. Ambos os métodos count(Predicate) e findAll(Predicate) são definidos em QueryDSLPredicatorExecutor.

Os métodos save e delete, definidos em JpaRepository (CrudRepository), também são acionados pelo controller:
  @RequestMapping(method = RequestMethod.POST)
  public void save(Mercadoria m) {
    repository.save(m);
  }
 
  @RequestMapping(method = RequestMethod.DELETE)
  public void delete(Long mercadoriaId) {
    repository.delete(mercadoriaId);
  }

Todas as operações de persistência foram centralizadas na interface MercadoriaRepository, mas ela delega o update em batch e consulta de tuplas para MercadoriaQuery, via métodos default do Java 8.

Outros detalhes sobre o projeto:
  • O build e as dependências do projeto são controladas pelo Maven e/ou Gradle;
  • Spring MVC atua como framework web;
  • JQuery e Foundation no front-end da aplicação