Capítulo 07: Spring Data JPA Repositories, Query Methods e Consultas JPQL
Especialização em Backend com Java & Spring Boot • Spring Boot 3 & Java 21 LTS • Spring Data JPA, Security, Microsserviços e Cloud
🗺️ Mapa Conceitual do Tópico
flowchart TD
A["Cliente HTTP / Frontend"] --> B["API Gateway / Router"]
B --> C["Controller / Handler"]
C --> D["Service Layer (Regras de Negócio)"]
D --> E["Repository / ORM (Persistência)"]
E --> F["Banco de Dados / Cache"]
subgraph ARQ["Arquitetura do Capítulo"]
G["Conceito: Spring Data JPA Repositories, Query Methods e Consultas JPQL"]
H["Segurança, Validação e Resiliência"]
I["Alta Performance e Escalabilidade"]
end
D --> ARQ
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style C fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style D fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style E fill:#fce4ec,stroke:#e91e63,stroke-width:2px
style F fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
🏛️ 1. Fundamentos Técnicos de Spring Data JPA Repositories, Query Methods e Consultas JPQL
Repositórios são proxies gerados em runtime. Ao declarar interface ProdutoRepository extends JpaRepository<Produto, Long>, nenhuma implementação é escrita à mão: o Spring Data cria, na inicialização do contexto, um proxy dinâmico (via SimpleJpaRepository) que implementa save, findById, delete, findAll etc. usando o EntityManager internamente. O tipo genérico informa a entidade e o tipo da chave primária.
Derived Query Methods. O nome do método é interpretado por um parser de convenção: findByEmailAndAtivoTrue(String email) vira SELECT p FROM Produto p WHERE p.email = ?1 AND p.ativo = true. Palavras-chave como GreaterThan, Containing, OrderBy, Between e In compõem cláusulas WHERE/ORDER BY sem escrever SQL. É produtivo para consultas simples, mas nomes de método crescem rápido em complexidade — a partir de 3-4 condições, @Query costuma ser mais legível.
@Query com JPQL e SQL nativo. JPQL opera sobre entidades e seus atributos (não sobre nomes de tabela/coluna reais), sendo portável entre bancos. Parâmetros nomeados via @Param protegem contra SQL Injection (o driver faz bind seguro, nunca concatenação de string). Quando a consulta exige recursos específicos do banco (CTEs, funções de janela, hints de otimizador), usa-se @Query(value = "...", nativeQuery = true), abrindo mão da portabilidade em favor de performance/expressividade.
Projeções. Retornar a entidade completa quando só se precisa de 2 campos desperdiça banda e memória. Interface-based projections (interfaces com apenas os getters desejados, ex. getId()/getNome()) instruem o Spring Data a gerar um SELECT que traz somente essas colunas. Projeções “fechadas” (todos os getters mapeiam diretamente atributos da entidade) permitem essa otimização; projeções “abertas” (com @Value e SpEL) ainda buscam a entidade inteira.
Paginação e ordenação. Declarar Pageable como parâmetro e retornar Page<T> faz o Spring Data aplicar LIMIT/OFFSET automaticamente e executar uma segunda consulta COUNT para preencher totalElements/totalPages. Slice<T> evita o COUNT quando só se precisa saber “existe próxima página?”, sendo mais barato em tabelas grandes.
Specifications e Criteria API. Para filtros dinâmicos (o usuário pode ou não informar nome, faixa de preço, status), concatenar strings SQL é arriscado e frágil. Specification<T> encapsula cada filtro como uma função (root, query, cb) -> Predicate, combinável com .and()/.or() — o repositório estende JpaSpecificationExecutor<T> para aceitar essas especificações em findAll(Specification<T> spec).
💻 2. Código de Demonstração Corporativo
package com.empresa.api.repository;
import com.empresa.api.model.Produto;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.math.BigDecimal;
import java.util.List;
public interface ProdutoRepository extends JpaRepository<Produto, Long>, JpaSpecificationExecutor<Produto> {
// Derived query method: interpretado pelo nome do método.
List<Produto> findByPrecoGreaterThanOrderByPrecoAsc(BigDecimal precoMinimo);
// JPQL com parâmetro nomeado, seguro contra SQL Injection.
@Query("SELECT p FROM Produto p WHERE p.categoria.id = :categoriaId AND p.ativo = true")
List<Produto> buscarAtivosPorCategoria(@Param("categoriaId") Long categoriaId);
// Paginação nativa do Spring Data.
Page<Produto> findByNomeContainingIgnoreCase(String nome, Pageable pageable);
// Projeção fechada: SELECT apenas de id e nome.
interface ResumoProduto {
Long getId();
String getNome();
}
List<ResumoProduto> findAllByAtivoTrue();
}
package com.empresa.api.repository;
import com.empresa.api.model.Produto;
import org.springframework.data.jpa.domain.Specification;
import java.math.BigDecimal;
public class ProdutoSpecifications {
public static Specification<Produto> nomeContem(String nome) {
return (root, query, cb) -> nome == null ? null
: cb.like(cb.lower(root.get("nome")), "%" + nome.toLowerCase() + "%");
}
public static Specification<Produto> precoMinimo(BigDecimal valor) {
return (root, query, cb) -> valor == null ? null
: cb.greaterThanOrEqualTo(root.get("preco"), valor);
}
// Uso: repository.findAll(nomeContem("mouse").and(precoMinimo(new BigDecimal("50"))));
}