Springboot and Lombok

70 Views Asked by At

I'm using Springboot and MySQL in order to make an API, I'm currently facing a problem, and I cannot find any answers on the internet :

The problem is in my association Table "EtapeRecette.java" below i try to initialize the getters and setters with Lombok, but when i use @Data, i get no errors but when I findAll() with the RecetteRepository, i get an array of "etapes" empty, but if I set the getters and setters in "EtapeRecette.java" manually it works all good, same if i use @Getters and @Setters, is there any reasons @Data doesnt work?

I got 3 tables :

Recette.java

package org.microferme.charme.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.sql.Timestamp;
import java.util.Date;
import java.util.Set;

@Data
@NoArgsConstructor
@Entity
@Table(name="Recette")
public class Recette {

    @Id
    private String id;

    @Column(name = "nom")
    private String nom;

    @Column(name = "nb_personne")
    private int nbPersonne;

    @Column(name = "prix_par_personne")
    private float prixParPersonne;

    @OneToMany(mappedBy = "recette", cascade = CascadeType.ALL, orphanRemoval = true)
    @JsonIgnoreProperties("recette")
    private Set<MediaRecette> medias;

    @OneToMany(mappedBy = "recette", cascade = CascadeType.ALL, orphanRemoval = true)
    @JsonIgnoreProperties("recette")
    private Set<StockUtile> ingredients;

    @OneToMany(mappedBy = "recette", cascade = CascadeType.ALL, orphanRemoval = true)
    @JsonIgnoreProperties("recette")
    private Set<EtapeRecette> etapes;

    public Recette(String nom, int nbPersonne, float prixParPersonne) {
        //Si l'id n'est pas spécifié on prend la timestamp actuelle
        Date date = new Date();
        this.id = String.valueOf(new Timestamp(date.getTime()).getTime());

        this.nom = nom;
        this.nbPersonne = nbPersonne;
        this.prixParPersonne = prixParPersonne;
    }

    public void addMedia(MediaRecette media) {
        this.medias.add(media);
    }

    public void addIngredient(StockUtile ingredient) {
        this.ingredients.add(ingredient);
    }

    public void addEtape(EtapeRecette etapeRecette) {
        this.etapes.add(etapeRecette);
    }

}

Etape.java

package org.microferme.charme.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;
import java.util.Set;

@Data
@Entity
@Table(name = "Etape")
public class Etape {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(name = "ordre")
    private float ordre;

    @Column(name = "duree")
    private float duree;

    @Column(name = "description")
    private String description;

    @ManyToOne
    private Ustensile ustensile;

    public Etape() {
    }

    public Etape(float ordre, float duree, String description, Ustensile ustensile) {
        this.ordre = ordre;
        this.duree = duree;
        this.description = description;
        this.ustensile = ustensile;
    }
}

EtapeRecette which is an association table between Recette and Etape in order to have a many to many relationship

package org.microferme.charme.model;

import jakarta.persistence.*;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.microferme.charme.model.embeddable.EtapeRecetteId;


@Entity
@Data
@Table(name = "EtapeRecette")
public class EtapeRecette {

    @EmbeddedId
    private EtapeRecetteId id;

    @ManyToOne(cascade = CascadeType.ALL)
    @MapsId("recetteId")
    private Recette recette;

    @ManyToOne(cascade = CascadeType.ALL)
    @MapsId("etapeId")
    private Etape etape;

    public EtapeRecette() {
    }

    public EtapeRecette(Recette recette, Etape etape) {
        this.id = new EtapeRecetteId(recette.getId(), etape.getId());
        this.recette = recette;
        this.etape = etape;
    }

}
2

There are 2 best solutions below

1
Mar-Z On BEST ANSWER

The difference is that @Data generates not only getters and setters but also the equals and hashCode methods. The default implementation of these methods does not work well with Hibernate relationships like one-to-many and many-to-one. You are better of if you use a combination of @Getter @Setter and @EqualsAndHashCode(onlyExplicitlyIncluded = true) and add @EqualsAndHashCode.Include to the ID field. Like this:

@Entity
@Getter
@Setter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Table(name="Recette")
public class Recette {

    @Id
    @EqualsAndHashCode.Include
    private String id;

    @OneToMany(mappedBy = "recette", cascade = CascadeType.ALL, orphanRemoval = true)
    @JsonIgnoreProperties("recette")
    private Set<EtapeRecette> etapes;

Based on: https://thorben-janssen.com/lombok-hibernate-how-to-avoid-common-pitfalls/

1
Dumbo On

@Data annotation

@Data annotation is equivalent to the combination of the following annotations:

  • @Getter
  • @Setter
  • @RequiredArgsConstructor
  • @ToString
  • @EqualsAndHashCode

@RequiredArgsConstructor issue

All entities should have public no-arguments constructor. It's not generated by default when @RequiredArgsConstructor is used. To fix that when using @Data need to add @NoArgsConstructor


@EqualsAndHashCode issue

This annotation causing performance and memory consumption issues when fetching entities. In example using lazy @OneToMany mapping - when calling hashCode() all mapped entities would be fetched even it's lazy loading.

Also, it can cause issues with using entities in HashSet or HashMap when saving entity to database and id is generated on save. If entity object would be saved in hashSet and then saved - same entity will not be in hashSet. Because the hashes are different now.


@ToString issue

Similar issue as @EqualsAndHashCode with @OneToMany mapping. It can be used only when all lazy fields are excluded. You can add @ToString.Exclude on field or @ToString(onlyExplicitlyIncluded = true) on entity class.


Conclusion

Don't use @Data for entities, because it has combination of another lobok annotations that is cannot be used for entities. Exclude lazy fields when using @ToString. Always add @NoArgsConstructor if entity has builder or all-arguments constructor. Correct entity example:

@Getter
@Setter
@ToString(onlyExplicitlyIncluded = true)
@Entity
@Table(name="Recette")
public class Recette { ... }