Thursday, April 30, 2015

Build com Gradle para construir projeto Spring Boot, MVC, JPA e QueryDSL

O Spring Boot foi criado para facilitar o setup e estrutura de projetos Java. Já estamos usando essa tecnologia na YaW a mais de um ano, com resultado bem satisfatório. Ele disponibiliza a infra necessária para criar e executar um projeto Java, stand-alone, em poucos passos. Fornece um web container embutido: Tomcat, Jetty ou Undertown. Integrado a ferramenta de build ele também turbina a configuração e gestão das dependências.

Exemplo de build.gradle:
buildscript {

  ext {
    springBootVersion = "1.3.0.BUILD-SNAPSHOT"
    springLoadedVersion = "1.2.0.RELEASE"
    queryDSLVersion = "3.6.3"
    hsqldbVersion = "2.3.2"
  }

  repositories {
    mavenLocal()
    mavenCentral()
    maven { url "http://repo.spring.io/release" }
    maven { url "http://repo.spring.io/milestone" }
    maven { url "http://repo.spring.io/snapshot" }
    maven { url "https://plugins.gradle.org/m2/" }
  }

  dependencies {
    classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
  }
}

apply plugin: "java"
apply plugin: "spring-boot"

sourceCompatibility = 1.8

repositories {
  mavenCentral()
  maven { url "http://repo.spring.io/snapshot" } 
}

sourceSets {
  generated {
    java {
      srcDirs = ["src/main/generated"]
    }
  }
}

configurations {
  querydslapt
}

dependencies {
  compile "org.springframework.boot:spring-boot-starter-data-jpa"
  compile "org.springframework.boot:spring-boot-starter-web"
  compile "com.mysema.querydsl:querydsl-jpa:${queryDSLVersion}"
  querydslapt "com.mysema.querydsl:querydsl-apt:${queryDSLVersion}"
  runtime "org.hsqldb:hsqldb:${hsqldbVersion}"
}

task generateQueryDSL(type: JavaCompile, group: "build", description: "Gera o codigo das classes do QueryDSL") {
  source = sourceSets.main.java
  classpath = configurations.compile + configurations.querydslapt
  options.compilerArgs = [
    "-proc:only",
    "-processor", "com.mysema.query.apt.jpa.JPAAnnotationProcessor"
  ]
  destinationDir = sourceSets.generated.java.srcDirs.iterator().next()
}

compileJava {
  dependsOn generateQueryDSL
  source generateQueryDSL.destinationDir
}

compileGeneratedJava {
  dependsOn generateQueryDSL
  classpath += sourceSets.main.runtimeClasspath
}

clean {
  delete sourceSets.generated.java.srcDirs
}

Por natureza o Gradle segue um sintaxe enxuta para o build. O Spring Boot atua como um wrapper para as dependências de Web e persistência. Precisamos indicar ao Gradle para carregar o driver HSQLDB em memória. O volume maior de configurações é por conta da task (generatedQueryDSL) para geração de código das entidades QueryDSL e das configurações de plugin. Compartilhei no Github esse arquivo build.gradle, junto com o projeto demonstração.

Para fazer o build e executar o projeto use a instrução:
$ gradle bootRun

@edermag

Sunday, April 26, 2015

Eficência na construção de consultas JPA com QueryDSL

A algum tempo incorporei o QueryDSL na stack de tecnologias utilizadas pela YaW. É uma ferramenta que vem me ajudando bastante no desenvolvimento de projetos Java. O QueryDSL é uma tecnologia unificada para a construção de consultas em projetos Java, através de diferentes mecanismos de persistência.

Além do Hibernate / JPA, o QueryDSL atua em conjunto com as seguintes tecnologias:
  • SQL (JDBC): construção de consultas tipadas sob JDBC (e driver);
  • JDO: construção de consultas tipadas sob o JDO (e provider SQL / NOSQL);
  • MongoDB: construção de consultas tipadas sob MongoDB (NOSQL);
  • Lucene: construção de consultas tipadas em full text search;
  • Coleções: consultas de pojos armazenados em coleções do Java;

Outro ponto interessante é que o QueryDSL pode ser utilizado com as linguagens Scala e Groovy. O projeto evoluiu bastante, apesar disso ainda mantém o foco principal: uma linguagem sofisticada e alto nível, para viabilizar a construção de consultas. Na minha opnião o ponto forte do QueryDSL, além da tipagem, é possibilidade de organizar e reaproveitar código para construir as consultas.

Bem, meu objetivo nesse post é demonstrar o QueryDSL com JPA / Hibernate, com banco de dados HSQLDB. Um exemplo de como construir consultas com QueryDSL, em entidades mapeadas via JPA. Compartilhei no Github um projeto que demonstra como trabalhar com o QueryDSL e JPA, vou usá-lo como referência. A primeira etapa é a configuração das dependências do QueryDSL e plugin complementar para Maven. No arquivo pom.xml, é possível visualizar as configurações de dependências com o conteúdo a seguir:
  ...
  <dependency>
    <groupId>com.mysema.querydsl</groupId>
    <artifactId>querydsl-apt</artifactId>
    <version>${querydsl.version}</version>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>com.mysema.querydsl</groupId>
    <artifactId>querydsl-jpa</artifactId>
    <version>${querydsl.version}</version>
  </dependency>
  ...

O plugin responsável por gerar as classes de consultas baseadas nas entidades da aplicação:

  <build>
    ...
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
                        
      <plugin>
        <groupId>com.mysema.maven</groupId>
        <artifactId>apt-maven-plugin</artifactId>
        <version>1.1.3</version>
        <executions>
          <execution>
            <goals>
              <goal>process</goal>
            </goals>
            <configuration>
              <outputDirectory>target/generated-sources/java</outputDirectory>
              <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

Dica: após baixar o projeto, antes de olhar o código fonte, execute o comando Maven:
$ mvn clean generate-sources

Dica para o Eclipse: será necessário adicionar a pasta target/generated-sources/java no classpath do projeto (em Java Build Path na aba Folders). A partir disso a toda nova entidade, será necessário executar a geração de código do Maven e refresh na pasta do Eclipse.

Uma característica importante do QueryDSL é a geração de código. A partir de cada entidade JPA mapeada na aplicação, o QueryDSL gera uma classe a qual iremos utilizar para construir as consultas. Por exemplo, para a entidade Mercadoria o QueryDSL gera a classe QMercadoria, uma extensão de Expression que representa uma expressão tipada. Cada atributo, original, da classe Mercadoria téra uma representação em QMercadoria. Por exemplo o atributo nome é definido como StringPath em QMercadoria, ou seja uma expressão tipada para String. Veja na classe QMercadoria o tipo do atributo categoria, uma outra entidade.

Com o ambiente configurado, a próxima etapa é construir as consultas com o QueryDSL. O código a seguir foi retirado da classe MercadoriaQuery, o método findAllByCriterio. A API DSL é utilizada para construir a consulta SQL sobre a entidade Mercadoria. Essa consulta varia de acordo com o preenchimento de dos filtros em uma página Web. Veja o código:
public static List<Mercadoria> findAllByCriterio(EntityManager em, 
    FiltrosPesquisaMercadoria filtros) {
  JPAQuery query = new JPAQuery(em);
  QMercadoria mercadoria = new QMercadoria("m");
  
  Predicate where = whereByCriterio(mercadoria, filtros);
  
  int offset = filtros.getOffset(); 
  return query.from(mercadoria)
    .where(where)
    .offset(offset)
    .limit(filtros.getLinhas())
    .list(mercadoria);
}

private static Predicate whereByCriterio(QMercadoria mercadoria, 
    FiltrosPesquisaMercadoria filtros) {
  BooleanBuilder builder = new BooleanBuilder();
  
  if (!Strings.isNullOrEmpty(filtros.getDescricaoMercadoria())) {
    builder.and(likeWithLowerCase(mercadoria.descricao.toLowerCase(), 
      filtros.getDescricaoMercadoria()));
  }
  if (!Strings.isNullOrEmpty(filtros.getNomeMercadoria())) {
    builder.and(likeWithLowerCase(mercadoria.nome, 
      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(likeWithLowerCase(mercadoria.categoria.descricao, 
      filtros.getCategoria()));
  }
  return builder;
}

O QueryDSL, via o método list, faz a conversão dos dados (ResultSet) usando o tipo Mercadoria. Enquanto o método whereByCriterio concentra a lógica para construir as condições da consulta SQL. Esse mesmo método é utilizado para construir outra consulta, responsável pelo contador da paginação.
public static long countByCriterio(EntityManager em, 
    FiltrosPesquisaMercadoria filtros) {
  JPAQuery query = new JPAQuery(em);
  QMercadoria mercadoria = new QMercadoria("m");
  
  Predicate where = whereByCriterio(mercadoria, filtros);
  return query.from(mercadoria)
    .where(where)
    .count();
}
Sobre a paginação, de volta ao método findAllByCriterio indico o offset e limit, informações necessárias para executar a consulta SQL usando a paginação.

O método findAllGroupedByCategoria é utilizado para construir uma consulta de Categorias, usando funções agregadoras SQL. Essa consulta retorna a quantidade de pedidos, o menor e maio preço por Categoria. O QueryDSL retorna esses dados encapsulados em um objeto do tipo Tuple. Mas é possível customizar um mapper, para converter um Tuple em um bean especifico. Verifique a classe CategoriaGroupMapper, veja como implementar um mapper para o QueryDSL. Veja o código para construir a consulta agrupada:
public static List<CategoriaGroup> findAllGroupedByCategoria(
    EntityManager em) {
  JPAQuery query = new JPAQuery(em);
  QMercadoria mercadoria = new QMercadoria("m");
  QCategoria categoria = new QCategoria("c");
    
  return query.from(mercadoria)
    .innerJoin(mercadoria.categoria, categoria)
    .groupBy(categoria.descricao)
    .list(new CategoriaGroupMapper(categoria, mercadoria));
}

Por fim, o último trecho desse post exibe uma outra funcionalidade do QueryDSL, a construção de comandos bulk UPDATE via API alto nível. O método updatePrecosByCriterio, define a lógica para aumentar o preço das mercadorias. Essa funcionalidade pode ser acionada a partir da listagem de Mercadorias, o comando UPDATE será construído de acordo com os filtros preenchidos no formulário.
public static long updatePrecosByCriterio(EntityManager em,
    double percentual, FiltrosPesquisaMercadoria filtros) {
  QMercadoria mercadoria = new QMercadoria("m");
  Predicate where = whereByCriterio(mercadoria, filtros);
  
  return new JPAUpdateClause(em, mercadoria)
    .where(where)
    .set(mercadoria.preco, 
      mercadoria.preco.doubleValue().multiply(percentual))
    .execute();
}

Nesse projeto, além de apresentar as funcionalidades do QueryDSL para JPA, utilizei outras tecnologias complementares:
  • Spring Boot: otimiza a organização de dependências do Maven e disponibiliza um container Web embutido (Tomcat) com a aplicação.
  • JQuery: framework Javascript.
  • Foundation: framework css.