Cambios en DAO

This commit is contained in:
Isidoro Nevares 2026-03-20 19:36:42 +01:00
parent 57ca157f3b
commit 7b040807ed
9 changed files with 225 additions and 170 deletions

View File

@ -1,14 +1,17 @@
# Gestión de secretos en distintos entornos (Java + Docker + CI/CD) # Configuración de entornos (Java + Docker + CI/CD)
Este documento resume cómo manejar de manera segura credenciales y secretos en distintos entornos: desarrollo local, Eclipse, Docker, docker-compose y CI/CD (GitHub Actions). Este documento resume cómo gestionar la configuración y los secretos de una aplicación en distintos entornos: desarrollo local, Eclipse, Docker, docker-compose y CI/CD (GitHub Actions).
--- ---
## 1. Desarrollo local (Java) ## 1. Desarrollo local (Java)
Leer secretos usando `System.getenv()`: Leer la configuración usando `System.getenv()`:
```java ```java
String host = System.getenv("DB_HOST");
String port = System.getenv("DB_PORT");
String name = System.getenv("DB_NAME");
String user = System.getenv("DB_USER"); String user = System.getenv("DB_USER");
String password = System.getenv("DB_PASSWORD"); String password = System.getenv("DB_PASSWORD");
@ -21,12 +24,18 @@ if (user == null || password == null) {
**Windows (PowerShell):** **Windows (PowerShell):**
```powershell ```powershell
$env:DB_HOST="localhost"
$env:DB_PORT="3306"
$env:DB_NAME="mapa_mundi"
$env:DB_USER="mi_usuario" $env:DB_USER="mi_usuario"
$env:DB_PASSWORD="mi_password" $env:DB_PASSWORD="mi_password"
``` ```
**Linux / Mac / WSL:** **Linux / Mac / WSL:**
```bash ```bash
export DB_HOST=localhost
export DB_PORT=3306
export DB_NAME=mapa_mundi
export DB_USER=mi_usuario export DB_USER=mi_usuario
export DB_PASSWORD=mi_password export DB_PASSWORD=mi_password
``` ```
@ -39,26 +48,34 @@ export DB_PASSWORD=mi_password
2. Selecciona tu aplicación → pestaña **Environment** 2. Selecciona tu aplicación → pestaña **Environment**
3. Añade las variables: 3. Añade las variables:
``` ```
Name: DB_HOST Value: localhost
Name: DB_PORT Value: 3306
Name: DB_NAME Value: mapa_mundi
Name: DB_USER Value: mi_usuario Name: DB_USER Value: mi_usuario
Name: DB_PASSWORD Value: mi_password Name: DB_PASSWORD Value: mi_password
``` ```
4. Apply → Run 4. Apply → Run
> Esto hace que `System.getenv("DB_USER")` funcione al ejecutar la app desde Eclipse. > Esto hace que `System.getenv("DB_HOST")` funcione al ejecutar la app desde Eclipse.
--- ---
## 2. Dockerfile ## 2. Dockerfile
Nunca incluir secretos directamente. Solo declarar variables vacías como documentación: Nunca incluir secretos directamente. Declarar todas las variables vacías como documentación:
```dockerfile ```dockerfile
# Documentación de variables esperadas en runtime # Configuración (no sensible)
ENV DB_HOST=""
ENV DB_PORT=""
ENV DB_NAME=""
# Secretos
ENV DB_USER="" ENV DB_USER=""
ENV DB_PASSWORD="" ENV DB_PASSWORD=""
``` ```
> La aplicación sigue leyendo los valores con `System.getenv()` en tiempo de ejecución. Los secretos reales se pasan al contenedor. > Los valores reales se pasan al contenedor en tiempo de ejecución. La aplicación los lee con `System.getenv()`.
--- ---
@ -66,13 +83,25 @@ ENV DB_PASSWORD=""
**Opción A — Variables en línea:** **Opción A — Variables en línea:**
```bash ```bash
docker run -e DB_USER=mi_usuario -e DB_PASSWORD=mi_password -p 8080:8080 mi-imagen docker run \
-e DB_HOST=localhost \
-e DB_PORT=3306 \
-e DB_NAME=mapa_mundi \
-e DB_USER=mi_usuario \
-e DB_PASSWORD=mi_password \
-p 8080:8080 mi-imagen
``` ```
**Opción B — Archivo `.env` (recomendado):** **Opción B — Archivo `.env` (recomendado):**
Archivo `.env`: Archivo `.env`:
``` ```
# Configuración (no sensible)
DB_HOST=mysql.vdlp
DB_PORT=3306
DB_NAME=mapa_mundi
# Secretos
DB_USER=mi_usuario DB_USER=mi_usuario
DB_PASSWORD=mi_password DB_PASSWORD=mi_password
``` ```
@ -89,6 +118,12 @@ docker run --env-file .env -p 8080:8080 mi-imagen
Archivo `.env`: Archivo `.env`:
``` ```
# Configuración (no sensible)
DB_HOST=mysql.vdlp
DB_PORT=3306
DB_NAME=mapa_mundi
# Secretos
DB_USER=mi_usuario DB_USER=mi_usuario
DB_PASSWORD=mi_password DB_PASSWORD=mi_password
DB_ROOT_PASSWORD=root DB_ROOT_PASSWORD=root
@ -132,7 +167,10 @@ volumes:
## 5. CI/CD — GitHub Actions ## 5. CI/CD — GitHub Actions
Definir los secretos en el repositorio: **Settings → Secrets and variables → Actions**. Definir en el repositorio (**Settings → Secrets and variables → Actions**):
- **Variables** (no sensibles): `DB_HOST`, `DB_PORT`, `DB_NAME` → en la pestaña *Variables*.
- **Secretos**: `DB_USER`, `DB_PASSWORD` → en la pestaña *Secrets*.
```yaml ```yaml
name: Build & Deploy name: Build & Deploy
@ -151,9 +189,12 @@ jobs:
- name: Build Docker image - name: Build Docker image
run: docker build -t mi-app . run: docker build -t mi-app .
- name: Run container with secrets - name: Run container
run: | run: |
docker run \ docker run \
-e DB_HOST=${{ vars.DB_HOST }} \
-e DB_PORT=${{ vars.DB_PORT }} \
-e DB_NAME=${{ vars.DB_NAME }} \
-e DB_USER=${{ secrets.DB_USER }} \ -e DB_USER=${{ secrets.DB_USER }} \
-e DB_PASSWORD=${{ secrets.DB_PASSWORD }} \ -e DB_PASSWORD=${{ secrets.DB_PASSWORD }} \
mi-app mi-app
@ -168,12 +209,26 @@ jobs:
- No subir `.env` al repositorio. - No subir `.env` al repositorio.
- No hardcodear credenciales en `config.properties` ni en el código. - No hardcodear credenciales en `config.properties` ni en el código.
- No incluir secretos en la imagen Docker. - No incluir secretos en la imagen Docker.
- Documentar las variables necesarias sin poner valores reales (README, Dockerfile vacío). - Documentar las variables necesarias sin valores reales (README, Dockerfile vacío).
- Separar visualmente configuración y secretos en el `.env`.
- Para producción avanzada: considerar **Secret Manager** (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault). - Para producción avanzada: considerar **Secret Manager** (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault).
--- ---
## 7. Tabla comparativa por entorno ## 7. Variables: configuración vs secretos
| Variable | Ejemplo | ¿Es secreto? |
|---|---|---|
| `DB_HOST` | `mysql.vdlp` | No |
| `DB_PORT` | `3306` | No |
| `DB_NAME` | `mapa_mundi` | No |
| `DB_USER` | `mi_usuario` | Sí |
| `DB_PASSWORD` | `mi_password` | Sí |
| `DB_ROOT_PASSWORD` | `root` | Sí |
---
## 8. Tabla comparativa por entorno
| Entorno | Mecanismo | Dónde se definen | ¿Se sube al repo? | | Entorno | Mecanismo | Dónde se definen | ¿Se sube al repo? |
|---|---|---|---| |---|---|---|---|
@ -182,8 +237,4 @@ jobs:
| Dockerfile | `ENV` (vacío) | El propio Dockerfile | Sí (sin valores) | | Dockerfile | `ENV` (vacío) | El propio Dockerfile | Sí (sin valores) |
| Docker runtime | `-e` / `--env-file` | Línea de comandos / `.env` | No | | Docker runtime | `-e` / `--env-file` | Línea de comandos / `.env` | No |
| Docker Compose | `env_file` | `.env` | No | | Docker Compose | `env_file` | `.env` | No |
| GitHub Actions | `secrets.*` | Settings del repositorio | No | | GitHub Actions | `vars.*` / `secrets.*` | Settings del repositorio | No |
---
Fuentes: [ChatGPT](https://chat.openai.com) + [Claude](https://claude.ai)

View File

@ -26,6 +26,12 @@
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<!-- JDBC starter: activa DataSource y HikariCP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- Starter test: incluye JUnit 5, Mockito, AssertJ, etc --> <!-- Starter test: incluye JUnit 5, Mockito, AssertJ, etc -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

View File

@ -20,7 +20,12 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/api/continentes") @RequestMapping("/api/continentes")
public class ContinenteController { public class ContinenteController {
private final ContinenteService continenteService = new ContinenteService(); private final ContinenteService continenteService;
// Spring inyecta automáticamente el service con su DAO
public ContinenteController(ContinenteService continenteService) {
this.continenteService = continenteService;
}
// GET /api/continentes - listar todos los continentes // GET /api/continentes - listar todos los continentes
@GetMapping @GetMapping

View File

@ -7,11 +7,20 @@ import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.lapaloma.mapamundi.dao.IContinenteDAO; import javax.sql.DataSource;
import org.lapaloma.mapamundi.gestores.GestorConexionJDBC;
import org.lapaloma.mapamundi.vo.Continente;
import org.lapaloma.mapamundi.dao.IContinenteDAO;
import org.lapaloma.mapamundi.vo.Continente;
import org.springframework.stereotype.Repository;
@Repository
public class ContinenteDaoJDBC implements IContinenteDAO { public class ContinenteDaoJDBC implements IContinenteDAO {
private final DataSource dataSource;
// Spring inyecta el DataSource configurado automáticamente
public ContinenteDaoJDBC(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override @Override
public Continente obtenerContinentePorClave(String codigo) { public Continente obtenerContinentePorClave(String codigo) {
@ -22,7 +31,7 @@ public class ContinenteDaoJDBC implements IContinenteDAO {
WHERE codigo=? WHERE codigo=?
"""; """;
try (Connection conexion = GestorConexionJDBC.getConexionSGDB(); try (Connection conexion = dataSource.getConnection();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
sentenciaJDBCPreparada.setString(1, codigo); sentenciaJDBCPreparada.setString(1, codigo);
@ -50,7 +59,7 @@ public class ContinenteDaoJDBC implements IContinenteDAO {
WHERE codigo=? WHERE codigo=?
"""; """;
try (Connection conexion = GestorConexionJDBC.getConexionSGDB(); try (Connection conexion = dataSource.getConnection();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
sentenciaJDBCPreparada.setString(1, continente.getNombre()); sentenciaJDBCPreparada.setString(1, continente.getNombre());
@ -74,7 +83,7 @@ public class ContinenteDaoJDBC implements IContinenteDAO {
VALUES (?, ?) VALUES (?, ?)
"""; """;
try (Connection conexion = GestorConexionJDBC.getConexionSGDB(); try (Connection conexion = dataSource.getConnection();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
sentenciaJDBCPreparada.setString(1, continente.getCodigo()); sentenciaJDBCPreparada.setString(1, continente.getCodigo());
@ -97,7 +106,7 @@ public class ContinenteDaoJDBC implements IContinenteDAO {
WHERE codigo=? WHERE codigo=?
"""; """;
try (Connection conexion = GestorConexionJDBC.getConexionSGDB(); try (Connection conexion = dataSource.getConnection();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
sentenciaJDBCPreparada.setString(1, continente.getCodigo()); sentenciaJDBCPreparada.setString(1, continente.getCodigo());
@ -118,7 +127,7 @@ public class ContinenteDaoJDBC implements IContinenteDAO {
SELECT * FROM T_CONTINENTE SELECT * FROM T_CONTINENTE
"""; """;
try (Connection conexion = GestorConexionJDBC.getConexionSGDB(); try (Connection conexion = dataSource.getConnection();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
System.out.println(sentenciaJDBCPreparada); System.out.println(sentenciaJDBCPreparada);
@ -146,7 +155,7 @@ public class ContinenteDaoJDBC implements IContinenteDAO {
WHERE nombre_continente LIKE ? WHERE nombre_continente LIKE ?
"""; """;
try (Connection conexion = GestorConexionJDBC.getConexionSGDB(); try (Connection conexion = dataSource.getConnection();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
sentenciaJDBCPreparada.setString(1, "%" + nombre + "%"); sentenciaJDBCPreparada.setString(1, "%" + nombre + "%");

View File

@ -1,29 +0,0 @@
package org.lapaloma.mapamundi.gestores;
import java.sql.Connection;
import java.sql.DriverManager;
public class GestorConexionJDBC {
// Evita que pueda construirse un objeto de la clase.
private GestorConexionJDBC() {
}
public static Connection getConexionSGDB() throws Exception {
Connection conexionSGDB = null;
// Datos URL
String urlBBDD = GestorFicheroConfiguracion.obtenerValor("jdbc.url");
String usuario = System.getenv("DB_USER");
String contrasenya = System.getenv("DB_PASSWORD");
String claseDriver = GestorFicheroConfiguracion.obtenerValor("jdbc.driver");
Class.forName(claseDriver);
conexionSGDB = DriverManager.getConnection(urlBBDD, usuario, contrasenya);
return conexionSGDB;
}
}

View File

@ -6,13 +6,20 @@ package org.lapaloma.mapamundi.service;
import java.util.List; import java.util.List;
import org.lapaloma.mapamundi.dao.IContinenteDAO; import org.lapaloma.mapamundi.dao.IContinenteDAO;
import org.lapaloma.mapamundi.dao.impl.ContinenteDaoJDBC;
import org.lapaloma.mapamundi.excepcion.ContinenteNoEncontradoException; import org.lapaloma.mapamundi.excepcion.ContinenteNoEncontradoException;
import org.lapaloma.mapamundi.vo.Continente; import org.lapaloma.mapamundi.vo.Continente;
import org.springframework.stereotype.Service;
@Service
public class ContinenteService { public class ContinenteService {
IContinenteDAO continenteDAO = new ContinenteDaoJDBC(); private final IContinenteDAO continenteDAO;
// Spring inyecta el DAO automáticamente
public ContinenteService(IContinenteDAO continenteDAO) {
this.continenteDAO = continenteDAO;
}
public Continente obtenerContinentePorClave(String codigo) { public Continente obtenerContinentePorClave(String codigo) {
@ -23,9 +30,6 @@ public class ContinenteService {
Continente continente = continenteDAO.obtenerContinentePorClave(codigo); Continente continente = continenteDAO.obtenerContinentePorClave(codigo);
// Esta línea simula un error de negocio, ignorando lo que devuelve el DAO
continente = null;
if (continente == null) { if (continente == null) {
throw new ContinenteNoEncontradoException( throw new ContinenteNoEncontradoException(
@ -40,6 +44,9 @@ public class ContinenteService {
List<Continente> lista = continenteDAO.obtenerListaContinentes(); List<Continente> lista = continenteDAO.obtenerListaContinentes();
// Esto provoca error
//lista = null;
if (lista == null || lista.isEmpty()) { if (lista == null || lista.isEmpty()) {
throw new RuntimeException("No hay continentes disponibles"); throw new RuntimeException("No hay continentes disponibles");
} }
@ -55,6 +62,9 @@ public class ContinenteService {
List<Continente> lista = continenteDAO.obtenerContinentePorNombre(nombre); List<Continente> lista = continenteDAO.obtenerContinentePorNombre(nombre);
// Al comentar este código desaparece el error
lista =null;
if (lista == null || lista.isEmpty()) { if (lista == null || lista.isEmpty()) {
throw new ContinenteNoEncontradoException( throw new ContinenteNoEncontradoException(
"No existen continentes con nombre: " + nombre "No existen continentes con nombre: " + nombre

View File

@ -0,0 +1,8 @@
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# Opcional: pool
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=2

View File

@ -1,4 +0,0 @@
# Parámetros de conexión a la base de datos MapaMundi en SGDB MySQL
jdbc.driver = com.mysql.cj.jdbc.Driver
jdbc.url = jdbc:mysql://172.16.0.181:3306/Mapa_Mundi

View File

@ -21,8 +21,7 @@ class ContinenteServiceTest {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
fakeDAO = new FakeContinenteDAO(); fakeDAO = new FakeContinenteDAO();
continenteService = new ContinenteService(); continenteService = new ContinenteService(fakeDAO);
continenteService.continenteDAO = fakeDAO;
} }
// ========================= // =========================