Saltar a contenido

🧭 Estructura de un proyecto con JDBC

Para las tareas de clase vamos a seguir una estructura que iremos perfilando basada en el MVC (modelo - vista - controlador). La idea principal es permitir independizar la lógica y la parte visual y la procedencia de los datos, usando un controlador que administra los procesos sirviendo de puente entre estos.

mvc

En el IntelliJ, crearemos un nuevo proyecto con la siguiente distribución de paquetes:

jdbc

1️⃣ - Creando la BBDD

Vamos a crear la base de datos en MySql. Esta puede ser creada previamente o ser creada en el mismo programa al inicio de la sesión comprobando si existe o no existe.

create schema if not exists `mi_DB`;

use `mi_DB`;

create table if not exists `Persona`(
    `id` int not null auto_increment,
    `dni` varchar(9) not null,
    `nombre` varchar(50) not null,
    `apellido` varchar(50) not null,
    `edad` int not null,
    primary key(`id`),
    unique key(`dni`)    
);
Y vamos a insertar algunos datos de ejemplo

INSERT INTO Persona (dni, nombre, apellido, edad) 
VALUES ('12345678A', 'Juan', 'Pérez', 30),
       ('87654321B', 'María', 'González', 25),
       ('55555555C', 'Pedro', 'Martínez', 40),
       ('99999999D', 'Laura', 'López', 35),
       ('11111111E', 'Ana', 'Sánchez', 28);

2️⃣ - Clase Connection

Para el paquete db vamos a crear la clase DBConnection, esta clase mantendrá una única conexión hacia la base de datos utilizando el patrón Singleton.

jdbc

Patrón Singleton

El patrón Singleton es un patrón de diseño de software que garantiza que una clase tenga solo una instancia y proporciona un punto de acceso global a esa instancia. Esto es útil cuando solo se necesita una instancia de una clase para coordinar acciones en todo el sistema, como por ejemplo, un objeto de conexión a una base de datos, un registro de eventos, un gestor de configuración, entre otros.

Para evitar la creación de nuevas instancias, se crea un constructor privado y se mantiene un variable de instancia static a una única instancia.

DBConnection.java
public class DBConnection {
    // URL de conexión a la base de datos MySQL
    private static final String URL = "jdbc:mysql://localhost:3306/mi_db";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "root123456";

    private static Connection connection;

    // Constructor privado para evitar instancias directas
    private DBConnection() {}

    // Método estático para obtener la instancia única de la conexión
    public static Connection getConnection() {
        if (connection == null) {
            // Bloqueo sincronizado para evitar concurrencia
            synchronized (DBConnection.class) {
                if (connection == null) {
                    try {
                       // Establecer la conexión
                        connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
                    } catch ( SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return connection;
    }

    // Método para cerrar la conexión
    public static void closeConnection() {
        if (connection != null) {
            try {
                connection.close();
                connection = null;
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

3️⃣ - Creando el modelo

El modelo contiene una representación de los datos que maneja la aplicación y su lógica de negocio. Permitirá mediante clases POJO identificar y asociar el modelo relacional con los objetos que manejará la aplicación

Para el ejemplo, creamos una clase persona que debe contener los atributos que contiene la tabla pesona como variables de la clase. Normalmente los modelos de la clase se encuentran en un paquete llamado model.

jdbc

Persona.java
public class Persona {
    private int id;
    private String dni;
    private String nombre;
    private String apellido;
    private int edad;

    public Persona(String dni, String nombre, String apellido, int edad) {
        this.dni = dni;
        this.nombre = nombre;
        this.apellido = apellido;
        this.edad = edad;
    }
    //gette y setter
}

4️⃣ - Patrón DAO. Clases para la manipulación de la base de datos

El patrón DAO (Data Access Object) es un patrón de diseño que se utiliza para separar la lógica de acceso a los datos de la lógica de negocio en una aplicación. Su objetivo principal es abstraer y encapsular todas las operaciones de acceso a la base de datos en un conjunto de clases dedicadas, permitiendo así una mayor flexibilidad, reutilización de código y mantenibilidad del sistema.

Permitirá la sustitución del Gestor de Base de Datos sin que afecte al resto del programa.

jdbc

En el ejemplo tenemos una clase en el paquete db que realiza el CRUD de la información referente a la tabla persona.

jdbc

PersonaDAO.java
public class PersonaDAO {
    // Objeto de conexión a la base de datos. Recuerda el patrón singleton de DBConnection
    private Connection connection=DBConnection.getConnection();

    // Consultas SQL para manipular la tabla Persona
    private static final String INSERT_QUERY = "INSERT INTO Persona (dni, nombre, apellido, edad) VALUES (?, ?, ?, ?)";
    private static final String SELECT_ALL_QUERY = "SELECT * FROM Persona";
    private static final String SELECT_BY_DNI_QUERY = "SELECT * FROM Persona WHERE dni = ?";
    private static final String UPDATE_QUERY = "UPDATE Persona SET nombre = ?, apellido = ?, edad = ? WHERE dni = ?";
    private static final String DELETE_QUERY = "DELETE FROM Persona WHERE dni = ?";

    // Método para insertar una nueva persona en la base de datos
    public void insertPersona(Persona persona) throws SQLException {
        try (PreparedStatement statement = connection.prepareStatement(INSERT_QUERY)) {
            statement.setString(1, persona.getDni());
            statement.setString(2, persona.getNombre());
            statement.setString(3, persona.getApellido());
            statement.setInt(4, persona.getEdad());

            statement.executeUpdate();
        }
    }

    // Método para obtener todas las personas de la base de datos
    public List<Persona> getAllPersonas() throws SQLException {
        List<Persona> personas = new ArrayList<>();
        try (PreparedStatement statement = connection.prepareStatement(SELECT_ALL_QUERY)) {
            ResultSet resultSet = statement.executeQuery();
            while (resultSet.next()) {
                Persona persona = resulSetToPersona(resultSet);
                personas.add(persona);
            }
        }
        return personas;
    }

    // Método para obtener una persona por su DNI
    public Persona getPersonaByDni(String dni) throws SQLException {
        Persona persona = null;
        try (PreparedStatement statement = connection.prepareStatement(SELECT_BY_DNI_QUERY)) {
            statement.setString(1, dni);
            ResultSet resultSet = statement.executeQuery();
            if (resultSet.next()) {
                persona = resulSetToPersona(resultSet);
            }
        }
        return persona;
    }

    // Método para actualizar los datos de una persona en la base de datos
    public void updatePersona(Persona persona) throws SQLException {
        try (PreparedStatement statement = connection.prepareStatement(UPDATE_QUERY)) {
            statement.setString(1, persona.getNombre());
            statement.setString(2, persona.getApellido());
            statement.setInt(3, persona.getEdad());
            statement.setString(4, persona.getDni());

            statement.executeUpdate();
        }
    }

    // Método para eliminar una persona de la base de datos por su DNI
    public void deletePersonaByDni(String dni) throws SQLException {
        try (PreparedStatement statement = connection.prepareStatement(DELETE_QUERY)) {
            statement.setString(1, dni);
            statement.executeUpdate();
        }
    }

    // Método auxiliar para mapear un ResultSet en la 
    //posición actual a un objeto Persona
    private Persona resulSetToPersona(ResultSet resultSet) throws SQLException {
        Persona persona = new Persona(
                resultSet.getString("dni"),
                resultSet.getString("nombre"),
                resultSet.getString("apellido"),
                resultSet.getInt("edad"));
        return persona;
    }
}

Patrón Repository

Actualmente, el acceso puede venir de varias fuentes de datos, como puede ser una base de datos por un lado y un servicio web por otro. El patrón DAO, ha evolucionado al patrón Repository que permite seleccionar la fuente de datos aislando del problema a las capas superiores jdbc

5️⃣ - Vista

En el paquete view mantenemos las clases encargadas para mostrar y recibir la información del usuario. Nosotros vamos a simplificar el programa por consola, pero una aplicación web o un programa JavaFX, manejará la interacción de eventos visuales con el usuario y avisará de ello al Controller

jdbc

Simplificando nuestro programa a consola, podemos tener la siguiente interacción con el usuario.

jdbc

VistaPersona.java
public class VistaPersona {
    public Scanner scanner;

    public VistaPersona() {
        scanner = new Scanner(System.in);
    }

    public void mostrarPersonas(List<Persona> personas) {
        System.out.println("Lista de Personas:");
        for (Persona persona : personas) {
            System.out.println(persona);
        }
    }

    public Persona crearPersona() {
        System.out.println("Introduce el DNI:");
        String dni = scanner.nextLine();
        System.out.println("Introduce el nombre:");
        String nombre = scanner.nextLine();
        System.out.println("Introduce el apellido:");
        String apellido = scanner.nextLine();
        System.out.println("Introduce la edad:");
        int edad = Integer.parseInt(scanner.nextLine());

        return new Persona(dni, nombre, apellido, edad);
    }

    public String obtenerDniAEliminar() {
        System.out.println("Introduce el DNI de la persona a eliminar:");
        return scanner.nextLine();
    }

    public Persona obtenerDatosActualizados() {
        System.out.println("Introduce el nuevo nombre:");
        String nombre = scanner.nextLine();
        System.out.println("Introduce el nuevo apellido:");
        String apellido = scanner.nextLine();
        System.out.println("Introduce la nueva edad:");
        int edad = Integer.parseInt(scanner.nextLine());

        return new Persona("", nombre, apellido, edad); // DNI no es necesario para actualizar
    }

    public void mostrarMensaje(String mensaje) {
        System.out.println(mensaje);
    }

    public String obtenerDni() {
        System.out.println("Introduce el DNI de la persona:");
        return scanner.nextLine();
    }
    //muestra los datos de la persona
    public void mostrarPersona(Persona persona) {
        if(persona!=null)
            System.out.println(persona);
        else
            System.out.println("La persona no existe");
    }

    // Otros métodos de la vista...
}

6️⃣ - Controller

El `Controller'(controlador) es una parte fundamental en la arquitectura del patrón Modelo-Vista-Controlador (MVC). Su tarea principal es actuar como intermediario entre el modelo (que maneja los datos de la aplicación) y la vista (que muestra la interfaz de usuario). El controlador interpreta las acciones del usuario desde la vista, realiza las operaciones necesarias en el modelo y actualiza la vista en consecuencia. En resumen, el controlador coordina la interacción entre el modelo y la vista para garantizar que la aplicación funcione correctamente y responda a las acciones del usuario de manera adecuada.

jdbc

En nuestro caso, mantenemos en la clase, instancias del DAO y VIEW de forma simplificada que permiten manejar el CRUD mediante el DAO con los datos obtenidos en la VIEW

jdbc

ControllerPersona.java
public class ControllerPersona {
    private PersonaDAO personaDAO;
    private VistaPersona vistaPersona;

    public ControllerPersona() {
        // Crear la instancias DAO y VIEW

            personaDAO = new PersonaDAO();
            vistaPersona = new VistaPersona();
    }

    public void mostrarTodasLasPersonas() {
        try {
            //recupera las personas
            List<Persona> personas = personaDAO.getAllPersonas();
            //las muestra en la Vista
            vistaPersona.mostrarPersonas(personas);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    public void mostraPersonaDNI() {
        try {
            String dni = vistaPersona.obtenerDni();
            //recupera Persona del DAO
            Persona persona = personaDAO.getPersonaByDni(dni);
            vistaPersona.mostrarPersona(persona);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void crearPersona() {
        try {
            Persona persona=vistaPersona.crearPersona();
            personaDAO.insertPersona(persona);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void actualizarPersona() {
        try {
            Persona persona=vistaPersona.obtenerDatosActualizados();
            personaDAO.updatePersona(persona);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void eliminarPersona() {
        try {
            String dni=vistaPersona.obtenerDniAEliminar();
            personaDAO.deletePersonaByDni(dni);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // Otros métodos del controlador...
}

Podemos tener una clase principal con un menú que realmente podría ser parte del View, pero para simplificar vamos a crear como inicio de la aplicación. Mostrará un menú que permitirá llamar al controller en diferentes acciones

jdbc

Main.java
public class Main {
    public static void main(String[] args) {

        ControllerPersona controlador = new ControllerPersona();
        Scanner scanner = new Scanner(System.in);

        int opcion;
        do {
            System.out.println("\nMenú:");
            System.out.println("1. Mostrar todas las personas");
            System.out.println("2. Crear persona");
            System.out.println("3. Actualizar persona");
            System.out.println("4. Eliminar persona");
            System.out.println("5. Mostrar persona por DNI");
            System.out.println("6. Salir");
            System.out.print("Elige una opción: ");
            opcion = Integer.parseInt(scanner.nextLine());

            switch (opcion) {
                case 1:
                    controlador.mostrarTodasLasPersonas();
                    break;
                case 2:
                    controlador.crearPersona();
                    System.out.println("Persona creada correctamente.");
                    break;
                case 3:
                    controlador.actualizarPersona();
                    System.out.println("Persona actualizada correctamente.");
                    break;
                case 4:
                    controlador.eliminarPersona();
                    System.out.println("Persona eliminada correctamente.");
                    break;
                case 5:
                    controlador.mostraPersonaDNI();
                    break;
                case 6:
                    System.out.println("Saliendo...");
                    break;
                default:
                    System.out.println("Opción no válida.");
            }
        } while (opcion != 6);
    }
}

Fichero README

Readme: el propio nombre, léeme, indica su propósito: {++ser leído++}. El archivo readme es el primer archivo que un desarrollador debe mirar antes de embarcarse en un proyecto, por lo que también es esencial saber cómo escribir un buen archivo readme, para que toda la información relevante se presente de forma compacta.

Consejo

El nombre del archivo se escribe README en mayúsculas. De este modo, los sistemas que diferencian entre mayúsculas y minúsculas listarán el archivo antes que todos los demás archivos que empiezan con minúsculas.

¿Qué suelen incluir los ficheros README?

Suelen incluir información sobre:

  • Una descripción general del sistema o proyecto.
  • El estado del proyecto, que es particularmente importante si el proyecto está todavía en desarrollo. En él se mencionan los cambios planeados y la dirección de desarrollo del proyecto, y se especifica directamente si un proyecto está terminado.
  • Los requisitos del entorno de desarrollo para la integración.
  • Una lista de las tecnologías utilizadas y, cuando proceda, enlaces con más información.
  • Bugs conocidos y posibles correcciones de errores.
  • Sección de preguntas frecuentes con todas las preguntas planteadas hasta la fecha.
  • Información sobre derechos de autor y licencias.

Cómo escribir un fichero README

El contenido del fichero README debe estar en inglés.

readme

Cómo crear un fichero README

Exportar la BBDD de MySQL

En MySQL workbench seleccionamos Server → Data Export

export

Selecciono el esquema de BBDD que quiero exportar y hago click en Start export

export

Workbench me muestra dónde se ha generado el fichero:

export