Capítulo 4: Lombok - Reduza o Código Boilerplate
Introdução
Pergunta: Quantas linhas de código para uma simples classe Product?
Sem Lombok: ~100 linhas (getters, setters, construtores, toString, equals, hashCode)
Com Lombok: ~10 linhas
Lombok = Produtividade 🚀
O que é Lombok?
Definição
Lombok é uma biblioteca Java que gera automaticamente código repetitivo (boilerplate) por meio de anotações.
Boilerplate = Código repetitivo e entediante 😴
Exemplos:
- Getters e setters
- Construtores
toString()equals()ehashCode()
Analogia
Sem Lombok:
Você é um chef que precisa:
- Lavar os legumes
- Cortar os legumes
- Cozinhar o prato
- Limpar a cozinha
Com Lombok:
Você diz "Prepare um prato para mim" e um assistente faz 1, 2 e 4 automaticamente. Você se concentra no 3 (cozinhar).
Instalação do Lombok
Etapa 1: Adicionar a Dependência Maven
Arquivo: pom.xml
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
Nota: <optional>true</optional> indica que Lombok é usado apenas em tempo de compilação.
Etapa 2: Instalar o Plugin no IDE
VS Code
- Abra Extensões (
Ctrl+Shift+XouCmd+Shift+X) - Pesquise: "Lombok Annotations Support for VS Code"
- Instale a extensão de Gabriel Basilio Brito
- Reinicie o VS Code
IntelliJ IDEA
- Vá em File → Settings → Plugins
- Pesquise: "Lombok"
- Instale o plugin
- Reinicie o IntelliJ
Eclipse
- Baixe
lombok.jarem projectlombok.org - Execute:
java -jar lombok.jar - Selecione sua instalação do Eclipse
- Reinicie o Eclipse
Etapa 3: Verificação
Crie uma classe de teste:
package com.example.demo.model;
import lombok.Data;
@Data
public class TestLombok {
private String name;
private Integer age;
}
Compile:
mvn clean compile
Verifique no console:
[INFO] BUILD SUCCESS
✅ Lombok está funcionando!
Principais Anotações
1. @Data
Gera automaticamente:
- Todos os getters
- Todos os setters
toString()equals()ehashCode()- Construtor para campos
final
Antes do Lombok
Arquivo: 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;
// Construtor vazio
public Product() {}
// Construtor com 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() e hashCode() (50+ linhas a mais)
@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 linhas
Depois do Lombok
Arquivo: 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 linhas
Redução: De 100 linhas para 15 linhas! (85% de redução) 🎉
2. @Getter / @Setter
Gera getters/setters individualmente.
Exemplo:
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 no nível da classe:
@Getter
@Setter
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}
Gera todos os getters e setters para todos os campos.
3. @NoArgsConstructor / @AllArgsConstructor
@NoArgsConstructor
Gera um construtor sem argumentos.
@NoArgsConstructor
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}
Equivalente a:
public Product() {}
@AllArgsConstructor
Gera um construtor com todos os 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;
}
Combinação comum:
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}
Gera:
Product()- Construtor vazioProduct(Long id, String name, Double price)- Construtor completo
4. @RequiredArgsConstructor
Gera um construtor com os campos final ou @NonNull.
Exemplo:
@RequiredArgsConstructor
@Entity
public class Product {
private Long id;
@NonNull
private String name;
@NonNull
private Double price;
}
Gera:
public Product(String name, Double price) {
this.name = name;
this.price = price;
}
Nota: id é excluído pois não é final nem @NonNull.
5. @Builder
Pattern Builder para criação de objetos.
Exemplo:
@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();
Vantagens:
- Código legível
- Parâmetros nomeados
- Ordem flexível
6. @ToString
Gera automaticamente o toString().
Exemplo:
@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
Gera equals() e hashCode().
Exemplo:
@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 apenas o ID:
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
public class Product {
@EqualsAndHashCode.Include
@Id
private Long id;
private String name;
private Double price;
}
✅ Boa prática para entidades JPA!
Combinações Comuns
Para Entidades JPA
Configuração 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;
}
Gera:
- Getters e setters
- Construtor vazio (requerido pelo JPA)
- Construtor completo
toString(),equals(),hashCode()
Para DTOs
Configuração 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 Services (Injeção de Dependências)
Configuração recomendada:
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
// Não precisa de construtor!
public List<Product> findAll() {
return productRepository.findAll();
}
}
Lombok gera automaticamente:
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
Vantagens e Desvantagens
Vantagens
✅ Menos código boilerplate (redução de 85%)
✅ Código mais legível (foco na lógica de negócio)
✅ Facilidade de manutenção (não precisa atualizar getters/setters)
✅ Produtividade aumentada (desenvolvimento mais rápido)
✅ Menos erros (código gerado automaticamente)
Desvantagens
❌ Dependência externa (adição de uma biblioteca)
❌ Necessita de plugin no IDE (para autocompletar código)
❌ Pode mascarar complexidade (código invisível)
❌ Curva de aprendizado (para iniciantes)
❌ Depuração mais complexa (código gerado não visível)
Melhores Práticas
1. Evite @Data para Entidades com Relações
Problema:
@Entity
@Data // ATENÇÃO!
public class Order {
@Id
private Long id;
@OneToMany
private List<OrderItem> items;
}
Risco: toString() pode causar loops infinitos com relações bidirecionais.
Solução:
@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. Use @RequiredArgsConstructor para Services
Bom:
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
}
Ruim:
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
}
Por quê? Injeção via construtor é preferível (testabilidade, imutabilidade).
3. Sempre Combine com @NoArgsConstructor para JPA
JPA exige um construtor vazio:
@Entity
@Data
@NoArgsConstructor // OBRIGATÓRIO para JPA
@AllArgsConstructor
public class Product {
@Id
private Long id;
private String name;
}
Resumo
Anotações Essenciais
| Anotação | Gera |
|---|---|
@Data | Getters, Setters, toString, equals, hashCode |
@Getter / @Setter | Getters ou Setters |
@NoArgsConstructor | Construtor vazio |
@AllArgsConstructor | Construtor completo |
@RequiredArgsConstructor | Construtor com final/@NonNull |
@Builder | Pattern Builder |
@ToString | toString() |
@EqualsAndHashCode | equals() e hashCode() |
Configuração JPA Padrão
@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, funcionalidade máxima!
Próximo Passo
Agora você tem todas as ferramentas teóricas do Módulo 4! Avance para o TP4.1 para migrar sua aplicação para PostgreSQL com Lombok.