9.c.1 - Inserción en Bases de Datos

Objetivo

Escribir código para almacenar información en bases de datos.

1. Contenidos

1.1 Idea general

Una vez que ya tenemos la conexión con la base de datos, podemos ejecutar sentencias SQL (si la base de datos es relacional) para gestionar la información. La base de datos será quien garantice la persistencia de los datos cuando nuestro programa deje de ejecutarse.

RECUERDA: Los datos que maneja un programa se almacenan en la memoria principal del ordenador. Como esta memoria es volátil, los datos se borran al terminar la ejecución o al apagar el ordenador. La persistencia nos garantiza que podemos salvar y recuperar los datos entre ejecuciones.

1.2 Ejemplo

Insertaremos datos aprovechando la conexión que creamos con SQLite:

Copia el Proyecto

Empieza copiando el proyecto del ejercicio anterior. Nos servirá de base para este ejemplo.

Escribe el código

Programamos una prueba de concepto para realizar la inserción.


		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 InsertarAutor {

			public static void main(String[] args) {

				//1. Establecemos la URL de conexión con la base de datos
				String url = "jdbc:sqlite:autores.sqlite";

				//2. Definimos las sentencia SQL que queremos ejecutar.
				// Los parámetros se identifican con ?
				String sqlInsertar = """
					INSERT INTO Autor (nombre, nacionalidad)
					VALUES (?, ?);
					""";

				//3. Intentamos realizar la conexión en un "try con recursos"
				// En el mismo try, creamos la sentencia.
				// Si el try falla, la conexión y la sentencia se cerrarán correctamente
				try (Connection conexion = DriverManager.getConnection(url)) {

					//4. Establecemos el valor de los datos a insertar
					String nombre = "Miguel de Cervantes";
					String nacionalidad = "Española";

					//5. Intentamos preparar la sentencia.
					try (PreparedStatement sentencia = conexion.prepareStatement(
							sqlInsertar,
							Statement.RETURN_GENERATED_KEYS
					)) {

						//6. Asociamos los datos a los parámetros de la sentencia preparada
						sentencia.setString(1, nombre);
						sentencia.setString(2, nacionalidad);

						//7. Ejecutamos la sentencia preparada
						Integer filasInsertadas = sentencia.executeUpdate();

						//8. Si se ha insertado, leemos el id que ha asignado la base de datos.
						// y mostramos los resultados
						if (filasInsertadas > 0) {
							try (ResultSet clavesGeneradas = sentencia.getGeneratedKeys()) {
								if (clavesGeneradas.next()) {
									Integer idGenerado = clavesGeneradas.getInt(1);
									System.out.println("Autor insertado correctamente.");
									System.out.println("ID generado: " + idGenerado);
								}
							}
						}
					}

				} catch (SQLException e) {
					System.err.println("Error al insertar el autor:");
					System.err.println(e.getMessage());
				}
			}
		}				
				

Compila y Ejecuta

Compila el programa con:


	javac InsertarAutor.java
				

Ejecuta enlazando la librería:


	(en linux) java -cp sqlite-jdbc-3.51.3.0.jar:. InsertarAutor
	(en windows) java -cp sqlite-jdbc-3.51.3.0.jar;. InsertarAutor
				

Debes ver en la consola el mensaje "Autor insertado correctamente." y el número de id asignado.

Compruébalo

Abre la aplicación DB SQLite Browser y abre con ella nuestra base de datos. Comprueba que se han insertado los datos en la tabla.

1.3 La importancia de las sentencias preparadas

Las sentencias preparadas son fundamentales en las aplicaciones que trabajan con bases de datos. Una razón es que permiten separar claramente dos cosas:

  1. La estructura de la sentencia SQL.
  2. Los datos que se van a insertar, actualizar o consultar.

Otra es que evitan los ataques por inyección de SQL.

La inyección SQL es uno de los problemas de seguridad más graves en aplicaciones que usan bases de datos. Supongamos que construimos la consulta concatenando texto:


	String sql = "INSERT INTO Autor (nombre, nacionalidad) VALUES ('"
			+ nombre + "', '"
			+ nacionalidad + "')";
				

Esto es peligroso, porque el usuario podría introducir texto malicioso. Por ejemplo, en un formulario podría escribir algo que alterase la sentencia SQL original (por ejemplo, asignar a nombre el valor ";DROP DATABASE"). En cambio, con una sentencia preparada, los valores no se interpretan como parte del SQL, sino como datos. Es decir, aunque el usuario introduzca caracteres especiales, comillas o palabras reservadas de SQL, la base de datos los tratará como texto normal. Por eso esta forma es mucho más segura.

Además, con las sentencias preparadas también evitamos problemas con los valores que tienen comillas dentro (como el apellido O'Reilly), gestionamos mejor los tipos de datos de las sentencias y podemos mejorar el rendimiento (pues la base de datos no tiene que repetir la preparación del plan de ejecución cada vez que se ejecuta la sentencia).

Por todo esto, utiliza SIEMPRE sentencias preparadas en tus programas.

2. Ejercicios

2.1 Tipos de Datos

En primer lugar vamos a cambiar la ubicación de la base de datos. En una aplicación real no estarían todos los ficheros en el mismo directorio. Crea la siguiente estructura:

En segundo lugar, crea una nueva base de datos de nombre tipos_datos.sqlite, que contendrá solo la siguiente tabla:


	CREATE TABLE Popurri (
	    integer_type INTEGER,
	    real_type REAL,
	    text_type TEXT,
	    blob_type BLOB,
	    numeric_type NUMERIC,
	    boolean_type BOOLEAN,
	    date_type DATE,
	    datetime_type DATETIME
	);
				

El directorio de proyecto debe quedar así:


.
	├── compilados 
	├── fuentes 
	├── libs 
	│   └── sqlite-jdbc-3.51.3.0.jar
	├── datos 
	│   └── tipos_datos.sqlite
	└── README.md 
				

Y en el directorio /fuentes crea el fichero Ejercicio9c1b.java. Tendrás que adaptar tu código para que compile y se ejecute bien. Anota los comandos en el fichero README.md

Luego, completa el código para insertar un ejemplo de cada uno de los tipos de datos que soporta SQLite.

Reto

MySQL soporta más tipos de datos que SQLite. No vamos a probar con todos (algunos son muy complicados), pero prueba con los que se detallan en W3Schools: Haz el código para conectarte e insertar todos esos tipos de datos en MySQL. (mantén la estructura de directorios del ejercicio anterior).

Bibliografía