Thymeleaf
¡Claro que sí! Con gusto te explico cómo usar Thymeleaf.
Thymeleaf es un motor de plantillas para Java que te permite generar páginas web (HTML,
XML, XHTML) de manera dinámica. Es la alternativa más popular a JSP y se integra de forma
excelente con Spring Boot.
La gran ventaja de Thymeleaf es que sus plantillas son HTML5 válido, lo que significa que puedes
abrirlas directamente en un navegador web (como Chrome o Firefox) para ver cómo se ve el diseño,
¡incluso antes de que la aplicación Java se ejecute! Esto facilita mucho el trabajo a los diseñadores
web.
¿Cómo funciona?
Thymeleaf utiliza atributos especiales en las etiquetas HTML (como th:text, th:each,
th:if, etc.) para inyectar datos dinámicos provenientes del backend (tu código Java). Cuando la
página es procesada por el servidor, estos atributos son reemplazados por los datos reales.
Veamos un ejemplo práctico usando un proyecto de Spring Boot.
Paso 1: Configuración en Spring Boot
Si aún no tienes un proyecto de Spring Boot, puedes crearlo en Apache NetBeans 26 o en Spring
Initializr. Asegúrate de añadir la dependencia de Thymeleaf en tu archivo [Link].
XML
<dependencies>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Paso 2: Crear una plantilla HTML básica
En tu proyecto de Spring Boot, las plantillas de Thymeleaf se guardan en la carpeta
src/main/resources/templates.
Crea un archivo llamado [Link] y pega el siguiente código. Nota el uso del atributo
th:text.
HTML
<!DOCTYPE html>
<html lang="es" xmlns:th="[Link]
<head>
<meta charset="UTF-8">
<title>Ejemplo de Thymeleaf</title>
</head>
<body>
<h1>¡Bienvenido, <span th:text="${nombre}">Usuario</span>!</h1>
<p th:text="'La fecha de hoy es: ' + ${fechaActual}">La fecha de hoy es: 25-
06-2024</p>
</body>
</html>
• xmlns:th="[Link] Esta línea en la etiqueta <html> le
dice a tu editor de código y a Thymeleaf que estás usando sus atributos.
• <span th:text="${nombre}">Usuario</span>: Aquí está la magia.
• th:text: Es el atributo de Thymeleaf.
• "${nombre}": Es una expresión de Thymeleaf que busca una variable llamada
nombre en el modelo del backend.
• Usuario: Este es el texto que se muestra si abres el HTML sin procesar (es HTML
estático válido). Cuando Spring procesa la plantilla, el texto "Usuario" es
reemplazado por el valor de la variable nombre.
Paso 3: Crear el controlador en Java
Ahora, necesitas un controlador de Spring que prepare los datos (el modelo) y los envíe a la
plantilla.
Crea un controlador en tu paquete controller (por ejemplo, [Link]) y
añade el siguiente código:
Java
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Controller
public class HolaController {
@GetMapping("/saludar")
public String saludar(Model model) {
// Agregamos la variable 'nombre' al modelo
[Link]("nombre", "Juan Pérez");
// Agregamos la variable 'fechaActual' al modelo
[Link]("fechaActual", new Date());
// Retornamos el nombre de la plantilla HTML (sin la extensión .html)
return "saludo";
}
}
• @Controller: Indica que esta clase es un controlador de Spring.
• @GetMapping("/saludar"): Mapea la URL /saludar a este método.
• Model model: Es un objeto que Spring inyecta automáticamente. Lo usamos para pasar
datos del backend al frontend.
• [Link]("nombre", "Juan Pérez"): Aquí es donde "enviamos"
la variable nombre con el valor "Juan Pérez" a la vista.
• return "saludo": Le dice a Spring que use la plantilla [Link] para renderizar
la respuesta.
Paso 4: Ejecutar y ver el resultado
1. Ejecuta tu aplicación Spring Boot.
2. Abre tu navegador y ve a la URL: [Link]
Verás el resultado dinámico:
HTML
<h1>¡Bienvenido, Juan Pérez!</h1>
<p>La fecha de hoy es: Fri Jun 27 15:30:00 PET 2025</p>
El texto "Usuario" y la fecha estática fueron reemplazados por los datos que enviaste desde tu
controlador Java.
Atributos comunes de Thymeleaf
Aquí tienes una tabla con los atributos más importantes que debes conocer:
Atributo
Descripción Ejemplo
Thymeleaf
th:text Reemplaza el texto dentro de <p th:text="${variable}">Texto
la etiqueta. estático</p>
th:utext Reemplaza el texto y procesa <p th:utext="${variable}">
el contenido HTML.
Itera sobre una colección <li th:each="item : ${lista}"
th:each
(lista, mapa, etc.). th:text="${[Link]}"></li>
Renderiza el elemento si la <div th:if="$
th:if
condición es verdadera. {[Link]}">...</div>
<div th:unless="$
th:unless Renderiza el elemento si la
condición es falsa. {[Link]}">...</div>
Establece el valor de un <a th:href="@{/perfil}">Mi
th:href
enlace <a>. perfil</a>
Atributo
Descripción Ejemplo
Thymeleaf
Establece la fuente de una
th:src <img th:src="@{/img/[Link]}">
imagen <img>.
<form th:action="@{/login}"
th:action Establece la URL de acción
de un formulario. method="post">
Establece el valor de un
<input type="text" th:value="$
th:value campo de formulario
{[Link]}">
(<input>).
Exportar a Hojas de cálculo
Expresiones importantes de Thymeleaf
• ${...}: Variables del modelo.
• *{...}: Variables del objeto de selección actual (útil en formularios).
• #{...}: Mensajes de internacionalización.
• @{...}: URLs, muy útil para evitar problemas con los contextos de la aplicación.
• ~{...}: Fragmentos de plantilla.
Usar Thymeleaf es sencillo una vez que entiendes cómo mapear los datos del backend a los
atributos de la vista. Su mayor ventaja es que tus archivos HTML siguen siendo legibles y
funcionales, incluso sin el backend.
Este es un caso de uso muy común en cualquier aplicación web.
Paso 1: Modificar el modelo de datos
Primero, necesitamos un modelo de Java para representar un producto. Crea una clase llamada
[Link] en tu paquete de modelos ([Link]).
Java
package [Link];
public class Producto {
private String codigo;
private String nombre;
private double precio;
// Constructor
public Producto(String codigo, String nombre, double precio) {
[Link] = codigo;
[Link] = nombre;
[Link] = precio;
}
// Getters y Setters
public String getCodigo() { return codigo; }
public void setCodigo(String codigo) { [Link] = codigo; }
public String getNombre() { return nombre; }
public void setNombre(String nombre) { [Link] = nombre; }
public double getPrecio() { return precio; }
public void setPrecio(double precio) { [Link] = precio; }
}
Paso 2: Crear la plantilla para listar y agregar productos
Ahora, crea una nueva plantilla HTML llamada [Link] en la carpeta
src/main/resources/templates.
Esta plantilla hará dos cosas:
1. Mostrar una tabla con la lista de productos usando th:each.
2. Presentar un formulario para agregar un nuevo producto.
HTML
<!DOCTYPE html>
<html lang="es" xmlns:th="[Link]
<head>
<meta charset="UTF-8">
<title>Gestión de Productos</title>
<style>
body { font-family: Arial, sans-serif; margin: 2em; }
table { width: 80%; border-collapse: collapse; margin-top: 1em; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
form { margin-top: 2em; padding: 1em; border: 1px solid #ccc; border-
radius: 5px; }
input, button { padding: 8px; margin: 5px 0; }
</style>
</head>
<body>
<h1>Gestión de Productos</h1>
<h2>Lista de Productos</h2>
<table>
<thead>
<tr>
<th>Código</th>
<th>Nombre</th>
<th>Precio</th>
</tr>
</thead>
<tbody>
<tr th:each="producto : ${productos}">
<td th:text="${[Link]}">C000</td>
<td th:text="${[Link]}">Producto Ejemplo</td>
<td th:text="${#[Link]([Link], 0,
'COMMA', 2, 'POINT')}">0.00</td>
</tr>
</tbody>
</table>
<p th:if="${[Link]}" style="color: green;">
¡Producto agregado con éxito!
</p>
---
<h2>Agregar Nuevo Producto</h2>
<form th:action="@{/productos/agregar}" method="post">
<div>
<label for="codigo">Código:</label>
<input type="text" id="codigo" name="codigo" required>
</div>
<div>
<label for="nombre">Nombre:</label>
<input type="text" id="nombre" name="nombre" required>
</div>
<div>
<label for="precio">Precio:</label>
<input type="number" id="precio" name="precio" step="0.01" required>
</div>
<button type="submit">Agregar Producto</button>
</form>
</body>
</html>
Explicación de los atributos de Thymeleaf:
• th:each="producto : ${productos}": Esta es la joya de la corona. Le dice a
Thymeleaf que recorra la lista de objetos llamada productos (que enviaremos desde el
backend). En cada iteración, el objeto actual se llamará producto.
• <td th:text="${[Link]}">: Dentro de la iteración, podemos acceder a
las propiedades de cada objeto ([Link], [Link], etc.) para
mostrarlas.
• th:text="${#[Link](...)}": Esto es una de las expresiones
de utilidad de Thymeleaf. Usamos #numbers para formatear el precio con dos decimales,
lo cual es muy útil.
• <form th:action="@{/productos/agregar}" method="post">: En el
formulario, th:action construye la URL de destino para la acción del formulario. @ es
una expresión de URL.
• name="codigo", name="nombre", name="precio": Los atributos name de los
campos del formulario son muy importantes, ya que Thymeleaf los usará para mapear los
datos a un objeto en el backend.
• <p th:if="${[Link]}">: Muestra este párrafo de éxito solo si el parámetro
exito existe en la URL (por ejemplo, /productos?exito).
Paso 3: Modificar el controlador en Java
Ahora, vamos a crear un controlador que gestione la lista de productos, la envíe a la plantilla y
reciba los datos del formulario.
Modifica tu controlador ([Link] o crea uno nuevo) con los siguientes
métodos.
Java
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Controller
public class ProductoController {
// Simulación de una base de datos con una lista estática
private static List<Producto> listaProductos = new ArrayList<>();
// Inicializamos algunos productos de ejemplo
static {
[Link](new Producto("TECL01", "Teclado Mecánico", 85.50));
[Link](new Producto("MOUSE02", "Mouse Óptico", 25.00));
[Link](new Producto("MONI03", "Monitor 27''", 350.99));
}
/**
* Muestra la lista de productos.
*/
@GetMapping("/productos")
public String listarProductos(Model model) {
// Agrega la lista de productos al modelo
[Link]("productos", listaProductos);
// Retorna el nombre de la plantilla
return "productos";
}
/**
* Recibe los datos del formulario y agrega un nuevo producto.
*/
@PostMapping("/productos/agregar")
public String agregarProducto(
@RequestParam String codigo,
@RequestParam String nombre,
@RequestParam double precio,
RedirectAttributes redirectAttributes
) {
// Crea un nuevo objeto Producto con los datos del formulario
Producto nuevoProducto = new Producto(codigo, nombre, precio);
// Agrega el nuevo producto a la lista
[Link](nuevoProducto);
// Agrega un parámetro de éxito para mostrar un mensaje en la vista
[Link]("exito", true);
// Redirige al usuario de vuelta a la página de productos
// Se usa "redirect:" para hacer una nueva petición HTTP y evitar el
envío doble del formulario
return "redirect:/productos";
}
}
Explicación del controlador:
• private static List<Producto> listaProductos: Para este ejemplo,
simulamos una base de datos con una lista estática. En una aplicación real, usarías un
repositorio y una base de datos.
• @GetMapping("/productos"): Cuando un usuario accede a /productos, este
método se ejecuta. Se encarga de poner la lista de productos en el modelo y devolver la
plantilla [Link].
• @PostMapping("/productos/agregar"): Este método se activa cuando se envía
el formulario (el method es post).
• @RequestParam String codigo: Spring automáticamente toma el valor del campo
del formulario llamado codigo y lo asigna a la variable codigo de Java. ¡Así de fácil!
• RedirectAttributes redirectAttributes: Este objeto es muy útil para enviar
datos (como un mensaje de éxito) a la siguiente solicitud HTTP después de una redirección.
• return "redirect:/productos": Después de agregar el producto, redireccionamos
al usuario a la URL /productos. Esto es una buena práctica para evitar que al recargar
la página se envíe el formulario de nuevo.
Paso 4: Ejecutar y probar
1. Ejecuta tu aplicación Spring Boot.
2. Abre el navegador y ve a [Link]
3. Verás la tabla con los productos de ejemplo.
4. Usa el formulario para agregar un nuevo producto.
5. Después de agregarlo, verás el mensaje de éxito y la tabla se actualizará con el nuevo
producto.
Con este ejemplo, ahora sabes cómo iterar sobre colecciones en Thymeleaf y cómo manejar la
entrada de datos de formularios para interactuar con tu backend. Es la base para crear cualquier
CRUD (Crear, Leer, Actualizar, Borrar) en una aplicación web.
Fuentes
Crear un CRUD (Crear, Leer, Actualizar, Eliminar) completo usando Thymeleaf y Spring Boot.
Un CRUD es la base de la mayoría de las aplicaciones web y te permite gestionar datos (en este
caso, productos) de manera completa.
Resumen del CRUD que haremos
• Crear (Create): Un formulario para agregar nuevos productos.
• Leer (Read): Una tabla que muestra todos los productos.
• Actualizar (Update): Un formulario para editar los detalles de un producto existente.
• Eliminar (Delete): Un botón para borrar un producto.
Paso 1: Configuración del Proyecto y Modelo
Si no tienes un proyecto de Spring Boot, créalo en NetBeans o Spring Initializr y añade las
siguientes dependencias en tu [Link]:
XML
<dependencies>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
Ahora, crea la clase [Link] en el paquete [Link].
Incluiremos un ID para identificar de forma única a cada producto.
Java
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
public class Producto {
private Long id;
@NotBlank(message = "El código no puede estar vacío")
@Size(min = 3, max = 10, message = "El código debe tener entre 3 y 10
caracteres")
private String codigo;
@NotBlank(message = "El nombre no puede estar vacío")
@Size(min = 2, max = 50, message = "El nombre debe tener entre 2 y 50
caracteres")
private String nombre;
@NotNull(message = "El precio es obligatorio")
@Min(value = 0, message = "El precio debe ser positivo")
private Double precio;
// Getters y Setters (Necesarios para Thymeleaf y Spring)
public Long getId() { return id; }
public void setId(Long id) { [Link] = id; }
public String getCodigo() { return codigo; }
public void setCodigo(String codigo) { [Link] = codigo; }
public String getNombre() { return nombre; }
public void setNombre(String nombre) { [Link] = nombre; }
public Double getPrecio() { return precio; }
public void setPrecio(Double precio) { [Link] = precio; }
}
Paso 2: El Controlador (Controller)
El controlador será el cerebro del CRUD. Se encargará de manejar todas las peticiones (GET y
POST) para leer, crear, actualizar y eliminar productos.
Crea la clase [Link] en el paquete
[Link].
Java
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link].*;
import [Link];
import [Link];
import [Link];
import [Link];
@Controller
@RequestMapping("/productos") // Base URL para todas las rutas de este
controlador
public class ProductoController {
// Simulación de una base de datos con una lista estática y un contador para
IDs
private static List<Producto> listaProductos = new ArrayList<>();
private static Long nextId = 1L;
// Inicializamos algunos productos de ejemplo
static {
[Link](createProduct(nextId++, "TECL01", "Teclado Mecánico",
85.50));
[Link](createProduct(nextId++, "MOUSE02", "Mouse Óptico",
25.00));
[Link](createProduct(nextId++, "MONI03", "Monitor 27''",
350.99));
}
private static Producto createProduct(Long id, String codigo, String nombre,
Double precio) {
Producto p = new Producto();
[Link](id);
[Link](codigo);
[Link](nombre);
[Link](precio);
return p;
}
// --- READ (Leer) ---
// Muestra la lista de productos y el formulario para crear uno nuevo.
@GetMapping({"", "/"})
public String listarProductos(Model model) {
[Link]("productos", listaProductos);
[Link]("producto", new Producto()); // Objeto para el
formulario de CREAR
return "productos/lista"; // Retorna la plantilla [Link]
}
// --- CREATE (Crear) ---
// Procesa el formulario de creación de un nuevo producto.
@PostMapping("/crear")
public String crearProducto(@Valid @ModelAttribute("producto") Producto
producto,
BindingResult result,
RedirectAttributes redirectAttributes) {
if ([Link]()) {
// Si hay errores de validación, vuelve a la página con los errores
return "productos/lista";
}
[Link](nextId++); // Asigna un nuevo ID
[Link](producto); // Agrega el producto a la lista
[Link]("mensajeExito", "Producto creado
con éxito!");
return "redirect:/productos"; // Redirige para evitar el envío doble del
formulario
}
// --- UPDATE (Actualizar) - Parte 1: Mostrar formulario de edición ---
// Muestra el formulario con los datos del producto a editar.
@GetMapping("/editar/{id}")
public String mostrarFormularioEdicion(@PathVariable Long id, Model model) {
Optional<Producto> productoOptional = [Link]()
.filter(p -> [Link]().equals(id))
.findFirst();
if ([Link]()) {
[Link]("producto", [Link]());
return "productos/editar"; // Retorna la plantilla [Link]
}
return "redirect:/productos"; // Si no se encuentra, redirige a la lista
}
// --- UPDATE (Actualizar) - Parte 2: Procesar la edición ---
// Procesa el formulario con los datos actualizados.
@PostMapping("/actualizar/{id}")
public String actualizarProducto(@PathVariable Long id,
@Valid @ModelAttribute("producto") Producto
productoActualizado,
BindingResult result,
RedirectAttributes redirectAttributes) {
if ([Link]()) {
// Si hay errores, vuelve al formulario de edición con los errores
return "productos/editar";
}
Optional<Producto> productoEncontrado = [Link]()
.filter(p -> [Link]().equals(id))
.findFirst();
if ([Link]()) {
Producto p = [Link]();
[Link]([Link]());
[Link]([Link]());
[Link]([Link]());
[Link]("mensajeExito", "Producto
actualizado con éxito!");
}
return "redirect:/productos"; // Redirige a la lista
}
// --- DELETE (Eliminar) ---
// Elimina un producto por su ID.
@GetMapping("/eliminar/{id}")
public String eliminarProducto(@PathVariable Long id, RedirectAttributes
redirectAttributes) {
[Link](p -> [Link]().equals(id));
[Link]("mensajeExito", "Producto eliminado
con éxito!");
return "redirect:/productos"; // Redirige a la lista
}
}
Explicación del Controlador:
• @RequestMapping("/productos"): Todas las rutas dentro de este controlador
comienzan con /productos.
• @GetMapping(""): Muestra la lista de productos y un formulario para crear uno nuevo.
• @PostMapping("/crear"): Recibe los datos del formulario de creación. @Valid y
BindingResult se usan para validación de datos.
• @GetMapping("/editar/{id}"): Muestra el formulario de edición para un producto
específico, cargando sus datos.
• @PostMapping("/actualizar/{id}"): Recibe los datos del formulario de edición
y actualiza el producto.
• @GetMapping("/eliminar/{id}"): Recibe el ID y elimina el producto
correspondiente.
Paso 3: Las Plantillas HTML con Thymeleaf
Ahora, crea la estructura de carpetas src/main/resources/templates/productos y
dentro, dos archivos: [Link] y [Link].
[Link] (Leer y Crear)
Esta plantilla tiene la tabla para Leer y el formulario para Crear.
HTML
<!DOCTYPE html>
<html lang="es" xmlns:th="[Link]
<head>
<meta charset="UTF-8">
<title>CRUD de Productos</title>
<link rel="stylesheet"
href="[Link]
</head>
<body>
<div class="container my-5">
<h1 class="mb-4">Gestión de Productos (CRUD)</h1>
<div th:if="${mensajeExito}" class="alert alert-success alert-
dismissible fade show" role="alert">
<span th:text="${mensajeExito}"></span>
<button type="button" class="btn-close" data-bs-dismiss="alert"
aria-label="Close"></button>
</div>
<h2>Lista de Productos</h2>
<table class="table table-striped table-hover mt-3">
<thead class="table-dark">
<tr>
<th>ID</th>
<th>Código</th>
<th>Nombre</th>
<th>Precio</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<tr th:each="producto : ${productos}">
<td th:text="${[Link]}">1</td>
<td th:text="${[Link]}">COD</td>
<td th:text="${[Link]}">Producto</td>
<td th:text="${#[Link]([Link], 0,
'COMMA', 2, 'POINT')}">0.00</td>
<td>
<a th:href="@{/productos/editar/{id}(id=$
{[Link]})}" class="btn btn-warning btn-sm">Editar</a>
<a th:href="@{/productos/eliminar/{id}(id=$
{[Link]})}" class="btn btn-danger btn-sm"
onclick="return confirm('¿Estás seguro de que quieres
eliminar este producto?');">Eliminar</a>
</td>
</tr>
</tbody>
</table>
<hr class="my-5">
<h2>Crear Nuevo Producto</h2>
<form th:action="@{/productos/crear}" th:object="${producto}"
method="post" class="mt-4">
<div class="mb-3">
<label for="codigo" class="form-label">Código:</label>
<input type="text" id="codigo" th:field="*{codigo}" class="form-
control" />
<p th:if="${#[Link]('codigo')}" th:errors="*{codigo}"
class="text-danger"></p>
</div>
<div class="mb-3">
<label for="nombre" class="form-label">Nombre:</label>
<input type="text" id="nombre" th:field="*{nombre}" class="form-
control" />
<p th:if="${#[Link]('nombre')}" th:errors="*{nombre}"
class="text-danger"></p>
</div>
<div class="mb-3">
<label for="precio" class="form-label">Precio:</label>
<input type="number" id="precio" th:field="*{precio}"
step="0.01" class="form-control" />
<p th:if="${#[Link]('precio')}" th:errors="*{precio}"
class="text-danger"></p>
</div>
<button type="submit" class="btn btn-primary">Guardar
Producto</button>
</form>
</div>
<script
src="[Link]
s"></script>
</body>
</html>
[Link] (Actualizar)
Esta plantilla es para el formulario de edición.
HTML
<!DOCTYPE html>
<html lang="es" xmlns:th="[Link]
<head>
<meta charset="UTF-8">
<title>Editar Producto</title>
<link rel="stylesheet"
href="[Link]
</head>
<body>
<div class="container my-5">
<h1 class="mb-4">Editar Producto</h1>
<form th:action="@{/productos/actualizar/{id}(id=${[Link]})}"
th:object="${producto}" method="post">
<div class="mb-3">
<label for="codigo" class="form-label">Código:</label>
<input type="text" id="codigo" th:field="*{codigo}" class="form-
control" />
<p th:if="${#[Link]('codigo')}" th:errors="*{codigo}"
class="text-danger"></p>
</div>
<div class="mb-3">
<label for="nombre" class="form-label">Nombre:</label>
<input type="text" id="nombre" th:field="*{nombre}" class="form-
control" />
<p th:if="${#[Link]('nombre')}" th:errors="*{nombre}"
class="text-danger"></p>
</div>
<div class="mb-3">
<label for="precio" class="form-label">Precio:</label>
<input type="number" id="precio" th:field="*{precio}"
step="0.01" class="form-control" />
<p th:if="${#[Link]('precio')}" th:errors="*{precio}"
class="text-danger"></p>
</div>
<button type="submit" class="btn btn-primary">Actualizar
Producto</button>
<a th:href="@{/productos}" class="btn btn-secondary">Cancelar</a>
</form>
</div>
<script
src="[Link]
s"></script>
</body>
</html>
Explicación de las plantillas:
• th:each="producto : ${productos}": Itera sobre la lista de productos para
mostrar cada fila de la tabla (READ).
• th:href="@{/productos/eliminar/{id}(id=${[Link]})}":
Construye la URL de eliminación, inyectando dinámicamente el id del producto en la ruta
(DELETE).
• th:object="${producto}": En los formularios, esta es la forma más limpia de
vincular un formulario a un objeto del modelo.
• th:field="*{codigo}": En lugar de name y value, th:field hace ambas cosas:
establece el name del campo y su valor por defecto (útil para la edición).
• th:errors="*{codigo}": Muestra los mensajes de validación que se generen en el
backend para un campo específico.
• th:if="${mensajeExito}": Muestra el mensaje de éxito solo si el controlador lo
envía a la vista.
Paso 4: Ejecutar el programa
1. Ejecuta tu aplicación Spring Boot desde NetBeans.
2. Abre tu navegador y ve a [Link]
Verás la interfaz completa del CRUD:
• Una tabla con los productos de ejemplo (Leer).
• Un formulario para Crear nuevos productos.
• Botones de "Editar" y "Eliminar" en cada fila.
Prueba las funcionalidades:
• Crear: Llena el formulario de "Crear Nuevo Producto" y haz clic en "Guardar". Verás cómo
se agrega a la tabla.
• Editar: Haz clic en "Editar" en un producto. Serás llevado a un formulario de edición con
los datos ya cargados.
• Eliminar: Haz clic en "Eliminar" en un producto. El producto desaparecerá de la tabla.