Grupo C, Subgrupo C1 · Parte 2: Supuesto Práctico · 17 de junio de 2023
Cada pregunta vale 1 punto. Tiempo estimado de escritura: ~12 min/pregunta
Una universidad pública aprueba el voto por correo en las elecciones a Rector. Se asigna a la unidad de informática el desarrollo de la aplicación. El modelo de datos proporcionado es:
Persona (DNI, NRP, Nombre, Apellidos)
DatosLaborales (NRP, Categoría, Centro)
DatosVotacion (NRP, Mesa, Centro)
SolicitudesVotoPorCorreo (DNI, Fecha, Estado(rechazado;aceptado;espera))
RecibidosVotosPorCorreo (DNI, Fecha)
EntregadosVotosPorCorreo (DNI, Mesa, Fecha)
La herencia es uno de los pilares fundamentales de la Programación Orientada a Objetos (POO). Permite que una clase (clase hija o subclase) adquiera automáticamente los atributos y métodos de otra clase (clase padre o superclase), pudiendo extenderlos o sobreescribirlos (override).
Ventajas:
- Reutilización de código: la clase hija no repite los atributos y métodos ya definidos en el padre.
- Polimorfismo: un objeto de la clase hija puede tratarse como un objeto del tipo padre.
- Mantenimiento: los cambios en la clase padre se propagan automáticamente a las hijas.
La herencia expresa una relación "es un" (IS-A): si ClaseHija extends ClasePadre, decimos que una instancia de ClaseHija "es un" ClasePadre.
Del modelo de datos, Persona y DatosLaborales comparten el campo NRP. Conceptualmente, un trabajador de la universidad es una Persona con datos laborales adicionales. Por tanto, modelamos:
- Persona como clase padre con los atributos comunes (DNI, NRP, Nombre, Apellidos).
- Trabajador como clase hija que hereda de Persona y añade los atributos laborales (Categoría, Centro).
Esta jerarquía tiene sentido semántico: todo trabajador es una persona, pero no toda persona es necesariamente un trabajador (podría haber estudiantes u otros roles).
// Clase padre
public class Persona {
private String dni;
private String nrp;
private String nombre;
private String apellidos;
// Constructor
public Persona(String dni, String nrp, String nombre, String apellidos) {
this.dni = dni;
this.nrp = nrp;
this.nombre = nombre;
this.apellidos = apellidos;
}
// Getters
public String getDni() { return dni; }
public String getNrp() { return nrp; }
public String getNombre() { return nombre; }
public String getApellidos() { return apellidos; }
// Método que puede ser sobreescrito
public String getInfo() {
return "DNI: " + dni + " | Nombre: " + nombre + " " + apellidos;
}
}
// Clase hija: hereda de Persona mediante 'extends'
public class Trabajador extends Persona {
private String categoria;
private String centro;
// Constructor: llama al constructor del padre con 'super'
public Trabajador(String dni, String nrp, String nombre,
String apellidos, String categoria, String centro) {
super(dni, nrp, nombre, apellidos); // Inicializa los atributos heredados
this.categoria = categoria;
this.centro = centro;
}
// Getters propios de la clase hija
public String getCategoria() { return categoria; }
public String getCentro() { return centro; }
// Sobreescritura (override) del método del padre
@Override
public String getInfo() {
return super.getInfo() + " | Categoría: " + categoria + " | Centro: " + centro;
}
}
// Uso
public class Main {
public static void main(String[] args) {
Trabajador t = new Trabajador("12345678A", "NRP001",
"Ana", "García López",
"Profesora Doctora", "ETSI Informática");
System.out.println(t.getInfo());
// Polimorfismo: un Trabajador puede referenciarse como Persona
Persona p = t;
System.out.println(p.getNombre()); // Accede a métodos heredados
}
}
class Persona {
protected string $dni;
protected string $nrp;
protected string $nombre;
protected string $apellidos;
public function __construct(string $dni, string $nrp,
string $nombre, string $apellidos) {
$this->dni = $dni;
$this->nrp = $nrp;
$this->nombre = $nombre;
$this->apellidos = $apellidos;
}
public function getInfo(): string {
return "DNI: {$this->dni} | Nombre: {$this->nombre} {$this->apellidos}";
}
}
class Trabajador extends Persona {
private string $categoria;
private string $centro;
public function __construct(string $dni, string $nrp, string $nombre,
string $apellidos, string $categoria, string $centro) {
parent::__construct($dni, $nrp, $nombre, $apellidos);
$this->categoria = $categoria;
$this->centro = $centro;
}
public function getCategoria(): string { return $this->categoria; }
public function getCentro(): string { return $this->centro; }
public function getInfo(): string {
return parent::getInfo() . " | Categoría: {$this->categoria} | Centro: {$this->centro}";
}
}
Se define una clase principal GestorVotoCorreo que encapsula la lógica de consulta sobre el estado del voto por correo de un trabajador, usando su DNI como identificador.
Para simplificar el modelo, se asume acceso a una base de datos a través de un objeto $db (PDO en PHP). Se trabaja con PHP por claridad y brevedad.
<?php
/**
* Clase que gestiona el estado del proceso de voto por correo
* para un trabajador identificado por su DNI.
*/
class GestorVotoCorreo {
private string $dni;
private PDO $db;
public function __construct(string $dni, PDO $db) {
$this->dni = $dni;
$this->db = $db;
}
/**
* a) Comprueba si el trabajador ha SOLICITADO el voto por correo.
* Existe registro en SolicitudesVotoPorCorreo para ese DNI.
*/
public function haSolicitadoVoto(): bool {
$stmt = $this->db->prepare(
"SELECT COUNT(*) FROM SolicitudesVotoPorCorreo WHERE DNI = ?"
);
$stmt->execute([$this->dni]);
return $stmt->fetchColumn() > 0;
}
/**
* b) Comprueba si se le ha CONCEDIDO el voto por correo.
* La solicitud existe y tiene Estado = 'aceptado'.
*/
public function haSidoConcedidoVoto(): bool {
$stmt = $this->db->prepare(
"SELECT COUNT(*) FROM SolicitudesVotoPorCorreo
WHERE DNI = ? AND Estado = 'aceptado'"
);
$stmt->execute([$this->dni]);
return $stmt->fetchColumn() > 0;
}
/**
* c) Comprueba si el voto ha sido RECIBIDO por correo.
* Existe registro en RecibidosVotosPorCorreo para ese DNI.
*/
public function haRecibidoVoto(): bool {
$stmt = $this->db->prepare(
"SELECT COUNT(*) FROM RecibidosVotosPorCorreo WHERE DNI = ?"
);
$stmt->execute([$this->dni]);
return $stmt->fetchColumn() > 0;
}
/**
* d) Comprueba si el voto ha sido ENTREGADO a la mesa correspondiente.
* Existe registro en EntregadosVotosPorCorreo para ese DNI.
*/
public function haEntregadoVoto(): bool {
$stmt = $this->db->prepare(
"SELECT COUNT(*) FROM EntregadosVotosPorCorreo WHERE DNI = ?"
);
$stmt->execute([$this->dni]);
return $stmt->fetchColumn() > 0;
}
}
// -----------------------------------------------
// Aplicación breve que utiliza las clases anteriores
// y saca la información por consola
// -----------------------------------------------
// Conexión PDO (ejemplo)
$db = new PDO("mysql:host=localhost;dbname=elecciones", "usuario", "password");
$dni = "12345678A";
$gestor = new GestorVotoCorreo($dni, $db);
echo "=== Estado del voto por correo para DNI: $dni ===" . PHP_EOL;
echo "a) ¿Ha solicitado el voto por correo? " . ($gestor->haSolicitadoVoto() ? "SÍ" : "NO") . PHP_EOL;
echo "b) ¿Le ha sido concedido el voto? " . ($gestor->haSidoConcedidoVoto() ? "SÍ" : "NO") . PHP_EOL;
echo "c) ¿Ha sido recibido el voto? " . ($gestor->haRecibidoVoto() ? "SÍ" : "NO") . PHP_EOL;
echo "d) ¿Ha sido entregado a la mesa? " . ($gestor->haEntregadoVoto() ? "SÍ" : "NO") . PHP_EOL;
Para gestionar el escrutinio del voto por correo se necesita modelar:
- Que cada mesa se compone de urnas.
- Que cada urna corresponde a una categoría laboral concreta.
- Que existen 4 categorías laborales con su ponderación en la votación final.
- Que cada urna acumula votos a distintos candidatos.
-- Entidades nuevas o modificadas marcadas con (**)
** Candidato (IdCandidato, Nombre, Apellidos)
** CategoriaLaboral (IdCategoria, Descripcion, Ponderacion)
-- Valores posibles:
-- 1 | "Profesores doctores con vinculación permanente" | 0.51
-- 2 | "Resto del profesorado y personal investigador" | 0.16
-- 3 | "Estudiantes" | 0.24
-- 4 | "Personal de administración y servicios" | 0.09
** Urna (IdUrna, Mesa, Centro, IdCategoria)
-- FK: IdCategoria → CategoriaLaboral
-- Una mesa tiene exactamente una urna por categoría laboral
-- DatosVotacion (NRP, Mesa, Centro) ya relaciona al trabajador con su mesa
** ResultadoUrna (IdUrna, IdCandidato, NumVotos)
-- FK: IdUrna → Urna
-- FK: IdCandidato → Candidato
-- Almacena los votos obtenidos por cada candidato en cada urna
-- Se actualiza conforme se van recibiendo y escrutando los votos por correo
DatosVotacion.Mesa y DatosVotacion.Centro relacionan al trabajador con su mesa de votación.DatosLaborales.Categoría determina en qué urna (y por tanto con qué ponderación) debe contarse su voto.Urna vincula mesa + categoría, permitiendo separar los votos según la categoría del votante.ResultadoUrna acumula el recuento parcial por candidato y urna.CategoriaLaboral (1) ──── (N) Urna (N) ──── (N) ResultadoUrna (N) ──── (1) Candidato
│
Mesa + Centro
│
DatosVotacion
│
DatosLaborales (Categoría)
Se implementa el método de escrutinio en la clase GestorEscrutinio. El cálculo pondera los votos de cada urna según el peso de su categoría y produce el resultado final por candidato.
Lógica del escrutinio ponderado:
Para cada candidato, el resultado ponderado es:
ResultadoPonderado(candidato) = Σ [ (VotosUrna / TotalVotosCategoria) × PonderacionCategoria ]
<?php
class GestorEscrutinio {
private PDO $db;
public function __construct(PDO $db) {
$this->db = $db;
}
/**
* Calcula el resultado parcial del escrutinio conforme se van
* registrando los votos en ResultadoUrna.
* Devuelve un array asociativo [IdCandidato => porcentajePonderado]
*/
public function calcularEscrutinio(): array {
// 1. Obtener todos los candidatos
$candidatos = $this->db->query(
"SELECT IdCandidato FROM Candidato"
)->fetchAll(PDO::FETCH_COLUMN);
$resultados = [];
foreach ($candidatos as $idCandidato) {
$puntuacionTotal = 0.0;
// 2. Para cada categoría laboral, calcular la contribución ponderada
$stmt = $this->db->query(
"SELECT cl.IdCategoria, cl.Ponderacion
FROM CategoriaLaboral cl"
);
$categorias = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($categorias as $categoria) {
$idCategoria = $categoria['IdCategoria'];
$ponderacion = (float) $categoria['Ponderacion'];
// Total de votos emitidos en TODAS las urnas de esta categoría
$stmtTotal = $this->db->prepare(
"SELECT COALESCE(SUM(ru.NumVotos), 0)
FROM ResultadoUrna ru
JOIN Urna u ON ru.IdUrna = u.IdUrna
WHERE u.IdCategoria = ?"
);
$stmtTotal->execute([$idCategoria]);
$totalVotosCategoria = (int) $stmtTotal->fetchColumn();
if ($totalVotosCategoria === 0) {
continue; // Sin votos en esta categoría todavía
}
// Votos del candidato en las urnas de esta categoría
$stmtVotos = $this->db->prepare(
"SELECT COALESCE(SUM(ru.NumVotos), 0)
FROM ResultadoUrna ru
JOIN Urna u ON ru.IdUrna = u.IdUrna
WHERE u.IdCategoria = ? AND ru.IdCandidato = ?"
);
$stmtVotos->execute([$idCategoria, $idCandidato]);
$votosCandidatoCategoria = (int) $stmtVotos->fetchColumn();
// Contribución ponderada de esta categoría
$puntuacionTotal += ($votosCandidatoCategoria / $totalVotosCategoria)
* $ponderacion;
}
$resultados[$idCandidato] = round($puntuacionTotal * 100, 4); // En %
}
// Ordenar de mayor a menor puntuación
arsort($resultados);
return $resultados;
}
/**
* Registra o actualiza el resultado de una urna concreta
* cuando se recibe el recuento de votos de esa urna.
*/
public function registrarResultadoUrna(int $idUrna,
int $idCandidato,
int $numVotos): void {
// INSERT o UPDATE (UPSERT)
$stmt = $this->db->prepare(
"INSERT INTO ResultadoUrna (IdUrna, IdCandidato, NumVotos)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE NumVotos = ?"
);
$stmt->execute([$idUrna, $idCandidato, $numVotos, $numVotos]);
}
}
// Uso de ejemplo
$db = new PDO("mysql:host=localhost;dbname=elecciones", "usuario", "password");
$gestor = new GestorEscrutinio($db);
// Registrar votos de una urna
$gestor->registrarResultadoUrna(idUrna: 1, idCandidato: 1, numVotos: 120);
$gestor->registrarResultadoUrna(idUrna: 1, idCandidato: 2, numVotos: 80);
// Calcular y mostrar escrutinio parcial
$resultados = $gestor->calcularEscrutinio();
echo "=== ESCRUTINIO PARCIAL ===" . PHP_EOL;
foreach ($resultados as $candidato => $puntuacion) {
echo "Candidato $candidato: {$puntuacion}%" . PHP_EOL;
}
Se definen los tres endpoints solicitados siguiendo las convenciones REST:
| Operación | Método HTTP | Endpoint | Descripción |
|---|---|---|---|
| Asignar centro y mesa a un trabajador | PUT |
/api/trabajadores/{nrp}/votacion |
Asigna o actualiza el centro y mesa de votación del trabajador |
| Saber si una persona ha pedido voto por correo | GET |
/api/trabajadores/{dni}/voto-correo |
Devuelve el estado de la solicitud de voto por correo |
| Actualizar estado de la petición de voto por correo | PATCH |
/api/trabajadores/{dni}/voto-correo/estado |
Actualiza el campo Estado en SolicitudesVotoPorCorreo |
Justificación de los métodos HTTP:
- GET: operación de solo lectura, sin efectos secundarios (idempotente).
- PUT: reemplaza/crea el recurso de votación completo para ese NRP.
- PATCH: actualiza parcialmente un recurso existente (solo el campo Estado), más apropiado que PUT para modificaciones parciales.
Códigos de respuesta HTTP relevantes:
- 200 OK: operación exitosa con cuerpo de respuesta.
- 204 No Content: operación exitosa sin cuerpo de respuesta (ej. actualización).
- 400 Bad Request: datos de entrada inválidos.
- 404 Not Found: recurso no encontrado.
- 409 Conflict: conflicto (ej. ya existe una solicitud).
<?php
// Enrutamiento simplificado. En producción se usaría un framework
// como Symfony, Laravel o Slim.
header('Content-Type: application/json');
$method = $_SERVER['REQUEST_METHOD'];
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$parts = explode('/', trim($uri, '/'));
// Ejemplo URI: /api/trabajadores/12345678A/voto-correo
$db = new PDO("mysql:host=localhost;dbname=elecciones", "usuario", "password");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// ------------------------------------------------------------------
// ENDPOINT 1: PUT /api/trabajadores/{nrp}/votacion
// Asigna el centro y mesa de votación a un trabajador
// ------------------------------------------------------------------
if ($method === 'PUT' && isset($parts[2]) && isset($parts[3])
&& $parts[3] === 'votacion') {
$nrp = $parts[2];
$body = json_decode(file_get_contents('php://input'), true);
$mesa = $body['mesa'] ?? null;
$centro = $body['centro'] ?? null;
if (!$mesa || !$centro) {
http_response_code(400);
echo json_encode(['error' => 'Se requieren los campos mesa y centro']);
exit;
}
// Verificar que el trabajador existe
$stmtCheck = $db->prepare("SELECT COUNT(*) FROM DatosLaborales WHERE NRP = ?");
$stmtCheck->execute([$nrp]);
if ($stmtCheck->fetchColumn() == 0) {
http_response_code(404);
echo json_encode(['error' => 'Trabajador no encontrado']);
exit;
}
// UPSERT: insertar o actualizar DatosVotacion
$stmt = $db->prepare(
"INSERT INTO DatosVotacion (NRP, Mesa, Centro) VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE Mesa = ?, Centro = ?"
);
$stmt->execute([$nrp, $mesa, $centro, $mesa, $centro]);
http_response_code(204);
exit;
}
// ------------------------------------------------------------------
// ENDPOINT 2: GET /api/trabajadores/{dni}/voto-correo
// Devuelve si la persona ha pedido el voto por correo y su estado
// ------------------------------------------------------------------
if ($method === 'GET' && isset($parts[2]) && isset($parts[3])
&& $parts[3] === 'voto-correo' && !isset($parts[4])) {
$dni = $parts[2];
$stmt = $db->prepare(
"SELECT DNI, Fecha, Estado FROM SolicitudesVotoPorCorreo WHERE DNI = ?"
);
$stmt->execute([$dni]);
$solicitud = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$solicitud) {
http_response_code(200);
echo json_encode(['solicitado' => false]);
exit;
}
http_response_code(200);
echo json_encode([
'solicitado' => true,
'fecha' => $solicitud['Fecha'],
'estado' => $solicitud['Estado']
]);
exit;
}
// ------------------------------------------------------------------
// ENDPOINT 3: PATCH /api/trabajadores/{dni}/voto-correo/estado
// Actualiza el estado de la solicitud de voto por correo
// ------------------------------------------------------------------
if ($method === 'PATCH' && isset($parts[4]) && $parts[4] === 'estado') {
$dni = $parts[2];
$body = json_decode(file_get_contents('php://input'), true);
$nuevoEstado = $body['estado'] ?? null;
$estadosValidos = ['aceptado', 'rechazado', 'espera'];
if (!in_array($nuevoEstado, $estadosValidos)) {
http_response_code(400);
echo json_encode(['error' => 'Estado no válido. Use: aceptado, rechazado, espera']);
exit;
}
$stmt = $db->prepare(
"UPDATE SolicitudesVotoPorCorreo SET Estado = ? WHERE DNI = ?"
);
$stmt->execute([$nuevoEstado, $dni]);
if ($stmt->rowCount() === 0) {
http_response_code(404);
echo json_encode(['error' => 'Solicitud no encontrada para ese DNI']);
exit;
}
http_response_code(204);
exit;
}
// Ruta no encontrada
http_response_code(404);
echo json_encode(['error' => 'Endpoint no encontrado']);
{
"trabajador": {
"dni": "12345678A",
"nrp": "NRP001",
"nombre": "Ana",
"apellidos": "García López",
"datosLaborales": {
"categoria": "Profesores doctores con vinculación permanente",
"centro": "ETSI Informática"
}
}
}
Justificación de la estructura: Se anida datosLaborales dentro del objeto trabajador porque los datos laborales no tienen entidad propia desde el punto de vista del cliente de la API: son atributos complementarios del mismo recurso. El NRP es la clave que une ambas tablas (Persona y DatosLaborales) y no necesita repetirse en el JSON anidado al estar ya en el nivel raíz.
Se definen dos clases que reflejan la estructura JSON anidada, usando el patrón habitual con Jackson o Gson.
import com.fasterxml.jackson.annotation.JsonProperty;
// Clase interna: representa datosLaborales
public class DatosLaboralesDTO {
@JsonProperty("categoria")
private String categoria;
@JsonProperty("centro")
private String centro;
// Getters y setters
public String getCategoria() { return categoria; }
public void setCategoria(String c) { this.categoria = c; }
public String getCentro() { return centro; }
public void setCentro(String c) { this.centro = c; }
}
// Clase interna: representa el objeto trabajador
public class TrabajadorDTO {
@JsonProperty("dni")
private String dni;
@JsonProperty("nrp")
private String nrp;
@JsonProperty("nombre")
private String nombre;
@JsonProperty("apellidos")
private String apellidos;
@JsonProperty("datosLaborales")
private DatosLaboralesDTO datosLaborales;
// Getters y setters
public String getDni() { return dni; }
public void setDni(String d) { this.dni = d; }
public String getNrp() { return nrp; }
public void setNrp(String n) { this.nrp = n; }
public String getNombre() { return nombre; }
public void setNombre(String n) { this.nombre = n; }
public String getApellidos() { return apellidos; }
public void setApellidos(String a) { this.apellidos = a; }
public DatosLaboralesDTO getDatosLaborales() { return datosLaborales; }
public void setDatosLaborales(DatosLaboralesDTO d){ this.datosLaborales = d; }
}
// Clase raíz: envuelve el objeto trabajador (refleja la clave raíz del JSON)
public class RespuestaTrabajadorDTO {
@JsonProperty("trabajador")
private TrabajadorDTO trabajador;
public TrabajadorDTO getTrabajador() { return trabajador; }
public void setTrabajador(TrabajadorDTO t) { this.trabajador = t; }
}
// Uso con Jackson
// ObjectMapper mapper = new ObjectMapper();
// RespuestaTrabajadorDTO respuesta = mapper.readValue(jsonString, RespuestaTrabajadorDTO.class);
// String nombre = respuesta.getTrabajador().getNombre();
REST (Representational State Transfer) es un estilo arquitectónico (no un protocolo) para diseñar servicios web, basado en los principios de la web y el protocolo HTTP.
Sus características principales son:
/api/trabajadores/123). Las operaciones se expresan con los verbos HTTP estándar: GET, POST, PUT, PATCH, DELETE.SOAP (Simple Object Access Protocol) es un protocolo estándar basado en XML para el intercambio de mensajes estructurados entre aplicaciones, definido por la W3C.
Sus características principales son:
<Envelope>) con cabecera (<Header>) y cuerpo (<Body>), siempre en XML.| Aspecto | REST | SOAP |
|---|---|---|
| Naturaleza | Estilo arquitectónico | Protocolo estándar |
| Formato de mensajes | JSON (habitual), XML, otros | XML obligatorio |
| Contrato formal | Opcional (OpenAPI/Swagger) | WSDL obligatorio |
| Verbos/operaciones | Usa verbos HTTP (GET, POST, PUT…) | Solo POST; las operaciones van en el cuerpo XML |
| Rendimiento | Mayor (JSON más ligero que XML) | Menor (overhead XML + cabeceras) |
| Estado | Sin estado (stateless) | Puede mantener estado |
| Seguridad | HTTPS + tokens (JWT, OAuth) | WS-Security (estándar integrado) |
| Facilidad de uso | Más sencillo, ampliamente adoptado en web | Más complejo; habitual en entornos empresariales |
| Casos de uso típicos | APIs web públicas, apps móviles, microservicios | Servicios bancarios, ERP, entornos corporativos que requieren contratos estrictos |
Un ORM (Object-Relational Mapper) como Hibernate (Java) o Doctrine (PHP) es una capa de abstracción que mapea objetos de programación a tablas relacionales, permitiendo trabajar con la base de datos usando el lenguaje orientado a objetos sin escribir SQL directamente.
1. Abstracción del SQL y portabilidad
// Con Hibernate (ORM) — sin SQL
Persona p = session.get(Persona.class, 1L);
// Sin ORM — SQL directo
PreparedStatement ps = conn.prepareStatement("SELECT * FROM Persona WHERE id = ?");
ps.setLong(1, 1L);
ResultSet rs = ps.executeQuery();
2. Productividad y mantenimiento
3. Gestión automática de relaciones, caché y transacciones
@OneToMany, @ManyToMany). El ORM ofrece caché de primer y segundo nivel para reducir consultas redundantes. La gestión de transacciones se integra de forma declarativa.Consideración adicional — Rendimiento:
El acceso directo a base de datos puede ser más eficiente para consultas muy complejas o de alto rendimiento, ya que el desarrollador tiene control total sobre el SQL generado. Los ORM pueden generar consultas subóptimas (problema N+1) si no se configuran correctamente, aunque ofrecen mecanismos como lazy loading, eager loading y la caché para mitigarlo.
Hibernate (con su herramienta de generación de esquemas, configurada mediante la propiedad hibernate.hbm2ddl.auto) leerá las anotaciones de la clase Persona y generará automáticamente la instrucción SQL CREATE TABLE correspondiente en la base de datos configurada.
El resultado sería equivalente a ejecutar el siguiente DDL:
CREATE TABLE Persona (
id BIGINT NOT NULL AUTO_INCREMENT, -- @Id + @GeneratedValue IDENTITY
DNI VARCHAR(255), -- @Column(name = "DNI")
NRP VARCHAR(255), -- @Column(name = "NRP")
Nombre VARCHAR(255), -- @Column(name = "Nombre")
Apellidos VARCHAR(255), -- @Column(name = "Apellidos")
PRIMARY KEY (id)
);
La tabla se crea con el nombre Persona (indicado en @Table), con una clave primaria autoincremental id y una columna por cada campo anotado con @Column.
@Entity
Indica a Hibernate que esta clase Java es una entidad persistente, es decir, que sus instancias deben ser gestionadas por el ORM y mapeadas a filas de una tabla en la base de datos. Es la anotación fundamental que activa el mapeo ORM para esa clase.
@Table(name = "Persona")
Especifica el nombre de la tabla en la base de datos a la que se mapea esta entidad. Si se omite, Hibernate usa el nombre de la clase Java como nombre de tabla por defecto. Con esta anotación se puede hacer que el nombre de la clase Java (Persona) y el nombre de la tabla (Persona) sean independientes.
@Id
Marca el campo como clave primaria de la entidad y de la tabla. Hibernate usará este campo para identificar unívocamente cada instancia y para las operaciones de búsqueda, actualización y borrado.
@GeneratedValue(strategy = GenerationType.IDENTITY)
Define la estrategia de generación del valor de la clave primaria. Con IDENTITY, Hibernate delega la generación del valor en la propia base de datos, utilizando la funcionalidad de autoincremento (AUTO_INCREMENT en MySQL, SERIAL en PostgreSQL). Otras estrategias posibles son SEQUENCE, TABLE y AUTO.
@Column(name = "DNI") (y análogamente las demás)
Mapea el atributo Java al nombre de columna especificado en la tabla. Si los nombres de atributo y columna coinciden, esta anotación puede omitirse; pero al especificarla se puede usar un nombre de columna diferente al del campo Java, o configurar restricciones adicionales como nullable = false, length = 9, unique = true, etc.
insertarPersona con HibernateEl método insertarPersona debe crear un objeto Persona, asignarle los valores recibidos como parámetros, y persistirlo en la base de datos usando la sesión de Hibernate ya abierta.
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import paquete.clase.Persona;
import paquete.util.HibernateUtil;
public class PersonaDAO {
public void insertarPersona(String dni, String nrp,
String nombre, String apellidos) {
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
try (Session session = sessionFactory.openSession()) {
Transaction transaction = session.beginTransaction();
// ── CÓDIGO QUE SE PIDE IMPLEMENTAR ──────────────────────
// 1. Crear una nueva instancia de la entidad Persona
Persona persona = new Persona();
// 2. Asignar los valores recibidos como parámetros
persona.setDni(dni);
persona.setNrp(nrp);
persona.setNombre(nombre);
persona.setApellidos(apellidos);
// 3. Persistir el objeto en la base de datos
// session.save() o session.persist() insertan una nueva fila
session.save(persona);
// ── FIN DEL CÓDIGO IMPLEMENTADO ──────────────────────────
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
// En una implementación real, se haría rollback explícito:
// if (transaction != null) transaction.rollback();
}
}
}
Explicación de los tres pasos implementados:
new Persona(): Se instancia un objeto Java de tipo Persona (entidad mapeada). En este punto no hay ninguna fila en la base de datos; el objeto está en estado transient (transitorio, no gestionado por Hibernate).
setDni(), setNrp(), setNombre(), setApellidos(): Se asignan los valores a los atributos del objeto mediante los setters generados. El campo id no se asigna porque es autoincremental (@GeneratedValue IDENTITY); Hibernate lo obtendrá de la base de datos tras la inserción.
session.save(persona): Hibernate transiciona el objeto al estado persistent (gestionado) y genera y ejecuta el INSERT INTO Persona (DNI, NRP, Nombre, Apellidos) VALUES (?, ?, ?, ?) correspondiente. El id generado por la base de datos se asigna automáticamente al campo id del objeto Java. El transaction.commit() ya estaba en el código base y consolida la transacción en la base de datos.
Nota: En versiones modernas de Hibernate (≥ 5.7 / JPA 2.2) se prefiere
session.persist(persona)sobresession.save(), ya quepersist()es el método estándar JPA. Ambos son válidos para este ejercicio.
Fin del examen · UPM 2023 · Bloque III: Desarrollo