Saltar al contenido principal

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() y hashCode()

Analogía​

Sin Lombok:

Usted es un chef que debe:

  1. Lavar los vegetales
  2. Cortar los vegetales
  3. Cocinar el plato
  4. 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​

  1. Abra Extensiones (Ctrl+Shift+X o Cmd+Shift+X)
  2. Busque: "Lombok Annotations Support for VS Code"
  3. Instale la extensión de Gabriel Basilio Brito
  4. Reinicie VS Code

IntelliJ IDEA​

  1. Vaya a File → Settings → Plugins
  2. Busque: "Lombok"
  3. Instale el plugin
  4. Reinicie IntelliJ

Eclipse​

  1. Descargue lombok.jar desde projectlombok.org
  2. Ejecute: java -jar lombok.jar
  3. Seleccione su instalación de Eclipse
  4. 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() y hashCode()
  • 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ío
  • Product(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ónGenera
@DataGetters, Setters, toString, equals, hashCode
@Getter / @SetterGetters o Setters
@NoArgsConstructorConstructor vacío
@AllArgsConstructorConstructor completo
@RequiredArgsConstructorConstructor con final/@NonNull
@BuilderPatrón Builder
@ToStringtoString()
@EqualsAndHashCodeequals() 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

⬅️ Capítulo anterior: Hibernate DDL

⬅️ Volver al Módulo 4