Diferença entre Statement e PreparedStatement

222

A Declaração Preparada é uma versão um pouco mais poderosa de uma Declaração e deve sempre ser pelo menos tão rápida e fácil de manusear quanto uma Declaração.
A declaração preparada pode ser parametrizada

A maioria dos bancos de dados relacionais manipula uma consulta JDBC / SQL em quatro etapas:

  1. Analisar a consulta SQL recebida
  2. Compile a consulta SQL
  3. Planejar / otimizar o caminho de aquisição de dados
  4. Execute a consulta / aquisição otimizada e retorne dados

Uma instrução sempre continuará com as quatro etapas acima para cada consulta SQL enviada ao banco de dados. Uma declaração preparada pré-executa as etapas (1) - (3) no processo de execução acima. Assim, ao criar uma instrução preparada, uma pré-otimização é realizada imediatamente. O efeito é diminuir a carga no mecanismo de banco de dados no tempo de execução.

Agora, minha pergunta é: "Existe alguma outra vantagem de usar a declaração preparada?"

CodeBee ..
fonte
12
o mais eficiente de acordo comigo é que sua consulta pode ser parametrizada dinamicamente
Hussain Akhtar Wahid 'Ghouri'

Respostas:

198

Vantagens de um PreparedStatement:

  • A pré-compilação e o armazenamento em cache do lado do banco de dados da instrução SQL levam à execução geral mais rápida e à capacidade de reutilizar a mesma instrução SQL em lotes .

  • Prevenção automática de ataques de injeção de SQL por escape interno de aspas e outros caracteres especiais. Observe que isso requer que você use qualquer um dos métodos para definir os valoresPreparedStatement setXxx()

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email, birthdate, photo) VALUES (?, ?, ?, ?)");
    preparedStatement.setString(1, person.getName());
    preparedStatement.setString(2, person.getEmail());
    preparedStatement.setTimestamp(3, new Timestamp(person.getBirthdate().getTime()));
    preparedStatement.setBinaryStream(4, person.getPhoto());
    preparedStatement.executeUpdate();

    e, portanto , não inline os valores na string SQL concatenando a string.

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email) VALUES ('" + person.getName() + "', '" + person.getEmail() + "'");
    preparedStatement.executeUpdate();
  • Facilita a definição de objetos Java não-padrão em uma string SQL, por exemplo Date, Time, Timestamp, BigDecimal, InputStream( Blob) e Reader( Clob). Na maioria desses tipos, você não pode "apenas" fazer o toString()que faria de uma maneira simples Statement. Você pode até refatorar tudo para usar PreparedStatement#setObject()dentro de um loop, como demonstrado no método utilitário abaixo:

    public static void setValues(PreparedStatement preparedStatement, Object... values) throws SQLException {
        for (int i = 0; i < values.length; i++) {
            preparedStatement.setObject(i + 1, values[i]);
        }
    }

    Qual pode ser usado como abaixo:

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email, birthdate, photo) VALUES (?, ?, ?, ?)");
    setValues(preparedStatement, person.getName(), person.getEmail(), new Timestamp(person.getBirthdate().getTime()), person.getPhoto());
    preparedStatement.executeUpdate();
BalusC
fonte
4
Um texto descritivo e explicativo, juntamente com referências e exemplos, é uma excelente resposta. 1
XenoRo 5/11
1
@RD Isso pode ser verdade porque uma instrução preparada requer 2 viagens de ida e volta ao banco de dados: a primeira a se preparar, a segunda a ser executada. No entanto, eu testaria. Presumo que o plano ainda seja armazenado em cache no servidor de banco de dados por um Statement, mas pode valer a pena um teste.
Brandon
2
Não posso ter certeza com Java, mas, em geral, uma declaração preparada não realiza "escape interno de aspas e outros caracteres especiais"; em vez disso, ele executa a separação de dados e SQL executáveis , enviando os parâmetros ao DBMS como pacotes de informações separados após a conversão do SQL em um plano de consulta.
IMSOP
@ BalusC - Obrigado pela explicação detalhada.
CodeBee .. 24/1218
49
  1. Eles são pré-compilados (uma vez), mais rápidos para execução repetida de SQL dinâmico (onde os parâmetros mudam)

  2. O cache da instrução do banco de dados aumenta o desempenho da execução do banco de dados

    Os bancos de dados armazenam caches de planos de execução para instruções executadas anteriormente. Isso permite que o mecanismo de banco de dados reutilize os planos para instruções que foram executadas anteriormente. Como o PreparedStatement usa parâmetros, cada vez que é executado aparece como o mesmo SQL, o banco de dados pode reutilizar o plano de acesso anterior, reduzindo o processamento. As instruções "incorporam" os parâmetros na cadeia de caracteres SQL e, portanto, não aparecem como o mesmo SQL do banco de dados, impedindo o uso do cache.

  3. Protocolo de comunicações binárias significa menos largura de banda e chamadas de comunicação mais rápidas para o servidor DB

    Instruções preparadas são normalmente executadas através de um protocolo binário não-SQL. Isso significa que há menos dados nos pacotes, portanto, a comunicação com o servidor é mais rápida. Como regra geral, as operações de rede são uma ordem de magnitude mais lenta que as operações de disco, que são uma ordem de magnitude mais lenta que as operações de CPU na memória. Portanto, qualquer redução na quantidade de dados enviados pela rede terá um bom efeito no desempenho geral.

  4. Eles protegem contra injeção de SQL, escapando do texto para todos os valores de parâmetro fornecidos.

  5. Eles fornecem uma separação mais forte entre o código da consulta e os valores dos parâmetros (em comparação com as seqüências SQL concatenadas), aumentando a legibilidade e ajudando os mantenedores de código a entender rapidamente as entradas e saídas da consulta.

  6. Em java, é possível chamar getMetadata () e getParameterMetadata () para refletir nos campos do conjunto de resultados e nos campos de parâmetro, respectivamente

  7. Em java, aceita objetos java de forma inteligente como tipos de parâmetro via setObject, setBoolean, setByte, setDate, setDouble, setDouble, setFloat, setInt, setLong, setShort, setTime, setTimestamp - converte para o formato de tipo JDBC que é compreensível para DB (não apenas toString () formato).

  8. Em java, aceita SQL ARRAYs, como tipo de parâmetro via método setArray

  9. Em java, aceita CLOBs, BLOBs, OutputStreams e Readers como parâmetro "feeds" através dos métodos setClob / setNClob, setBlob, setBinaryStream, setCharacterStream / setAsciiStream / setNCharacterStream, respectivamente

  10. Em java, permite que valores específicos do banco de dados sejam definidos para os métodos SQL DATALINK, SQL ROWID, SQL XML e NULL via setURL, setRowId, setSQLXML e setNull

  11. Em java, herda todos os métodos de Statement. Ele herda o método addBatch e, além disso, permite que um conjunto de valores de parâmetros seja adicionado para corresponder ao conjunto de comandos SQL em lote via método addBatch.

  12. Em java, um tipo especial de PreparedStatement (a subclasse CallableStatement) permite que procedimentos armazenados sejam executados - suportando alto desempenho, encapsulamento, programação procedural e SQL, administração / manutenção / ajuste de lógica / ajuste de lógica e uso de lógica e recursos proprietários de DB

Glen Best
fonte
Como todas essas maravilhas são possíveis quando ambas são apenas interfaces?!?!
Rafael
1
As 'maravilhas' são possíveis através de métodos padrão de fábrica que retornam implementações (específicas do fornecedor) das interfaces: Connection.createStatement e Connection.prepareStatement. Esse design obriga a trabalhar com interfaces, para que você não precise conhecer as classes de implementação específicas e evitar acoplamentos desnecessários com essas classes de implementação. Tudo explicado com exemplos nos documentos Java jdbc e Java. :)
Glen Best
Seu "como uma regra de ouro" parte não faz sentido, não é contrário 🤔
bhathiya-perera
38

PreparedStatementé uma defesa muito boa (mas não é infalível) na prevenção de ataques de injeção de SQL . Vincular valores de parâmetro é uma boa maneira de se proteger contra "pequenas mesas de Bobby", fazendo uma visita indesejada.

duffymo
fonte
6
Como alguém executaria injeção de SQL através de uma instrução preparada então?
Michael Borgwardt
2
Michael, Variáveis ​​passadas como argumentos para instruções preparadas serão escapadas automaticamente pelo driver JDBC.
CodeBee .. 17/07/10
3
Você pode dar um exemplo de como um ataque de injeção SQL funcionaria contra uma instrução preparada? Você está assumindo um bug no código do banco de dados?
Peter Recore
2
Sim, mas está muito além de "muito burro". É uma estupidez incrível. Ninguém com um pingo de conhecimento faria isso.
Duffymo #
2
Além disso, muitos fornecedores de banco de dados não suportam parametrização nomes das colunas (acho ORDER BY) e / ou constantes numéricas em certos lugares (pense LIMIT, OFFSETe outras soluções de paginação), assim que estes podem ser atacados por injeção de SQL, mesmo quando instruções preparadas e parametrização é usado sempre que possível.
Dnet #
31

Alguns dos benefícios do PreparedStatement over Statement são:

  1. O PreparedStatement nos ajuda a impedir ataques de injeção de SQL porque ele escapa automaticamente dos caracteres especiais.
  2. PreparedStatement nos permite executar consultas dinâmicas com entradas de parâmetros.
  3. PreparedStatement fornece diferentes tipos de métodos setter para definir os parâmetros de entrada para a consulta.
  4. PreparedStatement é mais rápido que Statement. Torna-se mais visível quando reutilizamos o PreparedStatement ou usamos seus métodos de processamento em lote para executar várias consultas.
  5. PreparedStatement nos ajuda a escrever código Orientado a objetos com métodos setter, enquanto que com Statement temos que usar Concatenação de String para criar a consulta. Se houver vários parâmetros a serem definidos, escrever a Consulta usando a concatenação de String parecerá muito feio e propenso a erros.

Leia mais sobre o problema de injeção de SQL em http://www.journaldev.com/2489/jdbc-statement-vs-preparedstatement-sql-injection-example

Pankaj
fonte
Eu li o seu artigo, muito bom. Minha pergunta agora é por que alguém usaria Statement ?! mesmo para uma consulta estática ?!
pedram Bashiri
Eu sempre uso PreparedStatement, não conheço nenhum cenário específico em que a declaração possa ter mais benefícios.
Pankaj
13

nada a acrescentar,

1 - se você deseja executar uma consulta em um loop (mais de uma vez), a instrução preparada pode ser mais rápida, devido à otimização que você mencionou.

A consulta com 2 parâmetros é uma boa maneira de evitar a injeção de SQL. As consultas parametrizadas estão disponíveis apenas no PreparedStatement.

mhshams
fonte
10

A declaração é estática e a declaração preparada é dinâmica.

A instrução é adequada para DDL e declaração preparada para DML.

A declaração é mais lenta, enquanto a declaração preparada é mais rápida.

mais diferenças (arquivadas)

sandeep vanama
fonte
7

Não é possível executar CLOBs em uma instrução.

E: (OraclePreparedStatement) ps

orbfish
fonte
7

Citado por mattjames

O uso de uma Instrução no JDBC deve ser 100% localizado para ser usado para DDL (ALTER, CREATE, GRANT, etc), pois esses são os únicos tipos de instrução que não podem aceitar VARIÁVEIS BIND. PreparedStatements ou CallableStatements devem ser usados ​​para TODOS OS OUTROS tipos de instrução (DML, Consultas). Como esses são os tipos de instrução que aceitam variáveis ​​de ligação.

Isso é um fato, uma regra, uma lei - use declarações preparadas EM TODA PARTE. Use DECLARAÇÕES quase em nenhum lugar.

Raiz
fonte
5

a injeção de sql é ignorada pela instrução preparada, para que a segurança seja aumentada na instrução preparada

ashish geol
fonte
4
  • É mais fácil de ler
  • Você pode facilmente tornar a string de consulta uma constante
Nanda
fonte
4

A instrução será usada para executar instruções SQL estáticas e não pode aceitar parâmetros de entrada.

PreparedStatement será usado para executar instruções SQL muitas vezes dinamicamente. Aceitará parâmetros de entrada.

MARA MP
fonte
4

Outra característica da Consulta Preparada ou Parametrizada: Referência retirada deste artigo.

Essa instrução é um dos recursos do sistema de banco de dados no qual a mesma instrução SQL é executada repetidamente com alta eficiência. As instruções preparadas são um tipo de modelo e usadas por aplicativos com parâmetros diferentes.

O modelo de instrução é preparado e enviado ao sistema de banco de dados e o sistema de banco de dados executa a análise, compilação e otimização nesse modelo e armazena-o sem executá-lo.

Alguns parâmetros como, em que a cláusula não é passada durante a aplicação posterior da criação do modelo, enviam esses parâmetros para o sistema de banco de dados e o sistema de banco de dados usa o modelo da Instrução SQL e é executado conforme a solicitação.

As instruções preparadas são muito úteis no SQL Injection, porque o aplicativo pode preparar parâmetros usando diferentes técnicas e protocolos.

Quando o número de dados está aumentando e os índices estão mudando com frequência naquele momento, as Instruções Preparadas podem falhar, pois nessa situação requer um novo plano de consulta.

Anvesh
fonte
3

Statement interface executa instruções SQL estáticas sem parâmetros

PreparedStatement interface (estendendo a instrução) executa uma instrução SQL pré-compilada com / sem parâmetros

  1. Eficiente para execuções repetidas

  2. Como pré-compilado, é mais rápido

Bernard
fonte
2

Não fique confuso: basta lembrar

  1. A instrução é usada para consultas estáticas, como DDLs, por exemplo, criar, soltar, alterar e preparar. A declaração é usada para consultas dinâmicas, ou seja, consulta DML.
  2. Em Instrução, a consulta não é pré-compilada enquanto na consulta prepareStatement é pré-compilada, porque esse prepareStatement é eficiente em termos de tempo.
  3. prepareStatement recebe um argumento no momento da criação, enquanto Statement não aceita argumentos. Por exemplo, se você deseja criar tabela e inserir elemento, então :: Crie tabela (estática) usando Statement e Inserir elemento (dinâmico) usando prepareStatement.
Roopam
fonte
1
prepareStatement recebe um argumento no momento da criação, enquanto Statement não aceita argumentos.
1

Eu segui todas as respostas desta pergunta para alterar um código legado em funcionamento usando - Statement(mas com injeções SQL) para uma solução usando PreparedStatementum código muito mais lento por causa do fraco entendimento da semântica em torno do Statement.addBatch(String sql)& PreparedStatement.addBatch().

Então, estou listando meu cenário aqui para que outros não cometam o mesmo erro.

Meu cenário foi

Statement statement = connection.createStatement();

for (Object object : objectList) {
    //Create a query which would be different for each object 
    // Add this query to statement for batch using - statement.addBatch(query);
}
statement.executeBatch();

Portanto, no código acima, eu tinha milhares de consultas diferentes, todas adicionadas à mesma instrução e esse código funcionava mais rápido porque as instruções que não estavam sendo armazenadas em cache eram boas e esse código era executado raramente no aplicativo.

Agora, para corrigir as injeções de SQL, alterei esse código para,

List<PreparedStatement> pStatements = new ArrayList<>();    
for (Object object : objectList) {
    //Create a query which would be different for each object 
    PreparedStatement pStatement =connection.prepareStatement(query);
    // This query can't be added to batch because its a different query so I used list. 
    //Set parameter to pStatement using object 
    pStatements.add(pStatement);
}// Object loop
// In place of statement.executeBatch(); , I had to loop around the list & execute each update separately          
for (PreparedStatement ps : pStatements) {
    ps.executeUpdate();
}

Entenda, comecei a criar milhares de PreparedStatementobjetos e, eventualmente, não consegui utilizar lotes porque meu cenário exigia isso - existem milhares de consultas UPDATE ou INSERT e todas essas consultas são diferentes.

A correção da injeção de SQL era obrigatória sem nenhum custo de degradação do desempenho e não acho que isso seja possível PreparedStatementnesse cenário.

Além disso, ao usar o recurso de lote embutido, você precisa se preocupar em fechar apenas uma instrução, mas com essa abordagem da lista, é necessário fechar a instrução antes de reutilizá-la, reutilizando uma declaração preparada

Sabir Khan
fonte