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:
1 2 3 4 5 6 7 8 9 10 11 12 13 | ...
< 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | < 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:
1 | $ 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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.
1 2 3 4 5 6 7 8 9 10 | 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:
1 2 3 4 5 6 7 8 9 10 11 | 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.
1 2 3 4 5 6 7 8 9 10 11 | 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.