Introdução ao Querydsl

Introdução ao Querydsl

1. Introdução

Este é um artigo introdutório para ajudá-lo a começar a trabalhar com a poderosa APIQuerydsl para persistência de dados.

O objetivo aqui é fornecer as ferramentas práticas para adicionar o Querydsl ao seu projeto, entender a estrutura e o objetivo das classes geradas e obter um entendimento básico de como escrever consultas de banco de dados com segurança de tipo para os cenários mais comuns.

2. O objetivo do Querydsl

As estruturas de mapeamento objeto-relacional estão no centro do Enterprise Java. Isso compensa a incompatibilidade entre a abordagem orientada a objetos e o modelo de banco de dados relacional. Eles também permitem que os desenvolvedores escrevam código de persistência mais limpo e conciso e lógica de domínio.

No entanto, uma das opções de design mais difíceis para uma estrutura ORM é a API para criar consultas corretas e com segurança de tipo.

Uma das estruturas Java ORM mais amplamente usadas, o Hibernate (e também o padrão JPA intimamente relacionado), propõe uma linguagem de consulta baseada em string HQL (JPQL) muito semelhante ao SQL. As desvantagens óbvias dessa abordagem são a falta de segurança de tipo e a ausência de verificação de consulta estática. Além disso, em casos mais complexos (por exemplo, quando a consulta precisa ser construída em tempo de execução, dependendo de algumas condições), a criação de uma consulta HQL geralmente envolve concatenação de strings, o que geralmente é muito inseguro e propenso a erros.

O padrão JPA 2.0 trouxe uma melhoria na forma deCriteria Query API - um método novo e seguro para o tipo de construção de consultas que aproveitou as classes de metamodelo geradas durante o pré-processamento de anotação. Infelizmente, por ser inovadora em sua essência, a API de consulta de critérios acabou sendo muito detalhada e praticamente ilegível. Aqui está um exemplo do tutorial Java EE para gerar uma consulta tão simples quantoSELECT p FROM Pet p:

EntityManager em = ...;
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(Pet.class);
Root pet = cq.from(Pet.class);
cq.select(pet);
TypedQuery q = em.createQuery(cq);
List allPets = q.getResultList();

Não é à toa que logo surgiu uma bibliotecaQuerydsl mais adequada, baseada na mesma ideia das classes de metadados geradas, mas implementada com uma API fluente e legível.

3. Geração de classe Querydsl

Vamos começar gerando e explorando as metaclasses mágicas responsáveis ​​pela API fluente do Querydsl.

3.1. Adicionando Querydsl ao Maven Build

Incluir Querydsl em seu projeto é tão simples quanto adicionar várias dependências ao arquivo de construção e configurar um plug-in para processar anotações JPA. Vamos começar com as dependências. A versão das bibliotecas Querydsl deve ser extraída para uma propriedade separada dentro da seção<project><properties>, como segue (para a versão mais recente das bibliotecas Querydsl, verifique o repositórioMaven Central):


    4.1.3

Em seguida, adicione as seguintes dependências à seção<project><dependencies> do arquivopom.xml:



    
        com.querydsl
        querydsl-apt
        ${querydsl.version}
        provided
    

    
        com.querydsl
        querydsl-jpa
        ${querydsl.version}
    

A dependênciaquerydsl-apt é uma ferramenta de processamento de anotações (APT) - implementação da API Java correspondente que permite o processamento de anotações em arquivos de origem antes de passarem para o estágio de compilação. Essa ferramenta gera os chamados Q-types - classes que se relacionam diretamente às classes de entidade do seu aplicativo, mas são prefixadas com a letra Q. Por exemplo, se você tiver uma classeUser marcada com a anotação@Entity em seu aplicativo, o tipo Q gerado residirá em um arquivo de origemQUser.java.

O escopoprovided da dependênciaquerydsl-apt significa que esse jar deve ser disponibilizado apenas no momento da construção, mas não incluído no artefato do aplicativo.

A biblioteca querydsl-jpa é o próprio Querydsl, projetado para ser usado junto com um aplicativo JPA.

Para configurar o plugin de processamento de anotação que tira vantagem dequerydsl-apt, adicione a seguinte configuração de plugin ao seu pom - dentro do elemento<project><build><plugins>:


    com.mysema.maven
    apt-maven-plugin
    1.1.3
    
        
            
                process
            
            
                target/generated-sources/java
                com.querydsl.apt.jpa.JPAAnnotationProcessor
            
        
    

Este plug-in garante que os tipos Q sejam gerados durante o objetivo do processo de criação do Maven. A propriedade de configuraçãooutputDirectory aponta para o diretório onde os arquivos de origem do tipo Q serão gerados. O valor dessa propriedade será útil mais tarde, quando você explorar os arquivos Q.

Você também deve adicionar esse diretório às pastas de origem do projeto, se o seu IDE não fizer isso automaticamente - consulte a documentação do seu IDE favorito sobre como fazer isso.

Para este artigo, usaremos um modelo JPA simples de um serviço de blog, consistindo emUsers e seusBlogPosts com uma relação um-para-muitos entre eles:

@Entity
public class User {

    @Id
    @GeneratedValue
    private Long id;

    private String login;

    private Boolean disabled;

    @OneToMany(cascade = CascadeType.PERSIST, mappedBy = "user")
    private Set blogPosts = new HashSet<>(0);

    // getters and setters

}

@Entity
public class BlogPost {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private String body;

    @ManyToOne
    private User user;

    // getters and setters

}

Para gerar Q-types para o seu modelo, basta executar:

mvn compile

3.2. Explorando classes geradas

Agora vá para o diretório especificado na propriedadeoutputDirectory do apt-maven-plugin (target/generated-sources/java em nosso exemplo). Você verá um pacote e uma estrutura de classes que refletem diretamente seu modelo de domínio, exceto que todas as classes começam com a letra Q (QUsereQBlogPost em nosso caso).

Abra o arquivoQUser.java. Este é o seu ponto de entrada para construir todas as consultas que têmUser como entidade raiz. A primeira coisa que você notará é a anotação@Generated, o que significa que este arquivo foi gerado automaticamente e não deve ser editado manualmente. Se você alterar qualquer uma das classes do seu modelo de domínio, terá que executarmvn compile novamente para regenerar todos os Q-types correspondentes.

Além de vários construtoresQUser presentes neste arquivo, você também deve observar uma instância final estática pública da classeQUser:

public static final QUser user = new QUser("user");

Essa é a instância que você pode usar na maioria das suas consultas Querydsl nessa entidade, exceto quando precisar escrever algumas consultas mais complexas, como juntar várias instâncias diferentes de uma tabela em uma única consulta.

A última coisa que deve ser observada é que para cada campo da classe de entidade existe um campo*Path correspondente no tipo Q, comoNumberPath id,StringPath logineSetPath blogPosts na classeQUser (observe que o nome do campo correspondente aSet está pluralizado). Esses campos são usados ​​como partes da API de consulta fluente que encontraremos mais adiante.

4. Consultando com Querydsl

4.1. Consulta e filtragem simples

Para construir uma consulta, primeiro precisamos de uma instância de aJPAQueryFactory, que é a forma preferida de iniciar o processo de construção. A única coisa queJPAQueryFactory precisa é umEntityManager, que já deve estar disponível em seu aplicativo JPA por meio da chamadaEntityManagerFactory.createEntityManager() ou injeção@PersistenceContext.

EntityManagerFactory emf =
  Persistence.createEntityManagerFactory("org.example.querydsl.intro");
EntityManager em = entityManagerFactory.createEntityManager();
JPAQueryFactory queryFactory = new JPAQueryFactory(em);

Agora vamos criar nossa primeira consulta:

QUser user = QUser.user;

User c = queryFactory.selectFrom(user)
  .where(user.login.eq("David"))
  .fetchOne();

Observe que definimos uma variável local do usuárioQUser e a inicializamos com a instância estáticaQUser.user. Isso é feito puramente por questões de brevidade, como alternativa, você pode importar o campoQUser.user estático.

O métodoselectFrom deJPAQueryFactory começa a construir uma consulta. Passamos a instânciaQUsere continuamos construindo a cláusula condicional da consulta com o método.where(). Ouser.login é uma referência a um campoStringPath da classeQUser que vimos antes. O objetoStringPath também possui o método.eq() que permite continuar construindo de forma fluente a consulta especificando a condição de igualdade do campo.

Finalmente, para buscar o valor do banco de dados no contexto de persistência, finalizamos a cadeia de construção com a chamada ao métodofetchOne(). Este método retornanull se o objeto não pode ser encontrado, mas lança umNonUniqueResultException se houver várias entidades que satisfaçam a condição.where().

4.2. Ordenação e agrupamento

Agora, vamos buscar todos os usuários em uma lista, classificados pelo logon em ordem crescente.

List c = queryFactory.selectFrom(user)
  .orderBy(user.login.asc())
  .fetch();

Essa sintaxe é possível porque as classes*Path possuem os métodos.asc()e.desc(). Você também pode especificar vários argumentos para o método.orderBy() para classificar por vários campos.

Agora vamos tentar algo mais difícil. Suponha que precisamos agrupar todas as postagens por título e contar títulos duplicados. Isso é feito com a cláusula.groupBy(). Também queremos solicitar os títulos pela contagem de ocorrências resultante.

NumberPath count = Expressions.numberPath(Long.class, "c");

List userTitleCounts = queryFactory.select(
  blogPost.title, blogPost.id.count().as(count))
  .from(blogPost)
  .groupBy(blogPost.title)
  .orderBy(count.desc())
  .fetch();

Selecionamos o título da postagem do blog e a contagem de duplicatas, agrupando por título e, em seguida, ordenando por contagem agregada. Observe que primeiro criamos um alias para o campocount() na cláusula.select(), porque precisávamos fazer referência a ele na cláusula.orderBy().

4.3. Consultas complexas com associações e subconsultas

Vamos encontrar todos os usuários que escreveram uma postagem intitulada "Olá, mundo!" Para essa consulta, poderíamos usar uma junção interna. Observe que criamos um aliasblogPost para a tabela unida para referenciá-la na cláusula.on():

QBlogPost blogPost = QBlogPost.blogPost;

List users = queryFactory.selectFrom(user)
  .innerJoin(user.blogPosts, blogPost)
  .on(blogPost.title.eq("Hello World!"))
  .fetch();

Agora vamos tentar alcançar o mesmo com a subconsulta:

List users = queryFactory.selectFrom(user)
  .where(user.id.in(
    JPAExpressions.select(blogPost.user.id)
      .from(blogPost)
      .where(blogPost.title.eq("Hello World!"))))
  .fetch();

Como podemos ver, as subconsultas são muito semelhantes às consultas e também são bastante legíveis, mas começam com métodos de fábricaJPAExpressions. Para conectar subconsultas à consulta principal, como sempre, fazemos referência aos aliases definidos e usados ​​anteriormente.

4.4. Modificando Dados

JPAQueryFactory permite não apenas construir consultas, mas também modificar e deletar registros. Vamos alterar o login do usuário e desativar a conta:

queryFactory.update(user)
  .where(user.login.eq("Ash"))
  .set(user.login, "Ash2")
  .set(user.disabled, true)
  .execute();

Podemos ter qualquer número de cláusulas.set() que quisermos para diferentes campos. A cláusula.where() não é necessária, então podemos atualizar todos os registros de uma vez.

Para excluir os registros correspondentes a uma determinada condição, podemos usar uma sintaxe semelhante:

queryFactory.delete(user)
  .where(user.login.eq("David"))
  .execute();

A cláusula.where() também não é necessária, mas tome cuidado, porque a omissão da cláusula.where() resulta na exclusão de todas as entidades de um determinado tipo.

Você pode se perguntar por queJPAQueryFactory não tem o método.insert(). Essa é uma limitação da interface JPA Query. O métodojavax.persistence.Query.executeUpdate() subjacente é capaz de executar instruções de atualização e exclusão, mas não de inserção. Para inserir dados, você deve simplesmente manter as entidades com o EntityManager.

Se você ainda deseja tirar proveito de uma sintaxe Querydsl semelhante para inserir dados, você deve usar a classeSQLQueryFactory que reside na biblioteca querydsl-sql.

5. Conclusão

Neste artigo, descobrimos uma API poderosa e segura para tipos de manipulação persistente de objetos, fornecida pelo Querydsl.

Aprendemos a adicionar o Querydsl ao projeto e exploramos os Q-types gerados. Também abordamos alguns casos de uso típicos e apreciamos sua concisão e legibilidade.

Todo o código-fonte dos exemplos pode ser encontrado emgithub repository.

Finalmente, há, é claro, muitos outros recursos que o Querydsl fornece, incluindo trabalhar com SQL bruto, coleções não persistentes, bancos de dados NoSQL e pesquisa de texto completo - e iremos explorar alguns deles em artigos futuros.