Nos conectamos… ¿y ahora cómo guardamos los productos?
En el material 02 aprendimos a solicitar una conexión a MySQL mediante DriverManager.getConnection(). Conseguimos comunicarnos con la base de datos, pero todavía no hemos guardado ningún producto.
En nuestra tienda seguimos creando objetos Java que almacenamos en un ArrayList<Producto>. Por ejemplo:
Producto producto = new Producto(101, "Batman: Año Uno", 790.0, 8);
El objeto contiene cuatro datos: código, nombre, precio y stock. En MySQL, estos mismos datos se organizarán en columnas de una tabla.
Java
Objeto Producto con cuatro atributos.
MySQL
Una fila de la tabla producto.
Antes de escribir Java: ¿dónde se guardarán los datos?
Para los ejemplos supondremos que ya existe una base de datos llamada tiendacomics. Dentro de ella utilizaremos una tabla denominada producto.
CREATE TABLE producto (
codigo BIGINT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
precio DECIMAL(10,2) NOT NULL,
stock INT NOT NULL
);Observa la relación entre los tipos:
| Producto en Java | Columna en SQL |
|---|---|
long codigo | BIGINT |
String nombre | VARCHAR(100) |
double precio | DECIMAL(10,2) |
int stock | INT |
Para mantenernos dentro del modelo actual de la tienda utilizaremos double para el precio. En una aplicación comercial, BigDecimal resulta más apropiado para dinero porque evita algunas imprecisiones de los números decimales binarios.
La primera instrucción SQL: INSERT
Para guardar un producto utilizaremos la instrucción SQL INSERT.
INSERT INTO producto (codigo, nombre, precio, stock) VALUES (101, 'Batman: Año Uno', 790.00, 8);
¿Qué significa cada parte?
INSERT INTO producto: queremos insertar una fila en esa tabla.(codigo, nombre, precio, stock): indicamos las columnas que recibirán datos.VALUES (...): proporcionamos los valores en el mismo orden.
Ese ejemplo sirve para un producto. Sin embargo, necesitamos que la misma operación funcione para cualquier objeto que el usuario agregue.
PreparedStatement: una sentencia con lugares reservados
Podríamos unir fragmentos de texto con los valores del producto. Pero aparecerían problemas si el nombre contiene una comilla y, además, estaríamos exponiendo el programa a riesgos de inyección SQL.
JDBC ofrece una herramienta adecuada para esta tarea: PreparedStatement.
String sql = "INSERT INTO producto (codigo, nombre, precio, stock) "
+ "VALUES (?, ?, ?, ?)";Los signos ? indican lugares reservados. Todavía no contienen los datos de ningún producto.
PreparedStatement sentencia = conexion.prepareStatement(sql);
Analicemos esta instrucción:
conexiones la referenciaConnectionobtenida en el material anterior.prepareStatement(sql)prepara la instrucción SQL en el contexto de esa conexión.sentenciaes una variable de tipoPreparedStatement; a través de ella asignaremos parámetros y ejecutaremos la operación.
¿Cómo colocamos los valores del objeto en cada parámetro?
Para completar los signos ? utilizamos métodos que empiezan con set. Cada uno recibe la posición del parámetro y el valor correspondiente.
sentencia.setLong(1, producto.getCodigo()); sentencia.setString(2, producto.getNombre()); sentencia.setDouble(3, producto.getPrecio()); sentencia.setInt(4, producto.getStock());
| Posición | Método | Valor del objeto |
|---|---|---|
| 1 | setLong | codigo |
| 2 | setString | nombre |
| 3 | setDouble | precio |
| 4 | setInt | stock |
Los parámetros JDBC se cuentan desde 1. No debemos confundirlos con las posiciones de los arreglos y listas de Java, que empiezan en 0.
executeUpdate(): ahora sí, ejecutamos el INSERT
Preparar la sentencia y asignar los parámetros no significa que el producto ya se haya guardado.
Tenemos que ejecutar la instrucción:
int filasAfectadas = sentencia.executeUpdate();
executeUpdate() se utiliza con instrucciones como INSERT, UPDATE y DELETE. Devuelve un número entero: la cantidad de filas afectadas por la operación.
if (filasAfectadas == 1) {
System.out.println("Se guardó un producto");
}El número 1 indica, en este caso, que se insertó una fila.
prepareStatement() prepara la instrucción; los métodos set...() asignan sus valores; executeUpdate() solicita ejecutarla.Unimos las piezas: guardar un producto
Reunamos lo aprendido. Este código es una demostración de JDBC; todavía no lo colocaremos en Controller ni en Service. Más adelante lo trasladaremos al DAO.
public static void insertarProducto(Producto producto)
throws SQLException {
String url = "jdbc:mysql://localhost:3306/tiendacomics";
String usuario = "usuario_prueba";
String clave = "clave_prueba";
String sql = "INSERT INTO producto (codigo, nombre, precio, stock) "
+ "VALUES (?, ?, ?, ?)";
try (Connection conexion = DriverManager.getConnection(url, usuario, clave);
PreparedStatement sentencia = conexion.prepareStatement(sql)) {
sentencia.setLong(1, producto.getCodigo());
sentencia.setString(2, producto.getNombre());
sentencia.setDouble(3, producto.getPrecio());
sentencia.setInt(4, producto.getStock());
int filasAfectadas = sentencia.executeUpdate();
System.out.println("Filas insertadas: " + filasAfectadas);
}
}Por ahora repetimos la llamada a DriverManager.getConnection(...) que aprendimos en el SCORM 02. Las credenciales son ficticias. En el próximo material veremos por qué nos conviene centralizar esa configuración, en lugar de copiarla en cada operación.
La estructura try-with-resources cierra la sentencia y la conexión al salir del bloque, incluso si ocurre una excepción. El método declara throws SQLException porque deja que esa excepción se propague a quien lo invoque.
SQLException o declarar que también puede propagarla.La operación contraria: SELECT para leer los productos
Supongamos ahora que volvemos a abrir nuestra aplicación. Necesitamos recuperar los productos almacenados.
En MySQL podemos consultar los datos con SELECT:
SELECT codigo, nombre, precio, stock FROM producto ORDER BY nombre;
Desde Java preparamos la sentencia como antes, pero ahora ejecutamos una consulta que devuelve filas:
String sql = "SELECT codigo, nombre, precio, stock "
+ "FROM producto ORDER BY nombre";
PreparedStatement sentencia = conexion.prepareStatement(sql);
ResultSet resultado = sentencia.executeQuery();Observa la diferencia:
executeUpdate()
Se utiliza al modificar datos. Devuelve la cantidad de filas afectadas.
executeQuery()
Ejecuta un SELECT y devuelve un objeto ResultSet.
ResultSet: recorrer las filas recibidas
ResultSet representa los resultados de nuestra consulta. Contiene un cursor que permite recorrer las filas disponibles.
Al principio, ese cursor se encuentra antes de la primera fila. Para avanzar utilizamos next().
while (resultado.next()) {
String nombre = resultado.getString("nombre");
System.out.println(nombre);
}Cuando next() consigue ubicarse en una fila, devuelve true. Cuando ya no hay más filas, devuelve false y termina el while.
Los métodos getLong(), getString(), getDouble() y getInt() permiten recuperar valores de columnas de distintos tipos.
ResultSet no es un ArrayList<Producto>. Es un resultado JDBC que debemos procesar para obtener objetos de nuestro modelo.Convertimos cada fila en un objeto Producto
Ahora queremos que la lista de resultados pueda ser utilizada por nuestra aplicación, que trabaja con objetos Producto.
Crearemos un nuevo Producto por cada fila y lo agregaremos a una lista:
List<Producto> productos = new ArrayList<Producto>();
while (resultado.next()) {
long codigo = resultado.getLong("codigo");
String nombre = resultado.getString("nombre");
double precio = resultado.getDouble("precio");
int stock = resultado.getInt("stock");
Producto producto = new Producto(codigo, nombre, precio, stock);
productos.add(producto);
}El código hace algo muy parecido a lo que ya conocemos: construye objetos e incorpora cada uno a un ArrayList. La diferencia es el origen de los datos: ahora provienen de una consulta SQL.
Al guardar
Objeto Java → atributos → parámetros → INSERT → fila de MySQL.
Al recuperar
SELECT → ResultSet → atributos → nuevo objeto Java.
El ejemplo completo de consulta y cierre de recursos
Veamos cómo se relacionan todas las instrucciones. Esta es todavía una demostración previa a la creación de nuestro ProductoDAO.
public static List<Producto> listarProductos()
throws SQLException {
List<Producto> productos = new ArrayList<Producto>();
String url = "jdbc:mysql://localhost:3306/tiendacomics";
String usuario = "usuario_prueba";
String clave = "clave_prueba";
String sql = "SELECT codigo, nombre, precio, stock FROM producto";
try (Connection conexion = DriverManager.getConnection(url, usuario, clave);
PreparedStatement sentencia = conexion.prepareStatement(sql);
ResultSet resultado = sentencia.executeQuery()) {
while (resultado.next()) {
Producto producto = new Producto(
resultado.getLong("codigo"),
resultado.getString("nombre"),
resultado.getDouble("precio"),
resultado.getInt("stock")
);
productos.add(producto);
}
}
return productos;
}try-with-resources declara tres recursos: Connection, PreparedStatement y ResultSet. Java los cerrará en orden inverso al de su declaración.
Observa también que devolvemos la lista después del bloque de recursos. Ya no necesitamos mantener una conexión abierta para utilizar los objetos que construimos.
return al final del método.¿Dónde deberíamos colocar todo este código?
Tenemos dos operaciones: insertar productos y recuperar una lista. Ambas necesitan SQL, conexión, sentencias y gestión de resultados.
¿Debería ProductoController encargarse de todas estas instrucciones? ¿Y ProductoService, cuya responsabilidad es comprobar las reglas de negocio?
Interfaz: botones y tabla
Coordina eventos
Aplica reglas de negocio
Se encargaría del acceso a datos
En la unidad anterior aprendimos a reconocer cuando una clase acumula responsabilidades. Aquí está ocurriendo algo semejante: necesitamos un componente especializado para las operaciones de persistencia.
Ese componente será un DAO, pero lo construiremos paso a paso en el próximo material.
Comprobamos lo aprendido
Selecciona una respuesta y revisa la devolución.
1. ¿Para qué utilizamos PreparedStatement?
2. ¿En qué posición se encuentra el primer signo ? de la sentencia preparada?
3. ¿Cuál de estos métodos devuelve un ResultSet?
4. ¿Qué ocurre cuando resultado.next() devuelve false?
5. Después de cerrar y abrir el programa, ¿qué hacemos con los registros recuperados?
Documentamos y dejamos preparada la siguiente clase
Microentrega de documentación
- Explica la diferencia entre
prepareStatement(),executeUpdate()yexecuteQuery(). - Indica a qué atributo de
Productocorresponde cada signo?de nuestroINSERT. - Describe qué sucede durante una iteración de
while(resultado.next()). - Realiza un pequeño diagrama: Producto → INSERT → tabla → SELECT → nuevo Producto.
- Justifica por qué el Controller no debería contener todo este código JDBC.
Próximo material: construiremos ProductoDAO y una clase de conexión reutilizable, integrando el acceso a datos con ProductoService sin mezclar las responsabilidades.

