SASE FormaciónApuntes MVC
DAM · Spring Boot · Proyecto CRUD de productos

MVC explicado paso a paso

Qué es MVC, qué hace cada pieza, dónde está en el proyecto y cómo viaja una petición desde Postman hasta PostgreSQL y vuelta. Todo con el código real del proyecto crud.

La idea en una frase Separar la aplicación en piezas con una sola responsabilidad cada una, para que cada parte sea fácil de entender, cambiar y probar por separado.
1 · Concepto

¿Qué es MVC?

MVC (Modelo-Vista-Controlador) es un patrón de arquitectura: una forma acordada de organizar el código. No es una librería ni algo que se instale; es una manera de repartir el trabajo.

M
Modelo

Los datos y las reglas sobre ellos. Qué es un producto, qué campos tiene, cómo se guarda.

Producto.java (+ servicio y repositorio)
V
Vista

Lo que ve el usuario: cómo se presentan los datos. En una página web es el HTML; en nuestra API REST es el JSON de la respuesta.

JSON (lo genera Spring)
C
Controlador

El intermediario: recibe la petición del usuario, pide al modelo lo que haga falta y elige qué vista devolver.

ProductoController.java
La confusión típica: «¿y el Service y el Repository dónde están en MVC?» MVC original solo habla de tres piezas. En Spring, la «M» se reparte en varias capas para no tenerlo todo en una clase: el Service (lógica de negocio), el Repository (acceso a la base de datos) y la entidad Producto (los datos). Por eso se dice «MVC + arquitectura en capas» o «Controller-Service-Repository». Son dos ideas que se usan juntas.

¿Por qué separar? Un ejemplo sin MVC

Imagina un único método que lee la URL, abre la conexión a PostgreSQL, escribe el SQL, comprueba si el producto existe, monta el JSON a mano y lo devuelve. Funciona… hasta que quieres cambiar de base de datos, añadir una regla («no se puede vender con stock 0») o probar solo una parte. Entonces hay que tocarlo todo. Con capas, cada cambio afecta a una sola pieza.

2 · Las piezas

Qué hace cada parte (y qué NO debe hacer)

La analogía del restaurante ayuda mucho: cada persona tiene un trabajo y no se mete en el de los demás.

Cliente Postman, navegador, app móvil…

RestauranteEl comensal: pide un plato y espera a que se lo traigan.

Envía una petición HTTP (verbo + URL + a veces un JSON) y recibe una respuesta (código de estado + JSON). No sabe nada de Java ni de la base de datos.

Controller @RestController

RestauranteEl camarero: toma nota del pedido, lo pasa a cocina y trae el plato. No cocina.

  • Sí: decir qué URL atiende cada método, leer datos de la petición (@PathVariable, @RequestBody), elegir el código HTTP.
  • No: lógica de negocio ni acceder a la base de datos. Nunca usa el repositorio directamente.

Service @Service

RestauranteEl cocinero: sabe la receta y decide cómo se prepara cada plato.

  • Sí: la lógica de negocio (las reglas): «si no existe, error», «el id lo pone la base de datos», validaciones, cálculos. Las transacciones (@Transactional).
  • No: saber nada de HTTP (URLs, códigos 200/404, JSON).

Repository @Repository · JpaRepository

RestauranteEl encargado del almacén: trae y guarda ingredientes, sin preguntar para qué.

  • Sí: guardar, buscar, actualizar y borrar en la base de datos. Spring Data JPA escribe el código por nosotros: solo declaramos la interfaz.
  • No: decidir reglas de negocio.

Modelo / Entidad @Entity

RestauranteLos ingredientes y el plato: lo que pasa de mano en mano.

Producto es una clase Java normal con anotaciones que dicen cómo se guarda en una tabla: cada objeto es una fila y cada atributo una columna. Viaja por todas las capas.

Base de datos PostgreSQL en Docker

RestauranteLa despensa: donde se guarda todo, aunque se cierre el restaurante.

Guarda los datos de forma permanente en la tabla productos. Solo habla SQL. Hibernate (dentro de JPA) traduce entre objetos Java y SQL.

La regla de oro de las capas Cada capa solo habla con la de al lado, y siempre en el mismo sentido: Controller → Service → Repository → BD. Las respuestas vuelven por el mismo camino al revés. Nadie se salta una capa.
3 · El flujo

El viaje de una petición, paso a paso

Elige una operación y pulsa Siguiente (o Reproducir). Fíjate en qué viaja entre capas en cada momento: primero texto HTTP, luego objetos Java, luego SQL… y de vuelta.

Fuera de la app
Cliente
Postman / navegador
Spring Boot
Controller
ProductoController
Spring Boot
Service
ProductoService
Spring Boot
Repository
ProductoRepository
Docker
PostgreSQL
tabla productos
Petición (va hacia la BD) Respuesta (vuelve al cliente) Error
    4 · Dónde vive cada parte

    Cada capa tiene su carpeta (paquete)

    Pulsa un fichero para ver qué es y su código. El nombre del paquete ya te dice qué capa es: si buscas dónde se atiende una URL, ve a controller; si buscas una regla de negocio, a service.

    5 · Cómo se conectan

    ¿Quién crea los objetos? Spring (inyección de dependencias)

    Nunca escribimos new ProductoService(). Al arrancar, Spring busca las clases con anotaciones, crea un objeto de cada una (un bean) y le pasa por el constructor las piezas que necesita. A esto se le llama inyección de dependencias.

    1. 1. main() llama a SpringApplication.run(...). Spring escanea el paquete com.saseformacion.crud y sus subpaquetes buscando anotaciones.
    2. 2. Encuentra @Repository en una interfaz. Spring Data JPA genera una clase que la implementa y crea el objeto.
    3. 3. Encuentra @Service. Su constructor pide un ProductoRepository… ¡ya lo tiene! Lo crea pasándoselo.
    4. 4. Encuentra @RestController. Su constructor pide un ProductoService. Lo crea pasándoselo.
    5. 5. Arranca Tomcat (el servidor web) en el puerto 8080. La aplicación ya puede recibir peticiones.
    Contenedor de Spring (los beans)
    productoRepository no necesita nada (lo genera Spring Data)
    productoService new ProductoService(productoRepository)
    productoController new ProductoController(productoService)
    Tomcat :8080 escuchando peticiones HTTP…

    Así se ve en el código

    controller/ProductoController.java
    private final ProductoService productoService;public ProductoController(ProductoService productoService) {    this.productoService = productoService;}
    service/ProductoService.java
    private final ProductoRepository productoRepository;public ProductoService(ProductoRepository productoRepository) {    this.productoRepository = productoRepository;}
    ¿Por qué por constructor? Porque así se ve claramente qué necesita cada clase para funcionar, el campo puede ser final (no cambia nunca) y en los tests podemos pasarle una pieza falsa (un mock). También existe @Autowired encima del campo, que verás en muchos tutoriales, pero se recomienda el constructor.
    6 · Chuleta

    CRUD ↔ HTTP ↔ capas ↔ SQL

    La misma operación tiene un nombre distinto en cada capa. Esta tabla es para tenerla a mano.

    CRUDHTTPURLControllerServiceRepositorySQLRespuesta
    Leer todosGET/api/productoslistar()listar()findAll()SELECT200 OK
    Leer unoGET/api/productos/7buscarPorId(7)buscarPorId(7)findById(7)SELECT … WHERE id=7200 OK · 404
    CrearPOST/api/productos + JSONcrear(producto)crear(producto)save(producto)INSERT201 Created
    ActualizarPUT/api/productos/7 + JSONactualizar(7, datos)actualizar(7, datos)findById + saveSELECT + UPDATE200 OK · 404
    BorrarDELETE/api/productos/7eliminar(7)eliminar(7)existsById + deleteByIdDELETE204 No Content · 404

    Anotaciones que verás en clase

    AnotaciónCapaQué significa
    @RestControllerControllerEsta clase atiende peticiones HTTP y lo que devuelvan sus métodos se convierte en JSON.
    @RequestMapping("/api/productos")ControllerPrefijo común de las URLs de toda la clase.
    @GetMapping @PostMapping @PutMapping @DeleteMappingControllerQué verbo HTTP (y ruta) atiende cada método.
    @PathVariableControllerSaca un valor de la URL: el 7 de /api/productos/7.
    @RequestBodyControllerConvierte el JSON del cuerpo de la petición en un objeto Java.
    @ResponseStatusController / excepciónQué código HTTP devolver (201, 204, 404…).
    @ServiceServiceClase de lógica de negocio. Spring crea un objeto de ella.
    @TransactionalServiceTodo lo que hace el método va en una transacción: o se guarda todo, o nada.
    @RepositoryRepositoryAcceso a datos. Con JpaRepository, Spring genera los métodos.
    @Entity @TableModeloEsta clase se guarda en una tabla de la base de datos.
    @Id @GeneratedValueModeloClave primaria, y la genera la base de datos automáticamente.
    @ColumnModeloDetalles de la columna: obligatoria, longitud máxima…

    Códigos de estado HTTP que usamos

    200 OKTodo bien, aquí tienes los datos.
    201 CreatedHe creado lo que me has mandado.
    204 No ContentHecho (borrado), no hay nada que devolver.
    400 Bad RequestTu petición está mal (datos inválidos). Error del cliente.
    404 Not FoundEso que pides no existe. Error del cliente.
    500 Internal Server ErrorAlgo ha fallado en el servidor. Error nuestro.

    Truco: 2xx = éxito · 4xx = el cliente se ha equivocado · 5xx = el servidor se ha equivocado.

    7 · La «V» de MVC

    API REST (nuestro proyecto) vs. MVC con páginas HTML

    El Controller, el Service y el Repository son iguales en los dos casos. Lo único que cambia es qué devuelve el controlador, es decir, la Vista.

    API REST · @RestController

    Devuelve datos en JSON. Quien los muestre bonitos (una web en JavaScript, una app Android, Postman…) es otro programa.

    Controller→ devuelve Producto→ Spring lo convierte en JSON
    { "id": 7, "nombre": "Teclado", "precio": 49.99 }

    ✔ Es lo que tenemos ahora.

    MVC clásico · @Controller + Thymeleaf

    Devuelve el nombre de una plantilla HTML y mete los datos en un Model. El servidor genera la página completa.

    Controller→ devuelve "productos/lista"→ Thymeleaf genera HTML
    model.addAttribute("productos", lista);return "productos/lista";  // templates/productos/lista.html

    Necesitaría añadir la dependencia Thymeleaf.

    8 · Autoevaluación

    ¿Lo has pillado? Comprueba

    Pulsa una respuesta. Si fallas, lee la explicación: es donde más se aprende.

    9 · Para repasar antes del examen

    Si solo te quedas con 7 ideas

    1. MVC separa la aplicación en Modelo (datos), Vista (lo que se muestra) y Controlador (intermediario).
    2. En Spring, la «M» se divide en capas: Service (reglas de negocio) y Repository (base de datos), más la entidad con los datos.
    3. El flujo siempre es Cliente → Controller → Service → Repository → BD, y la respuesta vuelve por el mismo camino.
    4. Cada capa solo habla con la siguiente. El controlador nunca toca el repositorio.
    5. El controlador traduce HTTP ↔ Java; el repositorio traduce Java ↔ SQL (gracias a JPA/Hibernate).
    6. Spring crea los objetos y los conecta por el constructor (inyección de dependencias). Nosotros nunca hacemos new de un servicio o un repositorio.
    7. En una API REST la Vista es el JSON; con Thymeleaf sería una página HTML.

    Glosario rápido

    Petición HTTP
    Mensaje que envía el cliente: verbo (GET, POST…) + URL + cabeceras + cuerpo opcional.
    Endpoint
    Una combinación de verbo + URL que atiende la API, p. ej. GET /api/productos/{id}.
    JSON
    Formato de texto para intercambiar datos: {"nombre": "Teclado"}.
    Bean
    Objeto que crea y gestiona Spring (controladores, servicios, repositorios…).
    Inyección de dependencias
    Spring le pasa a cada clase los objetos que necesita, en vez de crearlos ella.
    Entidad
    Clase Java que representa una tabla. Un objeto = una fila.
    JPA / Hibernate
    JPA es la especificación para guardar objetos en BD; Hibernate es la implementación que genera el SQL.
    Spring Data JPA
    Genera automáticamente la implementación de los repositorios a partir de la interfaz.
    Transacción
    Grupo de operaciones en BD que se hacen todas o ninguna.
    Lógica de negocio
    Las reglas propias del problema: «no se puede crear un producto con precio negativo».