🔍 Tutorial Completo - JPA Specifications para Filtros Dinâmicos
Finalidade: Guia prático para implementar filtros dinâmicos usando JPA Specifications
Foco: Implementação do filtro DoadorRequestFilterDTO
com Specifications
Tecnologia: Spring Data JPA, Criteria API
🔗 Links Rápidos para Documentação Oficial
Recurso | Link | Descrição |
---|---|---|
📖 JPA Specifications | Spring Data JPA Specs | Documentação oficial completa |
🔧 Criteria API | Oracle JPA Criteria | Tutorial oficial Oracle |
🚀 Spring Data JPA | Reference Guide | Guia completo Spring Data |
📊 Hibernate Docs | User Guide | Documentação Hibernate |
🎯 Spring Boot | Reference Guide | Guia oficial Spring Boot |
💡 Mais links detalhados disponíveis na seção de documentação oficial no final deste tutorial.
🎯 Objetivo deste Tutorial
Este tutorial ensina como implementar filtros dinâmicos usando JPA Specifications, permitindo consultas flexíveis e eficientes baseadas nos campos do DoadorRequestFilterDTO
.
✅ O que você vai aprender:
- Configurar JPA Specifications no projeto
- Criar Specifications customizadas para cada campo
- Implementar Repository com filtros dinâmicos
- Usar no Controller com paginação
- Otimizar consultas com Criteria API
🏗️ Estrutura da Implementação
📊 Seu DTO Atual:
public record DoadorRequestFilterDTO(
String cpf,
String fullName,
String email,
String telefonePrincipal,
String tipoSanguineo,
String cidade,
String estado,
String empresa,
String pesoKg,
String doadorHabitual
) {}
🎯 Resultado Final:
- ✅ Filtros opcionais (só aplica se não for null/vazio)
- ✅ Busca parcial para texto (LIKE)
- ✅ Paginação integrada
- ✅ Performance otimizada
- ✅ Type safety completo
📋 Passo 1: Configuração do Repository
🔧 Atualizar DoadorRepository
📖 Documentação: JpaSpecificationExecutor | JPA Repository
package com.faculdade.doesangue_api.repository;
import com.faculdade.doesangue_api.entities.Doador;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
@Repository
public interface DoadorRepository extends
JpaRepository<Doador, Long>,
JpaSpecificationExecutor<Doador> { // 👈 Interface necessária para Specifications
// Métodos existentes permanecem...
// Método opcional para busca com Specification customizada
default Page<Doador> findWithFilters(Specification<Doador> spec, Pageable pageable) {
return findAll(spec, pageable);
}
}
📋 Passo 2: Criar a Classe de Specifications
🔨 DoadorSpecifications.java
📖 Documentação: Specifications | Criteria Builder | Predicate
package com.faculdade.doesangue_api.specifications;
import com.faculdade.doesangue_api.dto.doador.DoadorRequestFilterDTO;
import com.faculdade.doesangue_api.entities.Doador;
import com.faculdade.doesangue_api.entities.TipoSanguineo;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Predicate;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
public class DoadorSpecifications {
/**
* 🎯 Método principal - Constrói Specification baseada no FilterDTO
*/
public static Specification<Doador> withFilters(DoadorRequestFilterDTO filters) {
return (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
// 📄 Filtro por CPF (busca exata)
if (StringUtils.hasText(filters.cpf())) {
predicates.add(
criteriaBuilder.equal(
root.get("cpf"),
filters.cpf().replaceAll("[^0-9]", "") // Remove formatação
)
);
}
// 👤 Filtro por Nome (busca parcial - LIKE)
if (StringUtils.hasText(filters.fullName())) {
predicates.add(
criteriaBuilder.like(
criteriaBuilder.lower(root.get("fullName")),
"%" + filters.fullName().toLowerCase() + "%"
)
);
}
// 📧 Filtro por Email (busca parcial)
if (StringUtils.hasText(filters.email())) {
predicates.add(
criteriaBuilder.like(
criteriaBuilder.lower(root.get("email")),
"%" + filters.email().toLowerCase() + "%"
)
);
}
// 📞 Filtro por Telefone (busca parcial)
if (StringUtils.hasText(filters.telefonePrincipal())) {
predicates.add(
criteriaBuilder.like(
root.get("telefonePrincipal"),
"%" + filters.telefonePrincipal().replaceAll("[^0-9]", "") + "%"
)
);
}
// 🩸 Filtro por Tipo Sanguíneo (JOIN com entidade relacionada)
if (StringUtils.hasText(filters.tipoSanguineo())) {
Join<Doador, TipoSanguineo> tipoSanguineoJoin =
root.join("tipoSanguineo", JoinType.LEFT);
predicates.add(
criteriaBuilder.like(
criteriaBuilder.lower(
criteriaBuilder.concat(
tipoSanguineoJoin.get("tipoAbo"),
tipoSanguineoJoin.get("fatorRh")
)
),
"%" + filters.tipoSanguineo().toLowerCase() + "%"
)
);
}
// 🏙️ Filtro por Cidade (busca parcial)
if (StringUtils.hasText(filters.cidade())) {
predicates.add(
criteriaBuilder.like(
criteriaBuilder.lower(root.get("cidade")),
"%" + filters.cidade().toLowerCase() + "%"
)
);
}
// 🗺️ Filtro por Estado (busca exata)
if (StringUtils.hasText(filters.estado())) {
predicates.add(
criteriaBuilder.equal(
criteriaBuilder.upper(root.get("estado")),
filters.estado().toUpperCase()
)
);
}
// 🏢 Filtro por Empresa (busca parcial)
if (StringUtils.hasText(filters.empresa())) {
predicates.add(
criteriaBuilder.like(
criteriaBuilder.lower(root.get("empresa")),
"%" + filters.empresa().toLowerCase() + "%"
)
);
}
// ⚖️ Filtro por Peso (range - maior ou igual)
if (StringUtils.hasText(filters.pesoKg())) {
try {
BigDecimal peso = new BigDecimal(filters.pesoKg());
predicates.add(
criteriaBuilder.greaterThanOrEqualTo(
root.get("pesoKg"),
peso
)
);
} catch (NumberFormatException e) {
// Ignora se não for um número válido
}
}
// 🔄 Filtro por Doador Habitual (boolean)
if (StringUtils.hasText(filters.doadorHabitual())) {
Boolean isHabitual = Boolean.parseBoolean(filters.doadorHabitual());
predicates.add(
criteriaBuilder.equal(
root.get("doadorHabitual"),
isHabitual
)
);
}
// 🚫 Sempre excluir registros deletados (soft delete)
predicates.add(
criteriaBuilder.isNull(root.get("deletedAt"))
);
// <span class="badge success">✅</span> Combina todos os predicates com AND
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
/**
* 🔍 Specifications individuais para casos específicos
*/
public static Specification<Doador> hasCpf(String cpf) {
return (root, query, criteriaBuilder) ->
StringUtils.hasText(cpf)
? criteriaBuilder.equal(root.get("cpf"), cpf.replaceAll("[^0-9]", ""))
: criteriaBuilder.conjunction();
}
public static Specification<Doador> hasNameLike(String name) {
return (root, query, criteriaBuilder) ->
StringUtils.hasText(name)
? criteriaBuilder.like(
criteriaBuilder.lower(root.get("fullName")),
"%" + name.toLowerCase() + "%"
)
: criteriaBuilder.conjunction();
}
public static Specification<Doador> hasEmailLike(String email) {
return (root, query, criteriaBuilder) ->
StringUtils.hasText(email)
? criteriaBuilder.like(
criteriaBuilder.lower(root.get("email")),
"%" + email.toLowerCase() + "%"
)
: criteriaBuilder.conjunction();
}
public static Specification<Doador> isActive() {
return (root, query, criteriaBuilder) ->
criteriaBuilder.isNull(root.get("deletedAt"));
}
public static Specification<Doador> inCity(String cidade) {
return (root, query, criteriaBuilder) ->
StringUtils.hasText(cidade)
? criteriaBuilder.like(
criteriaBuilder.lower(root.get("cidade")),
"%" + cidade.toLowerCase() + "%"
)
: criteriaBuilder.conjunction();
}
public static Specification<Doador> inState(String estado) {
return (root, query, criteriaBuilder) ->
StringUtils.hasText(estado)
? criteriaBuilder.equal(
criteriaBuilder.upper(root.get("estado")),
estado.toUpperCase()
)
: criteriaBuilder.conjunction();
}
}
📋 Passo 3: Atualizar o Service
🔧 DoadorService.java
📖 Documentação: Spring Transactions | Page and Pageable
package com.faculdade.doesangue_api.service;
import com.faculdade.doesangue_api.dto.doador.DoadorRequestFilterDTO;
import com.faculdade.doesangue_api.entities.Doador;
import com.faculdade.doesangue_api.repository.DoadorRepository;
import com.faculdade.doesangue_api.specifications.DoadorSpecifications;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(readOnly = true)
public class DoadorService {
private final DoadorRepository doadorRepository;
public DoadorService(DoadorRepository doadorRepository) {
this.doadorRepository = doadorRepository;
}
/**
* 🔍 Busca com filtros dinâmicos usando Specifications
*
* @param filters - DTO com filtros opcionais
* @param pageable - Configuração de paginação
* @return Page<Doador> - Resultados paginados
*/
public Page<Doador> buscarComFiltros(DoadorRequestFilterDTO filters, Pageable pageable) {
Specification<Doador> spec = DoadorSpecifications.withFilters(filters);
return doadorRepository.findAll(spec, pageable);
}
/**
* 🎯 Busca usando Specifications compostas (exemplo avançado)
*/
public Page<Doador> buscarDoadoresAtivosNaCidade(String cidade, Pageable pageable) {
Specification<Doador> spec = Specification
.where(DoadorSpecifications.isActive())
.and(DoadorSpecifications.inCity(cidade));
return doadorRepository.findAll(spec, pageable);
}
/**
* 📊 Exemplo de combinação de Specifications
*/
public Page<Doador> buscarDoadoresComCriterios(
String nome,
String cidade,
String estado,
Pageable pageable) {
Specification<Doador> spec = Specification
.where(DoadorSpecifications.isActive())
.and(DoadorSpecifications.hasNameLike(nome))
.and(DoadorSpecifications.inCity(cidade))
.and(DoadorSpecifications.inState(estado));
return doadorRepository.findAll(spec, pageable);
}
// Outros métodos existentes...
}
📋 Passo 4: Atualizar o Controller
🎮 DoadorController.java
📖 Documentação: Spring Web MVC | OpenAPI Annotations | Bean Validation
package com.faculdade.doesangue_api.controller;
import com.faculdade.doesangue_api.dto.doador.DoadorRequestFilterDTO;
import com.faculdade.doesangue_api.dto.doador.DoadorDTO;
import com.faculdade.doesangue_api.entities.Doador;
import com.faculdade.doesangue_api.service.DoadorService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/doadores")
@Tag(name = "Doadores", description = "API para gerenciamento de doadores de sangue")
@Validated
public class DoadorController {
private final DoadorService doadorService;
private final DoadorMapper doadorMapper;
public DoadorController(DoadorService doadorService, DoadorMapper doadorMapper) {
this.doadorService = doadorService;
this.doadorMapper = doadorMapper;
}
/**
* 🔍 Endpoint principal com filtros dinâmicos
*/
@GetMapping
@Operation(
summary = "🔍 Listar doadores com filtros dinâmicos",
description = """
Lista doadores com filtros opcionais usando JPA Specifications.
**Funcionalidades:**
- <span class="badge success">✅</span> Todos os filtros são opcionais
- <span class="badge success">✅</span> Busca parcial para texto (nome, email, cidade)
- <span class="badge success">✅</span> Busca exata para CPF e estado
- <span class="badge success">✅</span> Filtro por peso mínimo
- <span class="badge success">✅</span> Paginação e ordenação
- <span class="badge success">✅</span> Performance otimizada
**Exemplos de uso:**
- `GET /api/doadores?fullName=João&cidade=São Paulo&page=0&size=10`
- `GET /api/doadores?tipoSanguineo=O+&doadorHabitual=true`
- `GET /api/doadores?estado=SP&pesoKg=60&sort=fullName,asc`
"""
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "<span class="badge success">✅</span> Lista retornada com sucesso"),
@ApiResponse(responseCode = "400", description = "<span class="badge danger">❌</span> Parâmetros inválidos")
})
public ResponseEntity<Page<DoadorDTO>> listarComFiltros(
@Parameter(description = "CPF para busca exata (apenas números)")
@RequestParam(required = false) String cpf,
@Parameter(description = "Nome completo para busca parcial")
@RequestParam(required = false) String fullName,
@Parameter(description = "Email para busca parcial")
@RequestParam(required = false) String email,
@Parameter(description = "Telefone para busca parcial")
@RequestParam(required = false) String telefonePrincipal,
@Parameter(description = "Tipo sanguíneo (ex: O+, A-, AB+)")
@RequestParam(required = false) String tipoSanguineo,
@Parameter(description = "Cidade para busca parcial")
@RequestParam(required = false) String cidade,
@Parameter(description = "Estado (UF - 2 caracteres)")
@RequestParam(required = false) String estado,
@Parameter(description = "Empresa para busca parcial")
@RequestParam(required = false) String empresa,
@Parameter(description = "Peso mínimo em kg")
@RequestParam(required = false) String pesoKg,
@Parameter(description = "Filtrar doadores habituais (true/false)")
@RequestParam(required = false) String doadorHabitual,
@Parameter(description = "Número da página (0-based)")
@RequestParam(defaultValue = "0") @Min(0) int page,
@Parameter(description = "Tamanho da página")
@RequestParam(defaultValue = "20") @Min(1) @Max(100) int size,
@Parameter(description = "Campo para ordenação")
@RequestParam(defaultValue = "fullName") String sort,
@Parameter(description = "Direção da ordenação (asc/desc)")
@RequestParam(defaultValue = "asc") String direction
) {
// 📊 Criar DTO de filtros
DoadorRequestFilterDTO filters = new DoadorRequestFilterDTO(
cpf, fullName, email, telefonePrincipal, tipoSanguineo,
cidade, estado, empresa, pesoKg, doadorHabitual
);
// 📄 Configurar paginação e ordenação
Sort.Direction sortDirection = "desc".equalsIgnoreCase(direction)
? Sort.Direction.DESC
: Sort.Direction.ASC;
Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sort));
// 🔍 Executar busca com filtros
Page<Doador> doadoresPage = doadorService.buscarComFiltros(filters, pageable);
// 🔄 Converter para DTO
Page<DoadorDTO> doadoresDTO = doadoresPage.map(doadorMapper::toDTO);
return ResponseEntity.ok(doadoresDTO);
}
/**
* 🎯 Endpoint específico para busca por cidade (exemplo simples)
*/
@GetMapping("/por-cidade/{cidade}")
@Operation(summary = "Buscar doadores ativos por cidade")
public ResponseEntity<Page<DoadorDTO>> buscarPorCidade(
@PathVariable String cidade,
Pageable pageable) {
Page<Doador> doadores = doadorService.buscarDoadoresAtivosNaCidade(cidade, pageable);
Page<DoadorDTO> doadoresDTO = doadores.map(doadorMapper::toDTO);
return ResponseEntity.ok(doadoresDTO);
}
// Outros endpoints existentes...
}
📋 Passo 5: Testes e Validação
🧪 Exemplo de Teste Unitário
📖 Documentação: Spring Boot Testing | @DataJpaTest | AssertJ
package com.faculdade.doesangue_api.specifications;
import com.faculdade.doesangue_api.dto.doador.DoadorRequestFilterDTO;
import com.faculdade.doesangue_api.entities.Doador;
import com.faculdade.doesangue_api.repository.DoadorRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
class DoadorSpecificationsTest {
@Autowired
private DoadorRepository doadorRepository;
@Test
void deveFilterarPorNome() {
// Given
DoadorRequestFilterDTO filters = new DoadorRequestFilterDTO(
null, "João", null, null, null, null, null, null, null, null
);
// When
Specification<Doador> spec = DoadorSpecifications.withFilters(filters);
Page<Doador> resultado = doadorRepository.findAll(spec, PageRequest.of(0, 10));
// Then
assertThat(resultado.getContent())
.allMatch(doador -> doador.getFullName().toLowerCase().contains("joão"));
}
@Test
void deveFilterarPorCidadeEEstado() {
// Given
DoadorRequestFilterDTO filters = new DoadorRequestFilterDTO(
null, null, null, null, null, "São Paulo", "SP", null, null, null
);
// When
Specification<Doador> spec = DoadorSpecifications.withFilters(filters);
Page<Doador> resultado = doadorRepository.findAll(spec, PageRequest.of(0, 10));
// Then
assertThat(resultado.getContent())
.allMatch(doador ->
doador.getCidade().toLowerCase().contains("são paulo") &&
doador.getEstado().equals("SP")
);
}
}
🚀 Uso Prático - Exemplos de Requisições
📱 Exemplos de Chamadas da API
# 1. Buscar todos os doadores (sem filtros)
GET /api/doadores?page=0&size=20
# 2. Buscar por nome
GET /api/doadores?fullName=João Silva&page=0&size=10
# 3. Buscar por cidade e estado
GET /api/doadores?cidade=São Paulo&estado=SP
# 4. Buscar doadores habituais tipo O+
GET /api/doadores?tipoSanguineo=O+&doadorHabitual=true
# 5. Buscar por peso mínimo e ordenar por nome
GET /api/doadores?pesoKg=60&sort=fullName&direction=asc
# 6. Filtro complexo com múltiplos campos
GET /api/doadores?fullName=Maria&cidade=Rio&estado=RJ&empresa=Hospital&page=0&size=5
# 7. Buscar por CPF específico
GET /api/doadores?cpf=12345678901
# 8. Buscar por email parcial
GET /api/doadores?email=gmail.com
💡 Dicas e Boas Práticas
✅ Performance:
- Use
LEFT JOIN
para relacionamentos opcionais - Evite
EAGER
loading desnecessário - Considere índices para campos filtrados frequentemente
✅ Segurança:
- Sempre valide entrada de dados
- Use
StringUtils.hasText()
para verificar strings - Limite tamanho da página (máximo 100)
✅ Flexibilidade:
- Specifications são composáveis
- Pode combinar com outros métodos do repository
- Reutilizável em diferentes contextos
✅ Manutenibilidade:
- Separe Specifications por entidade
- Use nomes descritivos para métodos
- Documente comportamento de cada filtro
🔧 Configurações Opcionais
📊 Custom Repository (Avançado)
@Repository
public interface DoadorRepository extends
JpaRepository<Doador, Long>,
JpaSpecificationExecutor<Doador>,
DoadorRepositoryCustom {
}
interface DoadorRepositoryCustom {
Page<DoadorProjection> findDoadoresProjection(
DoadorRequestFilterDTO filters,
Pageable pageable
);
}
@Component
class DoadorRepositoryImpl implements DoadorRepositoryCustom {
@PersistenceContext
private EntityManager entityManager;
@Override
public Page<DoadorProjection> findDoadoresProjection(
DoadorRequestFilterDTO filters,
Pageable pageable) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<DoadorProjection> query = cb.createQuery(DoadorProjection.class);
Root<Doador> root = query.from(Doador.class);
// Projeção customizada para melhor performance
query.select(cb.construct(
DoadorProjection.class,
root.get("id"),
root.get("fullName"),
root.get("email"),
root.get("cidade")
));
// Aplicar filtros usando Specifications
Specification<Doador> spec = DoadorSpecifications.withFilters(filters);
Predicate predicate = spec.toPredicate(root, query, cb);
query.where(predicate);
// Executar query...
return new PageImpl<>(results, pageable, total);
}
}
📚 Documentação Oficial e Referências
🌟 Spring Data JPA - Documentação Principal
📖 JPA Specifications:
- Spring Data JPA Specifications - Guia oficial completo
- JPA Criteria API - Construção de predicados
- Type-safe Query Methods - Composição de Specifications
📖 Spring Data JPA - Geral:
- Spring Data JPA Reference - Documentação completa
- Query Methods - Métodos de consulta
- Paging and Sorting - Paginação e ordenação
- Custom Implementations - Implementações customizadas
🔧 JPA e Hibernate - Documentação Core
📖 JPA Criteria API:
- Oracle JPA Tutorial - Criteria API - Tutorial oficial Oracle
- JPA 3.1 Specification - Especificação oficial JPA
- Criteria API - Building Queries - Construção de queries
📖 Hibernate Documentation:
- Hibernate User Guide - Guia completo do Hibernate
- Criteria Queries - Hibernate Criteria
- Query by Example - QBE com Hibernate
🚀 Spring Framework - Base
📖 Spring Framework Core:
- Spring Framework Reference - Documentação completa
- Data Access with JDBC - Acesso a dados
- Transaction Management - Gerenciamento de transações
📖 Spring Boot:
- Spring Boot Reference Guide - Guia oficial Spring Boot
- Spring Boot Data JPA - Configuração JPA
- Auto-configuration - Configuração automática
📊 Validação e DTOs
📖 Bean Validation:
- Jakarta Bean Validation - Especificação oficial
- Hibernate Validator - Implementação de referência
- Spring Validation - Validação no Spring
📖 Java Records (Java 14+):
- Oracle Java Records - Documentação oficial
- JEP 395: Records - Proposta de especificação
- Java Record Patterns - Pattern matching
🎯 Testing e Qualidade
📖 Spring Boot Testing:
- Testing in Spring Boot - Testes no Spring Boot
- @DataJpaTest - Testes de JPA
- TestContainers - Testes com containers
📖 Frameworks de Teste:
- JUnit 5 User Guide - Framework de testes
- AssertJ - Assertions fluentes
- Mockito - Framework de mocks
📖 Performance e Otimização
📖 JPA Performance:
- JPA Performance Tips - Blog Vlad Mihalcea
- N+1 Query Problem - Estratégias de fetch
- Second Level Cache - Cache de segundo nível
📖 Database Optimization:
- SQL Server Performance - Otimização SQL Server
- Index Tuning - Estratégias de índices
- Query Execution Plans - Planos de execução
🔧 Ferramentas e IDEs
📖 Development Tools:
- IntelliJ IDEA - JPA Support - Suporte JPA no IntelliJ
- Eclipse - Dali JPA Tools - Ferramentas JPA no Eclipse
- Spring Tools Suite - IDE especializada Spring
📖 Database Tools:
- SQL Server Management Studio - SSMS oficial
- DBeaver - Cliente universal de banco
- pgAdmin - Para PostgreSQL (se aplicável)
📱 API Design e Documentação
📖 OpenAPI/Swagger:
- OpenAPI Specification - Especificação oficial
- SpringDoc OpenAPI - Integração Spring Boot
- Swagger Annotations - Anotações Swagger
📖 REST API Best Practices:
- REST API Tutorial - Guia completo REST
- HTTP Status Codes - Códigos de status
- Richardson Maturity Model - Níveis de maturidade REST
🎓 Tutoriais e Guias Práticos
📖 Spring Guides:
- Accessing Data with JPA - Tutorial básico
- Building REST services with Spring - Tutorial REST completo
- Consuming REST services - Consumindo APIs
📖 Baeldung Tutorials:
- Spring Data JPA Specifications - Tutorial prático
- Spring Data JPA Query - Queries com Spring Data
- JPA Criteria Queries - Criteria API
🔍 Blogs e Recursos Especializados
📖 Especialistas em JPA/Hibernate:
- Vlad Mihalcea's Blog - Expert em JPA/Hibernate
- Thorben Janssen's Blog - Hibernate tips
- Petri Kainulainen's Blog - Spring Data JPA
📖 Spring Community:
- Spring Blog - Blog oficial Spring
- Spring Academy - Cursos oficiais
- Spring Community - Comunidade Spring
⚡ Quick Reference Links
🔖 Cheat Sheets:
- JPA Annotations Cheat Sheet - Anotações JPA
- Spring Data JPA Cheat Sheet - Derivação de queries
- SQL Cheat Sheet - Referência SQL
🔖 GitHub Repositories:
- Spring Data Examples - Exemplos oficiais
- Spring Boot Samples - Amostras Spring Boot
- Awesome Spring - Recursos curados Spring
💡 Dica: Marque estes links nos seus favoritos para consulta rápida durante o desenvolvimento!
📋 Recursos Adicionais
📖 Links Úteis:
🔍 Próximos Passos:
- Implementar cache para consultas frequentes
- Adicionar métricas de performance
- Criar DSL customizada para queries complexas
- Implementar filtros geoespaciais
Este tutorial fornece uma implementação completa e profissional de JPA Specifications para o projeto DoeSangue, garantindo filtros dinâmicos, performance otimizada e código maintível.