W3docs

Java JDBC PreparedStatement

Execute SQL parametrizado com segurança em Java usando PreparedStatement para evitar injeção de SQL.

Um PreparedStatement é um template SQL com placeholders ? onde os valores são inseridos. Você define os valores separadamente por índice, e o driver envia o template e os dados em canais diferentes — assim, um valor jamais pode ser interpretado como SQL. Este é o hábito mais importante no JDBC: ele torna a injeção estruturalmente impossível e permite que o banco de dados reutilize o plano de consulta. Prefira-o em vez de um Statement simples para praticamente tudo.

Este capítulo aborda como criar, vincular e executar um PreparedStatement, por que ele bloqueia a injeção de SQL, como vincular NULL e valores tipados, e quando reutilizá-lo compensa.

Criando, vinculando, executando

String sql = "INSERT INTO users (name, age) VALUES (?, ?)";
try (Connection conn = DriverManager.getConnection(url, user, pw);
     PreparedStatement ps = conn.prepareStatement(sql)) {
  ps.setString(1, name);   // bind by 1-based index
  ps.setInt(2, age);
  int rows = ps.executeUpdate();
}

Os placeholders são numerados a partir de 1, não de 0 — uma fonte constante de erros de deslocamento por um. Cada setXxx corresponde ao tipo da coluna: setString, setInt, setBigDecimal, setTimestamp, entre outros.

Por que ele impede a injeção

Com um Statement, o valor faz parte do texto SQL, portanto uma aspa no valor pode encerrar o literal e injetar novos comandos. Com um PreparedStatement, o SQL é fixo e analisado antes de qualquer valor ser vinculado; o valor é então transmitido como um parâmetro tipado. Não há string da qual a aspa de um atacante possa escapar — o valor perigoso do capítulo sobre Statement simplesmente se torna um nome literal.

Após executar uma consulta, você lê suas linhas com um ResultSet, exatamente como faria com um Statement.

Vinculando NULL e tipos especiais

Você não pode passar um null Java para setInt (ele recebe um primitivo), e setString(i, null) é ambíguo para alguns tipos. A forma explícita é setNull(index, sqlType), informando o java.sql.Types da coluna:

ps.setNull(3, java.sql.Types.VARCHAR);

Reutilizando um prepared statement

Um prepared statement é construído para ser executado muitas vezes com valores diferentes — defina, execute, limpe, repita. O banco de dados analisa e planeja o SQL uma vez e o reutiliza, razão pela qual prepared statements também são mais rápidos em um loop do que reconstruir uma string de Statement a cada iteração. Para inserções em massa, combine isso com o processamento em lote.

Um exemplo prático: anatomia de um template e seus vínculos

Este programa trata o template SQL como dado: ele conta os placeholders, percorre os valores que seriam vinculados a eles (incluindo a string maliciosa que quebrou o Statement), e exibe o código de tipo do setNull — todas as partes móveis da vinculação de parâmetros, sem um banco de dados ativo.

java— editable, runs on the server

O que extrair da execução:

  • O template tem três placeholders ?, e o programa os conta — essa contagem é exatamente quantas chamadas setXxx você deve fazer. Uma incompatibilidade (vincular o parâmetro 4 de uma consulta com 3 placeholders) gera uma exceção na execução.
  • Os vínculos são baseados em 1: o parâmetro 1 é o primeiro ?. O loop imprime bind 1, bind 2, bind 3 para reforçar isso — o erro mais comum de iniciantes é começar em 0.
  • O primeiro valor é a mesma string x'; DROP TABLE users;-- que sequestrou o Statement no capítulo anterior. Aqui ela é apenas um dado vinculado ao parâmetro 1; o driver a armazena literalmente como um nome. A injeção é neutralizada por construção, não por escapamento.
  • O null no parâmetro 3 é a razão pela qual setNull(index, Types.VARCHAR) existe. O JDBC precisa do tipo SQL para informar ao banco de dados qual tipo de NULL é — você o nomeia com uma constante java.sql.Types.
  • Cada valor carrega um tipo implícito — String, int, NULL-of-VARCHAR — razão pela qual existe um setXxx por tipo em vez de um setter de tipo único baseado em string. Combinar o setter ao tipo da coluna é a disciplina que mantém os prepared statements seguros e corretos.

Prática

Prática
Por que um PreparedStatement impede a injeção de SQL mesmo quando um valor vinculado contém uma aspa simples e um ponto e vírgula?
Por que um PreparedStatement impede a injeção de SQL mesmo quando um valor vinculado contém uma aspa simples e um ponto e vírgula?
Was this page helpful?