Profe Macón
Programación Avanzada
2.º BT · Tecnologías de la Información
PM
Tienda de Cómics · Unidad de persistencia · Material 04

Una responsabilidad nueva:
ProductoDAO y operaciones CRUD

Ya sabemos conectarnos a MySQL, insertar y consultar registros. Ahora vamos a decidir dónde debe vivir ese código y cómo incorporarlo a nuestra arquitectura sin mezclar las responsabilidades de Vista, Controller y Service.

CET1 · ModularidadCET3 · Persistencia JDBCJava 17 + MySQLDAOCRUD
1 · Tienda de Cómics

¿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.

Pregunta inicial: ¿alcanza con que el programa funcione, o también importa que cada clase tenga una responsabilidad clara?
2 · Tienda de Cómics

¿Qué responsabilidad tenía cada componente?

Antes de añadir una clase, recuperemos nuestra organización actual:

Producto_Vista
Campos, botones y JTable
↓
ProductoController
Escucha eventos y coordina la interacción
↓
ProductoService
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.

3 · Tienda de Cómics

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.

Producto_Vista
Muestra y recoge datos
↓
ProductoController
Coordina eventos
↓
ProductoService
Comprueba reglas del negocio
↓
ProductoDAO
Accede a los datos: JDBC y SQL
↓
MySQL
Almacena registros
No confundas las tareas: ProductoDAO no dibuja botones, no muestra JOptionPane y no decide si un precio cumple las reglas del comercio.
4 · Tienda de Cómics

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.

5 · Tienda de Cómics

Construimos ConexionBD paso a paso

Partimos de los elementos que conocemos del SCORM 02: DriverManager, Connection y SQLException.

Paquete config · ConexionBD.java (ejemplo didáctico)
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.

Importante: los datos de acceso son ficticios. En proyectos reales no debemos publicar contraseñas en el código fuente ni subirlas al repositorio.
6 · Tienda de Cómics

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.

ProductoDAO.java · Primer método
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.

7 · Tienda de Cómics

¿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.

ProductoDAO.java · Método listar
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.

8 · Tienda de Cómics

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 tiendaOperaciónSQL
Guardar un producto nuevoCreateINSERT
Obtener los productos existentesReadSELECT
Cambiar precio o stockUpdateUPDATE
Quitar un productoDeleteDELETE

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.

Atención: executeUpdate() informa cuántas filas fueron afectadas. Si devuelve 0 al modificar o eliminar, conviene investigar si el producto identificado existía.
9 · Tienda de Cómics

¿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.

10 · Tienda de Cómics

¿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.

ProblemaEjemploResponsabilidad
FormatoEl usuario escribe letras donde se espera un número.La entrada se interpreta o valida antes de crear el producto.
NegocioSe intenta guardar un precio negativo.ProductoService aplica la regla.
PersistenciaMySQL 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.

11 · Tienda de Cómics

Seguimos el recorrido completo de Agregar

Supongamos que un estudiante agrega el cómic Watchmen desde la interfaz gráfica.

1. Producto_Vista
El usuario completa los campos y pulsa Agregar.
↓
2. ProductoController
Lee los valores y coordina la acción.
↓
3. ProductoService
Comprueba las reglas del negocio.
↓
4. ProductoDAO
Obtiene la conexión y ejecuta INSERT.
↓
5. MySQL
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.

La idea central: ahora el almacenamiento es persistente sin necesidad de convertir la vista ni el controlador en clases que escriban SQL.
12 · Tienda de Cómics

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?

13 · Tienda de Cómics

Microentrega y lo que viene

Documentamos nuestras decisiones

  1. Explica por qué ProductoController no debería contener instrucciones INSERT y SELECT.
  2. Completa el recorrido Vista → Controller → Service → DAO → MySQL con una responsabilidad de cada componente.
  3. Señala dónde debe comprobarse un precio negativo y dónde debe ejecutarse la consulta SQL.
  4. Explica qué ventaja obtenemos al utilizar una clase común ConexionBD.
  5. Dibuja un esquema que muestre el trayecto de un producto desde su creación hasta su almacenamiento.
Tres niveles: mínimo viable: identificar responsabilidades; estándar: explicar y leer 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.

Material en curso
← Índice del curso