Aller au contenu principal

Chapitre 4: Lombok - Réduire le Code Boilerplate

Introduction​

Question: Combien de lignes de code pour une simple classe Product?

Sans Lombok: ~100 lignes (getters, setters, constructeurs, toString, equals, hashCode)

Avec Lombok: ~10 lignes

Lombok = Productivité 🚀


Qu'est-ce que Lombok?​

Définition​

Lombok est une bibliothèque Java qui génère automatiquement du code répétitif (boilerplate) grâce à des annotations.

Boilerplate = Code répétitif et ennuyeux 😴

Exemples:

  • Getters et setters
  • Constructeurs
  • toString()
  • equals() et hashCode()

Analogie​

Sans Lombok:

Vous êtes un chef qui doit:

  1. Laver les légumes
  2. Couper les légumes
  3. Cuisiner le plat
  4. Nettoyer la cuisine

Avec Lombok:

Vous dites "Préparez-moi un plat" et un assistant fait 1, 2, et 4 automatiquement. Vous vous concentrez sur 3 (la cuisine).


Installation de Lombok​

Étape 1: Ajouter la Dépendance Maven​

Fichier: pom.xml

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

Note: <optional>true</optional> indique que Lombok n'est utilisé qu'à la compilation.


Étape 2: Installer le Plugin IDE​

VS Code​

  1. Ouvrez Extensions (Ctrl+Shift+X ou Cmd+Shift+X)
  2. Recherchez: "Lombok Annotations Support for VS Code"
  3. Installez l'extension de Gabriel Basilio Brito
  4. Redémarrez VS Code

IntelliJ IDEA​

  1. Allez dans File → Settings → Plugins
  2. Recherchez: "Lombok"
  3. Installez le plugin
  4. Redémarrez IntelliJ

Eclipse​

  1. Téléchargez lombok.jar depuis projectlombok.org
  2. Exécutez: java -jar lombok.jar
  3. Sélectionnez votre installation Eclipse
  4. Redémarrez Eclipse

Étape 3: Vérification​

Créez une classe de test:

package com.example.demo.model;

import lombok.Data;

@Data
public class TestLombok {
private String name;
private Integer age;
}

Compilez:

mvn clean compile

Vérifiez dans la console:

[INFO] BUILD SUCCESS

✅ Lombok fonctionne!


Annotations Principales​

1. @Data​

Génère automatiquement:

  • Tous les getters
  • Tous les setters
  • toString()
  • equals() et hashCode()
  • Constructeur pour les champs final

Avant Lombok​

Fichier: 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;

// Constructeur vide
public Product() {}

// Constructeur avec paramètres
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() et hashCode() (50+ lignes de plus)
@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 lignes


Après Lombok​

Fichier: 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 lignes

Réduction: De 100 lignes à 15 lignes! (85% de réduction) 🎉


2. @Getter / @Setter​

Génère les getters/setters individuellement.

Exemple:

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;
}

Équivalent à:

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; }

Utilisation au niveau de la classe:

@Getter
@Setter
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

Génère tous les getters et setters pour tous les champs.


3. @NoArgsConstructor / @AllArgsConstructor​

@NoArgsConstructor​

Génère un constructeur sans arguments.

@NoArgsConstructor
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

Équivalent à:

public Product() {}

@AllArgsConstructor​

Génère un constructeur avec tous les arguments.

@AllArgsConstructor
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

Équivalent à:

public Product(Long id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
}

Combinaison courante:

@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

Génère:

  • Product() - Constructeur vide
  • Product(Long id, String name, Double price) - Constructeur complet

4. @RequiredArgsConstructor​

Génère un constructeur avec les champs final ou @NonNull.

Exemple:

@RequiredArgsConstructor
@Entity
public class Product {
private Long id;

@NonNull
private String name;

@NonNull
private Double price;
}

Génère:

public Product(String name, Double price) {
this.name = name;
this.price = price;
}

Note: id est exclu car non final et non @NonNull.


5. @Builder​

Pattern Builder pour création d'objets.

Exemple:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
private Double price;
}

Utilisation:

Product product = Product.builder()
.name("Laptop")
.price(999.99)
.build();

Avantages:

  • Code lisible
  • Paramètres nommés
  • Ordre flexible

6. @ToString​

Génère automatiquement toString().

Exemple:

@ToString
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

Équivalent à:

@Override
public String toString() {
return "Product(id=" + id + ", name=" + name + ", price=" + price + ")";
}

Exclure des champs:

@ToString(exclude = {"price"})
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

Résultat:

Product(id=1, name=Laptop)

7. @EqualsAndHashCode​

Génère equals() et hashCode().

Exemple:

@EqualsAndHashCode
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

Équivalent à:

@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);
}

Utiliser uniquement l'ID:

@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
public class Product {
@EqualsAndHashCode.Include
@Id
private Long id;

private String name;
private Double price;
}

✅ Bonne pratique pour les entités JPA!


Combinaisons Courantes​

Pour les Entités JPA​

Configuration recommandée:

@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;
}

Génère:

  • Getters et setters
  • Constructeur vide (requis par JPA)
  • Constructeur complet
  • toString(), equals(), hashCode()

Pour les DTOs​

Configuration recommandée:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductDTO {
private Long id;
private String name;
private Double price;
}

Utilisation:

ProductDTO dto = ProductDTO.builder()
.id(1L)
.name("Laptop")
.price(999.99)
.build();

Pour les Services (Injection de Dépendances)​

Configuration recommandée:

@Service
@RequiredArgsConstructor
public class ProductService {

private final ProductRepository productRepository;

// Pas besoin de constructeur!

public List<Product> findAll() {
return productRepository.findAll();
}
}

Lombok génère automatiquement:

public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}

Avantages et Inconvénients​

Avantages​

✅ Moins de code boilerplate (85% de réduction)
✅ Code plus lisible (focus sur la logique métier)
✅ Maintenance facilitée (pas de getters/setters à mettre à jour)
✅ Productivité accrue (développement plus rapide)
✅ Moins d'erreurs (code généré automatiquement)


Inconvénients​

❌ Dépendance externe (ajout d'une bibliothèque)
❌ Nécessite plugin IDE (pour la complétion de code)
❌ Peut masquer la complexité (code invisible)
❌ Courbe d'apprentissage (pour les débutants)
❌ Débogage plus complexe (code généré non visible)


Meilleures Pratiques​

1. Éviter @Data pour les Entités avec Relations​

Problème:

@Entity
@Data // ATTENTION!
public class Order {
@Id
private Long id;

@OneToMany
private List<OrderItem> items;
}

Risque: toString() peut causer des boucles infinies avec les relations bidirectionnelles.

Solution:

@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. Utiliser @RequiredArgsConstructor pour les Services​

Bon:

@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
}

Mauvais:

@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
}

Pourquoi? L'injection par constructeur est préférable (testabilité, immutabilité).


3. Toujours Combiner avec @NoArgsConstructor pour JPA​

JPA requiert un constructeur vide:

@Entity
@Data
@NoArgsConstructor // OBLIGATOIRE pour JPA
@AllArgsConstructor
public class Product {
@Id
private Long id;
private String name;
}

Résumé​

Annotations Essentielles​

AnnotationGénère
@DataGetters, Setters, toString, equals, hashCode
@Getter / @SetterGetters ou Setters
@NoArgsConstructorConstructeur vide
@AllArgsConstructorConstructeur complet
@RequiredArgsConstructorConstructeur avec final/@NonNull
@BuilderPattern Builder
@ToStringtoString()
@EqualsAndHashCodeequals() et hashCode()

Configuration JPA Standard​

@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private Double price;
}

✅ Code minimal, fonctionnalité maximale!


Prochaine Étape​

Vous avez maintenant tous les outils théoriques du Module 4! Passez au TP4.1 pour migrer votre application vers PostgreSQL avec Lombok.

➡️ TP4.1: Migration PostgreSQL

⬅️ Chapitre précédent: Hibernate DDL

⬅️ Retour au Module 4