Pular para o conteúdo principal

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

Analogia​

Sem Lombok:

Você é um chef que precisa:

  1. Lavar os legumes
  2. Cortar os legumes
  3. Cozinhar o prato
  4. 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​

  1. Abra Extensões (Ctrl+Shift+X ou Cmd+Shift+X)
  2. Pesquise: "Lombok Annotations Support for VS Code"
  3. Instale a extensão de Gabriel Basilio Brito
  4. Reinicie o VS Code

IntelliJ IDEA​

  1. Vá em File → Settings → Plugins
  2. Pesquise: "Lombok"
  3. Instale o plugin
  4. Reinicie o IntelliJ

Eclipse​

  1. Baixe lombok.jar em projectlombok.org
  2. Execute: java -jar lombok.jar
  3. Selecione sua instalação do Eclipse
  4. 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() e hashCode()
  • 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 vazio
  • Product(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çãoGera
@DataGetters, Setters, toString, equals, hashCode
@Getter / @SetterGetters ou Setters
@NoArgsConstructorConstrutor vazio
@AllArgsConstructorConstrutor completo
@RequiredArgsConstructorConstrutor com final/@NonNull
@BuilderPattern Builder
@ToStringtoString()
@EqualsAndHashCodeequals() 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.

➡️ TP4.1: Migração PostgreSQL

⬅️ Capítulo anterior: Hibernate DDL

⬅️ Voltar ao Módulo 4