Bobby Tables y PreparedStatement

En este episodio os explico por qué PreparedStatement es infinitamente mejor que Statement cuando tenemos consultas con parámetros que dependen del mundo exterior. ¿Cómo? ¿No conoces a Bobby Tables? \u003ca href='https://xkcd.com/327/' target='_blank'\u003eMira esto\u003c/a\u003e.

Hay una versión nueva de este curso. Haz clic aquí para revisar JDBC, la versión actualizada de este curso.

Cuando trabajamos con bases de datos en Java, uno de los casos más comunes es realizar consultas SQL que incluyan parámetros para filtrar resultados. Por ejemplo, imaginemos que queremos obtener todos los alumnos que tengan un apellido específico, como López. Podríamos escribir una consulta SQL sencilla que devuelva esos registros, pero si intentamos replicar esto en Java usando JDBC, debemos tener mucho cuidado con cómo introducimos esos parámetros.

Un error frecuente es construir la consulta concatenando directamente el valor del parámetro en la cadena SQL. Por ejemplo, podríamos hacer algo así:

String apellido = "López";
String query = "SELECT * FROM alumnos WHERE apellidos = '" + apellido + "'";
Statement statement = connection.createStatement();
ResultSet rs = statement.executeQuery(query);

Aunque esto parece funcionar, es una práctica peligrosa porque abre la puerta a ataques de inyección SQL. Si el valor del parámetro proviene de un usuario, podría incluir código malicioso que altere la consulta y comprometa la seguridad de la base de datos. Este problema es tan conocido que existe una tira cómica famosa llamada Bobby Tables que ilustra perfectamente los riesgos de no sanitizar las entradas.

Para evitar estos problemas, JDBC nos ofrece una solución muy útil: los Prepared Statements. En lugar de concatenar valores directamente en la consulta, usamos un marcador de posición, representado por un signo de interrogación ?, y luego asignamos el valor del parámetro mediante métodos específicos. Así, la consulta se prepara y los valores se insertan de forma segura, evitando que caracteres especiales o código malicioso afecten la consulta.

Veamos cómo se hace esto en código:

String query = "SELECT * FROM alumnos WHERE apellidos = ?";
PreparedStatement preparedStatement = connection.prepareStatement(query);
preparedStatement.setString(1, "López");
ResultSet rs = preparedStatement.executeQuery();

Aquí, el ? indica el lugar donde irá el parámetro. Luego, con setString(1, "López"), indicamos que el primer parámetro debe ser la cadena López. Es importante recordar que la numeración de los parámetros comienza en 1, no en 0. JDBC también ofrece otros métodos para asignar parámetros de distintos tipos, como setInt, setLong, setBigDecimal, entre otros, lo que facilita trabajar con diferentes tipos de datos.

Además, si nuestra consulta tiene más de un parámetro, simplemente añadimos más signos de interrogación y los asignamos en orden:

String query = "SELECT * FROM alumnos WHERE apellidos = ? AND id_alumno < ?";
PreparedStatement preparedStatement = connection.prepareStatement(query);
preparedStatement.setString(1, "López");
preparedStatement.setInt(2, 5);
ResultSet rs = preparedStatement.executeQuery();

De esta forma, obtenemos todos los alumnos cuyo apellido sea López y cuyo ID sea menor que 5.

El uso de Prepared Statements no solo mejora la seguridad, sino que también puede optimizar el rendimiento, ya que la consulta se compila una sola vez y puede reutilizarse con diferentes parámetros. Pero lo más importante es que nos protege contra ataques de inyección SQL, un riesgo especialmente grave en aplicaciones web donde cualquier usuario puede manipular los parámetros de entrada.

Por eso, siempre que tengamos que incluir parámetros en nuestras consultas SQL, debemos evitar concatenar cadenas directamente y preferir el uso de Prepared Statements. Así garantizamos que los datos que entran a la base de datos se traten correctamente y evitamos vulnerabilidades que podrían comprometer la integridad y seguridad de nuestra aplicación.

Lista de reproducción
  1. 1
    Presentación de JDBC
    4 minutos
  2. 2
    Instalando MySQL
    7 minutos
  3. 3
    Creando tablas
    9 minutos
  4. 4
    Agregando el driver JAR
    7 minutos
  5. 5
    Estableciendo la conexión
    8 minutos
  6. 6
    Statement y ResultSet
    9 minutos
  7. 7
    Bobby Tables y PreparedStatement
    8 minutos
  8. 8
    Transacciones y MySQL
    6 minutos
  9. 9
    Transacciones, commits y rollbacks (parte 1)
    7 minutos
  10. 10
    Transacciones, commits y rollbacks (parte 2)
    6 minutos
  11. 11
    Ejemplo: Crear modelos
    9 minutos
  12. 12
    Ejemplo: Crear los DAO (parte 1)
    10 minutos
  13. 13
    Ejemplo: Crear los DAO (parte 2)
    12 minutos
  14. 14
    Ejemplo: Crear los DAO (parte 3)
    13 minutos
  15. 15
    Ejemplo: DAO Manager
    12 minutos
  16. 16
    Ejemplo: CRUD Alumnos (parte 1)
    15 minutos
  17. 17
    Ejemplo: CRUD Alumnos (parte 2)
    14 minutos
  18. 18
    Ejemplo: CRUD Alumnos (parte 3)
    13 minutos
  19. 19
    Ejemplo: CRUD Profesores
    25 minutos
  20. 20
    Ejemplo: CRUD Asignaturas
    alrededor de 1 hora
  21. 21
    Ejemplo: Login (final adelantado)
    17 minutos
  22. 22
    Conectar a PostgreSQL en Java con JDBC
    8 minutos
  23. 23
    Conectar a PostgreSQL en Kotlin con JDBC
    7 minutos
  24. 24
    Conectar a PostgreSQL en Java con JDBC (con NetBeans)
    10 minutos