graph LR
subgraph "Sin Spring (acoplamiento)"
A[Clase A] -->|"new B()"| B[Clase B]
B -->|"new C()"| C[Clase C]
end
Fundamentos, IoC/DI, Configuración, REST & Persistencia
Spring Boot es un framework que simplifica la creación de aplicaciones Java basadas en Spring, eliminando la configuración manual y permitiendo enfocarse en la lógica de negocio.
Tip
Navegación: Usa ↓ para profundizar en un tema, → para pasar al siguiente tema
Spring Framework es un framework de desarrollo Java empresarial creado por Rod Johnson en 2003. Su núcleo se basa en dos principios:
| Principio | Descripción |
|---|---|
| IoC (Inversión de Control) | El framework controla el ciclo de vida de los objetos, no el desarrollador |
| DI (Inyección de Dependencias) | Las dependencias se “inyectan” en los objetos en lugar de crearlas manualmente |
Nota
Spring eliminó la necesidad de EJBs (Enterprise JavaBeans), simplificando drásticamente el desarrollo Java empresarial.
graph LR
subgraph "Sin Spring (acoplamiento)"
A[Clase A] -->|"new B()"| B[Clase B]
B -->|"new C()"| C[Clase C]
end
graph LR
subgraph "Con Spring (IoC)"
CTX["Spring Container"] -->|"inyecta"| A2[Clase A]
CTX -->|"inyecta"| B2[Clase B]
CTX -->|"inyecta"| C2[Clase C]
end
style CTX fill:#6db33f,color:#fff
Spring Framework (puro):
Spring Boot:
application.properties / .ymlTip
Spring Boot no reemplaza a Spring, lo potencia eliminando la complejidad de configuración.
Spring Framework (puro)
mindmap
root((Spring Boot))
Opinionated Defaults
Configuración sensata
Convención sobre configuración
Funciona "out of the box"
Starters
spring-boot-starter-web
spring-boot-starter-data-jpa
spring-boot-starter-security
Servidor Embebido
Tomcat
Jetty
Netty (WebFlux)
Production Ready
Actuator
Health Checks
Métricas
Cero XML
Anotaciones Java
application.yml
Auto-configuración
| Versión | Año | Hitos Principales |
|---|---|---|
| 1.0 | 2014 | Primera versión, auto-configuración básica |
| 2.0 | 2018 | Spring WebFlux (reactivo), Kotlin support |
| 2.7 | 2022 | Última versión 2.x LTS |
| 3.0 | 2022 | Java 17+, Jakarta EE 9+, GraalVM Native |
| 3.2 | 2023 | Virtual Threads (Project Loom), RestClient |
| 3.x actual | 2024+ | Mejoras continuas en rendimiento, observabilidad y developer experience |
Advertencia
Spring Boot 3.x requiere Java 17+ y migró de javax.* a jakarta.* (Jakarta EE).
Creando tu primera aplicación Spring Boot desde cero.
Spring Initializr (start.spring.io) es la herramienta oficial para generar proyectos Spring Boot.
Configuración básica:
| Campo | Valor |
|---|---|
| Project | Gradle - Groovy/Kotlin |
| Language | Java |
| Spring Boot | Versión 3.x que genere el scaffold (Bancolombia) |
| Packaging | JAR |
| Java | 17 o 21 |
Starters comunes:
spring-boot-starter-webspring-boot-starter-data-jpaspring-boot-starter-validationspring-boot-starter-securityspring-boot-starter-actuatorspring-boot-starter-testgraph TD
ROOT[mi-proyecto/] --> SRC[src/]
ROOT --> BUILD[build.gradle]
ROOT --> SETTINGS[settings.gradle]
ROOT --> GRADLEW[gradlew]
SRC --> MAIN[main/]
SRC --> TEST[test/]
MAIN --> JAVA[java/com/ejemplo/miproyecto/]
MAIN --> RES[resources/]
JAVA --> APP["MiProyectoApplication.java<br/>(@SpringBootApplication)"]
JAVA --> CTRL[controller/]
JAVA --> SERV[service/]
JAVA --> REPO[repository/]
JAVA --> MODEL[model/]
JAVA --> CONFIG[config/]
RES --> APPYML[application.yml]
RES --> STATIC[static/]
RES --> TEMPLATES[templates/]
TEST --> TESTJAVA[java/com/ejemplo/miproyecto/]
TESTJAVA --> TESTAPP[MiProyectoApplicationTests.java]
style APP fill:#6db33f,color:#fff
style APPYML fill:#f1fa8c,color:#000
style CTRL fill:#8be9fd,color:#000
style SERV fill:#bd93f9,color:#fff
style REPO fill:#ffb86c,color:#000
@SpringBootApplication es una meta-anotación que combina tres anotaciones:
| Anotación | Función |
|---|---|
@SpringBootConfiguration |
Marca la clase como fuente de configuración (equivale a @Configuration) |
@EnableAutoConfiguration |
Activa la auto-configuración de Spring Boot |
@ComponentScan |
Escanea el paquete actual y sub-paquetes buscando componentes |
graph LR
SBA["@SpringBootApplication"] --> SBC["@SpringBootConfiguration"]
SBA --> EAC["@EnableAutoConfiguration"]
SBA --> CS["@ComponentScan"]
SBC --> CONF["@Configuration"]
style SBA fill:#6db33f,color:#fff
style SBC fill:#50fa7b,color:#000
style EAC fill:#8be9fd,color:#000
style CS fill:#ffb86c,color:#000
Advertencia
@ComponentScan solo escanea el paquete de la clase principal y sus sub-paquetes. Si colocas beans en un paquete diferente, Spring no los encontrará.
plugins {
id 'java'
id 'org.springframework.boot' version '<versión-generada-por-scaffold>'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'com.ejemplo'
version = '0.0.1-SNAPSHOT'
java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }
repositories { mavenCentral() }
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
runtimeOnly 'org.postgresql:postgresql'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') { useJUnitPlatform() }Nota
Los Starters incluyen todas las dependencias transitivas — no declaras versiones individuales. Usa la misma versión que genere el scaffold de Bancolombia.
Tip
bootRun para desarrollo rápido, bootJar para generar el artefacto desplegable.
sequenceDiagram
participant DEV as Developer
participant BOOT as Spring Boot
participant CTX as ApplicationContext
participant TOM as Tomcat (Embebido)
DEV->>BOOT: SpringApplication.run()
BOOT->>CTX: Crear ApplicationContext
CTX->>CTX: Component Scan
CTX->>CTX: Auto-Configuration
CTX->>CTX: Inyectar Dependencias
CTX->>TOM: Iniciar servidor en :8080
TOM-->>DEV: 🚀 Started in 2.3 seconds
Entendiendo cómo funciona Spring Boot por dentro.
Spring Boot examina las dependencias en el classpath y configura automáticamente los beans necesarios.
graph LR
CP["Classpath"] --> AC["Auto-Configuration"]
AC --> Q1{"starter-web?"} -->|Sí| TOMCAT[Tomcat + DispatcherServlet]
AC --> Q2{"starter-data-jpa?"} -->|Sí| JPA[DataSource + EntityManager]
AC --> Q3{"starter-security?"} -->|Sí| SEC[SecurityFilterChain]
style AC fill:#6db33f,color:#fff
style TOMCAT fill:#8be9fd,color:#000
style JPA fill:#ffb86c,color:#000
style SEC fill:#ff79c6,color:#fff
// Ejemplo simplificado de una auto-configuración
@AutoConfiguration
@ConditionalOnClass(DataSource.class) // Solo si DataSource está en classpath
@ConditionalOnMissingBean(DataSource.class) // Solo si el usuario no definió uno
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource dataSource(DataSourceProperties properties) {
return DataSourceBuilder.create()
.url(properties.getUrl())
.username(properties.getUsername())
.password(properties.getPassword())
.build();
}
}| Anotación Condicional | Se aplica cuando… |
|---|---|
@ConditionalOnClass |
Una clase está en el classpath |
@ConditionalOnMissingBean |
No existe un bean del tipo dado |
@ConditionalOnProperty |
Una propiedad tiene un valor específico |
@ConditionalOnWebApplication |
Es una aplicación web |
Un Starter es un conjunto curado de dependencias para un propósito específico.
| Starter | Incluye |
|---|---|
spring-boot-starter-web |
Spring MVC, Tomcat, Jackson, Validation |
spring-boot-starter-data-jpa |
Spring Data JPA, Hibernate, HikariCP |
spring-boot-starter-webflux |
Spring WebFlux, Reactor Netty, Project Reactor |
spring-boot-starter-security |
Spring Security, crypto |
spring-boot-starter-test |
JUnit 5, Mockito, AssertJ, MockMvc |
spring-boot-starter-actuator |
Micrometer, Health endpoints |
Nota
Los Starters siguen la convención spring-boot-starter-{nombre}. Starters de terceros usan {nombre}-spring-boot-starter.
Spring Boot incluye un servidor HTTP embebido — no necesitas instalar Tomcat externamente.
graph TB
subgraph "Tradicional"
TC[Tomcat Externo] --> WAR[tu-app.war]
TC --> WAR2[otra-app.war]
end
graph TB
subgraph "Spring Boot"
JAR["tu-app.jar<br/>(Tomcat incluido)"]
end
java -jar tu-app.jar| Servidor | Stack | Uso |
|---|---|---|
| Tomcat | Spring MVC (default) | Aplicaciones web tradicionales |
| Jetty | Spring MVC (alternativa) | Menor footprint |
| Netty | Spring WebFlux (default) | Aplicaciones reactivas |
| Undertow | Spring MVC (alternativa) | Alto rendimiento |
Tip
Tomcat es el default para MVC. Solo cámbialo si tienes requisitos específicos de rendimiento o footprint.
El corazón de Spring: cómo el framework gestiona tus objetos y sus dependencias.
Inversión de Control (IoC) significa que el framework controla la creación y el ciclo de vida de los objetos, no el desarrollador.
Sin IoC (control manual):
Problemas:
Con IoC (Spring gestiona):
@Service
public class OrderService {
// Spring INYECTA las dependencias
private final OrderRepository repo;
private final PaymentService payment;
private final EmailService email;
public OrderService(
OrderRepository repo,
PaymentService payment,
EmailService email) {
this.repo = repo;
this.payment = payment;
this.email = email;
}
}El ApplicationContext es el contenedor IoC de Spring. Gestiona todos los beans de tu aplicación.
| Origen | Acción | Ejemplo de Bean |
|---|---|---|
@ComponentScan |
Descubre automáticamente | OrderService, OrderRepository, PaymentService |
Auto-Configuration |
Crea por classpath | DataSource |
@Configuration |
Define manualmente | SecurityConfig |
Nota
Un Bean es un objeto gestionado por el contenedor de Spring. Spring lo crea, configura e inyecta donde se necesite.
graph TB
subgraph "ApplicationContext"
B1["OrderService"] --- B2["OrderRepository"]
B1 --- B3["PaymentService"]
B2 --- B4["DataSource"]
B5["SecurityConfig"]
end
SCAN["@ComponentScan"] -->|"Descubre"| B1
SCAN -->|"Descubre"| B2
SCAN -->|"Descubre"| B3
AUTO["Auto-Config"] -->|"Crea"| B4
CONF["@Configuration"] -->|"Define"| B5
style B1 fill:#bd93f9,color:#fff
style B2 fill:#ffb86c,color:#000
style B3 fill:#bd93f9,color:#fff
style B4 fill:#50fa7b,color:#000
style B5 fill:#f1fa8c,color:#000
Spring define estereotipos para clasificar beans según su rol arquitectónico:
graph TB
COMP["@Component"] --> CTRL["@Controller / @RestController"]
COMP --> SERV["@Service"]
COMP --> REPO["@Repository"]
COMP --> CONF["@Configuration"]
style COMP fill:#6db33f,color:#fff
style CTRL fill:#8be9fd,color:#000
style SERV fill:#bd93f9,color:#fff
style REPO fill:#ffb86c,color:#000
style CONF fill:#f1fa8c,color:#000
| Anotación | Capa | Función |
|---|---|---|
@Component |
Cualquiera | Bean genérico |
@Controller |
Presentación | Peticiones HTTP → vistas HTML |
@RestController |
Presentación | @Controller + @ResponseBody → JSON/XML |
@Service |
Negocio | Semántico, sin magia extra |
@Repository |
Datos | Traduce excepciones BD → DataAccessException |
@Configuration |
Config | Define beans con @Bean |
Tip
Todos heredan de @Component. La diferencia es semántica (claridad) excepto @Repository que sí traduce excepciones.
@Component marca una clase para que Spring la detecte automáticamente durante el component scan y la registre como bean.
// Spring detecta esta clase y crea una instancia (bean) automáticamente
@Component
public class EmailNotifier {
public void send(String to, String message) {
// lógica de envío de email...
}
}
// Puedes dar un nombre personalizado al bean
@Component("smsNotifier")
public class SmsNotificationService {
public void send(String phone, String message) {
// lógica de envío de SMS...
}
}Nota
Si no especificas nombre, Spring usa el nombre de la clase en camelCase: EmailNotifier → emailNotifier.
@Service es semánticamente un @Component para lógica de negocio. No añade magia extra, pero comunica intención.
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final InventoryService inventoryService;
private final PaymentService paymentService;
public OrderService(OrderRepository orderRepository,
InventoryService inventoryService,
PaymentService paymentService) {
this.orderRepository = orderRepository;
this.inventoryService = inventoryService;
this.paymentService = paymentService;
}
@Transactional
public Order createOrder(CreateOrderRequest request) {
inventoryService.reserveStock(request.getSku(), request.getQty());
Order saved = orderRepository.save(new Order(request.getSku(), request.getQty()));
paymentService.processPayment(saved);
return saved;
}
}Tip
@Service vs @Component: claridad arquitectónica. Un @Service comunica “aquí vive la lógica de negocio”.
@Repository sí añade funcionalidad extra: traduce excepciones de persistencia a DataAccessException de Spring.
@Repository
public class JdbcOrderRepository implements OrderRepository {
private final JdbcTemplate jdbcTemplate;
public JdbcOrderRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Order findById(Long id) {
// Si la BD lanza SQLException, Spring la traduce
// a DataAccessException (unchecked)
return jdbcTemplate.queryForObject(
"SELECT * FROM orders WHERE id = ?",
this::mapRow, id);
}
}Nota
Con Spring Data JPA, tus interfaces extienden JpaRepository y Spring genera la implementación @Repository automáticamente.
graph LR
SQL["SQLException<br/>(Checked, vendor-specific)"] -->|"@Repository<br/>traduce"| DAE["DataAccessException<br/>(Unchecked, genérica)"]
DAE --> DNF[DataNotFoundException]
DAE --> DI[DataIntegrityViolationException]
DAE --> DUP[DuplicateKeyException]
style SQL fill:#ff5555,color:#fff
style DAE fill:#50fa7b,color:#000
| Excepción Spring | Causa típica |
|---|---|
DataNotFoundException |
Registro no encontrado |
DataIntegrityViolationException |
Violación de constraint (FK, NOT NULL) |
DuplicateKeyException |
Clave duplicada (UNIQUE) |
Tip
Al usar @Repository, no necesitas hacer try/catch de SQLException específicas de cada BD. Spring las unifica.
@Controller — Retorna vistas (HTML)
Usa Model para pasar datos y retorna el nombre de la vista (Thymeleaf, etc.)
graph LR
RC["@RestController"] --> C["@Controller"]
RC --> RB["@ResponseBody"]
C --> COMP["@Component"]
style RC fill:#8be9fd,color:#000
style C fill:#8be9fd,color:#000
style RB fill:#50fa7b,color:#000
style COMP fill:#6db33f,color:#fff
@Configuration define una clase de configuración. @Bean registra beans manualmente en el contenedor.
@Configuration
public class AppConfig {
@Bean // Registra este objeto como bean con nombre "objectMapper"
public ObjectMapper objectMapper() {
return new ObjectMapper()
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
@Bean // Útil para beans de librerías externas que no puedes anotar
public RestTemplate restTemplate() {
return new RestTemplateBuilder()
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(10))
.build();
}
@Bean @Profile("dev") // Solo se crea en perfil "dev"
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2).build();
}
}Tip
Usa @Bean para objetos de librerías externas (no puedes ponerles @Component).
Constructor (recomendado)
final)Setter
Advertencia
Siempre usa inyección por constructor. Con un solo constructor, @Autowired es opcional.
Cuando hay múltiples beans del mismo tipo, Spring no sabe cuál inyectar. Usa @Qualifier o @Primary para resolver la ambigüedad.
public interface NotificationService {
void send(String to, String message);
}
@Service("email")
public class EmailNotificationService implements NotificationService {
public void send(String to, String message) { /* email */ }
}
@Service("sms")
@Primary // ← Este se inyecta por defecto
public class SmsNotificationService implements NotificationService {
public void send(String to, String message) { /* sms */ }
}@Service
public class AlertService {
// Opción 1: @Qualifier selecciona uno específico
public AlertService(@Qualifier("email") NotificationService notification) { }
// Opción 2: Sin @Qualifier, se inyecta el @Primary (sms)
public AlertService(NotificationService notification) { }
// Opción 3: Inyectar TODOS
public AlertService(List<NotificationService> notifications) { }
}| Scope | Descripción | Instancias |
|---|---|---|
| singleton (default) | Una sola instancia por ApplicationContext | 1 |
| prototype | Nueva instancia cada vez que se solicita | N |
| request | Una instancia por petición HTTP | 1 por request |
| session | Una instancia por sesión HTTP | 1 por sesión |
Tip
El 99% de los beans son singleton. Usa prototype solo cuando necesites estado independiente por uso.
Una única instancia compartida por todo el ApplicationContext. Es el scope por defecto.
graph LR
subgraph "Singleton (default)"
A[Bean A] --> INST[Instancia única]
B[Bean B] --> INST
C[Bean C] --> INST
end
style INST fill:#50fa7b,color:#000
Nota
Todos los beans @Service, @Repository, @Controller son singleton por defecto. Spring reutiliza la misma instancia.
Spring crea una nueva instancia cada vez que se solicita el bean.
graph LR
subgraph "Prototype"
A[Bean A] --> I1[Instancia 1]
B[Bean B] --> I2[Instancia 2]
C[Bean C] --> I3[Instancia 3]
end
style I1 fill:#ff79c6,color:#fff
style I2 fill:#bd93f9,color:#fff
style I3 fill:#ffb86c,color:#000
Advertencia
Spring no gestiona el ciclo de vida completo de beans prototype — no llama @PreDestroy. Úsalo para objetos con estado propio.
Scopes exclusivos de aplicaciones web — ligados al ciclo de vida HTTP.
graph TB
subgraph "Request Scope"
R1["GET /api/orders"] --> RI1["Instancia A"]
R2["POST /api/orders"] --> RI2["Instancia B"]
end
subgraph "Session Scope"
S1["Usuario 1 (sesión)"] --> SI1["Instancia X"]
S2["Usuario 2 (sesión)"] --> SI2["Instancia Y"]
end
style RI1 fill:#8be9fd,color:#000
style RI2 fill:#8be9fd,color:#000
style SI1 fill:#f1fa8c,color:#000
style SI2 fill:#f1fa8c,color:#000
Nota
proxyMode es necesario para inyectar beans request/session en beans singleton.
Spring gestiona el ciclo de vida completo de los beans. Puedes engancharte con callbacks.
sequenceDiagram
participant CTX as ApplicationContext
participant BEAN as Bean
CTX->>BEAN: 1. Instanciar (constructor)
CTX->>BEAN: 2. Inyectar dependencias
CTX->>BEAN: 3. @PostConstruct
Note over BEAN: Bean listo para usar
CTX->>BEAN: 4. En uso...
CTX->>BEAN: 5. @PreDestroy
Note over BEAN: Bean destruido
Nota
Beans prototype no reciben @PreDestroy. Beans session se destruyen al expirar la sesión HTTP.
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ShoppingCart {
private final List<Item> items = new ArrayList<>();
@PostConstruct // Se ejecuta al crear la sesión del usuario
public void init() {
log.info("Carrito creado para sesión");
}
@PreDestroy // Se ejecuta al expirar/cerrar la sesión
public void cleanup() {
log.info("Carrito destruido, {} items", items.size());
}
}Tip
@PostConstruct y @PreDestroy funcionan con todos los scopes (excepto @PreDestroy en prototype). Útiles para inicializar recursos o liberar conexiones.
Elimina código repetitivo con anotaciones en tiempo de compilación.
Project Lombok genera código repetitivo (boilerplate) automáticamente mediante anotaciones procesadas en tiempo de compilación.
Sin Lombok (boilerplate manual)
public class Product {
private Long id;
private String name;
private BigDecimal price;
public Product() {}
public Product(Long id, String name,
BigDecimal price) {
this.id = id;
this.name = name;
this.price = price;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String n) { this.name = n; }
// ... + toString, equals, hashCode
}Advertencia
En IntelliJ IDEA necesitas instalar el plugin de Lombok y habilitar Annotation Processing en Settings → Build → Compiler.
Nota
El scaffold de Bancolombia ya incluye Lombok como dependencia por defecto.
Generan getters y setters automáticamente. Se pueden aplicar a nivel de clase o campo individual.
Lo que Lombok genera:
public Long getId() { return this.id; }
public void setId(Long id) { this.id = id; }
public String getName() { return this.name; }
public void setName(String name) {
this.name = name;
}
public BigDecimal getPrice() {
return this.price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}Tip
AccessLevel permite controlar la visibilidad: PUBLIC, PROTECTED, PACKAGE, PRIVATE, NONE.
@ToString
@ToString
public class Product {
private Long id;
private String name;
private BigDecimal price;
}
// → Product(id=1, name=RTX 4090, price=1599.99)
// Excluir campos sensibles
@ToString(exclude = "password")
public class User {
private Long id;
private String email;
private String password;
}
// Solo incluir ciertos campos
@ToString(onlyExplicitlyIncluded = true)
public class Order {
@ToString.Include
private Long id;
@ToString.Include
private String status;
private List<OrderItem> items; // excluido
}@EqualsAndHashCode
Advertencia
En entidades JPA, nunca uses @EqualsAndHashCode con todos los campos. Compara solo por @Id o clave de negocio para evitar problemas con lazy loading.
| Anotación | Genera constructor con… |
|---|---|
@NoArgsConstructor |
Sin argumentos (requerido por JPA) |
@AllArgsConstructor |
Todos los campos |
@RequiredArgsConstructor |
Solo campos final y @NonNull |
@Service
@RequiredArgsConstructor // Genera: OrderService(OrderRepository, PaymentService)
public class OrderService {
private final OrderRepository orderRepository; // final → incluido
private final PaymentService paymentService; // final → incluido
private String tempValue; // no final → excluido
}Tip
@RequiredArgsConstructor es el mejor amigo de la inyección por constructor en Spring — elimina constructores manuales.
Genera un patrón Builder fluido para construir objetos paso a paso.
Nota
Usa @Builder.Default para campos con valores por defecto. Sin esta anotación, el builder los inicializa en null/0.
@Data (mutable) |
@Value (inmutable) |
|
|---|---|---|
| Incluye | @Getter @Setter @ToString @EqualsAndHashCode @RequiredArgsConstructor |
@Getter @ToString @EqualsAndHashCode @AllArgsConstructor + campos final + clase final |
| Setters | Sí | No |
| Ideal para | DTOs mutables, formularios | Value Objects, DTOs inmutables |
Advertencia
Nunca uses @Data en entidades JPA — @EqualsAndHashCode con todos los campos causa problemas con lazy loading y proxies de Hibernate.
@Slf4j — Logger automático
@Slf4j
@Service
public class OrderService {
public Order createOrder(CreateOrderRequest req) {
log.info("Creando orden para SKU: {}",
req.getSku());
// ...
log.debug("Orden {} guardada", order.getId());
return order;
}
}
// Equivale a:
// private static final Logger log =
// LoggerFactory.getLogger(OrderService.class);@NonNull — Validación de nulos
@Cleanup — Cierre automático
@SneakyThrows — Checked exceptions
Advertencia
Usa @SneakyThrows con precaución — oculta excepciones del compilador. Prefiere manejarlas explícitamente.
Entity (JPA)
@Entity
@Table(name = "products")
@Getter @Setter
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@ToString(exclude = "items")
public class Product {
@Id @GeneratedValue(strategy = IDENTITY)
@EqualsAndHashCode.Include
private Long id;
@Column(nullable = false, unique = true)
private String sku;
@Column(nullable = false)
private String name;
private BigDecimal price;
@OneToMany(mappedBy = "product")
private List<OrderItem> items;
}Service (Spring)
@Slf4j
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository repo;
public Product create(CreateProductRequest r) {
log.info("Creando producto: {}", r.sku());
Product p = new Product();
p.setSku(r.sku());
p.setName(r.name());
p.setPrice(r.price());
return repo.save(p);
}
}Tip
Patrón JPA recomendado: @Getter @Setter @NoArgsConstructor + @EqualsAndHashCode(onlyExplicitlyIncluded = true). Nunca @Data en entidades.
Cómo configurar tu aplicación de manera flexible y robusta.
application.properties
Tip
YAML es más legible por su estructura jerárquica. Ambos formatos son equivalentes.
@Value — Propiedades individuales
Advertencia
Prefiere @ConfigurationProperties sobre @Value. Es type-safe, validable, y agrupa la configuración lógicamente.
Los profiles permiten tener configuraciones diferentes por entorno (dev, staging, prod).
Advertencia
Evita fijar spring.profiles.active dentro del artefacto. Actívalo por env vars o CLI.
De menor a mayor prioridad:
graph TB
A["application.yml<br/>(en JAR)"] --> B
B["application-profile.yml<br/>(en JAR)"] --> C
C["application.yml<br/>(fuera JAR)"] --> D
D["application-profile.yml<br/>(fuera JAR)"]
style A fill:#6272a4,color:#fff
style B fill:#6272a4,color:#fff
style C fill:#50fa7b,color:#000
style D fill:#50fa7b,color:#000
graph TB
E["Variables de Entorno"] --> F
F["Argumentos CLI"] --> G
G["@TestPropertySource<br/>(solo en tests)"]
style E fill:#f1fa8c,color:#000
style F fill:#ffb86c,color:#000
style G fill:#ff5555,color:#fff
Tip
Env vars y CLI siempre ganan sobre .yml. Spring mapea: SPRING_DATASOURCE_URL → spring.datasource.url
Construyendo APIs RESTful con Spring Boot.
@RestController
@RequestMapping("/api/v1/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping // GET /api/v1/products
public List<Product> findAll() {
return productService.findAll();
}
@GetMapping("/{id}") // GET /api/v1/products/123
public Product findById(@PathVariable Long id) {
return productService.findById(id);
}
@PostMapping // POST /api/v1/products
@ResponseStatus(HttpStatus.CREATED)
public Product create(@Valid @RequestBody CreateProductRequest request) {
return productService.create(request);
}
@PutMapping("/{id}") // PUT /api/v1/products/123
public Product update(@PathVariable Long id,
@Valid @RequestBody UpdateProductRequest request) {
return productService.update(id, request);
}
@DeleteMapping("/{id}") // DELETE /api/v1/products/123
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) {
productService.delete(id);
}
}| Anotación | Método HTTP | Uso |
|---|---|---|
@GetMapping |
GET | Obtener recursos |
@PostMapping |
POST | Crear recursos |
@PutMapping |
PUT | Actualizar recurso completo |
@PatchMapping |
PATCH | Actualizar parcialmente |
@DeleteMapping |
DELETE | Eliminar recursos |
| Anotación de Parámetro | Uso | Ejemplo |
|---|---|---|
@PathVariable |
Variable en la URL | /products/{id} |
@RequestParam |
Query parameter | /products?category=gpu |
@RequestBody |
Cuerpo de la petición (JSON) | { "name": "RTX 4090" } |
@RequestHeader |
Header HTTP | Authorization: Bearer ... |
ResponseEntity permite controlar status code, headers, y body de la respuesta.
@RestController
@RequestMapping("/api/v1/products")
public class ProductController {
@GetMapping("/{id}")
public ResponseEntity<Product> findById(@PathVariable Long id) {
return productService.findById(id)
.map(ResponseEntity::ok) // 200 OK
.orElse(ResponseEntity.notFound().build()); // 404 Not Found
}
@PostMapping
public ResponseEntity<Product> create(@Valid @RequestBody CreateProductRequest req) {
Product product = productService.create(req);
URI location = URI.create("/api/v1/products/" + product.getId());
return ResponseEntity
.created(location) // 201 Created + Location header
.body(product);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
productService.delete(id);
return ResponseEntity.noContent().build(); // 204 No Content
}
}DTOs separan la representación de la API del modelo de dominio. Los records son ideales: inmutables, concisos y con equals/hashCode/toString automáticos.
Request DTO (lo que el cliente envía)
Response DTO (lo que el cliente recibe)
Tip
Nunca expongas tus @Entity directamente en la API. Usa DTOs para controlar qué datos se envían/reciben.
Acceso a datos simplificado con cero boilerplate.
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String sku;
@Column(nullable = false)
private String name;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal price;
@Column(nullable = false)
private Integer stock;
@CreationTimestamp
private Instant createdAt;
@UpdateTimestamp
private Instant updatedAt;
@Enumerated(EnumType.STRING)
private ProductStatus status = ProductStatus.ACTIVE;
// Constructor, getters, setters...
}| Anotación | Función |
|---|---|
@Entity |
Clase → entidad JPA |
@Table |
Nombre de tabla en BD |
@Id + @GeneratedValue |
PK auto-generada |
@Column |
Config de columna |
@CreationTimestamp |
Fecha de creación auto |
@UpdateTimestamp |
Fecha de update auto |
@Enumerated(STRING) |
Enum como texto |
Nota
Cada @Entity necesita un @Id. Usa GenerationType.IDENTITY para auto-incremento en PostgreSQL.
Spring Data JPA genera implementaciones automáticamente a partir de interfaces.
public interface ProductRepository extends JpaRepository<Product, Long> {
// Query Methods - Spring genera el SQL automáticamente
List<Product> findByStatus(ProductStatus status);
Optional<Product> findBySku(String sku);
List<Product> findByPriceBetween(BigDecimal min, BigDecimal max);
List<Product> findByNameContainingIgnoreCase(String keyword);
boolean existsBySku(String sku);
@Query("SELECT p FROM Product p WHERE p.stock < :threshold")
List<Product> findLowStock(@Param("threshold") int threshold);
@Query(value = "SELECT * FROM products WHERE stock > 0 ORDER BY price ASC",
nativeQuery = true)
List<Product> findAvailableOrderByPrice();
// Paginación y ordenamiento
Page<Product> findByStatus(ProductStatus status, Pageable pageable);
}Tip
Solo defines la interfaz — Spring Data genera la implementación @Repository automáticamente.
graph LR
REPO["ProductRepository<br/>(interface)"] -->|"extends"| JPA["JpaRepository<br/><Product, Long>"]
JPA -->|"extends"| CRUD["CrudRepository"]
JPA -->|"extends"| PAGING["PagingAndSortingRepository"]
style REPO fill:#ffb86c,color:#000
style JPA fill:#50fa7b,color:#000
| Interfaz | Métodos que hereda |
|---|---|
CrudRepository |
save(), findById(), findAll(), delete(), count() |
PagingAndSortingRepository |
findAll(Pageable), findAll(Sort) |
JpaRepository |
flush(), saveAndFlush(), deleteInBatch() |
Nota
Con JpaRepository heredas +20 métodos sin escribir una sola línea de implementación.
Spring Data interpreta el nombre del método y genera la query automáticamente.
| Método | SQL Generado |
|---|---|
findByName(String name) |
WHERE name = ?1 |
findByNameAndPrice(n, p) |
WHERE name = ?1 AND price = ?2 |
findByPriceGreaterThan(p) |
WHERE price > ?1 |
findByNameContaining(s) |
WHERE name LIKE %?1% |
findByStatusOrderByPriceDesc(s) |
WHERE status = ?1 ORDER BY price DESC |
countByStatus(s) |
SELECT COUNT(*) WHERE status = ?1 |
deleteByStatus(s) |
DELETE WHERE status = ?1 |
existsBySku(s) |
SELECT EXISTS(... WHERE sku = ?1) |
Tip
Para queries complejas, usa @Query con JPQL o SQL nativo en vez de nombres kilométricos.
@Entity
public class Order {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY) // Muchas órdenes → 1 cliente
@JoinColumn(name = "customer_id")
private Customer customer;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> items = new ArrayList<>(); // 1 orden → muchos items
}
@Entity
public class OrderItem {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id")
private Order order;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id")
private Product product;
private Integer quantity;
}Advertencia
Siempre usa fetch = FetchType.LAZY para evitar cargar datos innecesarios (N+1 problem).
En esta sesión cubrimos:
Tip
En la Parte 2 continuamos con: validación y errores, seguridad con Spring Security, testing por capas, actuator y WebFlux intermedio.

Spring Boot: 0 to Hero (Parte 1) - ENYOI