Skip to content

CRUD con Base de Datos

En este módulo aprenderemos a conectar nuestras aplicaciones Swing con bases de datos reales, implementando operaciones CRUD (Create, Read, Update, Delete) usando JDBC.

JDBC (Java Database Connectivity) es la API estándar de Java para conectarse a bases de datos relacionales.

  1. Agregar el driver JDBC al proyecto (MySQL Connector/J para MySQL)
  2. Configurar la base de datos con las tablas necesarias
  3. Crear la clase de conexión para gestionar la conectividad
database_setup.sql
-- Script SQL para crear la base de datos de ejemplo
CREATE DATABASE swing_app;
USE swing_app;
CREATE TABLE usuarios (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
email VARCHAR(150) UNIQUE NOT NULL,
telefono VARCHAR(20),
activo BOOLEAN DEFAULT TRUE,
fecha_registro TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE categorias (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(50) NOT NULL,
descripcion TEXT,
activo BOOLEAN DEFAULT TRUE
);
INSERT INTO usuarios (nombre, email, telefono) VALUES
('Juan Pérez', 'juan@email.com', '555-0001'),
('María García', 'maria@email.com', '555-0002'),
('Carlos López', 'carlos@email.com', '555-0003');
INSERT INTO categorias (nombre, descripcion) VALUES
('Electrónicos', 'Dispositivos electrónicos y gadgets'),
('Ropa', 'Vestimenta y accesorios'),
('Hogar', 'Artículos para el hogar');
ConexionDB.java
import java.sql.*;
public class ConexionDB {
private static final String URL = "jdbc:mysql://localhost:3306/swing_app";
private static final String USUARIO = "root";
private static final String PASSWORD = "password";
private static ConexionDB instancia;
private Connection conexion;
private ConexionDB() {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
this.conexion = DriverManager.getConnection(URL, USUARIO, PASSWORD);
System.out.println("Conexión exitosa a la base de datos");
} catch (ClassNotFoundException e) {
System.err.println("Driver no encontrado: " + e.getMessage());
} catch (SQLException e) {
System.err.println("Error de conexión: " + e.getMessage());
}
}
public static ConexionDB getInstance() {
if (instancia == null) {
instancia = new ConexionDB();
}
return instancia;
}
public Connection getConexion() {
try {
if (conexion == null || conexion.isClosed()) {
instancia = new ConexionDB();
}
} catch (SQLException e) {
System.err.println("Error verificando conexión: " + e.getMessage());
}
return conexion;
}
public void cerrarConexion() {
try {
if (conexion != null && !conexion.isClosed()) {
conexion.close();
System.out.println("Conexión cerrada");
}
} catch (SQLException e) {
System.err.println("Error cerrando conexión: " + e.getMessage());
}
}
}

Implementaremos una arquitectura en capas que separa claramente las responsabilidades:

graph TB
A[Capa de Presentación<br/>UI - Swing] --> B[Capa de Lógica de Negocio<br/>Services]
B --> C[Capa de Acceso a Datos<br/>DAO + Modelos]
C --> D[(Base de Datos)]
subgraph "Responsabilidades"
E["🖥️ Presentación:<br/>- Interfaz de usuario<br/>- Validaciones de UI<br/>- Eventos y listeners"]
F["⚙️ Lógica de Negocio:<br/>- Reglas de negocio<br/>- Validaciones complejas<br/>- Coordinación de operaciones"]
G["💾 Acceso a Datos:<br/>- Operaciones CRUD<br/>- Mapeo objeto-relacional<br/>- Gestión de conexiones"]
end

El patrón DAO (Data Access Object) encapsula el acceso a la fuente de datos.

Usuario.java
public class Usuario {
private int id;
private String nombre;
private String email;
private String telefono;
private boolean activo;
private java.sql.Timestamp fechaRegistro;
// Constructor vacío
public Usuario() {}
// Constructor con parámetros
public Usuario(String nombre, String email, String telefono, boolean activo) {
this.nombre = nombre;
this.email = email;
this.telefono = telefono;
this.activo = activo;
}
// Getters y Setters
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 getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getTelefono() { return telefono; }
public void setTelefono(String telefono) { this.telefono = telefono; }
public boolean isActivo() { return activo; }
public void setActivo(boolean activo) { this.activo = activo; }
public java.sql.Timestamp getFechaRegistro() { return fechaRegistro; }
public void setFechaRegistro(java.sql.Timestamp fechaRegistro) {
this.fechaRegistro = fechaRegistro;
}
@Override
public String toString() {
return "Usuario{" +
"id=" + id +
", nombre='" + nombre + ''' +
", email='" + email + ''' +
", telefono='" + telefono + ''' +
", activo=" + activo +
'}';
}
}

Primero definimos el contrato que debe cumplir nuestro DAO:

IUsuarioDAO.java
import java.util.List;
import java.util.Optional;
public interface IUsuarioDAO {
// Operaciones CRUD básicas
boolean insertar(Usuario usuario);
Optional<Usuario> obtenerPorId(int id);
List<Usuario> obtenerTodos();
boolean actualizar(Usuario usuario);
boolean eliminar(int id);
// Operaciones de búsqueda
List<Usuario> buscarPorNombre(String nombre);
List<Usuario> buscarPorEmail(String email);
List<Usuario> obtenerActivos();
// Operaciones de validación
boolean existeEmail(String email);
int contarUsuarios();
}
UsuarioDAO.java
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class UsuarioDAO implements IUsuarioDAO {
private Connection conexion;
public UsuarioDAO() {
this.conexion = ConexionDB.getInstance().getConexion();
}
// CREATE - Insertar usuario
public boolean insertar(Usuario usuario) {
String sql = "INSERT INTO usuarios (nombre, email, telefono, activo) VALUES (?, ?, ?, ?)";
try (PreparedStatement stmt = conexion.prepareStatement(sql)) {
stmt.setString(1, usuario.getNombre());
stmt.setString(2, usuario.getEmail());
stmt.setString(3, usuario.getTelefono());
stmt.setBoolean(4, usuario.isActivo());
int filasAfectadas = stmt.executeUpdate();
return filasAfectadas > 0;
} catch (SQLException e) {
System.err.println("Error insertando usuario: " + e.getMessage());
return false;
}
}
// READ - Obtener todos los usuarios
public List<Usuario> obtenerTodos() {
List<Usuario> usuarios = new ArrayList<>();
String sql = "SELECT * FROM usuarios ORDER BY id";
try (Statement stmt = conexion.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
Usuario usuario = new Usuario();
usuario.setId(rs.getInt("id"));
usuario.setNombre(rs.getString("nombre"));
usuario.setEmail(rs.getString("email"));
usuario.setTelefono(rs.getString("telefono"));
usuario.setActivo(rs.getBoolean("activo"));
usuario.setFechaRegistro(rs.getTimestamp("fecha_registro"));
usuarios.add(usuario);
}
} catch (SQLException e) {
System.err.println("Error obteniendo usuarios: " + e.getMessage());
}
return usuarios;
}
// READ - Obtener usuario por ID
public Usuario obtenerPorId(int id) {
String sql = "SELECT * FROM usuarios WHERE id = ?";
try (PreparedStatement stmt = conexion.prepareStatement(sql)) {
stmt.setInt(1, id);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
Usuario usuario = new Usuario();
usuario.setId(rs.getInt("id"));
usuario.setNombre(rs.getString("nombre"));
usuario.setEmail(rs.getString("email"));
usuario.setTelefono(rs.getString("telefono"));
usuario.setActivo(rs.getBoolean("activo"));
usuario.setFechaRegistro(rs.getTimestamp("fecha_registro"));
return usuario;
}
}
} catch (SQLException e) {
System.err.println("Error obteniendo usuario por ID: " + e.getMessage());
}
return null;
}
// UPDATE - Actualizar usuario
public boolean actualizar(Usuario usuario) {
String sql = "UPDATE usuarios SET nombre = ?, email = ?, telefono = ?, activo = ? WHERE id = ?";
try (PreparedStatement stmt = conexion.prepareStatement(sql)) {
stmt.setString(1, usuario.getNombre());
stmt.setString(2, usuario.getEmail());
stmt.setString(3, usuario.getTelefono());
stmt.setBoolean(4, usuario.isActivo());
stmt.setInt(5, usuario.getId());
int filasAfectadas = stmt.executeUpdate();
return filasAfectadas > 0;
} catch (SQLException e) {
System.err.println("Error actualizando usuario: " + e.getMessage());
return false;
}
}
// DELETE - Eliminar usuario
public boolean eliminar(int id) {
String sql = "DELETE FROM usuarios WHERE id = ?";
try (PreparedStatement stmt = conexion.prepareStatement(sql)) {
stmt.setInt(1, id);
int filasAfectadas = stmt.executeUpdate();
return filasAfectadas > 0;
} catch (SQLException e) {
System.err.println("Error eliminando usuario: " + e.getMessage());
return false;
}
}
// Buscar usuarios por nombre
public List<Usuario> buscarPorNombre(String nombre) {
List<Usuario> usuarios = new ArrayList<>();
String sql = "SELECT * FROM usuarios WHERE nombre LIKE ? ORDER BY nombre";
try (PreparedStatement stmt = conexion.prepareStatement(sql)) {
stmt.setString(1, "%" + nombre + "%");
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
Usuario usuario = new Usuario();
usuario.setId(rs.getInt("id"));
usuario.setNombre(rs.getString("nombre"));
usuario.setEmail(rs.getString("email"));
usuario.setTelefono(rs.getString("telefono"));
usuario.setActivo(rs.getBoolean("activo"));
usuario.setFechaRegistro(rs.getTimestamp("fecha_registro"));
usuarios.add(usuario);
}
}
} catch (SQLException e) {
System.err.println("Error buscando usuarios: " + e.getMessage());
}
return usuarios;
}
@Override
public Optional<Usuario> obtenerPorId(int id) {
String sql = "SELECT * FROM usuarios WHERE id = ?";
try (PreparedStatement stmt = conexion.prepareStatement(sql)) {
stmt.setInt(1, id);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return Optional.of(mapearUsuario(rs));
}
}
} catch (SQLException e) {
System.err.println("Error obteniendo usuario por ID: " + e.getMessage());
}
return Optional.empty();
}
@Override
public List<Usuario> buscarPorEmail(String email) {
List<Usuario> usuarios = new ArrayList<>();
String sql = "SELECT * FROM usuarios WHERE email LIKE ? ORDER BY email";
try (PreparedStatement stmt = conexion.prepareStatement(sql)) {
stmt.setString(1, "%" + email + "%");
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
usuarios.add(mapearUsuario(rs));
}
}
} catch (SQLException e) {
System.err.println("Error buscando usuarios por email: " + e.getMessage());
}
return usuarios;
}
@Override
public List<Usuario> obtenerActivos() {
List<Usuario> usuarios = new ArrayList<>();
String sql = "SELECT * FROM usuarios WHERE activo = true ORDER BY nombre";
try (Statement stmt = conexion.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
usuarios.add(mapearUsuario(rs));
}
} catch (SQLException e) {
System.err.println("Error obteniendo usuarios activos: " + e.getMessage());
}
return usuarios;
}
@Override
public boolean existeEmail(String email) {
String sql = "SELECT COUNT(*) FROM usuarios WHERE email = ?";
try (PreparedStatement stmt = conexion.prepareStatement(sql)) {
stmt.setString(1, email);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return rs.getInt(1) > 0;
}
}
} catch (SQLException e) {
System.err.println("Error verificando email: " + e.getMessage());
}
return false;
}
@Override
public int contarUsuarios() {
String sql = "SELECT COUNT(*) FROM usuarios";
try (Statement stmt = conexion.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
if (rs.next()) {
return rs.getInt(1);
}
} catch (SQLException e) {
System.err.println("Error contando usuarios: " + e.getMessage());
}
return 0;
}
// Método auxiliar para mapear ResultSet a Usuario
private Usuario mapearUsuario(ResultSet rs) throws SQLException {
Usuario usuario = new Usuario();
usuario.setId(rs.getInt("id"));
usuario.setNombre(rs.getString("nombre"));
usuario.setEmail(rs.getString("email"));
usuario.setTelefono(rs.getString("telefono"));
usuario.setActivo(rs.getBoolean("activo"));
usuario.setFechaRegistro(rs.getTimestamp("fecha_registro"));
return usuario;
}
}

Capa de Lógica de Negocio (Service Layer)

Section titled “Capa de Lógica de Negocio (Service Layer)”

La capa de servicios contiene la lógica de negocio y coordina las operaciones entre la UI y el DAO.

IUsuarioService.java
import java.util.List;
public interface IUsuarioService {
// Operaciones de negocio
ResultadoOperacion<Usuario> crearUsuario(Usuario usuario);
ResultadoOperacion<Usuario> actualizarUsuario(Usuario usuario);
ResultadoOperacion<Boolean> eliminarUsuario(int id);
// Consultas
List<Usuario> obtenerTodosLosUsuarios();
Usuario obtenerUsuarioPorId(int id);
List<Usuario> buscarUsuarios(String criterio);
List<Usuario> obtenerUsuariosActivos();
// Validaciones y estadísticas
boolean validarDatosUsuario(Usuario usuario);
int obtenerTotalUsuarios();
boolean existeEmailEnUso(String email, int idUsuarioExcluir);
}
// Clase para encapsular resultados de operaciones
class ResultadoOperacion<T> {
private boolean exitoso;
private String mensaje;
private T dato;
private List<String> errores;
public ResultadoOperacion(boolean exitoso, String mensaje, T dato) {
this.exitoso = exitoso;
this.mensaje = mensaje;
this.dato = dato;
this.errores = new ArrayList<>();
}
public ResultadoOperacion(boolean exitoso, String mensaje) {
this(exitoso, mensaje, null);
}
// Getters y setters
public boolean isExitoso() { return exitoso; }
public String getMensaje() { return mensaje; }
public T getDato() { return dato; }
public List<String> getErrores() { return errores; }
public void agregarError(String error) { this.errores.add(error); }
}
UsuarioService.java
import java.util.List;
import java.util.ArrayList;
import java.util.regex.Pattern;
public class UsuarioService implements IUsuarioService {
private IUsuarioDAO usuarioDAO;
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$");
public UsuarioService(IUsuarioDAO usuarioDAO) {
this.usuarioDAO = usuarioDAO;
}
@Override
public ResultadoOperacion<Usuario> crearUsuario(Usuario usuario) {
// Validaciones de negocio
List<String> errores = validarUsuarioCompleto(usuario, true);
if (!errores.isEmpty()) {
ResultadoOperacion<Usuario> resultado = new ResultadoOperacion<>(
false, "Error de validación", null);
errores.forEach(resultado::agregarError);
return resultado;
}
// Verificar email único
if (usuarioDAO.existeEmail(usuario.getEmail())) {
return new ResultadoOperacion<>(false,
"Ya existe un usuario con ese email", null);
}
// Aplicar reglas de negocio
usuario.setNombre(normalizarNombre(usuario.getNombre()));
usuario.setEmail(usuario.getEmail().toLowerCase().trim());
// Intentar insertar
boolean insertado = usuarioDAO.insertar(usuario);
if (insertado) {
return new ResultadoOperacion<>(true,
"Usuario creado exitosamente", usuario);
} else {
return new ResultadoOperacion<>(false,
"Error al crear el usuario en la base de datos", null);
}
}
@Override
public ResultadoOperacion<Usuario> actualizarUsuario(Usuario usuario) {
// Verificar que el usuario existe
if (usuarioDAO.obtenerPorId(usuario.getId()).isEmpty()) {
return new ResultadoOperacion<>(false,
"El usuario no existe", null);
}
// Validaciones de negocio
List<String> errores = validarUsuarioCompleto(usuario, false);
if (!errores.isEmpty()) {
ResultadoOperacion<Usuario> resultado = new ResultadoOperacion<>(
false, "Error de validación", null);
errores.forEach(resultado::agregarError);
return resultado;
}
// Verificar email único (excluyendo el usuario actual)
if (existeEmailEnUso(usuario.getEmail(), usuario.getId())) {
return new ResultadoOperacion<>(false,
"Ya existe otro usuario con ese email", null);
}
// Aplicar reglas de negocio
usuario.setNombre(normalizarNombre(usuario.getNombre()));
usuario.setEmail(usuario.getEmail().toLowerCase().trim());
// Intentar actualizar
boolean actualizado = usuarioDAO.actualizar(usuario);
if (actualizado) {
return new ResultadoOperacion<>(true,
"Usuario actualizado exitosamente", usuario);
} else {
return new ResultadoOperacion<>(false,
"Error al actualizar el usuario", null);
}
}
@Override
public ResultadoOperacion<Boolean> eliminarUsuario(int id) {
// Verificar que el usuario existe
if (usuarioDAO.obtenerPorId(id).isEmpty()) {
return new ResultadoOperacion<>(false,
"El usuario no existe", false);
}
// Reglas de negocio para eliminación
// Por ejemplo: no permitir eliminar si es el último administrador
boolean eliminado = usuarioDAO.eliminar(id);
if (eliminado) {
return new ResultadoOperacion<>(true,
"Usuario eliminado exitosamente", true);
} else {
return new ResultadoOperacion<>(false,
"Error al eliminar el usuario", false);
}
}
@Override
public List<Usuario> obtenerTodosLosUsuarios() {
return usuarioDAO.obtenerTodos();
}
@Override
public Usuario obtenerUsuarioPorId(int id) {
return usuarioDAO.obtenerPorId(id).orElse(null);
}
@Override
public List<Usuario> buscarUsuarios(String criterio) {
if (criterio == null || criterio.trim().isEmpty()) {
return obtenerTodosLosUsuarios();
}
String criterioBusqueda = criterio.trim();
// Buscar por nombre y email
List<Usuario> resultados = new ArrayList<>();
resultados.addAll(usuarioDAO.buscarPorNombre(criterioBusqueda));
resultados.addAll(usuarioDAO.buscarPorEmail(criterioBusqueda));
// Eliminar duplicados manteniendo el orden
return resultados.stream()
.distinct()
.collect(Collectors.toList());
}
@Override
public List<Usuario> obtenerUsuariosActivos() {
return usuarioDAO.obtenerActivos();
}
@Override
public boolean validarDatosUsuario(Usuario usuario) {
return validarUsuarioCompleto(usuario, true).isEmpty();
}
@Override
public int obtenerTotalUsuarios() {
return usuarioDAO.contarUsuarios();
}
@Override
public boolean existeEmailEnUso(String email, int idUsuarioExcluir) {
// Obtener todos los usuarios con ese email
List<Usuario> usuariosConEmail = usuarioDAO.buscarPorEmail(email);
// Verificar si alguno tiene un ID diferente al excluido
return usuariosConEmail.stream()
.anyMatch(u -> u.getId() != idUsuarioExcluir &&
u.getEmail().equalsIgnoreCase(email.trim()));
}
// Métodos privados para validaciones y reglas de negocio
private List<String> validarUsuarioCompleto(Usuario usuario, boolean esNuevo) {
List<String> errores = new ArrayList<>();
// Validar nombre
if (usuario.getNombre() == null || usuario.getNombre().trim().isEmpty()) {
errores.add("El nombre es obligatorio");
} else if (usuario.getNombre().trim().length() < 2) {
errores.add("El nombre debe tener al menos 2 caracteres");
} else if (usuario.getNombre().trim().length() > 100) {
errores.add("El nombre no puede exceder 100 caracteres");
}
// Validar email
if (usuario.getEmail() == null || usuario.getEmail().trim().isEmpty()) {
errores.add("El email es obligatorio");
} else if (!EMAIL_PATTERN.matcher(usuario.getEmail().trim()).matches()) {
errores.add("El formato del email es inválido");
} else if (usuario.getEmail().trim().length() > 150) {
errores.add("El email no puede exceder 150 caracteres");
}
// Validar teléfono (opcional)
if (usuario.getTelefono() != null && !usuario.getTelefono().trim().isEmpty()) {
String telefono = usuario.getTelefono().trim();
if (telefono.length() < 7 || telefono.length() > 20) {
errores.add("El teléfono debe tener entre 7 y 20 caracteres");
}
if (!telefono.matches("^[0-9+\-()\s]+$")) {
errores.add("El teléfono contiene caracteres inválidos");
}
}
return errores;
}
private String normalizarNombre(String nombre) {
if (nombre == null) return null;
// Eliminar espacios extra y capitalizar primera letra de cada palabra
return Arrays.stream(nombre.trim().split("\s+"))
.map(palabra -> palabra.substring(0, 1).toUpperCase() +
palabra.substring(1).toLowerCase())
.collect(Collectors.joining(" "));
}
}

Ahora la interfaz de usuario se comunica únicamente con la capa de servicios:

GestorUsuarios.java
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.*;
import java.util.List;
public class GestorUsuarios extends JFrame {
// Componentes de la UI
private DefaultTableModel modeloTabla;
private JTable tabla;
private JTextField txtNombre, txtEmail, txtTelefono, txtBuscar;
private JCheckBox chkActivo;
private JButton btnAgregar, btnActualizar, btnEliminar, btnLimpiar, btnBuscar;
private JLabel lblEstadisticas;
// Capa de servicios - ÚNICA dependencia de la UI
private IUsuarioService usuarioService;
private Usuario usuarioSeleccionado;
// Constructor con inyección de dependencias
public GestorUsuarios(IUsuarioService usuarioService) {
this.usuarioService = usuarioService;
initComponents();
cargarDatos();
actualizarEstadisticas();
setVisible(true);
}
// Constructor por defecto para compatibilidad
public GestorUsuarios() {
this(new UsuarioService(new UsuarioDAO()));
}
private void initComponents() {
setTitle("Gestor de Usuarios - CRUD con Base de Datos");
setSize(900, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
// Panel principal dividido
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
splitPane.setLeftComponent(crearPanelTabla());
splitPane.setRightComponent(crearPanelFormulario());
splitPane.setDividerLocation(550);
add(splitPane, BorderLayout.CENTER);
add(crearPanelBusqueda(), BorderLayout.NORTH);
add(crearPanelEstadisticas(), BorderLayout.SOUTH);
}
private JPanel crearPanelBusqueda() {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
panel.setBorder(BorderFactory.createTitledBorder("Búsqueda"));
panel.add(new JLabel("Buscar por nombre:"));
txtBuscar = new JTextField(20);
btnBuscar = new JButton("Buscar");
JButton btnMostrarTodos = new JButton("Mostrar Todos");
btnBuscar.addActionListener(e -> buscarUsuarios());
btnMostrarTodos.addActionListener(e -> cargarDatos());
// Buscar al presionar Enter
txtBuscar.addActionListener(e -> buscarUsuarios());
panel.add(txtBuscar);
panel.add(btnBuscar);
panel.add(btnMostrarTodos);
return panel;
}
private JPanel crearPanelTabla() {
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createTitledBorder("Lista de Usuarios"));
// Crear modelo de tabla
String[] columnas = {"ID", "Nombre", "Email", "Teléfono", "Activo", "Fecha Registro"};
modeloTabla = new DefaultTableModel(columnas, 0) {
@Override
public boolean isCellEditable(int row, int column) {
return false; // Tabla no editable
}
@Override
public Class<?> getColumnClass(int column) {
if (column == 4) return Boolean.class;
return String.class;
}
};
tabla = new JTable(modeloTabla);
tabla.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
// Listener para selección
tabla.getSelectionModel().addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) {
cargarUsuarioSeleccionado();
}
});
// Configurar columnas
tabla.getColumnModel().getColumn(0).setPreferredWidth(50);
tabla.getColumnModel().getColumn(1).setPreferredWidth(150);
tabla.getColumnModel().getColumn(2).setPreferredWidth(200);
tabla.getColumnModel().getColumn(3).setPreferredWidth(120);
tabla.getColumnModel().getColumn(4).setPreferredWidth(80);
tabla.getColumnModel().getColumn(5).setPreferredWidth(150);
panel.add(new JScrollPane(tabla), BorderLayout.CENTER);
return panel;
}
private JPanel crearPanelFormulario() {
JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(BorderFactory.createTitledBorder("Datos del Usuario"));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
// Campos del formulario
txtNombre = new JTextField(20);
txtEmail = new JTextField(20);
txtTelefono = new JTextField(20);
chkActivo = new JCheckBox("Usuario activo", true);
// Layout del formulario
gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST;
panel.add(new JLabel("Nombre:"), gbc);
gbc.gridx = 1;
panel.add(txtNombre, gbc);
gbc.gridx = 0; gbc.gridy = 1;
panel.add(new JLabel("Email:"), gbc);
gbc.gridx = 1;
panel.add(txtEmail, gbc);
gbc.gridx = 0; gbc.gridy = 2;
panel.add(new JLabel("Teléfono:"), gbc);
gbc.gridx = 1;
panel.add(txtTelefono, gbc);
gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 2;
panel.add(chkActivo, gbc);
// Panel de botones
JPanel panelBotones = new JPanel(new FlowLayout());
btnAgregar = new JButton("Agregar");
btnActualizar = new JButton("Actualizar");
btnEliminar = new JButton("Eliminar");
btnLimpiar = new JButton("Limpiar");
// Listeners de botones
btnAgregar.addActionListener(e -> agregarUsuario());
btnActualizar.addActionListener(e -> actualizarUsuario());
btnEliminar.addActionListener(e -> eliminarUsuario());
btnLimpiar.addActionListener(e -> limpiarFormulario());
panelBotones.add(btnAgregar);
panelBotones.add(btnActualizar);
panelBotones.add(btnEliminar);
panelBotones.add(btnLimpiar);
gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 2;
panel.add(panelBotones, gbc);
return panel;
}
private JPanel crearPanelEstadisticas() {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
panel.setBorder(BorderFactory.createTitledBorder("Estadísticas"));
lblEstadisticas = new JLabel("Total de usuarios: 0");
panel.add(lblEstadisticas);
return panel;
}
private void cargarDatos() {
modeloTabla.setRowCount(0); // Limpiar tabla
List<Usuario> usuarios = usuarioService.obtenerTodosLosUsuarios();
for (Usuario usuario : usuarios) {
Object[] fila = {
usuario.getId(),
usuario.getNombre(),
usuario.getEmail(),
usuario.getTelefono(),
usuario.isActivo(),
usuario.getFechaRegistro()
};
modeloTabla.addRow(fila);
}
actualizarEstadisticas();
}
private void actualizarEstadisticas() {
int total = usuarioService.obtenerTotalUsuarios();
lblEstadisticas.setText("Total de usuarios: " + total);
}
private void buscarUsuarios() {
String termino = txtBuscar.getText().trim();
if (termino.isEmpty()) {
cargarDatos();
return;
}
modeloTabla.setRowCount(0);
List<Usuario> usuarios = usuarioService.buscarUsuarios(termino);
for (Usuario usuario : usuarios) {
Object[] fila = {
usuario.getId(),
usuario.getNombre(),
usuario.getEmail(),
usuario.getTelefono(),
usuario.isActivo(),
usuario.getFechaRegistro()
};
modeloTabla.addRow(fila);
}
if (usuarios.isEmpty()) {
JOptionPane.showMessageDialog(this, "No se encontraron usuarios con ese criterio");
}
}
private void cargarUsuarioSeleccionado() {
int filaSeleccionada = tabla.getSelectedRow();
if (filaSeleccionada != -1) {
int id = (Integer) modeloTabla.getValueAt(filaSeleccionada, 0);
usuarioSeleccionado = usuarioService.obtenerUsuarioPorId(id);
if (usuarioSeleccionado != null) {
txtNombre.setText(usuarioSeleccionado.getNombre());
txtEmail.setText(usuarioSeleccionado.getEmail());
txtTelefono.setText(usuarioSeleccionado.getTelefono());
chkActivo.setSelected(usuarioSeleccionado.isActivo());
btnActualizar.setEnabled(true);
btnEliminar.setEnabled(true);
}
} else {
usuarioSeleccionado = null;
btnActualizar.setEnabled(false);
btnEliminar.setEnabled(false);
}
}
private void agregarUsuario() {
Usuario usuario = new Usuario(
txtNombre.getText().trim(),
txtEmail.getText().trim(),
txtTelefono.getText().trim(),
chkActivo.isSelected()
);
ResultadoOperacion<Usuario> resultado = usuarioService.crearUsuario(usuario);
if (resultado.isExitoso()) {
JOptionPane.showMessageDialog(this, resultado.getMensaje());
cargarDatos();
limpiarFormulario();
} else {
mostrarErrores(resultado);
}
}
private void actualizarUsuario() {
if (usuarioSeleccionado == null) {
JOptionPane.showMessageDialog(this, "Selecciona un usuario para actualizar");
return;
}
usuarioSeleccionado.setNombre(txtNombre.getText().trim());
usuarioSeleccionado.setEmail(txtEmail.getText().trim());
usuarioSeleccionado.setTelefono(txtTelefono.getText().trim());
usuarioSeleccionado.setActivo(chkActivo.isSelected());
ResultadoOperacion<Usuario> resultado = usuarioService.actualizarUsuario(usuarioSeleccionado);
if (resultado.isExitoso()) {
JOptionPane.showMessageDialog(this, resultado.getMensaje());
cargarDatos();
limpiarFormulario();
} else {
mostrarErrores(resultado);
}
}
private void eliminarUsuario() {
if (usuarioSeleccionado == null) {
JOptionPane.showMessageDialog(this, "Selecciona un usuario para eliminar");
return;
}
int confirmar = JOptionPane.showConfirmDialog(this,
"¿Estás seguro de eliminar el usuario: " + usuarioSeleccionado.getNombre() + "?",
"Confirmar eliminación",
JOptionPane.YES_NO_OPTION);
if (confirmar == JOptionPane.YES_OPTION) {
ResultadoOperacion<Boolean> resultado = usuarioService.eliminarUsuario(usuarioSeleccionado.getId());
if (resultado.isExitoso()) {
JOptionPane.showMessageDialog(this, resultado.getMensaje());
cargarDatos();
limpiarFormulario();
} else {
mostrarErrores(resultado);
}
}
}
private void limpiarFormulario() {
txtNombre.setText("");
txtEmail.setText("");
txtTelefono.setText("");
chkActivo.setSelected(true);
tabla.clearSelection();
usuarioSeleccionado = null;
btnActualizar.setEnabled(false);
btnEliminar.setEnabled(false);
}
// Método para mostrar errores de validación
private void mostrarErrores(ResultadoOperacion<?> resultado) {
StringBuilder mensaje = new StringBuilder(resultado.getMensaje());
if (!resultado.getErrores().isEmpty()) {
mensaje.append("
Detalles:");
for (String error : resultado.getErrores()) {
mensaje.append("
").append(error);
}
}
JOptionPane.showMessageDialog(this, mensaje.toString(), "Error", JOptionPane.ERROR_MESSAGE);
}
// Método simplificado - las validaciones ahora están en la capa de servicio
private boolean validarFormularioBasico() {
// Solo validaciones básicas de UI
if (txtNombre.getText().trim().isEmpty()) {
JOptionPane.showMessageDialog(this, "El nombre no puede estar vacío");
txtNombre.requestFocus();
return false;
}
if (txtEmail.getText().trim().isEmpty()) {
JOptionPane.showMessageDialog(this, "El email no puede estar vacío");
txtEmail.requestFocus();
return false;
}
return true;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new GestorUsuarios());
}
}
com.miapp.usuarios/
├── modelo/
│ ├── Usuario.java
│ └── ResultadoOperacion.java
├── dao/
│ ├── IUsuarioDAO.java
│ ├── UsuarioDAO.java
│ └── ConexionDB.java
├── servicio/
│ ├── IUsuarioService.java
│ └── UsuarioService.java
├── vista/
│ └── GestorUsuarios.java
└── Main.java

2. Clase Principal con Inyección de Dependencias

Section titled “2. Clase Principal con Inyección de Dependencias”
Main.java
public class Main {
public static void main(String[] args) {
// Configurar Look and Feel
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeel());
} catch (Exception e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(() -> {
// Crear las dependencias siguiendo el patrón de inyección
IUsuarioDAO usuarioDAO = new UsuarioDAO();
IUsuarioService usuarioService = new UsuarioService(usuarioDAO);
// Crear y mostrar la interfaz
new GestorUsuarios(usuarioService);
});
}
}

3. Configuración de Base de Datos Mejorada

Section titled “3. Configuración de Base de Datos Mejorada”
ConexionDB.java (Mejorada)
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class ConexionDB {
private static ConexionDB instancia;
private Connection conexion;
private Properties config;
private ConexionDB() {
cargarConfiguracion();
conectar();
}
private void cargarConfiguracion() {
config = new Properties();
try (InputStream input = getClass().getClassLoader()
.getResourceAsStream("database.properties")) {
if (input != null) {
config.load(input);
} else {
// Configuración por defecto
config.setProperty("db.url", "jdbc:mysql://localhost:3306/swing_app");
config.setProperty("db.username", "root");
config.setProperty("db.password", "password");
config.setProperty("db.driver", "com.mysql.cj.jdbc.Driver");
}
} catch (IOException e) {
System.err.println("Error cargando configuración: " + e.getMessage());
}
}
private void conectar() {
try {
String driver = config.getProperty("db.driver");
String url = config.getProperty("db.url");
String username = config.getProperty("db.username");
String password = config.getProperty("db.password");
Class.forName(driver);
this.conexion = DriverManager.getConnection(url, username, password);
// Configurar conexión
conexion.setAutoCommit(true);
System.out.println("✅ Conexión exitosa a la base de datos");
} catch (ClassNotFoundException e) {
System.err.println("❌ Driver no encontrado: " + e.getMessage());
} catch (SQLException e) {
System.err.println("❌ Error de conexión: " + e.getMessage());
}
}
public static ConexionDB getInstance() {
if (instancia == null) {
synchronized (ConexionDB.class) {
if (instancia == null) {
instancia = new ConexionDB();
}
}
}
return instancia;
}
public Connection getConexion() {
try {
if (conexion == null || conexion.isClosed()) {
conectar();
}
} catch (SQLException e) {
System.err.println("Error verificando conexión: " + e.getMessage());
conectar();
}
return conexion;
}
public void cerrarConexion() {
try {
if (conexion != null && !conexion.isClosed()) {
conexion.close();
System.out.println("Conexión cerrada");
}
} catch (SQLException e) {
System.err.println("Error cerrando conexión: " + e.getMessage());
}
}
}
database.properties
# Configuración de Base de Datos
db.driver=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/swing_app?useSSL=false&serverTimezone=UTC
db.username=root
db.password=tu_password
# Configuración de Pool de Conexiones (opcional)
db.pool.maxConnections=10
db.pool.timeout=30000

Capa de Presentación:

  • Maneja únicamente la interfaz de usuario
  • Captura eventos y muestra información
  • Validaciones básicas de UI

Capa de Lógica de Negocio:

  • Contiene las reglas del negocio
  • Validaciones complejas
  • Coordinación de operaciones

Capa de Acceso a Datos:

  • Operaciones CRUD
  • Mapeo objeto-relacional
  • Gestión de conexiones
public class ValidadorDatos {
public static boolean validarEmail(String email) {
if (email == null || email.trim().isEmpty()) {
return false;
}
String regex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$";
return email.matches(regex);
}
public static boolean validarTelefono(String telefono) {
if (telefono == null || telefono.trim().isEmpty()) {
return true; // Teléfono es opcional
}
String regex = "^[0-9-+()\s]+$";
return telefono.matches(regex) && telefono.length() >= 7;
}
public static boolean validarNombre(String nombre) {
if (nombre == null || nombre.trim().isEmpty()) {
return false;
}
return nombre.trim().length() >= 2 && nombre.trim().length() <= 100;
}
}

En este módulo hemos implementado una arquitectura en 3 capas completa para un CRUD con base de datos:

🏗️ Arquitectura en Capas
  • Capa de Presentación: Interfaz de usuario con Swing
  • Capa de Lógica de Negocio: Servicios con validaciones y reglas
  • Capa de Acceso a Datos: DAO con operaciones CRUD
🔧 Patrones Implementados
  • DAO Pattern: Abstracción del acceso a datos
  • Service Layer: Lógica de negocio centralizada
  • Dependency Injection: Inyección de dependencias
  • Singleton: Gestión de conexiones
✅ Beneficios Obtenidos
  • Separación de responsabilidades: Cada capa tiene un propósito específico
  • Mantenibilidad: Código más fácil de mantener y extender
  • Testabilidad: Cada capa puede probarse independientemente
  • Reutilización: La lógica puede usarse en diferentes interfaces
🚀 Características Avanzadas
  • Validaciones robustas en la capa de servicio
  • Manejo centralizado de errores
  • Configuración externa de base de datos
  • Interfaz de usuario responsiva

Esta arquitectura proporciona una base sólida para aplicaciones empresariales, facilitando el mantenimiento, testing y escalabilidad del código.

Implementaremos un sistema CRUD para gestión de personas con 4 tablas relacionadas usando el patrón de arquitectura en capas:

com.miapp.personas/
├── db/ // Capa de Datos
│ ├── IConexionDB.java // Interfaz de conexión
│ ├── ConexionFactory.java // Factory de conexiones
│ ├── ConexionOracle.java // Implementación Oracle
│ └── ConexionMysql.java // Implementación MySQL
├── modelo/ // Modelos de datos
│ ├── Persona.java
│ ├── Genero.java
│ ├── TipoDocumento.java
│ └── EstadoCivil.java
├── persistencia/ // Capa de Persistencia (DAO)
│ ├── IPersonaDAO.java
│ ├── PersonaDAO.java
│ ├── GeneroDAO.java
│ ├── TipoDocumentoDAO.java
│ └── EstadoCivilDAO.java
├── negocio/ // Capa de Negocio (Services)
│ ├── IPersonaService.java
│ ├── PersonaService.java
│ └── ResultadoOperacion.java
└── presentacion/ // Capa de Presentación
└── GestorPersonas.java

Interfaz de Conexión:

IConexionDB.java
package db;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Interfaz para manejo de conexiones a base de datos
* Permite implementar diferentes tipos de BD manteniendo la misma estructura
*/
public interface IConexionDB {
/**
* Obtiene una conexión a la base de datos
* @return Connection objeto de conexión activa
* @throws SQLException si hay error en la conexión
*/
Connection obtenerConexion() throws SQLException;
/**
* Cierra la conexión a la base de datos
* @throws SQLException si hay error al cerrar
*/
void cerrarConexion() throws SQLException;
/**
* Verifica si la conexión está activa
* @return true si está conectado, false en caso contrario
*/
boolean estaConectado();
/**
* Obtiene el tipo de base de datos
* @return String identificador del tipo de BD
*/
String getTipoBD();
/**
* Confirma las transacciones pendientes
* @throws SQLException si hay error en el commit
*/
void commit() throws SQLException;
/**
* Deshace las transacciones pendientes
* @throws SQLException si hay error en el rollback
*/
void rollback() throws SQLException;
}

Factory de Conexiones:

ConexionFactory.java
package db;
import java.sql.SQLException;
/**
* Factory para crear conexiones según el tipo de base de datos
*/
public class ConexionFactory {
public enum TipoBD {
MYSQL, ORACLE
}
/**
* Crea una conexión según el tipo especificado
* @param tipo Tipo de base de datos (MYSQL u ORACLE)
* @return IConexionDB implementación específica
* @throws SQLException si hay error al crear la conexión
*/
public static IConexionDB crearConexion(TipoBD tipo) throws SQLException {
switch (tipo) {
case MYSQL:
return ConexionMysql.getInstance();
case ORACLE:
return ConexionOracle.getInstance();
default:
throw new SQLException("Tipo de base de datos no soportado: " + tipo);
}
}
/**
* Crea una conexión MySQL por defecto
* @return IConexionDB conexión MySQL
* @throws SQLException si hay error
*/
public static IConexionDB crearConexionPorDefecto() throws SQLException {
return crearConexion(TipoBD.MYSQL);
}
/**
* Prueba todas las conexiones disponibles
*/
public static void probarConexiones() {
System.out.println("🔍 Probando conexiones disponibles...");
for (TipoBD tipo : TipoBD.values()) {
try {
IConexionDB conexion = crearConexion(tipo);
conexion.obtenerConexion();
if (conexion.estaConectado()) {
System.out.println("" + conexion.getTipoBD() + ": Conexión exitosa");
conexion.cerrarConexion();
} else {
System.out.println("" + conexion.getTipoBD() + ": Conexión fallida");
}
} catch (SQLException e) {
System.out.println("❌ Error con " + tipo + ": " + e.getMessage());
}
}
}
}

Implementación MySQL:

ConexionMysql.java
/*
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
*/
package db;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.DriverManager;
/**
* Implementación de conexión para MySQL
* @author Diego Frank Lipa Choque
*/
public class ConexionMysql implements IConexionDB {
// Variables estáticas para conexión MySQL
private static Connection conn = null;
private static ConexionMysql instancia = null;
private static final String LOGIN = "root"; // Usuario MySQL
private static final String CLAVE = ""; // Contraseña MySQL
private static final String URL = "jdbc:mysql://localhost:3306/venta?useSSL=false&serverTimezone=UTC";
// Constructor privado para Singleton
private ConexionMysql() {}
/**
* Obtiene la instancia única (Singleton)
* @return ConexionMysql instancia única
*/
public static synchronized ConexionMysql getInstance() {
if (instancia == null) {
instancia = new ConexionMysql();
}
return instancia;
}
@Override
public Connection obtenerConexion() throws SQLException {
try {
if (conn == null || conn.isClosed()) {
// Cargar el driver JDBC para MySQL
Class.forName("com.mysql.cj.jdbc.Driver");
// Establecer conexión
conn = DriverManager.getConnection(URL, LOGIN, CLAVE);
// Manejo manual de transacciones
conn.setAutoCommit(false);
System.out.println("======================================================");
System.out.println("✅ Diego Frank Lipa Choque");
System.out.println("🔗 Conexión MySQL Exitosa");
System.out.println("🏠 Servidor: localhost:3306");
System.out.println("🗄️ Base de datos: venta");
System.out.println("======================================================");
}
} catch (ClassNotFoundException e) {
throw new SQLException("Driver MySQL no encontrado: " + e.getMessage(), e);
}
return conn;
}
@Override
public void cerrarConexion() throws SQLException {
if (conn != null && !conn.isClosed()) {
conn.close();
conn = null;
System.out.println("🔌 Conexión MySQL cerrada correctamente");
}
}
@Override
public boolean estaConectado() {
try {
return conn != null && !conn.isClosed();
} catch (SQLException e) {
return false;
}
}
@Override
public String getTipoBD() {
return "MySQL Database";
}
@Override
public void commit() throws SQLException {
if (conn != null && !conn.isClosed()) {
conn.commit();
System.out.println("✅ Commit MySQL realizado");
}
}
@Override
public void rollback() throws SQLException {
if (conn != null && !conn.isClosed()) {
conn.rollback();
System.out.println("↩️ Rollback MySQL realizado");
}
}
// Método de prueba
public static void main(String[] args) {
try {
ConexionMysql mysql = ConexionMysql.getInstance();
mysql.obtenerConexion();
if (mysql.estaConectado()) {
System.out.println("✅ Prueba MySQL exitosa");
mysql.cerrarConexion();
}
} catch (SQLException e) {
System.err.println("❌ Error en prueba: " + e.getMessage());
}
}
}

Implementación Oracle:

ConexionOracle.java
/*
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
*/
package db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* Implementación de conexión para Oracle Database
* @author Diego Frank Lipa Choque
*/
public class ConexionOracle implements IConexionDB {
// Declaración de variables estáticas para la conexión
private static Connection conn = null;
private static ConexionOracle instancia = null;
private static final String LOGIN = "MATRICULA2"; // Usuario de la base de datos
private static final String CLAVE = "MATRICULA2"; // Contraseña de la base de datos
private static final String URL = "jdbc:oracle:thin:@localhost:1521:xe"; // URL de conexión a la base de datos
// Constructor privado para Singleton
private ConexionOracle() {}
/**
* Obtiene la instancia única (Singleton)
* @return ConexionOracle instancia única
*/
public static synchronized ConexionOracle getInstance() {
if (instancia == null) {
instancia = new ConexionOracle();
}
return instancia;
}
@Override
public Connection obtenerConexion() throws SQLException {
try {
if (conn == null || conn.isClosed()) {
// Cargar el controlador JDBC
Class.forName("oracle.jdbc.OracleDriver");
// Establecer la conexión con la base de datos
conn = DriverManager.getConnection(URL, LOGIN, CLAVE);
// Deshabilitar el autocommit para manejar transacciones manualmente
conn.setAutoCommit(false);
System.out.println("======================================================");
System.out.println("✅ Diego Frank Lipa Choque");
System.out.println("🔗 Conexión Oracle Exitosa");
System.out.println("🏠 Servidor: localhost:1521");
System.out.println("🗄️ Base de datos: xe");
System.out.println("======================================================");
}
} catch (ClassNotFoundException e) {
throw new SQLException("Driver Oracle no encontrado: " + e.getMessage(), e);
}
return conn;
}
@Override
public void cerrarConexion() throws SQLException {
if (conn != null && !conn.isClosed()) {
conn.close();
conn = null;
System.out.println("🔌 Conexión Oracle cerrada correctamente");
}
}
@Override
public boolean estaConectado() {
try {
return conn != null && !conn.isClosed();
} catch (SQLException e) {
return false;
}
}
@Override
public String getTipoBD() {
return "Oracle Database";
}
@Override
public void commit() throws SQLException {
if (conn != null && !conn.isClosed()) {
conn.commit();
System.out.println("✅ Commit Oracle realizado");
}
}
@Override
public void rollback() throws SQLException {
if (conn != null && !conn.isClosed()) {
conn.rollback();
System.out.println("↩️ Rollback Oracle realizado");
}
}
// Método main para probar la conexión (opcional)
public static void main(String[] args) {
try {
ConexionOracle oracle = ConexionOracle.getInstance();
oracle.obtenerConexion();
if (oracle.estaConectado()) {
System.out.println("✅ Prueba Oracle exitosa");
oracle.cerrarConexion();
}
} catch (SQLException e) {
System.err.println("❌ Error en prueba: " + e.getMessage());
}
}
}

Para completar la implementación, necesitas crear las siguientes tablas:

script_mysql.sql
-- Script para MySQL
CREATE DATABASE IF NOT EXISTS personas_db;
USE personas_db;
-- Tabla de géneros
CREATE TABLE generos (
id INT PRIMARY KEY AUTO_INCREMENT,
nombre VARCHAR(50) NOT NULL UNIQUE,
descripcion VARCHAR(200),
activo BOOLEAN DEFAULT TRUE,
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Tabla de tipos de documento
CREATE TABLE tipos_documento (
id INT PRIMARY KEY AUTO_INCREMENT,
nombre VARCHAR(100) NOT NULL,
abreviatura VARCHAR(10) NOT NULL UNIQUE,
descripcion VARCHAR(200),
activo BOOLEAN DEFAULT TRUE,
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Tabla de estados civiles
CREATE TABLE estados_civiles (
id INT PRIMARY KEY AUTO_INCREMENT,
nombre VARCHAR(50) NOT NULL UNIQUE,
descripcion VARCHAR(200),
activo BOOLEAN DEFAULT TRUE,
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Tabla principal de personas
CREATE TABLE personas (
id INT PRIMARY KEY AUTO_INCREMENT,
nombres VARCHAR(100) NOT NULL,
apellidos VARCHAR(100) NOT NULL,
numero_documento VARCHAR(20) NOT NULL UNIQUE,
fecha_nacimiento DATE NOT NULL,
telefono VARCHAR(20),
email VARCHAR(150) NOT NULL UNIQUE,
direccion VARCHAR(300),
genero_id INT NOT NULL,
tipo_documento_id INT NOT NULL,
estado_civil_id INT NOT NULL,
activo BOOLEAN DEFAULT TRUE,
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
fecha_modificacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (genero_id) REFERENCES generos(id),
FOREIGN KEY (tipo_documento_id) REFERENCES tipos_documento(id),
FOREIGN KEY (estado_civil_id) REFERENCES estados_civiles(id),
INDEX idx_nombres (nombres),
INDEX idx_apellidos (apellidos),
INDEX idx_documento (numero_documento),
INDEX idx_email (email),
INDEX idx_activo (activo)
);
-- Datos iniciales
INSERT INTO generos (nombre, descripcion) VALUES
('Masculino', 'Género masculino'),
('Femenino', 'Género femenino'),
('Otro', 'Otro género');
INSERT INTO tipos_documento (nombre, abreviatura, descripcion) VALUES
('Documento Nacional de Identidad', 'DNI', 'Documento de identidad nacional'),
('Carnet de Extranjería', 'CE', 'Documento para extranjeros'),
('Pasaporte', 'PAS', 'Documento de viaje internacional'),
('Cédula de Identidad', 'CI', 'Cédula de identidad');
INSERT INTO estados_civiles (nombre, descripcion) VALUES
('Soltero/a', 'Estado civil soltero'),
('Casado/a', 'Estado civil casado'),
('Divorciado/a', 'Estado civil divorciado'),
('Viudo/a', 'Estado civil viudo'),
('Conviviente', 'Unión de hecho');
script_oracle.sql
-- Script para Oracle
-- Crear secuencias
CREATE SEQUENCE seq_generos START WITH 1 INCREMENT BY 1;
CREATE SEQUENCE seq_tipos_documento START WITH 1 INCREMENT BY 1;
CREATE SEQUENCE seq_estados_civiles START WITH 1 INCREMENT BY 1;
CREATE SEQUENCE seq_personas START WITH 1 INCREMENT BY 1;
-- Tabla de géneros
CREATE TABLE generos (
id NUMBER PRIMARY KEY,
nombre VARCHAR2(50) NOT NULL UNIQUE,
descripcion VARCHAR2(200),
activo NUMBER(1) DEFAULT 1 CHECK (activo IN (0,1)),
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Tabla de tipos de documento
CREATE TABLE tipos_documento (
id NUMBER PRIMARY KEY,
nombre VARCHAR2(100) NOT NULL,
abreviatura VARCHAR2(10) NOT NULL UNIQUE,
descripcion VARCHAR2(200),
activo NUMBER(1) DEFAULT 1 CHECK (activo IN (0,1)),
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Tabla de estados civiles
CREATE TABLE estados_civiles (
id NUMBER PRIMARY KEY,
nombre VARCHAR2(50) NOT NULL UNIQUE,
descripcion VARCHAR2(200),
activo NUMBER(1) DEFAULT 1 CHECK (activo IN (0,1)),
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Tabla principal de personas
CREATE TABLE personas (
id NUMBER PRIMARY KEY,
nombres VARCHAR2(100) NOT NULL,
apellidos VARCHAR2(100) NOT NULL,
numero_documento VARCHAR2(20) NOT NULL UNIQUE,
fecha_nacimiento DATE NOT NULL,
telefono VARCHAR2(20),
email VARCHAR2(150) NOT NULL UNIQUE,
direccion VARCHAR2(300),
genero_id NUMBER NOT NULL,
tipo_documento_id NUMBER NOT NULL,
estado_civil_id NUMBER NOT NULL,
activo NUMBER(1) DEFAULT 1 CHECK (activo IN (0,1)),
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
fecha_modificacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (genero_id) REFERENCES generos(id),
FOREIGN KEY (tipo_documento_id) REFERENCES tipos_documento(id),
FOREIGN KEY (estado_civil_id) REFERENCES estados_civiles(id)
);
-- Triggers para auto-incremento
CREATE OR REPLACE TRIGGER trg_generos_id
BEFORE INSERT ON generos
FOR EACH ROW
BEGIN
:NEW.id := seq_generos.NEXTVAL;
END;
CREATE OR REPLACE TRIGGER trg_tipos_documento_id
BEFORE INSERT ON tipos_documento
FOR EACH ROW
BEGIN
:NEW.id := seq_tipos_documento.NEXTVAL;
END;
CREATE OR REPLACE TRIGGER trg_estados_civiles_id
BEFORE INSERT ON estados_civiles
FOR EACH ROW
BEGIN
:NEW.id := seq_estados_civiles.NEXTVAL;
END;
CREATE OR REPLACE TRIGGER trg_personas_id
BEFORE INSERT ON personas
FOR EACH ROW
BEGIN
:NEW.id := seq_personas.NEXTVAL;
END;
-- Trigger para fecha de modificación
CREATE OR REPLACE TRIGGER trg_personas_fecha_mod
BEFORE UPDATE ON personas
FOR EACH ROW
BEGIN
:NEW.fecha_modificacion := CURRENT_TIMESTAMP;
END;
-- Índices
CREATE INDEX idx_personas_nombres ON personas(nombres);
CREATE INDEX idx_personas_apellidos ON personas(apellidos);
CREATE INDEX idx_personas_documento ON personas(numero_documento);
CREATE INDEX idx_personas_email ON personas(email);
CREATE INDEX idx_personas_activo ON personas(activo);
-- Datos iniciales
INSERT INTO generos (nombre, descripcion) VALUES ('Masculino', 'Género masculino');
INSERT INTO generos (nombre, descripcion) VALUES ('Femenino', 'Género femenino');
INSERT INTO generos (nombre, descripcion) VALUES ('Otro', 'Otro género');
INSERT INTO tipos_documento (nombre, abreviatura, descripcion) VALUES ('Documento Nacional de Identidad', 'DNI', 'Documento de identidad nacional');
INSERT INTO tipos_documento (nombre, abreviatura, descripcion) VALUES ('Carnet de Extranjería', 'CE', 'Documento para extranjeros');
INSERT INTO tipos_documento (nombre, abreviatura, descripcion) VALUES ('Pasaporte', 'PAS', 'Documento de viaje internacional');
INSERT INTO tipos_documento (nombre, abreviatura, descripcion) VALUES ('Cédula de Identidad', 'CI', 'Cédula de identidad');
INSERT INTO estados_civiles (nombre, descripcion) VALUES ('Soltero/a', 'Estado civil soltero');
INSERT INTO estados_civiles (nombre, descripcion) VALUES ('Casado/a', 'Estado civil casado');
INSERT INTO estados_civiles (nombre, descripcion) VALUES ('Divorciado/a', 'Estado civil divorciado');
INSERT INTO estados_civiles (nombre, descripcion) VALUES ('Viudo/a', 'Estado civil viudo');
INSERT INTO estados_civiles (nombre, descripcion) VALUES ('Conviviente', 'Unión de hecho');
COMMIT;
Main.java (Completo)
// Clase principal con configuración completa
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
try {
// Configurar Look and Feel
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeel());
// Elegir tipo de base de datos
ConexionFactory.TipoBD tipoBD = ConexionFactory.TipoBD.MYSQL; // o ORACLE
// Crear las dependencias siguiendo la arquitectura en capas
IPersonaDAO personaDAO = new PersonaDAO(tipoBD);
IPersonaService personaService = new PersonaService(personaDAO);
// Crear y mostrar la interfaz
GestorPersonas ventana = new GestorPersonas(personaService);
System.out.println("🚀 Aplicación iniciada exitosamente");
System.out.println("📊 Base de datos: " + tipoBD);
System.out.println("👤 Desarrollado por: Diego Frank Lipa Choque");
} catch (Exception e) {
JOptionPane.showMessageDialog(null,
"Error al inicializar la aplicación:\n" + e.getMessage(),
"Error Crítico",
JOptionPane.ERROR_MESSAGE);
e.printStackTrace();
System.exit(1);
}
});
}
}

Esta implementación completa de CRUD con arquitectura en 4 capas proporciona:

🏗️ Arquitectura Robusta
  • Capa de Datos (DB): Abstracción de conexiones con soporte para MySQL y Oracle
  • Capa de Persistencia (DAO): Operaciones CRUD con manejo de transacciones
  • Capa de Negocio (Service): Validaciones, reglas de negocio y lógica compleja
  • Capa de Presentación (UI): Interfaz Swing funcional y responsive
🔧 Patrones Aplicados
  • Factory Pattern: Para crear conexiones según el tipo de BD
  • DAO Pattern: Abstracción del acceso a datos
  • Service Layer: Centralización de la lógica de negocio
  • Dependency Injection: Desacoplamiento entre capas
  • Singleton: Gestión eficiente de conexiones
✅ Características Implementadas
  • 4 Tablas relacionadas: Persona, Género, TipoDocumento, EstadoCivil
  • CRUD completo: Crear, leer, actualizar y eliminar registros
  • Validaciones robustas: En capa de servicio y presentación
  • Búsqueda avanzada: Por nombre y número de documento
  • Manejo de errores: Centralizado con mensajes descriptivos
  • Interfaz funcional: Sin diseño complejo, enfocada en funcionalidad
🚀 Beneficios Obtenidos
  • Mantenibilidad: Código organizado y fácil de modificar
  • Escalabilidad: Fácil agregar nuevas funcionalidades
  • Testabilidad: Cada capa puede probarse independientemente
  • Flexibilidad: Cambio fácil entre diferentes bases de datos
  • Reutilización: Componentes pueden usarse en otros proyectos

Esta arquitectura proporciona una base sólida para aplicaciones empresariales de nivel intermedio, demostrando buenas prácticas de desarrollo y separación de responsabilidades.

🐝