Clase AlumnoVO y AlumnoDAO - eliizquierdo/material_programacion_avanzada2026 GitHub Wiki
📦 Proyecto:
mini-crud-alumno-sql(Tema 4 — Herencia + MySQL) — ⬇️ Descargar ZIP · ☁️ Abrir en Codespace
- Operaciones de Lectura: listarAlumnos() y obtenerAlumnoPorCodigo()
- Operaciones de inserción: agregarAlumno()
- Actualizar Alumno
- Eliminar Alumno
La clase AlumnoVO extiende de PersonaVO y solo necesita los atributos específicos de un alumno,
en este caso, teniendo en cuenta la BD, el teléfono.
package modelo.vo;
public class AlumnoVO extends PersonaVO {
private String telefono;
// Constructor vacío
public AlumnoVO() {
super();
}
// Constructor completo (código, nombre y teléfono)
public AlumnoVO(int codigo, String nombre, String telefono) {
super(codigo, nombre);
this.telefono = telefono;
}
// Getters y Setters
public String getTelefono() {
return telefono;
}
public void setTelefono(String telefono) {
this.telefono = telefono;
}
// ... toString()
}Para obtener un AlumnoVO completo (con su nombre heredado de personas y su telefono de alumnos), se utiliza un INNER JOIN entre ambas tablas usando el campo codigo como vínculo.
FASE: listarAlumnos()
- FASE 1: SQL - Se define un INNER JOIN para obtener codigo, nombre (de personas) y telefono (de alumnos).
- FASE 5: Leer - Se recorre el ResultSet y se usa el Constructor Completo de AlumnoVO para crear los objetos.
package modelo.dao;
import modelo.conexion.Conexion;
import modelo.vo.AlumnoVO;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class AlumnoDAO {
// ============== LISTAR ALUMNOS ==============
public List<AlumnoVO> listarAlumnos() {
List<AlumnoVO> lista = new ArrayList<>();
// FASE 1: Definir la sentencia SQL
String sql = "SELECT p.codigo, p.nombre, a.telefono " +
"FROM personas p " +
"INNER JOIN alumnos a ON p.codigo = a.codigo";
// FASES 2, 3 y 4: conexión, PreparedStatement y ejecución en try-with-resources
try (Connection con = Conexion.obtener();
PreparedStatement ps = con.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
// FASE 5: Leer el ResultSet
while (rs.next()) {
AlumnoVO alumno = new AlumnoVO(
rs.getInt("codigo"),
rs.getString("nombre"),
rs.getString("telefono"));
lista.add(alumno);
}
} catch (SQLException e) {
System.err.println("Error al listar alumnos: " + e.getMessage());
e.printStackTrace();
}
return lista;
}
} // ============== OBTENER ALUMNO POR CÓDIGO ==============
public AlumnoVO obtenerAlumnoPorCodigo(int codigo) {
// FASE 1: Definir la sentencia SQL
String sql = "SELECT p.codigo, p.nombre, a.telefono " +
"FROM personas p " +
"INNER JOIN alumnos a ON p.codigo = a.codigo " +
"WHERE p.codigo = ?";
AlumnoVO alumno = null;
// FASES 2 y 3: conexión y PreparedStatement en try-with-resources
try (Connection con = Conexion.obtener();
PreparedStatement ps = con.prepareStatement(sql)) {
// FASE 3: Asignar parámetros
ps.setInt(1, codigo);
// FASE 4 & 5: Ejecutar y leer resultado
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
alumno = new AlumnoVO(
rs.getInt("codigo"),
rs.getString("nombre"),
rs.getString("telefono"));
}
}
} catch (SQLException e) {
System.err.println("Error al obtener alumno: " + e.getMessage());
e.printStackTrace();
}
return alumno;
}Esta operación necesita insertar en las tablas personas y alumnos, usando el mismo código para ambas.
Una primera versión, sin ningún cuidado especial, sería así:
public boolean agregarAlumno(AlumnoVO alumno) {
boolean exito = false;
int codigo = alumno.getCodigo();
// FASE 1: Definir las sentencias SQL
String sql1 = "INSERT INTO personas(codigo, nombre) VALUES (?, ?)";
String sql2 = "INSERT INTO alumnos(codigo, telefono) VALUES (?, ?)";
try (Connection con = Conexion.obtener();
PreparedStatement ps1 = con.prepareStatement(sql1);
PreparedStatement ps2 = con.prepareStatement(sql2)) {
ps1.setInt(1, codigo);
ps1.setString(2, alumno.getNombre());
int filasPersona = ps1.executeUpdate();
ps2.setInt(1, codigo);
ps2.setString(2, alumno.getTelefono());
int filasAlumno = ps2.executeUpdate();
exito = filasPersona > 0 && filasAlumno > 0;
} catch (SQLException e) {
System.err.println("Error al agregar alumno: " + e.getMessage());
e.printStackTrace();
}
return exito;
}¿Cuál es el problema? Si la inserción en personas tiene éxito pero la de alumnos falla (por ejemplo, se corta la conexión justo ahí), quedamos con una fila en personas que nunca llegó a ser un alumno — datos inconsistentes.
Para evitar ese escenario, agrupamos ambas inserciones en una sola transacción: o se aplican las dos, o no se aplica ninguna.
Componentes clave de una transacción:
| Método | Propósito |
|---|---|
con.setAutoCommit(false) |
Desactiva el guardado automático. Los cambios quedan pendientes hasta llamar a commit(). |
con.commit() |
Confirma todos los cambios pendientes. Los hace permanentes en la base de datos. |
con.rollback() |
Revierte todos los cambios pendientes. Deshace las operaciones como si nunca ocurrieran. |
IMPORTANTE: Para profundizar en el concepto de transacciones, revisar el Anexo de Transacciones (incluye, al final, cómo se haría esto mismo con código autoincremental).
// ============== AGREGAR ALUMNO (CON TRANSACCIÓN) ==============
/**
* Agrega un Alumno en dos tablas utilizando una transacción para garantizar
* que ambas inserciones se completen o ninguna lo haga (Atomicidad).
*/
public boolean agregarAlumno(AlumnoVO alumno) {
boolean exito = false;
int codigo = alumno.getCodigo();
// FASE 1: Definir las sentencias SQL
String sql1 = "INSERT INTO personas(codigo, nombre) VALUES (?, ?)";
String sql2 = "INSERT INTO alumnos(codigo, telefono) VALUES (?, ?)";
// FASES 2 y 3: conexión y PreparedStatement en try-with-resources
try (Connection con = Conexion.obtener();
PreparedStatement ps1 = con.prepareStatement(sql1);
PreparedStatement ps2 = con.prepareStatement(sql2)) {
// 1. INICIO DE LA TRANSACCIÓN: Desactivar el guardado automático
con.setAutoCommit(false);
try {
// FASE 3: Asignar parámetros | FASE 4: Ejecutar
ps1.setInt(1, codigo);
ps1.setString(2, alumno.getNombre());
ps1.executeUpdate(); // INSERT en personas
ps2.setInt(1, codigo);
ps2.setString(2, alumno.getTelefono());
ps2.executeUpdate(); // INSERT en alumnos
// 2. CONFIRMACIÓN: ambas inserciones fueron exitosas
con.commit();
exito = true;
System.out.println("Alumno agregado: " + alumno);
} catch (SQLException e) {
// 3. REVERSIÓN: cualquier fallo (por ejemplo, código duplicado) deshace todo
con.rollback();
System.out.println("Error: no se pudo agregar el alumno. Transacción revertida.");
throw e; // lo informa el catch de afuera
}
} catch (SQLException e) {
System.err.println("Error al agregar alumno: " + e.getMessage());
e.printStackTrace();
}
return exito;
}La actualización también modifica datos en las dos tablas, así que aplicamos la misma solución: transacción.
| FASE | Descripción y Notas Clave |
|---|---|
| Transacción | Se usa con.setAutoCommit(false) para garantizar que ambos UPDATE se ejecuten correctamente o ninguno. |
| FASE 1: SQL | Se definen dos UPDATE: sql1 para actualizar el nombre en personas, y sql2 para actualizar el teléfono en alumnos. Ambos usan el codigo como condición (WHERE). |
| FASES 2, 3 y 4 | Se abre la conexión y se preparan y ejecutan ambos PreparedStatement (ps1 y ps2) dentro de un try interno. |
| Confirmación | Si filasPersona > 0 y filasAlumno > 0 (lo que indica que se encontró el registro), se llama a con.commit(). |
| Error/Reversión | Si no se encuentra la persona (0 filas) o si ocurre una SQLException, se llama a con.rollback(). El try interno es necesario porque con solo existe dentro del try-with-resources. |
| Finalización | Los recursos se cierran automáticamente (try-with-resources). |
// ============== ACTUALIZAR ALUMNO (CON TRANSACCIÓN) ==============
public boolean actualizarAlumno(AlumnoVO alumno) {
boolean exito = false;
// FASE 1: Definir las sentencias SQL
String sql1 = "UPDATE personas SET nombre = ? WHERE codigo = ?";
String sql2 = "UPDATE alumnos SET telefono = ? WHERE codigo = ?";
// FASES 2 y 3: conexión y PreparedStatement en try-with-resources
try (Connection con = Conexion.obtener();
PreparedStatement ps1 = con.prepareStatement(sql1);
PreparedStatement ps2 = con.prepareStatement(sql2)) {
con.setAutoCommit(false); // INICIO DE LA TRANSACCIÓN
try {
// FASE 3: Asignar parámetros
ps1.setString(1, alumno.getNombre());
ps1.setInt(2, alumno.getCodigo());
ps2.setString(1, alumno.getTelefono());
ps2.setInt(2, alumno.getCodigo());
// FASE 4: Ejecutar
int filasPersona = ps1.executeUpdate();
int filasAlumno = ps2.executeUpdate();
if (filasPersona > 0 && filasAlumno > 0) {
con.commit(); // ambos UPDATE afectaron filas → confirmar
exito = true;
System.out.println("Alumno actualizado: " + alumno);
} else {
con.rollback(); // el código no existe → revertir
System.out.println("No se encontró alumno con código " + alumno.getCodigo());
}
} catch (SQLException e) {
con.rollback(); // cualquier error → revertir todo
throw e; // lo informa el catch de afuera
}
} catch (SQLException e) {
System.err.println("Error al actualizar alumno: " + e.getMessage());
e.printStackTrace();
}
return exito;
}Esta operación solo necesita eliminar de la tabla personas.
Gracias a la definición de la tabla alumnos en SQL:
FOREIGN KEY (codigo) REFERENCES personas (codigo) ON DELETE CASCADEAl ejecutar:
DELETE FROM personas WHERE codigo = ?;El motor de la base de datos elimina automáticamente la fila asociada en la tabla alumnos (hija). Por lo tanto, no es necesario un DELETE explícito ni una transacción adicional.
// ============== ELIMINAR ALUMNO ==============
public boolean eliminarAlumno(int codigo) {
boolean exito = false;
// FASE 1: Solo eliminamos de personas, el CASCADE eliminará de alumnos automáticamente
String sql = "DELETE FROM personas WHERE codigo = ?";
// FASES 2 y 3: conexión y PreparedStatement en try-with-resources
try (Connection con = Conexion.obtener();
PreparedStatement ps = con.prepareStatement(sql)) {
// FASE 3: Asignar parámetros
ps.setInt(1, codigo);
// FASE 4: Ejecutar la sentencia SQL
int filasAfectadas = ps.executeUpdate();
exito = filasAfectadas > 0;
if (exito) {
System.out.println("Alumno con código " + codigo + " eliminado exitosamente");
} else {
System.out.println("No se encontró alumno con código " + codigo);
}
} catch (SQLException e) {
System.err.println("Error al eliminar alumno: " + e.getMessage());
e.printStackTrace();
}
return exito;
}AlumnoVO · AlumnoDAO · extends PersonaVO · herencia Java · doble INSERT · transacción · setAutoCommit · commit · rollback · JOIN · FK · ON DELETE CASCADE · telefono · tabla alumnos
🧠 Autoevaluación — Herencia en Java y BD
← Introducción: Herencia · 🏠 Inicio · AlumnoControladorServlet →
