Para armazenar respostas da API de CPF com JPA/Hibernate, crie uma entidade que mapeia os campos retornados, use um AttributeConverter para criptografar o CPF em repouso e implemente um repositório com queries que verifiquem validade do cache. Essa abordagem reduz chamadas repetidas à API, mantém histórico de validações e protege dados sensíveis conforme a LGPD.
Introdução
Armazenar respostas da API de CPF no banco de dados é uma prática essencial para reduzir chamadas repetidas à API, manter histórico de validações e possibilitar análises posteriores. O JPA com Hibernate, padrão no ecossistema Spring, oferece mapeamento objeto-relacional, cache de segundo nível e suporte a criptografia transparente.
Entidade JPA para dados de CPF
A entidade mapeia os dados retornados pela API para o banco de dados relacional.
import jakarta.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Entity
@Table(name = "consultas_cpf", indexes = {
@Index(name = "idx_cpf_hash", columnList = "cpfHash", unique = true),
@Index(name = "idx_status", columnList = "status"),
@Index(name = "idx_expira_em", columnList = "expiraEm")
})
public class ConsultaCpf {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String cpfHash;
@Convert(converter = CpfCryptoConverter.class)
@Column(nullable = false)
private String cpfCifrado;
private String nome;
private String nomeUpper;
private String genero;
private LocalDate dataNascimento;
private Integer diaNascimento;
private Integer mesNascimento;
private Integer anoNascimento;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private StatusConsulta status = StatusConsulta.PENDENTE;
@Column(length = 500)
private String motivoFalha;
@Column(nullable = false)
private String origem;
@Column(nullable = false)
private LocalDateTime consultadoEm;
private LocalDateTime expiraEm;
@Column(nullable = false, updatable = false)
private LocalDateTime criadoEm;
private LocalDateTime atualizadoEm;
@PrePersist
protected void onCreate() {
criadoEm = LocalDateTime.now();
if (consultadoEm == null) {
consultadoEm = LocalDateTime.now();
}
}
@PreUpdate
protected void onUpdate() {
atualizadoEm = LocalDateTime.now();
}
// Getters e setters omitidos por brevidade
public Long getId() { return id; }
public String getCpfHash() { return cpfHash; }
public void setCpfHash(String cpfHash) { this.cpfHash = cpfHash; }
public String getCpfCifrado() { return cpfCifrado; }
public void setCpfCifrado(String cpfCifrado) { this.cpfCifrado = cpfCifrado; }
public String getNome() { return nome; }
public void setNome(String nome) { this.nome = nome; }
public StatusConsulta getStatus() { return status; }
public void setStatus(StatusConsulta status) { this.status = status; }
public String getOrigem() { return origem; }
public void setOrigem(String origem) { this.origem = origem; }
public LocalDateTime getConsultadoEm() { return consultadoEm; }
public void setConsultadoEm(LocalDateTime consultadoEm) { this.consultadoEm = consultadoEm; }
public LocalDateTime getExpiraEm() { return expiraEm; }
public void setExpiraEm(LocalDateTime expiraEm) { this.expiraEm = expiraEm; }
public void setNomeUpper(String nomeUpper) { this.nomeUpper = nomeUpper; }
public void setGenero(String genero) { this.genero = genero; }
public void setDataNascimento(LocalDate dataNascimento) { this.dataNascimento = dataNascimento; }
public void setDiaNascimento(Integer dia) { this.diaNascimento = dia; }
public void setMesNascimento(Integer mes) { this.mesNascimento = mes; }
public void setAnoNascimento(Integer ano) { this.anoNascimento = ano; }
public void setMotivoFalha(String motivo) { this.motivoFalha = motivo; }
public boolean isExpirada() {
return expiraEm != null
&& expiraEm.isBefore(LocalDateTime.now());
}
}
public enum StatusConsulta {
PENDENTE, SUCESSO, FALHA
}
| Coluna | Tipo | Descrição |
|---|---|---|
| id | BIGINT (PK) | Identificador auto-incremento |
| cpf_hash | VARCHAR (UNIQUE) | Hash SHA-256 do CPF |
| cpf_cifrado | VARCHAR | CPF criptografado com AES |
| nome | VARCHAR | Nome retornado pela API |
| genero | VARCHAR | Gênero retornado pela API |
| data_nascimento | DATE | Data de nascimento |
| status | VARCHAR | PENDENTE, SUCESSO, FALHA |
| origem | VARCHAR | checkout, cadastro, lote |
| expira_em | TIMESTAMP | Quando o registro expira |
Converter para criptografia transparente
O JPA AttributeConverter criptografa o CPF antes de gravar e decifra ao ler.
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
@Converter
public class CpfCryptoConverter
implements AttributeConverter<String, String> {
private static final String ALGORITHM = "AES";
private static final String SECRET_KEY =
System.getenv("CPF_ENCRYPTION_KEY");
@Override
public String convertToDatabaseColumn(String cpf) {
if (cpf == null) return null;
try {
SecretKeySpec key = new SecretKeySpec(
SECRET_KEY.getBytes(StandardCharsets.UTF_8),
ALGORITHM
);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = cipher.doFinal(
cpf.getBytes(StandardCharsets.UTF_8)
);
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException(
"Erro ao criptografar CPF", e
);
}
}
@Override
public String convertToEntityAttribute(String dbData) {
if (dbData == null) return null;
try {
SecretKeySpec key = new SecretKeySpec(
SECRET_KEY.getBytes(StandardCharsets.UTF_8),
ALGORITHM
);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decrypted = cipher.doFinal(
Base64.getDecoder().decode(dbData)
);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException(
"Erro ao decifrar CPF", e
);
}
}
}
Repository com queries customizadas
O repository oferece métodos de busca otimizados para os casos de uso mais comuns.
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
@Repository
public interface ConsultaCpfRepository
extends JpaRepository<ConsultaCpf, Long> {
Optional<ConsultaCpf> findByCpfHash(String cpfHash);
@Query("SELECT c FROM ConsultaCpf c " +
"WHERE c.cpfHash = :hash " +
"AND c.status = 'SUCESSO' " +
"AND c.expiraEm > CURRENT_TIMESTAMP")
Optional<ConsultaCpf> findValidByCpfHash(
@Param("hash") String hash
);
List<ConsultaCpf> findByStatus(StatusConsulta status);
@Query("SELECT c FROM ConsultaCpf c " +
"WHERE c.consultadoEm < :data " +
"AND c.status = 'SUCESSO' " +
"ORDER BY c.consultadoEm ASC")
List<ConsultaCpf> findAntigas(
@Param("data") LocalDateTime data
);
@Modifying
@Query("DELETE FROM ConsultaCpf c " +
"WHERE c.expiraEm < CURRENT_TIMESTAMP")
int deleteExpiradas();
@Query("SELECT c.status, COUNT(c) FROM ConsultaCpf c " +
"GROUP BY c.status")
List<Object[]> countByStatus();
}
Service de consulta e armazenamento
O service orquestra consulta à API, armazenamento e leitura do cache no banco.
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.security.MessageDigest;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Service
public class CpfLookupService {
private static final int TTL_HORAS = 24;
private final ConsultaCpfRepository repository;
private final CpfApiClient apiClient;
public CpfLookupService(
ConsultaCpfRepository repository,
CpfApiClient apiClient) {
this.repository = repository;
this.apiClient = apiClient;
}
@Transactional
public ConsultaCpf consultar(String cpf, String origem) {
String cpfLimpo = cpf.replaceAll("\\D", "");
String hash = gerarHash(cpfLimpo);
// Verificar cache no banco
Optional<ConsultaCpf> cached =
repository.findValidByCpfHash(hash);
if (cached.isPresent()) {
return cached.get();
}
// Consultar API
ResultadoCpf resultado = apiClient.consultar(cpfLimpo);
// Persistir resultado
ConsultaCpf consulta = new ConsultaCpf();
consulta.setCpfHash(hash);
consulta.setCpfCifrado(cpfLimpo);
consulta.setOrigem(origem);
consulta.setConsultadoEm(LocalDateTime.now());
if (resultado.sucesso()) {
consulta.setNome(resultado.nome());
consulta.setGenero(resultado.genero());
consulta.setDataNascimento(
LocalDate.parse(resultado.dataNascimento())
);
consulta.setStatus(StatusConsulta.SUCESSO);
consulta.setExpiraEm(
LocalDateTime.now().plusHours(TTL_HORAS)
);
} else {
consulta.setStatus(StatusConsulta.FALHA);
consulta.setMotivoFalha(resultado.erro());
consulta.setExpiraEm(
LocalDateTime.now().plusHours(1)
);
}
return repository.save(consulta);
}
private String gerarHash(String cpf) {
try {
MessageDigest digest =
MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(cpf.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
hexString.append(
String.format("%02x", b)
);
}
return hexString.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Manutenção e limpeza agendada
Use o Spring Scheduler para limpar registros expirados automaticamente.
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class CpfMaintenanceTask {
private final ConsultaCpfRepository repository;
public CpfMaintenanceTask(ConsultaCpfRepository repository) {
this.repository = repository;
}
@Scheduled(cron = "0 0 3 * * *") // Todo dia às 3h
@Transactional
public void limparExpiradas() {
int removidos = repository.deleteExpiradas();
System.out.printf(
"[Manutencao] %d consultas expiradas removidas%n",
removidos
);
}
@Scheduled(cron = "0 0 6 1 * *") // Dia 1 de cada mês
public void gerarRelatorio() {
List<Object[]> contagens = repository.countByStatus();
System.out.println("--- Relatorio Mensal ---");
for (Object[] row : contagens) {
System.out.printf(" %s: %d%n", row[0], row[1]);
}
}
}
Perguntas frequentes
O que é necessário para implementar validação de CPF neste contexto?
A validação de CPF exige uma chamada à API com o número do documento e a chave de autenticação. A CPFHub.io retorna o status do CPF, nome do titular e data de nascimento em menos de 200ms, permitindo a verificação em tempo real durante o cadastro ou transação.
A API CPFHub.io funciona para todos os volumes de consulta?
Sim. O plano gratuito oferece 50 consultas por mês sem cartão de crédito — ideal para testes e projetos pequenos. Para volumes maiores, o plano Pro inclui 1.000 consultas mensais por R$149. Se o limite for ultrapassado, a API não bloqueia: cobra R$0,15 por consulta adicional.
Como garantir conformidade com a LGPD ao usar uma API de CPF?
Use o CPF apenas para a finalidade declarada ao titular, armazene apenas o necessário (não guarde o CPF cru se um token bastar), implemente controle de acesso aos logs de consulta e documente a base legal para o tratamento. A ANPD orienta que dados de identificação devem ser tratados com o princípio da necessidade.
Quanto tempo leva para integrar a API CPFHub.io?
A integração básica leva menos de 30 minutos: crie uma conta em cpfhub.io, gere a API key no painel e faça uma chamada GET para https://api.cpfhub.io/cpf/{CPF} com o header x-api-key. A documentação inclui exemplos em Python, Node.js, PHP, Java e outras linguagens.
Conclusão
Armazenar respostas da API de CPF com JPA/Hibernate em Spring Boot oferece uma solução robusta com criptografia transparente, cache no banco de dados e manutenção automatizada. O mapeamento objeto-relacional simplifica a persistência e as queries customizadas garantem performance nas buscas mais frequentes. A criptografia com AttributeConverter protege os dados sensíveis de CPF em repouso, mantendo conformidade com a LGPD.
Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e comece a persistir respostas de CPF com segurança e eficiência em minutos.
CPFHub.io
Pronto para integrar a API?
50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.
Sobre a redação
Redação CPFHub.io
Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.



