Showing posts with label MongoDB. Show all posts
Showing posts with label MongoDB. Show all posts

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

Monday, March 30, 2015

Spark: Java Web com MongoDB a partir do método main

O Spark é um micro-container para aplicações Web em Java. Baseado no Sinatra, um ambiente para execução de aplicações Web em Ruby, trata-se de uma tecnologia leve e compacta. O projeto, até o momento, não é apadrinhado por nenhum player (empresa / fundação). A última versão emprega componentes e usa o modelo de programação do Java 8, tornando o código mais limpo e direto.

Trata-se de uma alternativa bem interessante para executar projetos, sem nenhuma burocracia, com foco apenas no código das funcionalidades. O trecho de código a seguir foi extraído do site do projeto, o tradicional HelloWorld, na versão Spark. Parecido com uma aplicação Java pura, o ponto de entrada é o método main. Nele usamos o método get do Spark para definir o fluxo de processamento para uma determinada URL:
import static spark.Spark.get;

public class HelloWorld {
    
    public static void main(String[] args) {
        get("/", (request, response) -> "Bem-vindo ao Spark!");
    }
    
}

Uma vez que a aplicação é executada e a URL http://localhost:4567/ acionada, o código dentro do método main é processado e o texto Bem-vindo ao Spark! apresentado pelo navegador. O Spark usa a porta 4567 por default.

Além do método get, na classe spark.Spark existem diversos métodos para semantica HTTP, como por exemplo: post, put, delete. A estrutura é bem simples, esses métodos possuem uma versão recebendo os argumentos:
  • String path: indica a URL que quando acionada executa o fluxo de código;
  • Route route: uma SAM (Lambda Java 8) que define o método handle responsável pelo fluxo de execução, com os argumentos request e response, retornando Object. 
Com o Maven, o setup do Spark é trivial. Basta definir a dependência para o JAR spark-core, de 89Kb.

Tenho usado o Spark a algum tempo, para implementar projetos pontuais. Vou destacar um projeto, baseado nas demonstrações da YaW, aonde demonstro como utilizar o MongoDB em uma aplicação Java Web. O projeto foi compartilhado e está disponível no meu Github. Outro detalhe sobre esse projeto é que o front-end foi desenvolvido com a tecnologia Freemaker, um template engine responsável por gerar HTML a partir de templates.

A seguir o trecho da classe MercadoriaController, responsável por carregar a lista de mercadorias:
    ...
    get("/", (request, response) -> {
        List mercadorias = mercadoriaDao.getAll();
        
        SimpleHash root = new SimpleHash();
        root.put("activeLista", true);
        root.put("mercadorias", mercadorias);
        
        if (request.params(":error") != null) {
            root.put("error", request.params("error"));
        }
            
        return new ModelAndView(root, "lista_template.ftl");
    }, new CustomFreeMarkerEngine());
    ...

Além de usar os componentes de acesso, o método get faz uso de FreeMarkerEngine para carregar o template Freemaker. A informação do template e o bean, com os dados dinâmicos para gerar o HTML são definidos no componente ModelAndView.

Nesse outro trecho da controller, o código que coloca a mercadoria em edição:
    ...
    get("/edit/:id", (request, response) -> {
        String idMercadoria = request.params(":id");
        Mercadoria mercadoria = null;
        
        try {
            mercadoria = mercadoriaDao.findById(new ObjectId(idMercadoria));
        } catch (Exception ex) {
            response.redirect("/?error=Registro não econtrado!");
            return null;
        }
         
        SimpleHash root = new SimpleHash();
        root.put("activeIncluir", true);
        root.put("mercadoria", new MercadoriaConverter().toRequest(mercadoria));
            
        return new ModelAndView(root, "mercadoria_template.ftl");
    }, new CustomFreeMarkerEngine());
    ...

Além do Freemaker, utilizamos nesse projeto o Bootstrap e JQuery.

No site oficial e no Github do Spark são disponibilizados maiores detalhes sobre o projeto.

@edermag

Thursday, August 01, 2013

Overview das ferramentas do Spring para persistência de dados

Durante o desenvolvimento ou manutenção (principalmente) de um projeto de software, seja qual for o estilo, é sempre delicado lidar com os componentes de infra-estrutura responsáveis pela camada de persistência.

Vou descrever alternativas que o Spring Framework oferece para simplificar esse trabalho, em cenários variados, como o foco em base de dados relacional. Ou seja, um overview das ferramentas do Spring para trabalhar com os componentes de persistência.

Código legado, bem legado...

Projetos Java antigos, desenvolvidos a + de 8 anos, normalmente não utilizam uma solução ORM (Mapeamento Objeto Relacional). Nesses projetos é muito comum o uso de bibliotecas "caseiras", escritas in house, para otimizar o uso do JDBC.

O Spring JDBC é um modulo do Spring interessante para esse tipo de cenário. Com ele é possível reduzir consideravelmente o volume de código JDBC. O principal componente do Spring JDBC é o JdbcTemplate, ele disponibiliza uma série de métodos para operações CRUD, consultas e comandos em lote. Para tirar proveito do uso contextos, injeção de dependências e inversão de controle, faz todo o sentido trabalhar em conjunto com o Spring Bean. Dessa forma seria possível injetar a referência do JdbcTemplate nos componente DAO (pattern Data Access Object).

O código a seguir demonstra um fragmento do DAO que utiliza o JdbcTemplate para inserir/atualizar uma entidade (Mercadoria).
@Component
public class MercadoriaDAO {

  //SQL
  private final static String INSERT_MERCADORIA = "INSERT INTO mercadoria (nome,descricao,preco,quantidade) VALUES (?,?,?,?)";
  private final static String UPDATE_MERCADORIA = "UPDATE mercadoria SET nome = ?, descricao = ?, preco = ?, quantidade = ? WHERE id = ?";
  private final static String GET_MERCADORIA_BY_ID = "SELECT * FROM mercadoria WHERE id = ?";
  private final static String GET_MERCADORIAS_BY_NOME = "SELECT * FROM mercadoria WHERE nome like ?";
  
  @Autowired
  private JdbcTemplate jdbcTemplate;
  
  public void save(Mercadoria m) {
    if (m.getId() == null) {
      jdbcTemplate.update(INSERT_MERCADORIA, 
        new Object[]{ m.getNome(), m.getDescricao(), m.getPreco() });
    } else {
      jdbcTemplate.update(UPDATE_MERCADORIA,
        new Object[]{ m.getNome(), m.getDescricao(), m.getPreco(), m.getId() });
    }
  }
  ...
}

Além do método update também pode ser utilizado para realizar a remoção da entidade. No trecho de código a seguir coloco dois exemplos de consultas, utilizando JdbcTemplate. Note que na consulta utilizamos o componente RowMapper, o MercadoriaRowMapper. O RowMapper é utilizado pelos métodos query de JdbcTemplate, ele lê os dados do ResultSet e faz a transformação em uma instância de Mercadoria.

O método queryForObject é utilizado para retornar uma instância da entidade (ou null) com filtro por id, por exemplo. Enquanto o método query retorna uma lista de objetos que podem ser encontrados de acordo com o SQL.
  ... //ainda em MercadoriaDAO
  
  private class MercadoriaRowMapper implements RowMapper<Mercadoria> {

    public Mercadoria mapRow(ResultSet rs, int row) throws SQLException {
      int id = rs.getInt("id");
      String nome = rs.getString("nome");
      String descricao = rs.getString("descricao");
      double preco = rs.getDouble("preco");
   
      return new Mercadoria(id, nome, descricao, qtde, preco);
    }
  }

  public Mercadoria findById(Integer id) {
    return jdbcTemplate.queryForObject(GET_MERCADORIA_BY_ID,
      new Object[] { id }, new MercadoriaRowMapper());
  }
  
  public List<Mercadoria> getMercadoriasByNome(String nome) {
    return jdbcTemplate.query(GET_MERCADORIAS_BY_NOME,
      new Object[] { nome + "%" }, new MercadoriaRowMapper());
  }
  ...

Outra estratégia, que não me agrada, seria fazer no DAO a Mercadoria uma herança para JdbcDaoSupport, e acessar o JdbcTemplate encapsulado nesse componente. Com Spring JDBC o código DAO pode ficar bem mais compacto. Para ilustrar isso, compare o DAO c/ JDBC puro e o DAO utilizando Spring JDBC. Veja também as configurações do Spring e o o pom.xml com as dependências para esses módulos.

Projetos com Hibernate

O Spring também oferece soluções para reduzir o esforço e agregar funcionalidades durante o desenvolvimento de projetos Java utilizando soluções ORM, como Hibernate ou JPA. O Spring ORM é outro módulo da suíte Spring, ele oferece funcionalidades para facilitar o uso de soluções baseadas em Mapeamento Objeto Relacional.

Em versões antigas do Hibernate, era trabalhoso manter a Session vinculada ao contexto de execução, por exemplo utilizar a mesma instância em diferentes DAOs dentro do mesmo fluxo de request. Por isso o Spring criou o HibernateTemplate, com a proposta de disponibilizar a Session corrente ao contexto de execução.

Mas a partir do Hibernate 3.0.1 com contextual sessions, isso não é mais necessário. Nas versões mais recentes do Spring é possível usar a Session no contexto de execução, além de centralizar as configurações e realizar a injeção da SessionFactory nos DAOs.

A seguir o código do DAO da Mercadoria com Hibernate:
@Component
public class MercadoriaDAO {

  @Autowired
  private SessionFactory sessionFactory;
  
  private final Session getCurrentSession(){
    return this.sessionFactory.getCurrentSession();
  }
  
  public void save(Mercadoria m) {
    if (m.getId() == null) {
      this.getCurrentSession().persist(m);
    } else {
      this.getCurrentSession().merge(m);
    }
  }
  
  public Mercadoria findById(Integer id) {
    return (Mercadoria) this.getCurrentSession().get(Mercadoria.class, id);
  }
  
  public List<Mercadoria> getMercadoriasByNome(String nome) {
    return this.getCurrentSession()
      .createQuery("from model.Mercadoria m where m.nome like ?")
      .setParameter(0, nome+"%")
      .list();
  }
  ...
}

O spring-config.xml a seguir centraliza no Spring as configurações com banco de dados e do Hibernate.  Nesse exemplo o banco de dados utilizado é o HSQLDB (local).
<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd">
  
  <context:component-scan base-package="." />
  
  <!-- SessionFactory, DataSource, ... -->

  <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="ds" />
    <property name="packagesToScan" value="model" />
  
    <property name="hibernateProperties">
      <props>
        <prop key="hibernate.dialect"> org.hibernate.dialect.HSQLDialect</prop>
        <prop key="hibernate.show_sql">true</prop>
        <prop key="hibernate.hbm2ddl.auto">create</prop>
      </props>
    </property>
  </bean>
  
  <bean id="ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
    <property name="url" value="jdbc:hsqldb:file:mercadoria"/>
    <property name="username" value="sa"/>
    <property name="password" value=""/>
  </bean>
  ...
</beans>

Projetos com JPA++

O Spring também oferece funcionalidades bem interessantes para projetos que utilizam a Java Persistence API (JPA), através do módulo Spring Data JPA. Através da interface JpaRepository o Spring define os métodos de consulta e CRUD. O desenvolvedor trabalha de forma alto-nível, criando uma interface de persistência, enquanto a implementação fica por conta do próprio Spring. Outro elemento do Spring Data JPA é a anotação @Query, responsável por informar consultas customizadas via JPAQL.

Para esse tipo de componente o Spring adota o pattern Repository, que abstrai uma coleção de objetos com o design mais próximo ao domínio da aplicação do que com o banco de dados. Veja como ficaria a interface MercadoriaRepository:
public interface MercadoriaRepository extends JpaRepository<Mercadoria, Integer> {

  @Query("select m from Mercadoria m where m.nome like ?1")
  List<Mercadoria> getMercadoriasByNome(String nome);

}

Uma vez que a interface foi definida, o Spring cria um proxy que implementa os métodos de persistência. Esse proxy será injetado em classe de negócio/controller pelo Spring. Veja o exemplo:
@Component
public class MercadoriaService {

  @Autowired
  private MercadoriaRepository repo;
  
  public void save(Mercadoria m) {
    m.save(m);
  }
  ...
}

Nesse exemplo demonstrei funcionalidades básicas da interface JpaRepository, mas existem outrs funcionalidades como o suporte a paginação da consulta SQL, veja esse exemplo. No github compartilhamos outros dois projetos que utilizam Spring Data JPA, uma aplicação web e outra desktop. Ambas fazem uso do JpaRepository.

Um pouco além: NoSQL

Na verdade esse módulo compõe o Spring Data, uma solução "guarda-chuva" com o objetivo de unificar e simplicar o armazenamento de dados em bancos relacionais e NoSQL. Abaixo desse projeto existe o módulo Spring Data MongoDB, responsável por abstrair o acesso ao MongoDB. Não é o proposito desse post abordar soluções NoSQL, mas para ter uma idéia o trecho de código a seguir demonstra repositório da Mercadoria (como Document) em versão MongoDB:
public interface MercadoriaRepository extends MongoRepository<User, String> { 
  
  @Query("{ nome: ?0 }")
  List<User> getMercadoriasByNome(String nome);
  
}

Abaixo do Spring Data ainda existem sub-projetos para Neo4j, Apache Hadoop, REST e outros. É possível saber um pouco mais sobre essa tecnologia, em artigo introdutório sobre Spring Data no InfoQ Brasil.

Esse é apenas um resumo de algumas soluções oferecidas pelo Spring Framework para resolver questões relacionadas a persistência em projetos Java. Sou da turma que gosta do Spring e de Java EE. Acredito que tirando proveito das melhoras funcionalidades das duas stacks, aumentamos o nosso poder fogo e logo a possibilidade de desenvolver um projeto com sucesso.

http://twitter.com/edermag
http://www.yaw.com.br

Wednesday, May 15, 2013

Configurar conexões remotas para o MongoDB no Ubuntu

Uma vez que o mongoDB já foi instalado no Ubuntu, é muito simples habilitar conexões remotas para o serviço. Basta indicar o uso do ip e porta no arquivo de configurações do mongoDB, mongodb.conf. (Acesse c/ super-usuário - sudo)

$ sudo gedit /etc/mongodb.conf

Inclua esses dois parametros:
bind_ip = 0.0.0.0
port = 27017

A propriedade port já vem comentada na instalação padrão.

Importante: nesse post não levo em consideração aspectos de segurança e restrição de acesso ao mongoDB.

Você pode acessar o mongoDB de outra máquina, indicando o ip dá maquina aonde o banco foi instalado. Esse acesso pode ocorrer via terminal ou com um ferramenta GUI para o MongoDB, como o UMongo (app p/ Linux).

É isso!

http://twitter.com/edermag
http://www.yaw.com.br