Si bien es cierto que, en muchos casos, una instrucción SQL básica hará el trabajo para muchos cambios o consultas en la base de datos, a menudo es una práctica recomendada para hacer uso de la flexibilidad y las ventajas que le brinda el uso de PreparedStatements
.
Las principales diferencias entre una instrucción JDBC estándar y una PreparedStatement
se definen mejor por los beneficios que una PreparedStatement
le ofrece a usted y a su aplicación. A continuación, examinaremos las tres ventajas principales de PreparedStatements
sobre declaraciones regulares de JDBC/SQL.
Prevención de inyección SQL
El primer beneficio de usar un PreparedStatement
es que puede aprovechar la multitud de .setXYZ()
métodos, como .setString()
, que permite que su código escape automáticamente de caracteres especiales como comillas dentro de la declaración SQL pasada, evitando la siempre peligrosa SQL injection
ataque.
Por ejemplo, en una sentencia SQL estándar, puede ser típico insertar valores directamente en línea con la sentencia, así:
statement = "INSERT INTO books (title, primary_author, published_date) VALUES ('" + book.getTitle() + "', '" + book.getPrimaryAuthor() + "', '" + new Timestamp(book.getPublishedDate().getTime()) + "'";
Esto lo obligaría a ejecutar su propio código para evitar inyecciones de SQL escapando de las comillas y otros caracteres especiales de los valores insertados.
Por el contrario, una PreparedStatement
podría invocarse de la siguiente manera, usando el .setXYZ()
métodos para insertar valores con carácter de escape automático durante la ejecución del 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();
Precompilación
Otro beneficio de una PreparedStatement
es que el propio SQL está pre-compiled
una sola vez y luego el sistema lo retiene en la memoria, en lugar de compilarlo todas y cada una de las veces que se llama a la declaración. Esto permite una ejecución más rápida, particularmente cuando un PreparedStatement
se usa junto con batches
, que le permiten ejecutar una serie (o batch
) de sentencias SQL todas a la vez durante una única conexión a la base de datos.
Por ejemplo, aquí tenemos una función que acepta una List
de libros. Para cada book
en la lista, queremos ejecutar un INSERT
declaración, pero vamos a agregarlos todos a un lote de PreparedStatements
y ejecutarlos todos de una sola 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();
}
}
Inserción de tipos de datos anormales en declaraciones SQL
La ventaja final de PreparedStatements
lo que cubriremos es la capacidad de insertar tipos de datos anormales en la instrucción SQL, como Timestamp
, InputStream
y muchos más.
Por ejemplo, podemos usar un PreparedStatement
para agregar una foto de portada a nuestro registro de libros usando .setBinaryStream()
método:
ps = connection.prepareStatement("INSERT INTO books (cover_photo) VALUES (?)");
ps.setBinaryStream(1, book.getPhoto());
ps.executeUpdate();