Proyecto compartido. En el taller de Intermodular que abre esta semana has trabajado preparar la transición a persistencia. En Servidor continúas la implementación del mismo producto.
Se explica
25 minutos · explicación y demostración
El almacenamiento actual desaparece al reiniciar. PostgreSQL es un servidor de bases de datos que conserva la información fuera del proceso Java. JPA define cómo relacionar objetos con tablas; Hibernate implementa ese trabajo y Spring Data simplificará los repositorios. Hoy prepararás la conexión, antes de guardar tu primera entidad.
El día en que reiniciar duele
Durante cuatro unidades hemos fingido que un ArrayList dentro de una clase de repositorio era suficiente. Servía para validar los endpoints HTTP, comprobar los códigos de estado en Postman y escribir tests unitarios de las reglas de negocio.
Arrastraba, sin embargo, un defecto conocido desde la UD1: cada vez que el servidor se reinicia, el estado se evapora por completo.
La solución inmediata que a todo programador principiante se le pasa por la cabeza es volcar los objetos en un archivo JSON o en un fichero binario en el disco. Parece sencillo hasta que te haces tres preguntas:
- ¿Qué ocurre si dos peticiones HTTP intentan escribir en el archivo en el mismo milisegundo?
- Si la aplicación cae a mitad de una escritura, ¿cómo evitas que el archivo quede corrupto e ilegible?
- Para buscar una tarea por título entre dos millones de registros, ¿vas a cargar dos millones de objetos en la memoria RAM para filtrarlos con un bucle?
Por eso no guardamos archivos a mano: delegamos en un Sistema Gestor de Bases de Datos Relacionales (RDBMS) como PostgreSQL. Un sistema independiente, optimizado durante décadas, capaz de gestionar accesos concurrentes sin corromper datos, con índices para búsquedas en microsegundos y garantías matemáticas de atomicidad y durabilidad.
La base de datos no es tu disco duro particular
Una base de datos relacional no es un trastero donde volcar la memoria de Java. Es un motor de datos con su propio ciclo de vida, sus propios tipos, su propio lenguaje (SQL) y sus propias reglas de integridad.
Tu aplicación es solo un cliente más conectándose a través de la red. Si mañana otra aplicación escrita en Python o Node.js necesita consultar los proyectos, hablará con las mismas tablas sin saber nada de tus clases Java.
Dos mundos que chocan: el desajuste de impedancia
Traspasar información entre Java y una base de datos relacional no es una simple copia de campos. Es conectar dos paradigmas concebidos bajo premisas incompatibles. En ingeniería de software este choque se conoce como el desajuste de impedancia objeto-relacional (Object-Relational Impedance Mismatch).
- Objetos (Java): grafos de memoria, referencias navegables, herencia, encapsulación y tipos ricos
- Tablas (SQL): conjuntos bidimensionales, relaciones por clave foránea, tipos escalares y operaciones basadas en álgebra relacional
Las diferencias se manifiestan en cuatro áreas críticas:
| Dimensión | En el mundo de los objetos (Java) | En el mundo relacional (SQL) |
|---|---|---|
| Identidad | Dos objetos son iguales por referencia de memoria (==) o por estado semántico (equals()). |
Dos filas son iguales si comparten el mismo valor en su clave primaria (PK). |
| Relaciones | Direccionales (tarea.getProyecto()). Para que sea navegable en ambos sentidos, necesitas dos punteros independientes. |
Bidireccionales por naturaleza: una clave foránea (FK) permite consultar en ambas direcciones mediante JOIN. |
| Navegación | Recorrer un grafo de punteros en memoria: tarea.getProyecto().getCliente().getNombre(). |
Realizar operaciones de conjunto (SELECT ... JOIN ... WHERE ...) proyectando datos escalares. |
| Granularidad | Frecuente crear tipos ricos (Email, Dinero, Direccion) dentro de una clase. |
Todo se aplana a columnas primitivas (VARCHAR, NUMERIC, INTEGER, BOOLEAN). |
De dónde venimos: JDBC, Hibernate y JPA
Para salvar ese abismo, el ecosistema Java ha atravesado tres etapas bien diferenciadas.
En los inicios de Java la única opción estándar era JDBC (Java Database Connectivity). Con JDBC eres tú quien escribe las sentencias SQL en cadenas de texto, gestiona las conexiones y traduce fila a fila cada resultado:
// Lo que había que escribir con JDBC puro para guardar una tarea
String sql = "INSERT INTO tareas (titulo, prioridad, completada) VALUES (?, ?, ?)";
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, tarea.getTitulo());
ps.setString(2, tarea.getPrioridad());
ps.setBoolean(3, tarea.isCompletada());
ps.executeUpdate();
try (ResultSet rs = ps.getGeneratedKeys()) {
if (rs.next()) {
tarea.setId(rs.getLong(1));
}
}
}
Funciona, pero el coste es altísimo: código repetitivo, propenso a errores tipográficos que el compilador no detecta, y la necesidad de escribir manualmente la traducción de cada objeto que entra o sale de la base de datos.
A principios de los 2000 surgió Hibernate, un Object-Relational Mapper (ORM). Su promesa: tú defines tus clases Java, configuras un mapa que indique qué clase corresponde a qué tabla y qué atributo a qué columna, y el ORM se encarga de generar el SQL, ejecutarlo y devolver objetos ya instanciados.
Como cada fabricante de ORM inventaba sus propias anotaciones y métodos, la comunidad estandarizó la solución bajo una especificación oficial: JPA (originalmente Java Persistence API, hoy Jakarta Persistence).
Especificación frente a implementación
JPA constituye una especificación, no una librería ejecutable. Define interfaces (EntityManager, EntityTransaction) y anotaciones (@Entity, @Table, @Id, @Column).
Hibernate es la implementación real que contiene el código que ejecuta esas interfaces. Si usas JPA, tu código depende de la norma estándar, no de una librería particular, aunque por debajo el motor que haga el trabajo pesado sea Hibernate.
- Spring Data JPA
- JPA (Jakarta)
- Hibernate (ORM)
- Driver JDBC
- PostgreSQL
Spring Data JPA se sitúa en la cúspide: nos permitirá declarar interfaces sin escribir ni una sola línea de implementación para las operaciones comunes.
La anatomía del acceso a datos
En el trabajo anterior dejamos claro que PostgreSQL es un proceso independiente que se ejecuta en su propio espacio de memoria (o en un contenedor) y escucha peticiones a través de la red, habitualmente en el puerto TCP 5432.
Para que un método de tu repositorio pueda enviar una sentencia SQL y recibir registros, deben intervenir varios componentes en cadena:
- Tu Servicio
- JPA / Hibernate
- HikariCP (Pool)
- Driver JDBC
- PostgreSQL (TCP 5432)
- Tu Servicio y Repositorio: trabajan con objetos del dominio (
Tarea,Proyecto) e invocan métodos Java. - JPA y Hibernate: traducen las intenciones de tu código en sentencias SQL estándar y dialecto específico de PostgreSQL.
- DataSource y HikariCP: gestionan el estanque (pool) de conexiones abiertas. Abrir una conexión TCP con autenticación y cifrado SSL cuesta entre 20 y 80 milisegundos. Si lo hiciéramos en cada petición HTTP, la API colapsaría con unos pocos usuarios. HikariCP mantiene un conjunto de conexiones calientes listas para prestar y recuperar en microsegundos.
- Driver JDBC de PostgreSQL: la librería (
org.postgresql.Driver) que sabe hablar el protocolo binario nativo que entiende el servidor PostgreSQL a través del cable de red. - Servidor PostgreSQL: ejecuta el SQL, accede a los ficheros del sistema de archivos y devuelve los bloques de datos.
Se trabaja
140 minutos · implementación guiada sobre el proyecto propio
Paso 1 · Retomar el proyecto y preparar la comprobación
- Arranca el proyecto y reproduce la pérdida de un registro tras reiniciar. Abre la implementación del repositorio en memoria y
application.properties. - Comprueba qué PostgreSQL usarás: instalación del aula o contenedor del procedimiento de esta sesión. Necesitarás dirección, puerto, base de datos, usuario y contraseña de desarrollo.
- Abre
pom.xmly prepara las dependencias que se indican más abajo. Registra los nombres de las variables de configuración, sin subir sus contraseñas al repositorio.
Paso 2 · Relacionar el código de persistencia con el SQL que ejecuta
Aquí aparece la gran mentira que muchos cursos y tutoriales transmiten: «Como tenemos un ORM, ya no necesitas saber SQL».
Es exactamente lo contrario.
Joel Spolsky formuló en 2002 la célebre Ley de las abstracciones con fugas (Law of Leaky Abstractions): todas las abstracciones no triviales tienen fugas en algún momento. El ORM intenta ocultar que debajo hay un motor relacional, pero esa ilusión se rompe rápidamente:
- Si no entiendes cómo traduce Hibernate una relación, generarás una consulta inicial seguida de 50 consultas individuales para cargar detalles (el demoledor problema N+1 que resolveremos en la sesión 26).
- Si no entiendes de claves primarias y secuencias, bloquearás la base de datos o harás que las inserciones masivas vayan a paso de tortuga.
- Si ignoras cómo funcionan las transacciones y los bloqueos, dos usuarios simultáneos sobrescribirán datos sin que nadie se entere.
El ORM te quita el trabajo aburrido de teclear rs.getString("titulo"), pero tú sigues siendo el responsable de qué SQL se ejecuta en tu servidor.
Paso 3 · El mapa de traducción: de Java a PostgreSQL
Abre tu modelo actual y completa la tabla antes de modificarlo. El esquema muestra Long como tipo de los identificadores que utilizaremos con PostgreSQL; si aún tienes int o Integer, la migración se realiza en la sesión 20 en modelo, DTO, servicio, controlador y pruebas. En este paso solo diseñas la correspondencia. Guarda el borrador SQL en docs/diseno/schema-inicial.sql: aún no debe ejecutarse automáticamente al arrancar Spring.
Miremos nuestra clase Java de partida:
public class Tarea {
private Long id;
private String titulo;
private String prioridad;
private boolean completada;
private Long proyectoId;
}
Para cada atributo debemos tomar tres decisiones:
- ¿Qué tipo de dato SQL en PostgreSQL puede almacenar ese valor sin pérdida ni desperdicio?
- ¿Qué restricciones de integridad (
NULL,UNIQUE,CHECK) debe imponer la base de datos? - ¿Cómo se genera la identidad de cada registro?
| Atributo Java | Tipo Java | Columna SQL | Tipo PostgreSQL | Restricción / Propósito |
|---|---|---|---|---|
id |
Long |
id |
BIGINT |
PRIMARY KEY GENERATED ALWAYS AS IDENTITY |
titulo |
String |
titulo |
VARCHAR(120) |
NOT NULL (no se admiten tareas sin nombre) |
prioridad |
String |
prioridad |
VARCHAR(20) |
NOT NULL CHECK (prioridad IN ('baja', 'media', 'alta')) |
completada |
boolean |
completada |
BOOLEAN |
NOT NULL DEFAULT FALSE |
proyectoId |
Long |
proyecto_id |
BIGINT |
REFERENCES proyectos(id) (clave foránea) |
- Por qué
LongyBIGINTen lugar deint - Un
INTEGERde 32 bits permite unos dos mil millones de identificadores positivos. En aplicaciones reales con alto volumen de registros esa cifra se alcanza antes de lo que parece. Pasar deINTEGERaBIGINTen una base de datos en producción con millones de filas exige reconstruir índices y tablas enteras con cortes de servicio. UsarBIGINTdesde el primer día cuesta cero y previene una migración traumática. - Por qué la restricción vive en la base de datos y no solo en el DTO
- En la UD3 validamos en el DTO con
@NotBlanky@Size. Ese es el control de entrada en la capa HTTP. La base de datos constituye la última línea de defensa: si mañana entra un script de migración, una carga desde CSV o una consulta manual por consola SQL, las restricciones de la tabla garantizan que ningún dato corrupto quede almacenado.
Paso 4 · El esquema de proyectos y usuarios
Diseña en un archivo schema.sql (o directamente en tu consola SQL) la definición completa para las tablas proyectos y usuarios.
Tu clase Proyecto tiene los campos id, nombre, descripcion, activo y fechaCreacion (LocalDate).
- Escribe la sentencia
CREATE TABLEcon los tipos de PostgreSQL correspondientes. - Asegúrate de que el nombre del proyecto sea obligatorio y único en el sistema.
- Define el valor por defecto para
activo.
Diseña la tabla para almacenar los miembros del equipo:
- Campos:
id,email,nombreCompleto,rol(ADMIN,DEV,VIEWER),fechaAlta. - ¿Qué restricción fundamental debe tener la columna
email? - ¿Qué tipo de dato de PostgreSQL se adapta a
fechaAltasi necesitamos guardar también la hora y minuto exactos?
Configurar PostgreSQL y Spring
Paso 5 · Levantar la base de datos PostgreSQL
Elige una sola forma de ejecutar PostgreSQL. Si utilizas Docker, abre primero Docker Desktop y comprueba docker version en una terminal; Docker ejecuta el servicio en un contenedor y tu programa Java se conecta a él por el puerto publicado. Crea docker-compose.yml en la raíz del repositorio con el bloque indicado y ejecuta docker compose up -d desde esa carpeta; docker compose ps debe mostrar el servicio activo. En los siguientes arranques reutiliza ese contenedor y su volumen. Si utilizas PostgreSQL instalado en el aula, prepara la base de datos desde su cliente SQL y no ejecutes además Docker sobre el mismo puerto.
Es la opción más limpia porque no instala servicios permanentes en tu sistema operativo, no ensucia el registro y garantiza que todo el equipo trabaja con la misma versión exacta:
docker run --name gestor-postgres \
-e POSTGRES_DB=gestor_db \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-p 5432:5432 \
-d postgres:16-alpine
O si prefieres definirlo en un archivo docker-compose.yml en la raíz de tu proyecto:
services:
database:
image: postgres:16-alpine
container_name: gestor-postgres
environment:
POSTGRES_DB: gestor_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
volumes:
- datos_postgres:/var/lib/postgresql/data
volumes:
datos_postgres:
Basta con ejecutar docker compose up -d para tener la base de datos lista en dos segundos.
Si tienes PostgreSQL instalado como servicio en tu máquina (Windows, macOS o Linux), entra en el cliente de línea de comandos psql o abre tu herramienta de administración (como pgAdmin o DBeaver) y crea la base de datos para la aplicación:
CREATE DATABASE gestor_db;
Asegúrate de recordar el usuario, la contraseña y el puerto que configuraste durante la instalación (el estándar es 5432).
Paso 6 · Declarar las dependencias en el pom.xml
Abre el archivo pom.xml de tu proyecto Spring Boot y añade las dos dependencias necesarias dentro del bloque <dependencies>:
<!-- Spring Data JPA: trae Hibernate, HikariCP y la API de persistencia -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Driver JDBC oficial de PostgreSQL -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
- Por qué el driver tiene
<scope>runtime</scope> - Porque tu código Java jamás debe importar clases de
org.postgresql.*. Tu código se compila contra las interfaces universales de JDBC y JPA. La implementación concreta del driver solo se necesita en tiempo de ejecución, cuando la aplicación arranca y necesita abrir los sockets de red contra PostgreSQL. - Qué nos ahorra
spring-boot-starter-data-jpa - Incluye de forma transitiva Hibernate Core, Jakarta Persistence API, el pool de conexiones HikariCP y toda la infraestructura de Spring Data. No necesitas gestionar versiones individuales: el gestor de dependencias de Spring Boot garantiza que todas las piezas sean compatibles entre sí.
Paso 7 · Configurar conexión y credenciales externas en application.properties
Con la base de datos encendida, añade las propiedades al archivo existente conservando nombre y puerto de la aplicación. spring.datasource.url indica dirección, puerto y nombre de base de datos, no una ruta de archivo. Usa exactamente el usuario y contraseña con los que acabas de conectarte. Para configuración externa en PowerShell, asigna por ejemplo $env:DB_PASSWORD="tu valor local" en la terminal que arrancará Java; en Linux/macOS utiliza export DB_PASSWORD='tu valor local'. Un archivo .env no lo lee Spring Boot automáticamente: documenta qué herramienta lo carga si decides usarlo.
# ------------------------------------------------------------------------------
# Conexión a la base de datos PostgreSQL
# ------------------------------------------------------------------------------
spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:gestor_db}
spring.datasource.username=${DB_USER:postgres}
spring.datasource.password=${DB_PASSWORD:postgres}
spring.datasource.driver-class-name=org.postgresql.Driver
# ------------------------------------------------------------------------------
# Pool de conexiones HikariCP
# ------------------------------------------------------------------------------
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=20000
# ------------------------------------------------------------------------------
# JPA y Hibernate
# ------------------------------------------------------------------------------
# Estrategia de creación del esquema (update para desarrollo local)
spring.jpa.hibernate.ddl-auto=update
# Mostrar las consultas SQL en la consola formateadas
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.highlight_sql=true
# Desactivar Open Session In View para evitar consultas perezosas fuera de la transacción
spring.jpa.open-in-view=false
Detengámonos en tres decisiones de configuración críticas:
La sintaxis ${VARIABLE:valor_por_defecto}
Fíjate en ${DB_PASSWORD:postgres}. Esta directiva le dice a Spring: «Si existe una variable de entorno llamada DB_PASSWORD en el sistema operativo, usa su valor; si no existe, usa “postgres” como alternativa local».
Esto permite que en tu máquina de desarrollo el proyecto arranque sin configuraciones manuales complejas, mientras que en un servidor de despliegue o en GitHub Actions se inyecte la contraseña real de producción desde el entorno, sin escribir secretos en archivos rastreados por Git.
El parámetro ddl-auto: poderes y peligros
La propiedad spring.jpa.hibernate.ddl-auto controla qué hace Hibernate con la estructura física de la base de datos al arrancar la aplicación:
| Valor | Qué hace al iniciar | Cuándo se utiliza |
|---|---|---|
none |
No toca la base de datos. Si las tablas no existen, fallará. | Producción. |
validate |
Comprueba que las tablas y columnas coinciden con tus entidades @Entity. Si algo falta o difiere, aborta el arranque. |
Entornos de integración y producción. |
update |
Compara las entidades con las tablas. Si falta una tabla o una columna, la crea. Nunca borra columnas ni tablas existentes. | Desarrollo inicial y talleres. |
create-drop |
Borra todas las tablas al arrancar, crea el esquema desde cero y lo borra todo al apagar la aplicación. | Tests automatizados. |
La regla de oro de ddl-auto
En este taller usaremos update para comprobar de forma inmediata cómo nuestras entidades crean tablas en PostgreSQL sin escribir DDL a mano.
En un entorno profesional real, ddl-auto jamás se pone en update en producción. Un cambio involuntario de tipo de dato o una mala interpretación del ORM podría bloquear tablas o alterar esquemas en caliente. En producción el esquema se gestiona con herramientas de migración versionadas (como Flyway o Liquibase) y ddl-auto=validate.
open-in-view=false
Por defecto Spring Boot activa Open Session In View (OSIV). Es un mecanismo que mantiene la conexión a la base de datos abierta durante todo el ciclo de vida de la petición HTTP, incluso mientras se renderiza el JSON en el controlador. Aunque parece cómodo para novatos, es un antipatrón que monopoliza conexiones del pool y permite que ocurran consultas inesperadas en la capa web. Ponerlo a false fuerza a que todo acceso a datos termine en la capa del servicio.
Paso 8 · Arrancar y saber leer los logs
Ejecuta tu aplicación Spring Boot desde el IDE o con ./mvnw spring-boot:run.
No te limites a mirar si sale la palabra STARTED. Aprende a leer la secuencia de inicialización del subsistema de datos en la consola:
2026-09-02T10:15:30.102+02:00 INFO 12345 --- [gestor] [main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2026-09-02T10:15:30.340+02:00 INFO 12345 --- [gestor] [main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@5c80cf32
2026-09-02T10:15:30.342+02:00 INFO 12345 --- [gestor] [main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2026-09-02T10:15:30.510+02:00 INFO 12345 --- [gestor] [main] org.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2026-09-02T10:15:30.590+02:00 INFO 12345 --- [gestor] [main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect
2026-09-02T10:15:31.850+02:00 INFO 12345 --- [gestor] [main] com.ejemplo.gestor.GestorApplication : Started GestorApplication in 2.451 seconds (process running for 2.890)
Fíjate en los tres hitos clave:
HikariPool-1 - Added connection: el pool ha podido negociar un socket TCP con PostgreSQL y autenticarse con éxito.Using dialect: org.hibernate.dialect.PostgreSQLDialect: Hibernate ha detectado que habla con PostgreSQL y adaptará su SQL a sus tipos específicos (BIGINT,BOOLEAN, secuencias).Started GestorApplication: la aplicación está viva y conectada a la base de datos.
Paso 9 · Laboratorio de diagnóstico · Los tres fallos inevitables
En algún momento de este curso tu aplicación no arrancará por culpa de la base de datos. Cuando eso ocurra, no reinicies a ciegas: busca en el stack trace la última línea que empiece por Caused by:.
Vamos a provocar intencionadamente los tres errores más comunes para aprender a reconocerlos:
Detén el contenedor o servicio de PostgreSQL e intenta arrancar Spring Boot. La aplicación fallará con un mensaje similar a:
Caused by: java.net.ConnectException: Connection refused: no further information
Caused by: org.postgresql.util.PSQLException: Connection to localhost:5432 refused.
Diagnóstico: tu código está bien, pero no hay ningún proceso escuchando en la IP y puerto especificados. Comprueba que el contenedor de Docker está levantado (docker ps) o que el servicio de PostgreSQL está iniciado.
Cambia temporalmente la propiedad a spring.datasource.password=password_inventada y arranca:
Caused by: org.postgresql.util.PSQLException: FATAL: password authentication failed for user "postgres"
Diagnóstico: la red funciona y PostgreSQL responde, pero las credenciales han sido rechazadas. Revisa mayúsculas, espacios en blanco o si el usuario configurado tiene permisos de conexión.
Cambia la URL a jdbc:postgresql://localhost:5432/base_que_no_existe:
Caused by: org.postgresql.util.PSQLException: FATAL: database "base_que_no_existe" does not exist
Diagnóstico: PostgreSQL no crea la base de datos automáticamente por conectarse a ella. Debe existir previamente antes de que Spring Boot intente iniciar el pool.
Paso 10 · Conectar un cliente SQL externo
Configura el acceso a PostgreSQL desde una herramienta de cliente gráfico (DBeaver, IntelliJ Database Tools, pgAdmin o la extensión de PostgreSQL para VS Code) y comprueba la salud del servidor.
- Abre tu cliente y crea una nueva conexión seleccionando el driver PostgreSQL.
- Introduce los mismos parámetros configurados en tu
application.properties:- Host:
localhost - Puerto:
5432 - Base de datos:
gestor_db - Usuario:
postgres - Contraseña:
postgres(o la que hayas definido)
- Host:
- Pulsa en Test Connection y confirma que conecta.
- Abre una ventana de consola SQL y ejecuta:
SELECT current_database(), current_user, version();
Comprueba que devuelve una fila con el nombre de tu base de datos y la versión del motor. Esta consola será tu ventana de verificación durante las próximas tres semanas para comprobar qué hace Hibernate por debajo.
Paso 11 · Comprobar y registrar el resultado del proyecto
- Arranca el backend y verifica en los logs que se conecta a la base de datos elegida. Confirma la misma conexión desde el cliente SQL.
- Provoca por separado un puerto incorrecto y unas credenciales incorrectas en tu entorno local; identifica sus mensajes y restaura la configuración válida antes de terminar.
Ampliación si has completado el trabajo
Primero termina y verifica los pasos anteriores. Estos retos profundizan en el mismo contenido; no sustituyen la entrega ni obligan a iniciar otro proyecto.
Reto · Las tres trampas de la identidad y los tipos
Examina estas tres situaciones reales y explica por qué son decisiones técnicas erróneas:
- El identificador primitivo: Un desarrollador decide que el atributo
idde su entidad sea unlongprimitivo en lugar deLong(objeto). ¿Qué valor tiene ese campo en memoria antes de guardar el objeto por primera vez en la base de datos? ¿Por qué eso confunde por completo a un ORM al decidir si debe hacer unINSERTo unUPDATE? - La lista en un solo campo: Para no crear otra tabla, alguien propone guardar las etiquetas de una tarea como un
VARCHARseparado por comas:"backend,urgente,seguridad". Explica qué ocurre cuando un usuario pide: «dame todas las tareas con etiqueta seguridad ordenadas por fecha». ¿Puede la base de datos usar un índice en esa consulta? - El hashcode como clave: Otro compañero propone: «En lugar de que PostgreSQL genere un id, podemos usar el
hashCode()del objeto Java como clave primaria». Describe exactamente cómo fallará esa idea el día que dos tareas distintas generen la misma colisión de hash o cuando se reinicie la máquina virtual.
Tarea comprendida, con tipos SQL y restricciones justificadas.schema.sql completo con proyectos y usuarios, incluyendo tipos temporales y restricciones de unicidad.Ver respuestas
1 · Porque JPA es únicamente una especificación: un conjunto de interfaces y anotaciones sin código ejecutable. Hibernate es la implementación que contiene el motor real que traduce a SQL y gestiona conexiones.
2 · En Java las relaciones son referencias direccionales en memoria (punteros). En SQL son valores escalares en columnas de clave foránea (FK) que se vinculan de manera bidireccional mediante operaciones JOIN.
3 · Porque carece de control de concurrencia seguro ante escrituras simultáneas, no tiene soporte transaccional para recuperarse de caídas a mitad de escritura ni índices eficientes para consultar sin cargar todo en memoria.
4 · El motor PostgreSQL rechazará la operación lanzando un error de violación de longitud de cadena (value too long for type character varying(120)), provocando que la transacción aborte y el ORM propague una excepción.
Reto · Variables de entorno reales y dimensionamiento del pool
Resuelve estas dos cuestiones de ingeniería práctica:
Demuestra que la configuración de ${DB_PASSWORD:postgres} funciona en la práctica. Modifica la contraseña en tu servidor PostgreSQL para que sea secreto_seguro_2026.
- Si arrancas directamente con
./mvnw spring-boot:run, la aplicación debe fallar con un error de autenticación. - Arranca ahora la aplicación pasándole la variable de entorno desde la terminal sin modificar una sola línea de código:
- En Linux / macOS / Git Bash:
DB_PASSWORD=secreto_seguro_2026 ./mvnw spring-boot:run - En Windows PowerShell:
$env:DB_PASSWORD="secreto_seguro_2026"; ./mvnw spring-boot:run
- En Linux / macOS / Git Bash:
- Comprueba que arranca limpiamente.
Muchos programadores novatos razonan así: «Si mi servidor va a recibir 500 peticiones por segundo, debo configurar maximum-pool-size=500 para que nadie espere».
- Investiga la fórmula de dimensionamiento recomendada por los creadores de HikariCP:
conexiones = (núcleos de CPU × 2) + husos de disco - Explica por qué tener 500 conexiones simultáneas compitiendo por 4 núcleos de CPU provoca que la base de datos vaya más lenta y consuma más recursos que teniendo solo 10 conexiones encoladas de forma ordenada.
Ver respuestas
1 · Porque la negociación TCP, el cifrado SSL y la autenticación con la base de datos consumen decenas de milisegundos y ciclos de CPU; el pool mantiene conexiones precalentadas que se reutilizan en microsegundos.
2 · validate solo comprueba que el esquema existente coincide con el modelo Java y aborta si hay diferencias (seguro para producción); update modifica las tablas para añadir tablas o columnas nuevas que falten (cómodo en desarrollo).
3 · Que Spring buscará una variable de entorno llamada DB_PORT en el sistema; si no está definida, utilizará el valor por defecto 5432.
4 · El servidor PostgreSQL no está en ejecución, está detenido en Docker o está escuchando en un puerto distinto al 5432.
Cierre
15 minutos · resultado comprobable y explicación individual
Al terminar la sesión:
La aplicación conecta a la base de datos sin credenciales en el repositorio y se identifica qué configuración cambia entre entornos.
Cada integrante explica una decisión del código apoyándose en una de las comprobaciones realizadas.