¿Dónde colocamos todo el código de JDBC?
En el material anterior aprendimos a preparar un INSERT, consultar productos con SELECT y convertir las filas de un ResultSet en objetos Java. Pero todavía tenemos una decisión pendiente: ¿qué clase se hará cargo de esas operaciones?
Imagina que copiamos las conexiones, las sentencias SQL y sus excepciones dentro de ProductoController. Nuestro controlador tendría que escuchar los botones, leer los campos, gestionar la tabla, crear productos, abrir conexiones y ejecutar SQL.
¿Recuerdas por qué quitamos responsabilidades de Main? Estamos por repetir el mismo problema.
¿Qué responsabilidad tenía cada componente?
Antes de añadir una clase, recuperemos nuestra organización actual:
Campos, botones y JTable
Escucha eventos y coordina la interacción
Aplica las reglas del negocio
Una regla del negocio puede ser que el precio no sea negativo o que el nombre del producto no esté vacío. Estas reglas siguen siendo necesarias aunque dejemos de usar MySQL y guardemos los datos de otra manera.
Por eso ProductoService no debería convertirse en una colección de instrucciones JDBC. Necesitamos separar la decisión de si un producto es válido de la operación técnica de guardarlo.
Un nuevo componente: ProductoDAO
La solución que utilizaremos es el patrón DAO, sigla de Data Access Object (objeto de acceso a datos).
Crearemos una clase llamada ProductoDAO. Su función será acceder a los datos persistentes de los productos. Allí estarán las instrucciones SQL y el código JDBC necesario para ejecutarlas.
Muestra y recoge datos
Coordina eventos
Comprueba reglas del negocio
Accede a los datos: JDBC y SQL
Almacena registros
ProductoDAO no dibuja botones, no muestra JOptionPane y no decide si un precio cumple las reglas del comercio.Hay otro código que no queremos repetir
Supongamos que ya tenemos un método para insertar productos y otro para listarlos. Ambos necesitan la misma información: dirección del servidor, base de datos, usuario y contraseña.
String url = "jdbc:mysql://localhost:3306/tiendacomics"; String usuario = "usuario_prueba"; String clave = "clave_prueba"; Connection conexion = DriverManager.getConnection(url, usuario, clave);
Si más adelante creamos ClienteDAO, ¿vamos a copiar esos datos otra vez?
Podemos centralizar esta responsabilidad en una clase llamada ConexionBD, ubicada en el paquete config. Su objetivo es proporcionar conexiones cuando otro componente las necesite.
No se encarga de insertar productos ni de escribir un SELECT; esa será la responsabilidad de los DAO.
Construimos ConexionBD paso a paso
Partimos de los elementos que conocemos del SCORM 02: DriverManager, Connection y SQLException.
package uy.edu.utu.programacion.config;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class ConexionBD {
private static final String URL =
"jdbc:mysql://localhost:3306/tiendacomics";
private static final String USUARIO = "usuario_prueba";
private static final String CLAVE = "clave_prueba";
public static Connection obtenerConexion() throws SQLException {
Connection conexion = DriverManager.getConnection(
URL, USUARIO, CLAVE);
return conexion;
}
}¿Por qué usamos static? Porque obtenerConexion() puede invocarse directamente sobre el nombre de la clase, sin crear antes un objeto ConexionBD.
Connection conexion = ConexionBD.obtenerConexion();
¿Por qué usamos throws SQLException? Porque el método no resuelve aquí los posibles errores de conexión: deja que la excepción se comunique al código que solicitó la conexión.
La primera responsabilidad concreta del DAO: guardar
Ahora el DAO ya no necesita saber cómo están definidas la URL, el usuario y la clave. Solamente solicita una conexión.
public void guardar(Producto producto) throws SQLException {
String sql = "INSERT INTO producto (codigo, nombre, precio, stock) "
+ "VALUES (?, ?, ?, ?)";
try (Connection conexion = ConexionBD.obtenerConexion();
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());
sentencia.executeUpdate();
}
}Compara con el material anterior. Las instrucciones SQL y los métodos de PreparedStatement son los mismos; lo que cambió fue el lugar donde asumimos la responsabilidad.
Observa también el bloque try-with-resources: cuando termina, Java cierra la sentencia y la conexión, incluso si se produce una excepción.
No hay botones ni JOptionPane. Aquí solamente hay persistencia.
¿Cómo devolvemos productos al resto de la aplicación?
En lugar de entregar un ResultSet al controlador, el DAO puede convertir cada registro en un objeto Producto y devolver una lista. Así las otras capas siguen trabajando con objetos Java.
public List<Producto> listar() throws SQLException {
List<Producto> productos = new ArrayList<>();
String sql = "SELECT codigo, nombre, precio, stock FROM producto";
try (Connection conexion = ConexionBD.obtenerConexion();
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;
}Se necesitan las importaciones java.util.List, java.util.ArrayList y las correspondientes a JDBC. El fragmento muestra solo el método para concentrarnos en su funcionamiento.
El método devuelve una lista de objetos nuevos, reconstruidos desde los registros de MySQL. Respetamos además el criterio de un único return al final.
Las operaciones CRUD no son cuatro botones
Con frecuencia hablamos de CRUD: crear, leer, actualizar y eliminar datos. Pero debemos distinguir el botón de la interfaz de la operación de persistencia.
| Necesidad de la tienda | Operación | SQL |
|---|---|---|
| Guardar un producto nuevo | Create | INSERT |
| Obtener los productos existentes | Read | SELECT |
| Cambiar precio o stock | Update | UPDATE |
| Quitar un producto | Delete | DELETE |
El controlador recibe el evento de un botón. El servicio comprueba las reglas y solicita una operación. El DAO implementa su persistencia mediante SQL.
Por ejemplo, para modificar un producto necesitaríamos una instrucción como esta:
UPDATE producto SET nombre = ?, precio = ?, stock = ? WHERE codigo = ?;
El WHERE identifica qué producto queremos actualizar. Sin esa condición, un UPDATE podría afectar muchas filas de la tabla.
executeUpdate() informa cuántas filas fueron afectadas. Si devuelve 0 al modificar o eliminar, conviene investigar si el producto identificado existía.¿Qué cambia en ProductoService?
Hasta ahora el servicio utilizaba un ArrayList<Producto> para conservar productos mientras el programa estaba abierto. Ahora necesitamos que delegue el almacenamiento a ProductoDAO.
La idea puede expresarse con estas líneas abreviadas:
// Dentro de ProductoService
private ProductoDAO productoDAO;
public ProductoService(ProductoDAO productoDAO) {
this.productoDAO = productoDAO;
}El servicio recibe un objeto DAO en su constructor. A continuación, cuando un producto supera las validaciones, podrá solicitar:
productoDAO.guardar(producto);
Ese fragmento no reemplaza las comprobaciones de ProductoService ni su manejo de las excepciones de negocio; solo muestra la nueva colaboración entre componentes.
En Main, encargado de reunir los componentes, crearemos las dependencias y las entregaremos a quien las necesite. No modificaremos aún toda la aplicación: primero comprendamos cómo cambia el recorrido de una acción.
¿Quién comunica los errores de persistencia?
En la unidad anterior diferenciamos problemas de formato y reglas del negocio. Ahora agregamos errores de base de datos.
| Problema | Ejemplo | Responsabilidad |
|---|---|---|
| Formato | El usuario escribe letras donde se espera un número. | La entrada se interpreta o valida antes de crear el producto. |
| Negocio | Se intenta guardar un precio negativo. | ProductoService aplica la regla. |
| Persistencia | MySQL está apagado o se repite una clave primaria. | El DAO encuentra el error JDBC y puede propagar SQLException. |
El DAO no debería mostrar una ventana al usuario. La capa que coordina la interacción puede decidir qué mensaje mostrar, evitando exponer contraseñas o información técnica sensible.
Una excepción SQL no debe confundirse con una excepción personalizada de negocio: surgen de responsabilidades distintas.
Seguimos el recorrido completo de Agregar
Supongamos que un estudiante agrega el cómic Watchmen desde la interfaz gráfica.
El usuario completa los campos y pulsa Agregar.
Lee los valores y coordina la acción.
Comprueba las reglas del negocio.
Obtiene la conexión y ejecuta INSERT.
Almacena el registro del producto.
Cuando necesitamos listar los productos, el DAO ejecuta un SELECT, construye los objetos y los devuelve para que la vista pueda actualizar la JTable.
Comprueba lo aprendido
Selecciona una respuesta y revisa la explicación.
1. ¿Cuál es la responsabilidad principal de ProductoDAO?
2. ¿Para qué creamos ConexionBD?
3. ¿Dónde debemos comprobar que el precio no sea negativo?
4. ¿Qué debería devolver listar()?
5. Si la conexión con MySQL falla, ¿qué debería evitar el DAO?
Microentrega y lo que viene
Documentamos nuestras decisiones
- Explica por qué
ProductoControllerno debería contener instruccionesINSERTySELECT. - Completa el recorrido Vista → Controller → Service → DAO → MySQL con una responsabilidad de cada componente.
- Señala dónde debe comprobarse un precio negativo y dónde debe ejecutarse la consulta SQL.
- Explica qué ventaja obtenemos al utilizar una clase común
ConexionBD. - Dibuja un esquema que muestre el trayecto de un producto desde su creación hasta su almacenamiento.
guardar() y listar(); desafío: proponer la sentencia DELETE con WHERE codigo = ? y justificar el parámetro.Próximo y último material de la secuencia: veremos que distintos DAO necesitan operaciones semejantes. ¿Cómo establecer un contrato común sin duplicar las implementaciones? La respuesta nos llevará a interfaces, polimorfismo y bajo acoplamiento.

