Capítulo 4: Lombok - Reducir el Código Boilerplate
Introducción
Pregunta: ¿Cuántas líneas de código para una simple clase Product?
Sin Lombok: ~100 líneas (getters, setters, constructores, toString, equals, hashCode)
Con Lombok: ~10 líneas
Lombok = Productividad 🚀
¿Qué es Lombok?
Definición
Lombok es una biblioteca de Java que genera automáticamente código repetitivo (boilerplate) mediante anotaciones.
Boilerplate = Código repetitivo y aburrido 😴
Ejemplos:
- Getters y setters
- Constructores
toString()equals()yhashCode()
Analogía
Sin Lombok:
Usted es un chef que debe:
- Lavar los vegetales
- Cortar los vegetales
- Cocinar el plato
- Limpiar la cocina
Con Lombok:
Usted dice "Prepáreme un plato" y un asistente hace 1, 2 y 4 automáticamente. Usted se concentra en 3 (la cocina).
Instalación de Lombok
Paso 1: Agregar la Dependencia Maven
Archivo: pom.xml
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
Nota: <optional>true</optional> indica que Lombok solo se utiliza en compilación.
Paso 2: Instalar el Plugin del IDE
VS Code
- Abra Extensiones (
Ctrl+Shift+XoCmd+Shift+X) - Busque: "Lombok Annotations Support for VS Code"
- Instale la extensión de Gabriel Basilio Brito
- Reinicie VS Code
IntelliJ IDEA
- Vaya a File → Settings → Plugins
- Busque: "Lombok"
- Instale el plugin
- Reinicie IntelliJ
Eclipse
- Descargue
lombok.jardesde projectlombok.org - Ejecute:
java -jar lombok.jar - Seleccione su instalación de Eclipse
- Reinicie Eclipse
Paso 3: Verificación
Cree una clase de prueba:
package com.example.demo.model;
import lombok.Data;
@Data
public class TestLombok {
private String name;
private Integer age;
}
Compile:
mvn clean compile
Verifique en la consola:
[INFO] BUILD SUCCESS
✅ ¡Lombok funciona!
Anotaciones Principales
1. @Data
Genera automáticamente:
- Todos los getters
- Todos los setters
toString()equals()yhashCode()- Constructor para los campos
final
Antes de Lombok
Archivo: model/Product.java
package com.example.demo.model;
import jakarta.persistence.*;
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Double price;
// Constructor vacío
public Product() {}
// Constructor con parámetros
public Product(String name, Double price) {
this.name = name;
this.price = price;
}
// Getters
public Long getId() {
return id;
}
public String getName() {
return name;
}
public Double getPrice() {
return price;
}
// Setters
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setPrice(Double price) {
this.price = price;
}
// toString()
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
'}';
}
// equals() y hashCode() (más de 50 líneas)
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Product product = (Product) o;
return Objects.equals(id, product.id) &&
Objects.equals(name, product.name) &&
Objects.equals(price, product.price);
}
@Override
public int hashCode() {
return Objects.hash(id, name, price);
}
}
Total: ~100 líneas
Después de Lombok
Archivo: model/Product.java
package com.example.demo.model;
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Double price;
}
Total: ~15 líneas
Reducción: ¡De 100 líneas a 15 líneas! (85% de reducción) 🎉
2. @Getter / @Setter
Genera los getters/setters individualmente.
Ejemplo:
package com.example.demo.model;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
@Getter @Setter
private String name;
@Getter @Setter
private Double price;
}
Equivalente a:
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Double getPrice() { return price; }
public void setPrice(Double price) { this.price = price; }
Uso a nivel de clase:
@Getter
@Setter
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}
Genera todos los getters y setters para todos los campos.
3. @NoArgsConstructor / @AllArgsConstructor
@NoArgsConstructor
Genera un constructor sin argumentos.
@NoArgsConstructor
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}
Equivalente a:
public Product() {}
@AllArgsConstructor
Genera un constructor con todos los argumentos.
@AllArgsConstructor
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}
Equivalente a:
public Product(Long id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
}
Combinación común:
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}
Genera:
Product()- Constructor vacíoProduct(Long id, String name, Double price)- Constructor completo
4. @RequiredArgsConstructor
Genera un constructor con los campos final o @NonNull.
Ejemplo:
@RequiredArgsConstructor
@Entity
public class Product {
private Long id;
@NonNull
private String name;
@NonNull
private Double price;
}
Genera:
public Product(String name, Double price) {
this.name = name;
this.price = price;
}
Nota: id se excluye porque no es final ni @NonNull.
5. @Builder
Patrón Builder para creación de objetos.
Ejemplo:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
private Double price;
}
Uso:
Product product = Product.builder()
.name("Laptop")
.price(999.99)
.build();
Ventajas:
- Código legible
- Parámetros nombrados
- Orden flexible
6. @ToString
Genera automáticamente toString().
Ejemplo:
@ToString
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}
Equivalente a:
@Override
public String toString() {
return "Product(id=" + id + ", name=" + name + ", price=" + price + ")";
}
Excluir campos:
@ToString(exclude = {"price"})
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}
Resultado:
Product(id=1, name=Laptop)
7. @EqualsAndHashCode
Genera equals() y hashCode().
Ejemplo:
@EqualsAndHashCode
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}
Equivalente a:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Product product = (Product) o;
return Objects.equals(id, product.id) &&
Objects.equals(name, product.name) &&
Objects.equals(price, product.price);
}
@Override
public int hashCode() {
return Objects.hash(id, name, price);
}
Usar solo el ID:
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
public class Product {
@EqualsAndHashCode.Include
@Id
private Long id;
private String name;
private Double price;
}
✅ ¡Buena práctica para entidades JPA!
Combinaciones Comunes
Para Entidades JPA
Configuración recomendada:
@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
private String name;
@Min(0)
private Double price;
}
Genera:
- Getters y setters
- Constructor vacío (requerido por JPA)
- Constructor completo
toString(),equals(),hashCode()
Para los DTOs
Configuración recomendada:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductDTO {
private Long id;
private String name;
private Double price;
}
Uso:
ProductDTO dto = ProductDTO.builder()
.id(1L)
.name("Laptop")
.price(999.99)
.build();
Para los Servicios (Inyección de Dependencias)
Configuración recomendada:
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
// ¡No necesita constructor!
public List<Product> findAll() {
return productRepository.findAll();
}
}
Lombok genera automáticamente:
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
Ventajas y Desventajas
Ventajas
✅ Menos código boilerplate (85% de reducción)
✅ Código más legible (enfoque en la lógica de negocio)
✅ Mantenimiento facilitado (no hay getters/setters que actualizar)
✅ Productividad aumentada (desarrollo más rápido)
✅ Menos errores (código generado automáticamente)
Desventajas
❌ Dependencia externa (agrega una biblioteca)
❌ Requiere plugin del IDE (para autocompletado de código)
❌ Puede ocultar la complejidad (código invisible)
❌ Curva de aprendizaje (para principiantes)
❌ Depuración más compleja (código generado no visible)
Mejores Prácticas
1. Evitar @Data para Entidades con Relaciones
Problema:
@Entity
@Data // ¡ATENCIÓN!
public class Order {
@Id
private Long id;
@OneToMany
private List<OrderItem> items;
}
Riesgo: toString() puede causar bucles infinitos con relaciones bidireccionales.
Solución:
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = {"items"})
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Order {
@EqualsAndHashCode.Include
@Id
private Long id;
@OneToMany
private List<OrderItem> items;
}
2. Usar @RequiredArgsConstructor para los Servicios
Bueno:
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
}
Malo:
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
}
¿Por qué? La inyección por constructor es preferible (testabilidad, inmutabilidad).
3. Siempre Combinar con @NoArgsConstructor para JPA
JPA requiere un constructor vacío:
@Entity
@Data
@NoArgsConstructor // OBLIGATORIO para JPA
@AllArgsConstructor
public class Product {
@Id
private Long id;
private String name;
}
Resumen
Anotaciones Esenciales
| Anotación | Genera |
|---|---|
@Data | Getters, Setters, toString, equals, hashCode |
@Getter / @Setter | Getters o Setters |
@NoArgsConstructor | Constructor vacío |
@AllArgsConstructor | Constructor completo |
@RequiredArgsConstructor | Constructor con final/@NonNull |
@Builder | Patrón Builder |
@ToString | toString() |
@EqualsAndHashCode | equals() y hashCode() |
Configuración JPA Estándar
@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Double price;
}
✅ ¡Código mínimo, funcionalidad máxima!
Próximo Paso
¡Ahora tiene todas las herramientas teóricas del Módulo 4! Pase al TP4.1 para migrar su aplicación a PostgreSQL con Lombok.
➡️ TP4.1: Migración PostgreSQL