
TUTORIAL DE SPRING BOOT PARA APLICACIONES MODERNAS
Introducción a Spring Boot para aplicaciones modernas
Spring Boot es un marco de trabajo robusto y versátil que simplifica el desarrollo de aplicaciones Java, especialmente para proyectos web y empresariales. Este tutorial te guiará paso a paso en la creación de un sistema de reservas de amenidades, ideal para un entorno como un complejo de apartamentos, donde los usuarios pueden reservar servicios como gimnasio, piscina o sauna, respetando límites de capacidad. Usaremos herramientas modernas como Spring Boot, Hibernate, Thymeleaf y Spring Security para construir una aplicación funcional y segura en poco tiempo. Este enfoque es perfecto para prototipos rápidos, como los desarrollados en hackathons o startups, donde la prioridad es un sistema que funcione sin buscar la perfección absoluta.
Requisitos previos
Antes de comenzar, asegúrate de tener conocimientos básicos de Java, programación orientada a objetos, bases de datos relacionales (como relaciones uno a muchos) y HTML. Familiaridad con Spring será útil, aunque no indispensable. Además, necesitarás las siguientes herramientas instaladas:
- Java Development Kit (JDK) 17 o superior
- Maven como sistema de gestión de dependencias
- Un entorno de desarrollo integrado (IDE) como IntelliJ IDEA
- Navegador web para pruebas
- Opcionalmente, Git para clonar el repositorio de ejemplo
¿Qué construiremos?
El objetivo es desarrollar un sistema de reservas para un complejo de apartamentos, donde los usuarios puedan iniciar sesión y reservar amenidades como gimnasio, piscina o sauna. Cada amenidad tiene una capacidad máxima para garantizar un uso seguro, especialmente en contextos como la pandemia de Covid-19. Las características principales incluyen:
- Inicio de sesión para usuarios precreados (sin registro).
- Visualización de reservas existentes por usuario.
- Creación de nuevas reservas seleccionando tipo de amenidad, fecha y hora.
- Restricción de acceso a páginas de reservas solo para usuarios autenticados.
- Validación de capacidad para evitar reservas que excedan el límite.
Tecnologías utilizadas
El proyecto aprovechará varias tecnologías modernas para acelerar el desarrollo y garantizar un código limpio:
- Spring Boot: Simplifica la configuración y desarrollo de aplicaciones Java.
- Hibernate y JPA: Gestionan la persistencia de datos y mapeo objeto-relacional.
- H2 Database: Base de datos en memoria para prototipos rápidos.
- Thymeleaf: Motor de plantillas para renderizar vistas dinámicas.
- Spring Security: Proporciona autenticación y autorización robustas.
- Maven: Gestiona dependencias y compilación del proyecto.
- Bootify: Genera código inicial para reducir configuraciones manuales.
- Swagger: Documenta y prueba APIs automáticamente.
- Bootstrap: Framework CSS para interfaces responsivas.
- Lombok: Reduce código repetitivo mediante anotaciones.
Por qué elegir Spring Boot
Spring Boot es ideal para proyectos empresariales, pero también es eficiente para prototipos rápidos. Sus ventajas incluyen:
- Desarrollo basado en anotaciones que genera código automáticamente.
- Soporte para bases de datos en memoria como H2, eliminando la necesidad de configuraciones complejas.
- Ecosistema maduro con abundante documentación y soporte comunitario.
- Configuración mínima gracias a la eliminación de archivos XML complejos.
- Spring Security integrado para aplicaciones seguras.
- Automatización de tareas comunes, permitiendo enfocarse en la lógica de negocio.
Crear el proyecto con Bootify
Bootify es una herramienta freemium que genera código inicial para Spring Boot, ahorrando tiempo en configuraciones. Para iniciar, visita el sitio de Bootify y selecciona:
- Tipo de compilación: Maven
- Versión de Java: 17
- Habilitar Lombok: Sí
- Base de datos: H2
- Añadir fechas de creación/actualización: Sí
- Paquetes: Technical
- Habilitar OpenAPI/Swagger: Sí
- Dependencias adicionales: spring-boot-devtools
Define las entidades del proyecto:
- Reservation: Almacena datos de la reserva (fecha, hora de inicio, hora de fin, usuario).
- User: Representa a los usuarios con relación a sus reservas.
- AmenityType: Enum para tipos de amenidades (piscina, sauna, gimnasio).
Crea una relación muchos-a-uno entre Reservation y User, asegurando que cada reserva pertenezca a un solo usuario. Descarga el proyecto generado y ábrelo en tu IDE. La estructura inicial será similar a:
├── pom.xml
├── src
│ └── main
│ ├── java
│ │ └── com.amenity_reservation_system
│ │ ├── AmenityReservationSystemApplication.java
│ │ ├── model
│ │ │ ├── Reservation.java
│ │ │ ├── User.java
│ │ ├── repos
│ │ │ ├── ReservationRepository.java
│ │ │ ├── UserRepository.java
│ └── resources
│ └── application.yml
Explorar el código generado
El código generado incluye capas bien definidas:
- Repositorios: Extienden JpaRepository para consultas automáticas a la base de datos. Por ejemplo,
UserRepository
permite buscar usuarios por ID o nombre de usuario.
public interface UserRepository extends JpaRepository<User, Long> {
User findUserByUsername(String username);
}
- Modelos: Definen entidades como
User
yReservation
, mapeadas a tablas de base de datos mediante Hibernate.
@Entity
@Getter
@Setter
public class Reservation {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@Column(nullable = false)
private LocalDate reservationDate;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
}
- Servicios: Contienen la lógica de negocio, manteniendo los controladores ligeros.
- Controladores: Gestionan solicitudes HTTP y devuelven vistas o respuestas JSON.
Para probar el código, ejecuta la aplicación y accede a http://localhost:8080/swagger-ui/index.html
para explorar la API generada con Swagger.
Configurar la base de datos H2
H2 es una base de datos en memoria ideal para prototipos. Configura application.yml
para habilitar la consola de H2:
spring:
datasource:
url: jdbc:h2:mem:amenity-reservation-system
username: sa
password:
jpa:
hibernate:
ddl-auto: update
h2:
console:
enabled: true
Accede a la consola en http://localhost:8080/h2-console
para verificar datos. Usa el usuario “sa” sin contraseña.
Ajustar el código generado
El código generado está orientado a APIs REST, pero nuestro proyecto usa vistas MVC. Elimina las carpetas model
y rest
para evitar conflictos con DTOs y controladores REST:
rm -rf src/main/java/com/amenity_reservation_system/model
rm -rf src/main/java/com/amenity_reservation_system/rest
Actualiza los servicios para usar entidades directamente:
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User get(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
}
Crear vistas con Thymeleaf
Thymeleaf permite crear interfaces dinámicas. Agrega la dependencia en pom.xml
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Crea un directorio templates
bajo src/main/resources
y añade index.html
:
<!DOCTYPE html>
<html lang="es" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Sistema de Reservas</title>
<link
th:rel="stylesheet"
th:href="@{/webjars/bootstrap/4.0.0-2/css/bootstrap.min.css}"
/>
</head>
<body>
<div class="container">
<h1>Bienvenido al Sistema de Reservas</h1>
<a href="/reservations" class="btn btn-success">Reservar Ahora</a>
</div>
<script th:src="@{/webjars/jquery/3.0.0/jquery.min.js}"></script>
<script
th:src="@{/webjars/bootstrap/4.0.0-2/js/bootstrap.min.js}"
></script>
</body>
</html>
Crea un controlador para renderizar la vista:
@Controller
public class HomeController {
@GetMapping("/")
public String index(Model model) {
return "index";
}
}
Definir tipos de amenidades
Para gestionar tipos de amenidades, crea un enum AmenityType
:
public enum AmenityType {
POOL("Piscina"), SAUNA("Sauna"), GYM("Gimnasio");
private final String name;
AmenityType(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
Actualiza Reservation
para incluir el tipo de amenidad:
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private AmenityType amenityType;
Mostrar reservas de usuarios
Para cargar datos iniciales, usa CommandLineRunner
en la clase principal:
@Bean
public CommandLineRunner loadData(UserRepository userRepository, ReservationRepository reservationRepository) {
return args -> {
User user = userRepository.save(new User("Juan Pérez", "juanp", "password"));
Reservation reservation = Reservation.builder()
.reservationDate(LocalDate.now())
.startTime(LocalTime.of(12, 0))
.endTime(LocalTime.of(13, 0))
.amenityType(AmenityType.POOL)
.user(user)
.build();
reservationRepository.save(reservation);
};
}
Crea una vista reservations.html
para mostrar reservas:
<!DOCTYPE html>
<html lang="es" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Reservas</title>
<link
th:rel="stylesheet"
th:href="@{/webjars/bootstrap/4.0.0-2/css/bootstrap.min.css}"
/>
</head>
<body>
<div class="container">
<h3>Bienvenido <span th:text="${session.user.fullName}"></span></h3>
<table class="table">
<thead>
<tr>
<th>Amenidad</th>
<th>Fecha</th>
<th>Hora Inicio</th>
<th>Hora Fin</th>
</tr>
</thead>
<tbody>
<tr th:each="reservation : ${session.user.reservations}">
<td th:text="${reservation.amenityType}"></td>
<td th:text="${reservation.reservationDate}"></td>
<td th:text="${reservation.startTime}"></td>
<td th:text="${reservation.endTime}"></td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
Actualiza el controlador para manejar sesiones:
@GetMapping("/reservations")
public String reservations(Model model, HttpSession session) {
User user = userService.get(10000L);
session.setAttribute("user", user);
model.addAttribute("reservation", new Reservation());
return "reservations";
}
Crear nuevas reservas
Añade un formulario en reservations.html
para crear reservas usando un modal de Bootstrap:
<button
type="button"
class="btn btn-primary"
data-toggle="modal"
data-target="#createReservationModal"
>
Crear Reserva
</button>
<div
th:insert="fragments/modal :: modal"
th:with="reservation=${reservation}"
></div>
Crea modal.html
en templates/fragments
:
<div
class="modal fade"
th:fragment="modal"
id="createReservationModal"
tabindex="-1"
role="dialog"
>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Crear Reserva</h5>
<button type="button" class="close" data-dismiss="modal">
×
</button>
</div>
<div class="modal-body">
<form
th:action="@{/reservations-submit}"
th:object="${reservation}"
method="post"
>
<div class="form-group">
<label for="type-select">Amenidad</label>
<select
class="form-control"
id="type-select"
th:field="*{amenityType}"
>
<option value="POOL">Piscina</option>
<option value="SAUNA">Sauna</option>
<option value="GYM">Gimnasio</option>
</select>
</div>
<div class="form-group">
<label for="start-date">Fecha</label>
<input
class="form-control"
type="date"
id="start-date"
th:field="*{reservationDate}"
/>
</div>
<button type="submit" class="btn btn-primary">
Guardar
</button>
</form>
</div>
</div>
</div>
</div>
Actualiza el controlador para procesar el formulario:
@PostMapping("/reservations-submit")
public String reservationsSubmit(@ModelAttribute Reservation reservation, @SessionAttribute("user") User user) {
reservation.setUser(user);
reservationService.create(reservation);
user.getReservations().add(reservation);
userService.update(user.getId(), user);
return "redirect:/reservations";
}
Implementar autenticación con Spring Security
Añade dependencias de Spring Security en pom.xml
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
Configura la seguridad en WebSecurityConfig.java
:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsServiceImpl userDetailsService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurityConfig(UserDetailsServiceImpl userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/webjars/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.logout().permitAll().logoutSuccessUrl("/");
}
}
Crea UserDetailsServiceImpl
para autenticación:
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
public UserDetailsServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findUserByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(username);
}
return User.withUsername(user.getUsername())
.password(user.getPasswordHash())
.roles("USER")
.build();
}
}
Actualiza User
con campos para autenticación:
@Column(nullable = false, unique = true)
private String username;
@Column
private String passwordHash;
Mostrar reservas del usuario autenticado
Modifica el controlador para usar el usuario autenticado:
@GetMapping("/reservations")
public String reservations(Model model, HttpSession session) {
UserDetails principal = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
User user = userService.getUserByUsername(principal.getUsername());
session.setAttribute("user", user);
model.addAttribute("reservation", new Reservation());
return "reservations";
}
Añade en UserService
:
public User getUserByUsername(String username) {
return userRepository.findUserByUsername(username);
}
Controlar la capacidad de amenidades
Crea una entidad Capacity
para gestionar límites:
@Entity
@Getter
@Setter
public class Capacity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@Enumerated(EnumType.STRING)
@Column(nullable = false, unique = true)
private AmenityType amenityType;
@Column(nullable = false)
private int capacity;
}
Crea CapacityRepository
:
public interface CapacityRepository extends JpaRepository<Capacity, Long> {
Capacity findByAmenityType(AmenityType amenityType);
}
Inicializa capacidades en AmenityReservationSystemApplication
:
private Map<AmenityType, Integer> initialCapacities = new HashMap<>() {{
put(AmenityType.GYM, 20);
put(AmenityType.POOL, 4);
put(AmenityType.SAUNA, 1);
}};
@Bean
public CommandLineRunner loadData(UserRepository userRepository, CapacityRepository capacityRepository) {
return args -> {
for (AmenityType type : initialCapacities.keySet()) {
capacityRepository.save(new Capacity(type, initialCapacities.get(type)));
}
};
}
Actualiza ReservationService
para verificar capacidad:
public Long create(Reservation reservation) {
int capacity = capacityRepository.findByAmenityType(reservation.getAmenityType()).getCapacity();
int overlapping = reservationRepository.findReservationsByReservationDateAndStartTimeBeforeAndEndTimeAfterOrStartTimeBetween(
reservation.getReservationDate(),
reservation.getStartTime(), reservation.getEndTime(),
reservation.getStartTime(), reservation.getEndTime()
).size();
if (overlapping >= capacity) {
throw new RuntimeException("Capacidad completa para esta amenidad");
}
return reservationRepository.save(reservation).getId();
}
Conclusiones
Este tutorial demuestra cómo construir un sistema de reservas funcional con Spring Boot, integrando herramientas modernas como Hibernate, Thymeleaf y Spring Security. Desde la generación inicial del proyecto con Bootify hasta la implementación de autenticación y control de capacidad, el enfoque muestra la rapidez y robustez de Spring Boot para prototipos y aplicaciones escalables. Puedes encontrar el código completo en el repositorio de GitHub correspondiente. Continúa explorando Spring Boot para optimizar tus proyectos de desarrollo web.