Commit inicial

This commit is contained in:
Isidoro Nevares Martín 2026-06-12 12:48:24 +02:00
commit 5de37d63bb
13 changed files with 475 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
/bin/
*.class
/target/
# Eclipse
.classpath
.project
.settings/

4
README.md Normal file
View File

@ -0,0 +1,4 @@
# Repositorio para el examen de ORDINARIA de Ampliación de Entornos (RA3-RA4-RA5).
¡Ánimo y a por ello!

68
pom.xml Normal file
View File

@ -0,0 +1,68 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.lapaloma.examen.aaee</groupId>
<artifactId>aaee_gobierno</artifactId>
<version>0.0.2</version>
<name>Prueba de Springboot</name>
<description>Proyecto para poder probar el funcionamiento de SpringBoot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.1</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>23</java.version>
<junit.version>6.0.3</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<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>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.7.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,13 @@
package org.lapaloma.examen.aaee;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AppGesconSB {
public static void main(String[] args) {
SpringApplication.run(AppGesconSB.class, args);
}
}

View File

@ -0,0 +1,40 @@
package org.lapaloma.examen.aaee.controller;
import java.net.URI;
import java.util.List;
import javax.validation.Valid;
import org.lapaloma.examen.aaee.service.CocheService;
import org.lapaloma.examen.aaee.vo.Coche;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/coches")
public class CocheController {
private final CocheService cocheService;
public CocheController(CocheService cocheService) {
this.cocheService = cocheService;
}
@GetMapping
public List<Coche> consultarCochesPorMarca(@RequestParam(required = false) String marca) {
return cocheService.consultarCochesPorMarca(marca);
}
@PostMapping
public ResponseEntity<Coche> crearNuevoCoche(@Valid @RequestBody Coche coche) {
Coche creado = cocheService.crearNuevoCoche(coche);
URI location = URI.create("/coches/" + creado.getId());
return ResponseEntity.created(location).body(creado);
}
}

View File

@ -0,0 +1,11 @@
package org.lapaloma.examen.aaee.dao;
import java.util.List;
import org.lapaloma.examen.aaee.vo.Coche;
public interface ICocheDAO {
List<Coche> obtenerCochesPorMarca(String marca);
Coche crearCoche(Coche coche);
}

View File

@ -0,0 +1,89 @@
package org.lapaloma.examen.aaee.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.lapaloma.examen.aaee.dao.ICocheDAO;
import org.lapaloma.examen.aaee.vo.Coche;
import org.springframework.stereotype.Repository;
@Repository
public class CocheDaoJDBC implements ICocheDAO {
private final DataSource dataSource;
public CocheDaoJDBC(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public List<Coche> obtenerCochesPorMarca(String marca) {
List<Coche> lista = new ArrayList<>();
String sqlAll = "SELECT id, marca, modelo, cilindrada FROM coche";
String sqlByMarca = "SELECT id, marca, modelo, cilindrada FROM coche WHERE LOWER(marca) = LOWER(?)";
try (Connection conexion = dataSource.getConnection();
PreparedStatement ps = (marca == null || marca.isBlank()) ? conexion.prepareStatement(sqlAll)
: conexion.prepareStatement(sqlByMarca);) {
if (marca != null && !marca.isBlank()) {
ps.setString(1, marca);
}
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
Coche c = new Coche();
c.setId(rs.getInt("id"));
c.setMarca(rs.getString("marca"));
c.setModelo(rs.getString("modelo"));
c.setCilindrada(rs.getInt("cilindrada"));
lista.add(c);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return lista;
}
@Override
public Coche crearCoche(Coche coche) {
String insertSQL = "INSERT INTO coche (marca, modelo, cilindrada) VALUES (?, ?, ?)";
try (Connection conexion = dataSource.getConnection();
PreparedStatement ps = conexion.prepareStatement(insertSQL, Statement.RETURN_GENERATED_KEYS);) {
ps.setString(1, coche.getMarca());
ps.setString(2, coche.getModelo());
ps.setInt(3, coche.getCilindrada());
int filas = ps.executeUpdate();
if (filas == 0) {
throw new SQLException("No se insertó el coche");
}
try (ResultSet keys = ps.getGeneratedKeys()) {
if (keys.next()) {
coche.setId(keys.getInt(1));
}
}
return coche;
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException("Error al crear coche", e);
}
}
}

View File

@ -0,0 +1,9 @@
package org.lapaloma.examen.aaee.excepcion;
public class CocheNoEncontradoException extends RuntimeException {
private static final long serialVersionUID = 1L;
public CocheNoEncontradoException(String mensaje) {
super(mensaje);
}
}

View File

@ -0,0 +1,36 @@
package org.lapaloma.examen.aaee.excepcion;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error -> errors.put(error.getField(), error.getDefaultMessage()));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Map<String, String>> handleIllegalArgument(IllegalArgumentException ex) {
Map<String, String> error = new HashMap<>();
error.put("error", ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
@ExceptionHandler(CocheNoEncontradoException.class)
public ResponseEntity<Map<String, String>> handleNotFound(CocheNoEncontradoException ex) {
Map<String, String> error = new HashMap<>();
error.put("error", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
}

View File

@ -0,0 +1,37 @@
package org.lapaloma.examen.aaee.service;
import java.util.List;
import org.lapaloma.examen.aaee.dao.ICocheDAO;
import org.lapaloma.examen.aaee.excepcion.CocheNoEncontradoException;
import org.lapaloma.examen.aaee.vo.Coche;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class CocheService {
private final ICocheDAO cocheDAO;
public CocheService(ICocheDAO cocheDAO) {
this.cocheDAO = cocheDAO;
}
@Transactional(readOnly = true)
public List<Coche> consultarCochesPorMarca(String marca) {
return cocheDAO.obtenerCochesPorMarca(marca);
}
@Transactional
public Coche crearNuevoCoche(Coche coche) {
if (coche.getCilindrada() == null || coche.getCilindrada() <= 0) {
throw new IllegalArgumentException("Cilindrada debe ser mayor que 0");
}
return cocheDAO.crearCoche(coche);
}
@Transactional(readOnly = true)
public Coche obtenerPorId(Integer id) {
return cocheRepository.findById(id).orElseThrow(() -> new CocheNoEncontradoException("Coche con id '" + id + "' no encontrado"));
}
}

View File

@ -0,0 +1,66 @@
package org.lapaloma.examen.aaee.vo;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
public class Coche {
private Integer id;
@NotBlank(message = "Marca es obligatoria")
private String marca;
@NotBlank(message = "Modelo es obligatorio")
private String modelo;
@NotNull(message = "Cilindrada es obligatoria")
@Positive(message = "Cilindrada debe ser mayor que 0")
private Integer cilindrada;
public Coche() {}
public Coche(Integer id, String marca, String modelo, Integer cilindrada) {
this.id = id;
this.marca = marca;
this.modelo = modelo;
this.cilindrada = cilindrada;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getMarca() {
return marca;
}
public void setMarca(String marca) {
this.marca = marca;
}
public String getModelo() {
return modelo;
}
public void setModelo(String modelo) {
this.modelo = modelo;
}
public Integer getCilindrada() {
return cilindrada;
}
public void setCilindrada(Integer cilindrada) {
this.cilindrada = cilindrada;
}
@Override
public String toString() {
return "Coche [id=" + id + ", marca=" + marca + ", modelo=" + modelo + ", cilindrada=" + cilindrada + "]";
}
}

View File

@ -0,0 +1,15 @@
# Puerto en que escucha Spring Boot
server.port=8080
# Servicio de la base de datos.
# Ejemplo url base dator:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://${DB_HOST:192.168.1.36}:${DB_PORT:3306}/${DB_NAME:Gobierno}
spring.datasource.username=${DB_USER:root}
spring.datasource.password=${DB_PASSWORD:mysql_123}
# Parámetros del Pool de conexiones HikariCP
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=2

View File

@ -0,0 +1,80 @@
package org.lapaloma.examen.aaee.service;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lapaloma.examen.aaee.dao.ICocheDAO;
import org.lapaloma.examen.aaee.vo.Coche;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class CocheServiceTest {
@Mock
private ICocheDAO cocheDAO;
@InjectMocks
private CocheService cocheService;
@Test
void consultarCochesPorMarca_marcaExistente_devuelveLista() {
List<Coche> lista = Arrays.asList(
new Coche(1, "Seat", "Ibiza", 1200),
new Coche(2, "Seat", "Leon", 1400),
new Coche(3, "Seat", "Toledo", 1600));
when(cocheDAO.obtenerCochesPorMarca("Seat")).thenReturn(lista);
List<Coche> resultado = cocheService.consultarCochesPorMarca("Seat");
assertEquals(3, resultado.size());
verify(cocheDAO).obtenerCochesPorMarca("Seat");
}
@Test
void consultarCochesPorMarca_marcaInexistente_devuelveListaVacia() {
when(cocheDAO.obtenerCochesPorMarca("Ferrari")).thenReturn(Collections.emptyList());
List<Coche> resultado = cocheService.consultarCochesPorMarca("Ferrari");
assertTrue(resultado.isEmpty());
verify(cocheDAO).obtenerCochesPorMarca("Ferrari");
}
@Test
void crearNuevoCoche_cilindradaNula_lanzaIllegalArgumentException() {
Coche coche = new Coche(null, "Volkswagen", "Golf", null);
assertThrows(IllegalArgumentException.class, () -> cocheService.crearNuevoCoche(coche));
}
@Test
void crearNuevoCoche_cilindradaNoPositiva_lanzaIllegalArgumentException() {
Coche coche = new Coche(null, "Renault", "Clio", 0);
assertThrows(IllegalArgumentException.class, () -> cocheService.crearNuevoCoche(coche));
}
@Test
void crearNuevoCoche_datosValidos_devuelveCocheCreado() {
Coche entrada = new Coche(null, "Volkswagen", "Golf", 2000);
Coche creado = new Coche(10, "Volkswagen", "Golf", 2000);
when(cocheDAO.crearCoche(entrada)).thenReturn(creado);
Coche resultado = cocheService.crearNuevoCoche(entrada);
assertNotNull(resultado);
assertEquals(creado.getId(), resultado.getId());
verify(cocheDAO).crearCoche(entrada);
}
}