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)
Leer secretos usando `System.getenv()`:
Leer la configuración usando `System.getenv()`:
```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 password = System.getenv("DB_PASSWORD");
@ -21,12 +24,18 @@ if (user == null || password == null) {
**Windows (PowerShell):**
```powershell
$env:DB_HOST="localhost"
$env:DB_PORT="3306"
$env:DB_NAME="mapa_mundi"
$env:DB_USER="mi_usuario"
$env:DB_PASSWORD="mi_password"
```
**Linux / Mac / WSL:**
```bash
export DB_HOST=localhost
export DB_PORT=3306
export DB_NAME=mapa_mundi
export DB_USER=mi_usuario
export DB_PASSWORD=mi_password
```
@ -39,26 +48,34 @@ export DB_PASSWORD=mi_password
2. Selecciona tu aplicación → pestaña **Environment**
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_PASSWORD Value: mi_password
```
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
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
# 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_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:**
```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):**
Archivo `.env`:
```
# Configuración (no sensible)
DB_HOST=mysql.vdlp
DB_PORT=3306
DB_NAME=mapa_mundi
# Secretos
DB_USER=mi_usuario
DB_PASSWORD=mi_password
```
@ -89,6 +118,12 @@ docker run --env-file .env -p 8080:8080 mi-imagen
Archivo `.env`:
```
# Configuración (no sensible)
DB_HOST=mysql.vdlp
DB_PORT=3306
DB_NAME=mapa_mundi
# Secretos
DB_USER=mi_usuario
DB_PASSWORD=mi_password
DB_ROOT_PASSWORD=root
@ -132,7 +167,10 @@ volumes:
## 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
name: Build & Deploy
@ -151,9 +189,12 @@ jobs:
- name: Build Docker image
run: docker build -t mi-app .
- name: Run container with secrets
- name: Run container
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_PASSWORD=${{ secrets.DB_PASSWORD }} \
mi-app
@ -168,12 +209,26 @@ jobs:
- No subir `.env` al repositorio.
- No hardcodear credenciales en `config.properties` ni en el código.
- 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).
---
## 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? |
|---|---|---|---|
@ -182,8 +237,4 @@ jobs:
| Dockerfile | `ENV` (vacío) | El propio Dockerfile | Sí (sin valores) |
| Docker runtime | `-e` / `--env-file` | Línea de comandos / `.env` | No |
| Docker Compose | `env_file` | `.env` | No |
| GitHub Actions | `secrets.*` | Settings del repositorio | No |
---
Fuentes: [ChatGPT](https://chat.openai.com) + [Claude](https://claude.ai)
| GitHub Actions | `vars.*` / `secrets.*` | Settings del repositorio | No |

View File

@ -26,6 +26,12 @@
<artifactId>spring-boot-starter-web</artifactId>
</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 -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -20,8 +20,13 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/api/continentes")
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
@GetMapping
public List<Continente> getAll() {

View File

@ -7,172 +7,181 @@ import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.lapaloma.mapamundi.dao.IContinenteDAO;
import org.lapaloma.mapamundi.gestores.GestorConexionJDBC;
import org.lapaloma.mapamundi.vo.Continente;
import org.springframework.stereotype.Repository;
@Repository
public class ContinenteDaoJDBC implements IContinenteDAO {
private final DataSource dataSource;
@Override
public Continente obtenerContinentePorClave(String codigo) {
Continente continente = null;
// Spring inyecta el DataSource configurado automáticamente
public ContinenteDaoJDBC(DataSource dataSource) {
this.dataSource = dataSource;
}
String sentenciaSQL = """
SELECT * FROM T_CONTINENTE
WHERE codigo=?
""";
@Override
public Continente obtenerContinentePorClave(String codigo) {
Continente continente = null;
try (Connection conexion = GestorConexionJDBC.getConexionSGDB();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
String sentenciaSQL = """
SELECT * FROM T_CONTINENTE
WHERE codigo=?
""";
sentenciaJDBCPreparada.setString(1, codigo);
System.out.println(sentenciaJDBCPreparada);
try (Connection conexion = dataSource.getConnection();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
ResultSet resultadoSentencia = sentenciaJDBCPreparada.executeQuery();
sentenciaJDBCPreparada.setString(1, codigo);
System.out.println(sentenciaJDBCPreparada);
if (resultadoSentencia.next()) {
continente = getLineaFromResultSet(resultadoSentencia);
}
ResultSet resultadoSentencia = sentenciaJDBCPreparada.executeQuery();
} catch (Exception e) {
e.printStackTrace();
}
if (resultadoSentencia.next()) {
continente = getLineaFromResultSet(resultadoSentencia);
}
return continente;
}
} catch (Exception e) {
e.printStackTrace();
}
@Override
public void actualizarContinente(Continente continente) {
return continente;
}
String sentenciaSQL = """
UPDATE T_CONTINENTE
SET nombre_continente=?
WHERE codigo=?
""";
@Override
public void actualizarContinente(Continente continente) {
try (Connection conexion = GestorConexionJDBC.getConexionSGDB();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
String sentenciaSQL = """
UPDATE T_CONTINENTE
SET nombre_continente=?
WHERE codigo=?
""";
sentenciaJDBCPreparada.setString(1, continente.getNombre());
sentenciaJDBCPreparada.setString(2, continente.getCodigo());
try (Connection conexion = dataSource.getConnection();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
System.out.println(sentenciaJDBCPreparada);
sentenciaJDBCPreparada.setString(1, continente.getNombre());
sentenciaJDBCPreparada.setString(2, continente.getCodigo());
sentenciaJDBCPreparada.executeUpdate();
System.out.println(sentenciaJDBCPreparada);
} catch (Exception e) {
e.printStackTrace();
}
sentenciaJDBCPreparada.executeUpdate();
}
} catch (Exception e) {
e.printStackTrace();
}
@Override
public void crearContinente(Continente continente) {
}
String sentenciaSQL = """
INSERT INTO T_CONTINENTE (codigo, nombre_continente)
VALUES (?, ?)
""";
@Override
public void crearContinente(Continente continente) {
try (Connection conexion = GestorConexionJDBC.getConexionSGDB();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
String sentenciaSQL = """
INSERT INTO T_CONTINENTE (codigo, nombre_continente)
VALUES (?, ?)
""";
sentenciaJDBCPreparada.setString(1, continente.getCodigo());
sentenciaJDBCPreparada.setString(2, continente.getNombre());
try (Connection conexion = dataSource.getConnection();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
System.out.println(sentenciaJDBCPreparada);
sentenciaJDBCPreparada.setString(1, continente.getCodigo());
sentenciaJDBCPreparada.setString(2, continente.getNombre());
sentenciaJDBCPreparada.executeUpdate();
System.out.println(sentenciaJDBCPreparada);
} catch (Exception e) {
e.printStackTrace();
}
}
sentenciaJDBCPreparada.executeUpdate();
@Override
public void borrarContinente(Continente continente) {
} catch (Exception e) {
e.printStackTrace();
}
}
String sentenciaSQL = """
DELETE FROM T_CONTINENTE
WHERE codigo=?
""";
@Override
public void borrarContinente(Continente continente) {
try (Connection conexion = GestorConexionJDBC.getConexionSGDB();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
String sentenciaSQL = """
DELETE FROM T_CONTINENTE
WHERE codigo=?
""";
sentenciaJDBCPreparada.setString(1, continente.getCodigo());
try (Connection conexion = dataSource.getConnection();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
sentenciaJDBCPreparada.executeUpdate();
sentenciaJDBCPreparada.setString(1, continente.getCodigo());
} catch (Exception e) {
e.printStackTrace();
}
}
sentenciaJDBCPreparada.executeUpdate();
@Override
public List<Continente> obtenerListaContinentes() {
} catch (Exception e) {
e.printStackTrace();
}
}
List<Continente> lista = new ArrayList<>();
@Override
public List<Continente> obtenerListaContinentes() {
String sentenciaSQL = """
SELECT * FROM T_CONTINENTE
""";
List<Continente> lista = new ArrayList<>();
try (Connection conexion = GestorConexionJDBC.getConexionSGDB();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
String sentenciaSQL = """
SELECT * FROM T_CONTINENTE
""";
System.out.println(sentenciaJDBCPreparada);
try (Connection conexion = dataSource.getConnection();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
ResultSet resultadoSentencia = sentenciaJDBCPreparada.executeQuery();
System.out.println(sentenciaJDBCPreparada);
while (resultadoSentencia.next()) {
lista.add(getLineaFromResultSet(resultadoSentencia));
}
ResultSet resultadoSentencia = sentenciaJDBCPreparada.executeQuery();
} catch (Exception e) {
e.printStackTrace();
}
while (resultadoSentencia.next()) {
lista.add(getLineaFromResultSet(resultadoSentencia));
}
return lista;
}
} catch (Exception e) {
e.printStackTrace();
}
@Override
public List<Continente> obtenerContinentePorNombre(String nombre) {
return lista;
}
List<Continente> lista = new ArrayList<>();
@Override
public List<Continente> obtenerContinentePorNombre(String nombre) {
String sentenciaSQL = """
SELECT * FROM T_CONTINENTE
WHERE nombre_continente LIKE ?
""";
List<Continente> lista = new ArrayList<>();
try (Connection conexion = GestorConexionJDBC.getConexionSGDB();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
String sentenciaSQL = """
SELECT * FROM T_CONTINENTE
WHERE nombre_continente LIKE ?
""";
sentenciaJDBCPreparada.setString(1, "%" + nombre + "%");
try (Connection conexion = dataSource.getConnection();
PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) {
System.out.println(sentenciaJDBCPreparada);
sentenciaJDBCPreparada.setString(1, "%" + nombre + "%");
ResultSet resultadoSentencia = sentenciaJDBCPreparada.executeQuery();
System.out.println(sentenciaJDBCPreparada);
while (resultadoSentencia.next()) {
lista.add(getLineaFromResultSet(resultadoSentencia));
}
ResultSet resultadoSentencia = sentenciaJDBCPreparada.executeQuery();
} catch (Exception e) {
e.printStackTrace();
}
while (resultadoSentencia.next()) {
lista.add(getLineaFromResultSet(resultadoSentencia));
}
return lista;
}
} catch (Exception e) {
e.printStackTrace();
}
private Continente getLineaFromResultSet(ResultSet resultadoSentencia) throws SQLException {
return lista;
}
Continente continente = new Continente();
private Continente getLineaFromResultSet(ResultSet resultadoSentencia) throws SQLException {
continente.setCodigo(resultadoSentencia.getString("codigo"));
continente.setNombre(resultadoSentencia.getString("nombre_continente"));
Continente continente = new Continente();
return continente;
}
continente.setCodigo(resultadoSentencia.getString("codigo"));
continente.setNombre(resultadoSentencia.getString("nombre_continente"));
return continente;
}
}

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,14 +6,21 @@ package org.lapaloma.mapamundi.service;
import java.util.List;
import org.lapaloma.mapamundi.dao.IContinenteDAO;
import org.lapaloma.mapamundi.dao.impl.ContinenteDaoJDBC;
import org.lapaloma.mapamundi.excepcion.ContinenteNoEncontradoException;
import org.lapaloma.mapamundi.vo.Continente;
import org.springframework.stereotype.Service;
@Service
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) {
@ -23,9 +30,6 @@ public class ContinenteService {
Continente continente = continenteDAO.obtenerContinentePorClave(codigo);
// Esta línea simula un error de negocio, ignorando lo que devuelve el DAO
continente = null;
if (continente == null) {
throw new ContinenteNoEncontradoException(
@ -40,6 +44,9 @@ public class ContinenteService {
List<Continente> lista = continenteDAO.obtenerListaContinentes();
// Esto provoca error
//lista = null;
if (lista == null || lista.isEmpty()) {
throw new RuntimeException("No hay continentes disponibles");
}
@ -55,6 +62,9 @@ public class ContinenteService {
List<Continente> lista = continenteDAO.obtenerContinentePorNombre(nombre);
// Al comentar este código desaparece el error
lista =null;
if (lista == null || lista.isEmpty()) {
throw new ContinenteNoEncontradoException(
"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
void setUp() {
fakeDAO = new FakeContinenteDAO();
continenteService = new ContinenteService();
continenteService.continenteDAO = fakeDAO;
continenteService = new ContinenteService(fakeDAO);
}
// =========================