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.
No comments:
Post a Comment