¿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.
Los datos y las reglas sobre ellos. Qué es un producto, qué campos tiene, cómo se guarda.
Producto.java (+ servicio y repositorio)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)El intermediario: recibe la petición del usuario, pide al modelo lo que haga falta y elige qué vista devolver.
ProductoController.javaProducto (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.
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.
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.
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.
¿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.
main()llama aSpringApplication.run(...). Spring escanea el paquetecom.saseformacion.crudy sus subpaquetes buscando anotaciones. - 2. Encuentra
@Repositoryen una interfaz. Spring Data JPA genera una clase que la implementa y crea el objeto. - 3. Encuentra
@Service. Su constructor pide unProductoRepository… ¡ya lo tiene! Lo crea pasándoselo. - 4. Encuentra
@RestController. Su constructor pide unProductoService. Lo crea pasándoselo. - 5. Arranca Tomcat (el servidor web) en el puerto
8080. La aplicación ya puede recibir peticiones.
Así se ve en el código
private final ProductoService productoService;public ProductoController(ProductoService productoService) { this.productoService = productoService;}
private final ProductoRepository productoRepository;public ProductoService(ProductoRepository productoRepository) { this.productoRepository = productoRepository;}
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.
CRUD ↔ HTTP ↔ capas ↔ SQL
La misma operación tiene un nombre distinto en cada capa. Esta tabla es para tenerla a mano.
| CRUD | HTTP | URL | Controller | Service | Repository | SQL | Respuesta |
|---|---|---|---|---|---|---|---|
| Leer todos | GET | /api/productos | listar() | listar() | findAll() | SELECT | 200 OK |
| Leer uno | GET | /api/productos/7 | buscarPorId(7) | buscarPorId(7) | findById(7) | SELECT … WHERE id=7 | 200 OK · 404 |
| Crear | POST | /api/productos + JSON | crear(producto) | crear(producto) | save(producto) | INSERT | 201 Created |
| Actualizar | PUT | /api/productos/7 + JSON | actualizar(7, datos) | actualizar(7, datos) | findById + save | SELECT + UPDATE | 200 OK · 404 |
| Borrar | DELETE | /api/productos/7 | eliminar(7) | eliminar(7) | existsById + deleteById | DELETE | 204 No Content · 404 |
Anotaciones que verás en clase
| Anotación | Capa | Qué significa |
|---|---|---|
@RestController | Controller | Esta clase atiende peticiones HTTP y lo que devuelvan sus métodos se convierte en JSON. |
@RequestMapping("/api/productos") | Controller | Prefijo común de las URLs de toda la clase. |
@GetMapping @PostMapping @PutMapping @DeleteMapping | Controller | Qué verbo HTTP (y ruta) atiende cada método. |
@PathVariable | Controller | Saca un valor de la URL: el 7 de /api/productos/7. |
@RequestBody | Controller | Convierte el JSON del cuerpo de la petición en un objeto Java. |
@ResponseStatus | Controller / excepción | Qué código HTTP devolver (201, 204, 404…). |
@Service | Service | Clase de lógica de negocio. Spring crea un objeto de ella. |
@Transactional | Service | Todo lo que hace el método va en una transacción: o se guarda todo, o nada. |
@Repository | Repository | Acceso a datos. Con JpaRepository, Spring genera los métodos. |
@Entity @Table | Modelo | Esta clase se guarda en una tabla de la base de datos. |
@Id @GeneratedValue | Modelo | Clave primaria, y la genera la base de datos automáticamente. |
@Column | Modelo | Detalles de la columna: obligatoria, longitud máxima… |
Códigos de estado HTTP que usamos
Truco: 2xx = éxito · 4xx = el cliente se ha equivocado · 5xx = el servidor se ha equivocado.
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.
{ "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.
model.addAttribute("productos", lista);return "productos/lista"; // templates/productos/lista.html
Necesitaría añadir la dependencia Thymeleaf.
¿Lo has pillado? Comprueba
Pulsa una respuesta. Si fallas, lee la explicación: es donde más se aprende.
Si solo te quedas con 7 ideas
- MVC separa la aplicación en Modelo (datos), Vista (lo que se muestra) y Controlador (intermediario).
- En Spring, la «M» se divide en capas: Service (reglas de negocio) y Repository (base de datos), más la entidad con los datos.
- El flujo siempre es Cliente → Controller → Service → Repository → BD, y la respuesta vuelve por el mismo camino.
- Cada capa solo habla con la siguiente. El controlador nunca toca el repositorio.
- El controlador traduce HTTP ↔ Java; el repositorio traduce Java ↔ SQL (gracias a JPA/Hibernate).
- Spring crea los objetos y los conecta por el constructor (inyección de dependencias). Nosotros nunca hacemos
newde un servicio o un repositorio. - 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».
