PreparedStatement
Consultas parametrizadas seguras
PreparedStatement es una lección gratuita de Java Academy en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Java Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Java Academy incluye 4 lecciones en total.
¿Por qué PreparedStatement?
Construir SQL concatenando la entrada del usuario es peligroso y lento.
PreparedStatement resuelve ambos problemas:
- Seguridad: los valores se envían por separado del SQL, lo que evita la inyección de SQL.
- Rendimiento: la base de datos puede analizar y planificar la consulta una vez y reutilizarla después.
El problema de la inyección de SQL
Considere la construcción de una consulta mediante concatenación de cadenas. Si un usuario escribe '; DROP TABLE users; -- como nombre, el SQL concatenado ejecuta ese contenido como un comando.
Esto es la inyección de SQL, una de las vulnerabilidades web más antiguas y dañinas. PreparedStatement la impide por diseño.
public class Main {
public static void main(String[] args) {
String userInput = "'; DROP TABLE users; --";
// Dangerous: never do this
String unsafe = "SELECT * FROM users WHERE name = '" + userInput + "'";
System.out.println("Injected SQL becomes:");
System.out.println(unsafe);
}
}Marcadores de posición
En un PreparedStatement, escriba un ? por cada valor que vaya a proporcionar más adelante.
Después, asigne los valores mediante setters tipados:
ps.setString(1, name)ps.setInt(2, age)
Los índices de los parámetros están basados en 1 y siguen el orden de los marcadores ?.
Una consulta segura
Cree la sentencia con prepareStatement, asigne los parámetros y, después, llame a executeQuery.
El valor nunca entra en el texto SQL, por lo que no puede producirse una inyección, independientemente de lo que escriba el usuario.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Main {
public static void main(String[] args) {
String url = "jdbc:postgresql://localhost:5432/shop";
String sql = "SELECT id FROM users WHERE name = ?";
try (Connection conn = DriverManager.getConnection(url, "app", "secret");
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, "Ada");
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) System.out.println(rs.getInt("id"));
}
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
}Asignar varios parámetros
Cada ? requiere su propia llamada a un setter. El orden es importante: el parámetro 1 corresponde al primer ?, el parámetro 2 al segundo, y así sucesivamente.
Utilice el setter que corresponda al tipo de columna: setInt, setString, setDouble, setBoolean, etc.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Main {
public static void main(String[] args) {
String url = "jdbc:postgresql://localhost:5432/shop";
String sql = "INSERT INTO users(name, age, active) VALUES (?, ?, ?)";
try (Connection conn = DriverManager.getConnection(url, "app", "secret");
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, "Grace");
ps.setInt(2, 42);
ps.setBoolean(3, true);
int rows = ps.executeUpdate();
System.out.println("Inserted " + rows + " row");
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
}Gestionar NULL
Para asignar un NULL de SQL, utilice setNull(index, type) con una constante de java.sql.Types.
No pase un null de Java a setString esperando que funcione; sea explícito:
ps.setNull(2, java.sql.Types.INTEGER)
Recuperar claves generadas
Después de un INSERT, a menudo necesita el identificador generado automáticamente.
Pase Statement.RETURN_GENERATED_KEYS al preparar la sentencia y, después, lea ps.getGeneratedKeys().
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Main {
public static void main(String[] args) {
String url = "jdbc:postgresql://localhost:5432/shop";
String sql = "INSERT INTO users(name) VALUES (?)";
try (Connection conn = DriverManager.getConnection(url, "app", "secret");
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, "Linus");
ps.executeUpdate();
try (ResultSet keys = ps.getGeneratedKeys()) {
if (keys.next()) System.out.println("New id: " + keys.getLong(1));
}
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
}Inserciones por lotes
Insertar miles de filas una sentencia a la vez es lento. Utilice el procesamiento por lotes:
- Asigne los parámetros y, después, llame a
ps.addBatch(). - Repita el proceso para cada fila.
- Llame una vez a
ps.executeBatch().
Esto envía las filas juntas y reduce considerablemente los viajes de ida y vuelta.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Main {
public static void main(String[] args) {
String url = "jdbc:postgresql://localhost:5432/shop";
String sql = "INSERT INTO tags(label) VALUES (?)";
String[] labels = {"java", "jdbc", "sql"};
try (Connection conn = DriverManager.getConnection(url, "app", "secret");
PreparedStatement ps = conn.prepareStatement(sql)) {
for (String label : labels) {
ps.setString(1, label);
ps.addBatch();
}
int[] counts = ps.executeBatch();
System.out.println("Batched " + counts.length + " inserts");
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
}Reutilizar una sentencia
Una única PreparedStatement puede ejecutarse muchas veces con parámetros diferentes.
Vuelva a asignar los valores de los marcadores y llame de nuevo a execute. La base de datos reutiliza el plan de ejecución almacenado en caché, por lo que las sentencias preparadas son más rápidas para las consultas repetidas.
Nunca concatene
La regla de oro: cualquier valor que varíe debe ser un parámetro ?.
Solo se pueden concatenar identificadores fijos y controlados por el código (como un nombre de columna incluido en una lista de permitidos), e incluso en ese caso debe validarlos contra una lista de permitidos. Los datos proporcionados por el usuario siempre se asignan como parámetros, nunca se concatenan.
Cierre y ámbito de reutilización
Un PreparedStatement está vinculado a su Connection y debe cerrarse cuando termine de utilizarlo.
Dentro de try-with-resources, declare la sentencia para que se cierre antes que la conexión. Si reutiliza una sentencia muchas veces en un bucle, prepárela una sola vez fuera del bucle y vuelva a asignar únicamente los parámetros dentro de él; preparar la sentencia de nuevo en cada iteración descarta el plan almacenado en caché.
Comprobación rápida
¿Por qué un PreparedStatement evita la inyección SQL?
Repaso
Ha aprendido a escribir consultas seguras y rápidas:
- Utilice marcadores de posición
?y setters tipados (índice basado en 1). - Vincule valores con
setString,setInt,setNull, etc. - Obtenga claves generadas automáticamente con
RETURN_GENERATED_KEYSygetGeneratedKeys. - Agrupe muchas escrituras por lotes con
addBatch+executeBatch. - No concatene nunca la entrada del usuario con SQL.
Preguntas frecuentes
¿La lección «PreparedStatement» es gratis?
Sí — el texto completo de «PreparedStatement» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Java Academy, actualiza a CoddyKit PRO. El curso de Java Academy incluye 4 lecciones en total.
¿Qué aprenderé en «PreparedStatement»?
Consultas parametrizadas seguras Practicas Java Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Java Academy?
No se requiere experiencia previa. Java Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.
¿Cuánto tiempo toma la lección «PreparedStatement»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Java Academy?
Sí. Cada lección de Java Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.