commit 5de37d63bb477712a5a14dc2ac8d430ca3bb0a62 Author: Isidoro Nevares Martín Date: Fri Jun 12 12:48:24 2026 +0200 Commit inicial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0293886 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/bin/ +*.class +/target/ +# Eclipse +.classpath +.project +.settings/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5ebd19c --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# Repositorio para el examen de ORDINARIA de Ampliación de Entornos (RA3-RA4-RA5). + +¡Ánimo y a por ello! + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..8c4c639 --- /dev/null +++ b/pom.xml @@ -0,0 +1,68 @@ + + 4.0.0 + org.lapaloma.examen.aaee + aaee_gobierno + 0.0.2 + + Prueba de Springboot + Proyecto para poder probar el funcionamiento de SpringBoot + + + org.springframework.boot + spring-boot-starter-parent + 4.0.1 + + + + UTF-8 + 23 + 6.0.3 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + com.mysql + mysql-connector-j + 9.7.0 + compile + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + + + + + diff --git a/src/main/java/org/lapaloma/examen/aaee/AppGesconSB.java b/src/main/java/org/lapaloma/examen/aaee/AppGesconSB.java new file mode 100644 index 0000000..046fcb2 --- /dev/null +++ b/src/main/java/org/lapaloma/examen/aaee/AppGesconSB.java @@ -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); + } + +} diff --git a/src/main/java/org/lapaloma/examen/aaee/controller/CocheController.java b/src/main/java/org/lapaloma/examen/aaee/controller/CocheController.java new file mode 100644 index 0000000..5e8f005 --- /dev/null +++ b/src/main/java/org/lapaloma/examen/aaee/controller/CocheController.java @@ -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 consultarCochesPorMarca(@RequestParam(required = false) String marca) { + return cocheService.consultarCochesPorMarca(marca); + } + + @PostMapping + public ResponseEntity crearNuevoCoche(@Valid @RequestBody Coche coche) { + Coche creado = cocheService.crearNuevoCoche(coche); + URI location = URI.create("/coches/" + creado.getId()); + return ResponseEntity.created(location).body(creado); + } + +} diff --git a/src/main/java/org/lapaloma/examen/aaee/dao/ICocheDAO.java b/src/main/java/org/lapaloma/examen/aaee/dao/ICocheDAO.java new file mode 100644 index 0000000..8fd1116 --- /dev/null +++ b/src/main/java/org/lapaloma/examen/aaee/dao/ICocheDAO.java @@ -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 obtenerCochesPorMarca(String marca); + + Coche crearCoche(Coche coche); +} diff --git a/src/main/java/org/lapaloma/examen/aaee/dao/impl/CocheDaoJDBC.java b/src/main/java/org/lapaloma/examen/aaee/dao/impl/CocheDaoJDBC.java new file mode 100644 index 0000000..8128742 --- /dev/null +++ b/src/main/java/org/lapaloma/examen/aaee/dao/impl/CocheDaoJDBC.java @@ -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 obtenerCochesPorMarca(String marca) { + List 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); + } + } + +} diff --git a/src/main/java/org/lapaloma/examen/aaee/excepcion/CocheNoEncontradoException.java b/src/main/java/org/lapaloma/examen/aaee/excepcion/CocheNoEncontradoException.java new file mode 100644 index 0000000..f888c1d --- /dev/null +++ b/src/main/java/org/lapaloma/examen/aaee/excepcion/CocheNoEncontradoException.java @@ -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); + } +} diff --git a/src/main/java/org/lapaloma/examen/aaee/excepcion/GlobalExceptionHandler.java b/src/main/java/org/lapaloma/examen/aaee/excepcion/GlobalExceptionHandler.java new file mode 100644 index 0000000..71f0bcb --- /dev/null +++ b/src/main/java/org/lapaloma/examen/aaee/excepcion/GlobalExceptionHandler.java @@ -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> handleValidationExceptions(MethodArgumentNotValidException ex) { + Map 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> handleIllegalArgument(IllegalArgumentException ex) { + Map error = new HashMap<>(); + error.put("error", ex.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + + @ExceptionHandler(CocheNoEncontradoException.class) + public ResponseEntity> handleNotFound(CocheNoEncontradoException ex) { + Map error = new HashMap<>(); + error.put("error", ex.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); + } + +} diff --git a/src/main/java/org/lapaloma/examen/aaee/service/CocheService.java b/src/main/java/org/lapaloma/examen/aaee/service/CocheService.java new file mode 100644 index 0000000..7089d31 --- /dev/null +++ b/src/main/java/org/lapaloma/examen/aaee/service/CocheService.java @@ -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 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")); + } +} diff --git a/src/main/java/org/lapaloma/examen/aaee/vo/Coche.java b/src/main/java/org/lapaloma/examen/aaee/vo/Coche.java new file mode 100644 index 0000000..776033f --- /dev/null +++ b/src/main/java/org/lapaloma/examen/aaee/vo/Coche.java @@ -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 + "]"; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..aea67c4 --- /dev/null +++ b/src/main/resources/application.properties @@ -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 + diff --git a/src/test/java/org/lapaloma/examen/aaee/service/CocheServiceTest.java b/src/test/java/org/lapaloma/examen/aaee/service/CocheServiceTest.java new file mode 100644 index 0000000..a616b6f --- /dev/null +++ b/src/test/java/org/lapaloma/examen/aaee/service/CocheServiceTest.java @@ -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 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 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 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); + } + +}