Java Persistence Query Language (JPQL) é uma linguagem de consulta orientada a objetos definida como parte da especificação JPA (Java Persistence API).
Ao contrário do SQL tradicional, que opera diretamente sobre tabelas de banco de dados, JPQL trabalha com entidades JPA, que são mapeamentos de classes Java para tabelas do banco de dados.
Neste artigo, você vai aprender a como utilizar JPQL com Spring Boot para realizar operações de persistência e consultas em um banco de dados relacional. Vamos abordar desde a configuração do projeto até a execução de consultas complexas.
Configuração do Projeto Spring Boot
Use o Spring Initializr (https://start.spring.io/) para criar um novo projeto Spring Boot. Selecione as seguintes dependências:
- Spring Data JPA
- Spring Web
- Banco de dados (H2, MySQL, PostgreSQL, etc.)
Após gerar o projeto, importe-o em sua IDE preferida.
Configurar o Banco de Dados:
Configure o acesso ao banco de dados no arquivo application.properties ou application.yml.
Em nosso exemplo, vamos utilizar o H2, mais você pode seguir o tutorial em qualquer outro banco de dados.
spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=update
Criar Entidades JPA:
Vamos criar uma entidade simples chamada Produto.
package br.com.virandoprogramador.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Produto { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String nome; private double preco; // Getters and Setters }
Criar o Repositório JPA:
Crie um repositório para a entidade Produto, em nosso exemplo vamos criar com o nome ProdutoRepository.
package br.com.virandoprogramador.repository; import br.com.virandoprogramador.model.Produto; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; public interface ProdutoRepository extends JpaRepository<Produto, Long> { }
Usando JPQL com Spring Boot
JPQL permite a execução de consultas utilizando uma sintaxe orientada a objetos. Abaixo, estão alguns exemplos de consultas JPQL:
package br.com.virandoprogramador.repository; import br.com.virandoprogramador.model.Produto; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; public interface ProdutoRepository extends JpaRepository<Produto, Long> { @Query("SELECT p FROM Produto p WHERE p.preco > :preco") List<Produto> findProdutosComPrecoMaiorQue(@Param("preco") double preco); @Query("SELECT p FROM Produto p WHERE p.nome LIKE %:nome%") List<Produto> findProdutosPorNome(@Param("nome") String nome); }
Consultas com Join:
Suponha que temos uma entidade Categoria e queremos buscar produtos juntamente com suas categorias.
@Entity public class Categoria { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String nome; @OneToMany(mappedBy = "categoria") private List<Produto> produtos; } @Entity public class Produto { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String nome; private double preco; @ManyToOne @JoinColumn(name = "categoria_id") private Categoria categoria; }
@Query("SELECT p FROM Produto p JOIN p.categoria c WHERE c.nome = :nomeCategoria") List<Produto> findProdutosPorCategoria(@Param("nomeCategoria") String nomeCategoria);
Consultas com Funções de Agregação:
Para encontrar o preço médio dos produtos, podemos usar funções de agregação.
@Query("SELECT AVG(p.preco) FROM Produto p") double findPrecoMedio();
Boas Práticas com JPQL
Uso de Named Queries:
Named Queries são definidas em nível de entidade e são pré-compiladas no início da execução da aplicação, melhorando a performance.
@Entity @NamedQuery(name = "Produto.findByNome", query = "SELECT p FROM Produto p WHERE p.nome = :nome") public class Produto { }
Paginação e Ordenação:
Use a interface Pageable para consultas que retornam muitas entidades.
@Query("SELECT p FROM Produto p WHERE p.preco > :preco") Page<Produto> findProdutosComPrecoMaiorQue(@Param("preco") double preco, Pageable pageable);
Evite N+1 Problemas:
Use a anotação @EntityGraph para carregar entidades associadas, evitando consultas adicionais.
@EntityGraph(attributePaths = {"categoria"}) @Query("SELECT p FROM Produto p") List<Produto> findAllWithCategoria();
Exemplos de Consultas com @Query
Consulta Simples com Condição
Vamos buscar todos os produtos com preço maior que um valor específico.
@Query("SELECT p FROM Produto p WHERE p.preco > :preco") List<Produto> findProdutosComPrecoMaiorQue(@Param("preco") double preco);
Consulta com Like para Busca por Nome
Buscando produtos cujo nome contém uma substring específica.
@Query("SELECT p FROM Produto p WHERE p.nome LIKE %:nome%") List<Produto> findProdutosPorNome(@Param("nome") String nome);
Consulta com Join
Buscando produtos juntamente com suas categorias com base no nome da categoria.
@Query("SELECT p FROM Produto p JOIN p.categoria c WHERE c.nome = :nomeCategoria") List<Produto> findProdutosPorCategoria(@Param("nomeCategoria") String nomeCategoria);
Consulta com Ordenação
Buscando todos os produtos e ordenando-os por preço em ordem decrescente.
@Query("SELECT p FROM Produto p ORDER BY p.preco DESC") List<Produto> findAllProdutosOrderByPrecoDesc();
Consulta com Parâmetros Nomeados
Buscando produtos dentro de uma faixa de preços.
@Query("SELECT p FROM Produto p WHERE p.preco BETWEEN :minPreco AND :maxPreco") List<Produto> findProdutosInPriceRange(@Param("minPreco") double minPreco, @Param("maxPreco") double maxPreco);
Consulta com Contagem
Contando o número de produtos em uma determinada categoria.
@Query("SELECT COUNT(p) FROM Produto p WHERE p.categoria.nome = :nomeCategoria") long countProdutosByCategoria(@Param("nomeCategoria") String nomeCategoria);
Consulta Dinâmica com Caso Insensível
Buscando produtos por nome, ignorando maiúsculas e minúsculas.
@Query("SELECT p FROM Produto p WHERE LOWER(p.nome) LIKE LOWER(CONCAT('%', :nome, '%'))") List<Produto> findProdutosByNomeIgnoreCase(@Param("nome") String nome);
Implementação na Classe de Repositório
Aqui está um exemplo de como a classe do repositório pode parecer com todos esses métodos.
package br.com.virandoprogramador.repository; import br.com.virandoprogramador.model.Produto; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; public interface ProdutoRepository extends JpaRepository<Produto, Long> { @Query("SELECT p FROM Produto p WHERE p.preco > :preco") List<Produto> findProdutosComPrecoMaiorQue(@Param("preco") double preco); @Query("SELECT p FROM Produto p WHERE p.nome LIKE %:nome%") List<Produto> findProdutosPorNome(@Param("nome") String nome); @Query("SELECT p FROM Produto p JOIN p.categoria c WHERE c.nome = :nomeCategoria") List<Produto> findProdutosPorCategoria(@Param("nomeCategoria") String nomeCategoria); @Query("SELECT AVG(p.preco) FROM Produto p") double findPrecoMedio(); @Query("SELECT p FROM Produto p ORDER BY p.preco DESC") List<Produto> findAllProdutosOrderByPrecoDesc(); @Query("SELECT p FROM Produto p WHERE p.preco BETWEEN :minPreco AND :maxPreco") List<Produto> findProdutosInPriceRange(@Param("minPreco") double minPreco, @Param("maxPreco") double maxPreco); @Query("SELECT COUNT(p) FROM Produto p WHERE p.categoria.nome = :nomeCategoria") long countProdutosByCategoria(@Param("nomeCategoria") String nomeCategoria); @EntityGraph(attributePaths = {"categoria"}) @Query("SELECT p FROM Produto p") List<Produto> findAllWithCategoria(); @Query("SELECT p FROM Produto p WHERE LOWER(p.nome) LIKE LOWER(CONCAT('%', :nome, '%'))") List<Produto> findProdutosByNomeIgnoreCase(@Param("nome") String nome); @Query("SELECT p FROM Produto p WHERE p.preco > :preco") Page<Produto> findProdutosComPrecoMaiorQue(@Param("preco") double preco, Pageable pageable); }
Quando Utilizar JPQL em Vez de SQL Nativo?
Java Persistence Query Language (JPQL) e SQL nativo são ferramentas poderosas para a execução de consultas em bancos de dados relacionais. No entanto, cada uma tem seus próprios casos de uso e vantagens específicas.
A escolha entre JPQL e SQL nativo deve ser baseada nas necessidades do projeto e nas características das operações a serem realizadas. A seguir, exploramos os cenários em que JPQL pode ser mais vantajoso em comparação com SQL nativo.
Abaixo, vamos listar4 vantagens:
1. Abstração de Banco de Dados
JPQL oferece uma abstração que permite que o código seja mais independente do banco de dados subjacente. Consultas JPQL são escritas em termos de entidades JPA e seus relacionamentos, e não em termos de tabelas e colunas específicas de um banco de dados. Isso facilita a portabilidade entre diferentes sistemas de gerenciamento de banco de dados (SGBDs).
Cenário: Quando se espera que a aplicação possa mudar de banco de dados no futuro ou quando se deseja suportar múltiplos SGBDs sem modificar as consultas.
2. Trabalhar com Objetos e Relacionamentos
JPQL permite escrever consultas em termos de entidades e seus relacionamentos, aproveitando a modelagem orientada a objetos. Isso pode simplificar o desenvolvimento e a manutenção do código, especialmente em projetos complexos com muitos relacionamentos entre entidades.
Cenário: Em sistemas onde o modelo de dados é altamente relacional e a navegação entre entidades é comum, como em sistemas de gestão de conteúdo (CMS) ou sistemas de gestão de recursos empresariais (ERP).
3. Integração com JPA
Ao usar JPQL, você pode aproveitar todo o poder do JPA, incluindo cache de segundo nível, controle transacional automático e gerenciamento de entidades. Isso pode melhorar o desempenho e a consistência do aplicativo.
Cenário: Em aplicações onde o desempenho e a consistência são críticos, e onde se deseja aproveitar as otimizações automáticas de JPA, como em aplicativos financeiros ou de comércio eletrônico.
4. Consultas Dinâmicas e Segurança
JPQL, quando combinado com o uso de métodos de repositório Spring Data JPA, pode proporcionar uma forma mais segura e dinâmica de construir consultas, evitando injeção de SQL (SQL Injection). Parâmetros podem ser facilmente ligados, melhorando a segurança.
Cenário: Em aplicações que requerem a construção dinâmica de consultas baseadas em entrada de usuário ou parâmetros variáveis, como em motores de busca ou filtros de produtos em e-commerce.
Conclusão sobre JPQL
As consultas JPQL avançadas permitem a criação de funcionalidades ricas e complexas para a manipulação e recuperação de dados. Utilizando subconsultas, funções de agregação, CASE, JOIN FETCH, e outras técnicas, podemos construir consultas sofisticadas que atendem a uma ampla gama de requisitos de negócios.
Com Spring Boot e Spring Data JPA, a implementação dessas consultas torna-se intuitiva e eficiente.