9.11 DAO
Para realizar el ejemplo que seguiremos en este apartado (y en el ejercicio 12: paquete redes) utilizaremos el siguiente fichero sql redes.sql sobre una base de datos nueva denominada redes.
En programación existen una serie de estándares denominados Patrones de Diseño que debes conocer para poder programar según estos patrones y no reinventar la rueda.
Nota
Nosotros vamos a implementar “Repository Pattern” porque también os va a servir para cualquier aplicación tanto web, móvil o de escritorio.
Según la documentación de Android:
The repository pattern is a design pattern that isolates the data layer from the rest of the app. The data layer refers to the part of your app, separate from the UI, that handles the app’s data and business logic, exposing consistent APIs for the rest of your app to access this data.
1. Patrón DAO (Data Access Object)
El patrón DAO es un patrón de diseño que separa la lógica de acceso a datos de la lógica de negocio. Proporciona una abstracción de acceso a datos donde cada entidad (como en el ejercicio 12 de actividades: Usuario, Post, Comentario) tiene su propio DAO que maneja las operaciones CRUD (Create, Read, Update, Delete) específicas de esa entidad. Esto mejora la modularidad y la reutilización del código, ya que la lógica de acceso a datos está encapsulada en objetos DAO dedicados.
2. Implementación paso a paso
Vamos a implementar un ejemplo básico utilizando JDBC para interactuar con una base de datos relacional. Utilizaremos la estructura de base de datos que se proporciona en el ejercicio 12: paquete redes:
2.1. Paso 0: Definir la clase DbConnect
Para simplificar la implementar y conexión a la base de datos podemos crear la clase DbConnect (como ya hemos implementado anteriormente en algunos ejercicios):
| package ut09.redes;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DbConnect {
private static final String JDBC_URL = "jdbc:mysql://localhost:3306/redes";
private static final String USUARIO = "pr_tuNombre";
private static final String PASSWORD = "pr_tuContraseña";
private static DbConnect dbInstance; // Variable para almacenar la única instancia de la clase
private static Connection con;
// Constructor vacío privado para evitar la instanciación directa
private DbConnect() {
}
// Método estático para obtener la instancia única (Singleton)
public static DbConnect getInstance() {
if (dbInstance == null) {
dbInstance = new DbConnect();
}
return dbInstance;
}
// Método estático para obtener la conexión a la base de datos
public Connection getConnection() throws SQLException {
if (con == null || con.isClosed()) {
con = DriverManager.getConnection(JDBC_URL, USUARIO, PASSWORD);
}
return con;
}
}
|
2.2. Paso 1: Definir las clases de las entidades
Definiremos las clases Usuario, Post, Comentario que representan nuestras entidades:
Por ejemplo: Usuario.java
| package ut09.redes;
public class Usuario {
private int id;
private String nombre;
private String apellido;
// constructor
public Usuario(int id, String nombre, String apellido) {
this.id = id;
this.nombre = nombre;
this.apellido = apellido;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getApellido() {
return apellido;
}
public void setApellido(String apellido) {
this.apellido = apellido;
}
@Override
public String toString() {
return "Usuario [id=" + id + ", nombre=" + nombre + ", apellido=" + apellido + "]";
}
}
|
2.3. Paso 2: Definir la interface DAO
Luego, definiremos la interfaz DAO para después implementarlas en cada entidad.
Por ejemplo: IRepository.java
| package ut09.redes;
import java.sql.SQLException;
import java.util.List;
public interface IRepository<T> {
void crear(T entidad) throws SQLException;; // C: Create
T obtener(int id) throws SQLException;; // R: Read
List<T> obtenerTodos() throws SQLException;; // R: Read (all)
void actualizar(T entidad) throws SQLException;; // U: Update
void eliminar(int id) throws SQLException;; // D: Delete
}
|
2.4. Paso 3: Implementar las clases DAO
Luego, implementaremos las clases DAO para cada entidad utilizando JDBC para interactuar con la base de datos.
Por ejemplo: UsuarioRepositoryImpl.java
```java
package ut09.redes;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class UsuarioRepositoryImpl implements IRepository {
private static final String INSERT_QUERY = "INSERT INTO users (name, lastName) VALUES (?, ?)";
private static final String SELECT_BY_ID_QUERY = "SELECT * FROM users WHERE id = ?";
private static final String SELECT_ALL_QUERY = "SELECT * FROM users";
private static final String UPDATE_QUERY = "UPDATE users SET name = ?, lastName = ? WHERE id = ?";
private static final String DELETE_QUERY = "DELETE FROM users WHERE id = ?";
@Override
public void crear(Usuario usuario) throws SQLException {
try (Connection con = DbConnect.getInstance().getConnection();
PreparedStatement pst = con.prepareStatement(INSERT_QUERY, Statement.RETURN_GENERATED_KEYS)) {
pst.setString(1, usuario.getNombre());
pst.setString(2, usuario.getApellido());
int filasInsertadas = pst.executeUpdate();
if (filasInsertadas > 0) {
ResultSet rs = pst.getGeneratedKeys();
if (rs.next()) {
int id = rs.getInt(1);
usuario.setId(id);
}
}
}
}
@Override
public Usuario obtener(int id) throws SQLException {
Usuario usuario = null;
try (Connection con = DbConnect.getInstance().getConnection();
PreparedStatement pst = con.prepareStatement(SELECT_BY_ID_QUERY)) {
pst.setInt(1, id);
ResultSet rs = pst.executeQuery();
if (rs.next()) {
String nombre = rs.getString("name");
String apellido = rs.getString("lastName");
usuario = new Usuario(id, nombre, apellido);
}
}
return usuario;
}
@Override
public List<Usuario> obtenerTodos() throws SQLException {
List<Usuario> usuarios = new ArrayList<>();
try (Connection con = DbConnect.getInstance().getConnection();
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(SELECT_ALL_QUERY)) {
while (rs.next()) {
int id = rs.getInt("id");
String nombre = rs.getString("name");
String apellido = rs.getString("lastName");
Usuario usuario = new Usuario(id, nombre, apellido);
usuarios.add(usuario);
}
}
return usuarios;
}
@Override
public void actualizar(Usuario usuario) throws SQLException {
try (Connection con = DbConnect.getInstance().getConnection();
PreparedStatement pst = con.prepareStatement(UPDATE_QUERY)) {
pst.setString(1, usuario.getNombre());
pst.setString(2, usuario.getApellido());
pst.setInt(3, usuario.getId());
pst.executeUpdate();
}
}
@Override
public void eliminar(int id) throws SQLException {
try (Connection con = DbConnect.getInstance().getConnection();
PreparedStatement pst = con.prepareStatement(DELETE_QUERY)) {
pst.setInt(1, id);
pst.executeUpdate();
}
}
}
```
2.5. Paso 4: Implementar la lógica de la aplicación
Finalmente, implementaremos la lógica de la aplicación en una clase principal Main donde podremos interactuar con los DAOs y la base de datos.
Por ejemplo: Main.java
| package ut09.redes;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.List;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner entrada = new Scanner(System.in);
IRepository<Usuario> usuarioDAO = new UsuarioRepositoryImpl();
IRepository<Post> postDAO = new PostRepositoryImpl();
// IRepository<Comentario> comentarioDAO = new ComentarioRepositoryImpl();
try (Connection con = DbConnect.getInstance().getConnection()) {
while (true) {
menuPrincipal();
int opcion = entrada.nextInt();
entrada.nextLine(); // Limpiar el buffer del entrada
switch (opcion) {
case 1:
gestionarUsuarios(entrada, usuarioDAO);
break;
case 2:
// gestionarPosts(entrada, postDAO);
break;
case 3:
// gestionarComentarios(entrada, comentarioDAO, usuarioDAO, postDAO);
break;
case 0:
System.out.println("Saliendo...");
entrada.close();
System.exit(0);
default:
System.out.println("Opción inválida. Intenta de nuevo.");
}
}
} catch (SQLException e) {
System.out.println("Error en la conexión con la base de datos: " + e.getMessage());
}
}
private static void gestionarUsuarios(Scanner entrada, IRepository<Usuario> usuarioDAO) {
while (true) {
menuUsuarios();
int opcion = entrada.nextInt();
entrada.nextLine(); // Limpiar el buffer del entrada
switch (opcion) {
case 1:
System.out.print("Introduce el nombre del usuario: ");
String nombre = entrada.nextLine();
System.out.print("Introduce el apellido del usuario: ");
String apellido = entrada.nextLine();
Usuario nuevoUsuario = new Usuario(0, nombre, apellido); // El ID se genera automáticamente
try {
usuarioDAO.crear(nuevoUsuario);
System.out.println("Usuario creado con ID: " + nuevoUsuario.getId());
} catch (SQLException e) {
System.out.println("Error al crear usuario: " + e.getMessage());
}
break;
case 2:
System.out.print("Introduce el ID del usuario a consultar: ");
int idUsuario = entrada.nextInt();
entrada.nextLine(); // Limpiar el buffer del entrada
try {
Usuario usuario = usuarioDAO.obtener(idUsuario);
if (usuario != null) {
System.out.println("Usuario encontrado:");
System.out.printf(" %-3d %-15s %-15s%n",
usuario.getId(),
usuario.getNombre(),
usuario.getApellido());
} else {
System.out.println("No se encontró ningún usuario con ese ID.");
}
} catch (SQLException e) {
System.out.println("Error al obtener usuario: " + e.getMessage());
}
break;
case 3:
try {
List<Usuario> usuarios = usuarioDAO.obtenerTodos();
if (!usuarios.isEmpty()) {
System.out.println("Listado de Usuarios:");
for (Usuario u : usuarios) {
System.out.println("ID: " + u.getId() + ", Nombre: " + u.getNombre() + ", Apellido: " + u.getApellido());
}
} else {
System.out.println("No hay usuarios registrados.");
}
} catch (SQLException e) {
System.out.println("Error al obtener todos los usuarios: " + e.getMessage());
}
break;
case 4:
System.out.print("Introduce el ID del usuario a actualizar: ");
int idActualizar = entrada.nextInt();
entrada.nextLine(); // Limpiar el buffer del entrada
try {
Usuario usuarioActualizar = usuarioDAO.obtener(idActualizar);
if (usuarioActualizar != null) {
System.out.print("Introduce el nuevo nombre del usuario (deja en blanco para mantener el actual): ");
String nuevoNombre = entrada.nextLine();
if (!nuevoNombre.isEmpty()) {
usuarioActualizar.setNombre(nuevoNombre);
}
System.out.print("Introduce el nuevo apellido del usuario (deja en blanco para mantener el actual): ");
String nuevoApellido = entrada.nextLine();
if (!nuevoApellido.isEmpty()) {
usuarioActualizar.setApellido(nuevoApellido);
}
usuarioDAO.actualizar(usuarioActualizar);
System.out.println("Usuario actualizado correctamente.");
} else {
System.out.println("No se encontró ningún usuario con ese ID.");
}
} catch (SQLException e) {
System.out.println("Error al actualizar usuario: " + e.getMessage());
}
break;
case 5:
System.out.print("Introduce el ID del usuario a eliminar: ");
int idEliminar = entrada.nextInt();
entrada.nextLine(); // Limpiar el buffer del entrada
try {
Usuario usuarioActualizar = usuarioDAO.obtener(idEliminar);
if (usuarioActualizar != null) {
// eliminar primero los comentarios y posts asociados al usuario
// PostRepositoryImpl.eliminarPostPorUsuario(idEliminar);
// luego eliminar el usuario
usuarioDAO.eliminar(idEliminar);
System.out.println("Usuario eliminado correctamente.");
} else {
System.out.println("No se encontró ningún usuario con ese ID.");
}
} catch (SQLException e) {
System.out.println("Error al eliminar usuario: " + e.getMessage());
}
break;
case 0:
return;
default:
System.out.println("Opción inválida. Intenta de nuevo.");
}
}
}
// private static void gestionarPosts(Scanner entrada, IRepository<Post> postDAO) {
// while (true) {
// menuPosts();
// int opcion = entrada.nextInt();
// entrada.nextLine(); // Limpiar el buffer del entrada
// switch (opcion) {
// case 1:
// System.out.print("Introduce el texto del post: ");
// String texto = entrada.nextLine();
// System.out.println("Elige un usuario: ");
// UsuarioRepositoryImpl usuarioRepository = new UsuarioRepositoryImpl();
// List<Usuario> usuarios;
// int usuarioId = 0;
// try {
// usuarios = usuarioRepository.obtenerTodos();
// for (Usuario usuario : usuarios) {
// System.out.println(usuario.getId() + " - " + usuario.getNombre() + " " + usuario.getApellido());
// }
// System.out.print("Introduce el ID del usuario que publica el post: ");
// usuarioId = entrada.nextInt();
// } catch (SQLException e) {
// System.out.println("ERROR: " + e.getMessage());
// }
// Post nuevoPost = new Post(0, texto, 0, LocalDate.now().toString(), usuarioId); // El ID se genera automáticamente
// try {
// postDAO.crear(nuevoPost);
// System.out.println("Post creado con ID: " + nuevoPost.getId());
// } catch (SQLException e) {
// System.out.println("Error al crear post: " + e.getMessage());
// }
// break;
// case 2:
// break;
// case 3:
// break;
// case 4:
// break;
// case 5:
// break;
// case 0:
// return;
// default:
// System.out.println("Opción inválida. Intenta de nuevo.");
// }
// }
// }
// private static void gestionarComentarios(Connection con, Scanner entrada, IRepository<Comentario> comentarioDAO, IRepository<Usuario> usuarioDAO, IRepository<Post> postDAO) {
// // Implementar lógica similar a gestionarUsuarios para Comentarios
// }
// MENÚ PRINCIPAL: menuPrincipal()
private static void menuPrincipal() {
System.out.println("\nMenú Principal:");
System.out.println("1. Gestionar Usuarios");
System.out.println("2. Gestionar Posts");
System.out.println("3. Gestionar Comentarios");
System.out.println("0. Salir");
System.out.print("Selecciona una opción: ");
}
// MENÚ SECUNDARIO: menuUsuarios()
private static void menuUsuarios() {
System.out.println("\nMenú de Usuarios:");
System.out.println("1. Crear usuario");
System.out.println("2. Consultar usuario por ID");
System.out.println("3. Listar todos los usuarios");
System.out.println("4. Actualizar usuario");
System.out.println("5. Eliminar usuario");
System.out.println("0. Volver al menú principal");
System.out.print("Selecciona una opción: ");
}
// MENÚ SECUNDARIO: menuPosts()
private static void menuPosts() {
System.out.println("\nMenú de Posts:");
System.out.println("1. Crear post");
System.out.println("2. Consultar post por ID");
System.out.println("3. Listar todos los posts");
System.out.println("4. Actualizar post");
System.out.println("5. Eliminar post");
System.out.println("0. Volver al menú principal");
System.out.print("Selecciona una opción: ");
}
// MENÚ SECUNDARIO: menuPosts()
private static void menuComentarios() {
System.out.println("\nMenú de Comentarios:");
System.out.println("1. Crear comentario");
System.out.println("2. Consultar comentario por ID");
System.out.println("3. Listar todos los comentarios");
System.out.println("4. Actualizar comentario");
System.out.println("5. Eliminar comentario");
System.out.println("0. Volver al menú principal");
System.out.print("Selecciona una opción: ");
}
}
|
2.6. Consideraciones
- JDBC y SQL Queries: Hemos utilizado JDBC para conectarnos y realizar operaciones en la base de datos. Es importante manejar excepciones y cerrar correctamente las conexiones y recursos.
- Patrón DAO: Este patrón nos ayuda a mantener un código organizado y aislado, separando la lógica de acceso a datos de la lógica de negocio.
- Lógica de la aplicación: En la clase
Main, hemos implementado un menú interactivo que permite al usuario gestionar usuarios, posts y comentarios utilizando los métodos proporcionados por los DAOs.
- Adaptabilidad: Puedes expandir este ejemplo añadiendo más funcionalidades o haciendo ajustes según los requisitos de tu aplicación.