Embora seja verdade que, em muitos casos, uma instrução SQL básica fará o trabalho para muitas alterações ou consultas de banco de dados, geralmente é uma prática recomendada para fazer uso da flexibilidade e vantagens oferecidas a você usando
PreparedStatements
. As principais diferenças entre uma instrução JDBC padrão e uma
PreparedStatement
são melhor definidos pelos benefícios que um PreparedStatement
oferece a você e sua aplicação. Abaixo, examinaremos as três principais vantagens de PreparedStatements
sobre instruções JDBC/SQL regulares. Prevenção de injeção de SQL
O primeiro benefício de usar um
PreparedStatement
é que você pode tirar vantagem da multiplicidade de .setXYZ()
métodos, como .setString()
, que permite que seu código escape automaticamente de caracteres especiais, como aspas na instrução SQL passada, evitando a sempre perigosa SQL injection
ataque. Por exemplo, em uma instrução SQL padrão, pode ser comum inserir valores diretamente alinhados com a instrução, assim:
statement = "INSERT INTO books (title, primary_author, published_date) VALUES ('" + book.getTitle() + "', '" + book.getPrimaryAuthor() + "', '" + new Timestamp(book.getPublishedDate().getTime()) + "'";
Isso forçaria você a executar seu próprio código para evitar injeções de SQL escapando aspas e outros caracteres especiais dos valores inseridos.
Por outro lado, um
PreparedStatement
pode ser invocado da seguinte forma, usando o .setXYZ()
métodos para inserir valores com escape automático de caracteres durante a execução do método:ps = connection.prepareStatement("INSERT INTO books (title, primary_author, published_date) VALUES (?, ?, ?)");
ps.setString(1, book.getTitle());
ps.setString(2, book.getPrimaryAuthor());
ps.setTimestamp(3, new Timestamp(book.getPublishedDate().getTime()));
ps.executeUpdate();
Pré-compilação
Outro benefício de um
PreparedStatement
é que o próprio SQL é pre-compiled
uma única vez e, em seguida, retido na memória pelo sistema, em vez de ser compilado toda vez que a instrução é chamada. Isso permite uma execução mais rápida, principalmente quando um PreparedStatement
é usado em conjunto com batches
, que permitem que você execute uma série (ou batch
) de instruções SQL de uma só vez durante uma única conexão de banco de dados. Por exemplo, aqui temos uma função que aceita uma
List
de livros. Para cada book
na lista, queremos executar um INSERT
instrução, mas vamos adicionar todos eles a um lote de PreparedStatements
e executá-los todos de uma só vez:public void createBooks(List<Entity> books) throws SQLException {
try (
Connection connection = dataSource.getConnection();
PreparedStatement ps = connection.prepareStatement("INSERT INTO books (title, primary_author, published_date) VALUES (?, ?, ?)");
) {
for (Entity book : books) {
ps.setString(1, book.getTitle());
ps.setString(2, book.getPrimaryAuthor());
ps.setTimestamp(3, new Timestamp(book.getPublishedDate().getTime()));
ps.addBatch();
}
ps.executeBatch();
}
}
Inserção de tipos de dados anormais na instrução SQL
A vantagem final de
PreparedStatements
que abordaremos é a capacidade de inserir tipos de dados anormais na própria instrução SQL, como Timestamp
, InputStream
, e muitos mais. Por exemplo, podemos usar um
PreparedStatement
para adicionar uma foto de capa ao nosso registro de livro usando o .setBinaryStream()
método:ps = connection.prepareStatement("INSERT INTO books (cover_photo) VALUES (?)");
ps.setBinaryStream(1, book.getPhoto());
ps.executeUpdate();