Home > Blockchain >  Spring JPA hibernate how to persist children (remove, add, or update) from @OneToMany parent column?
Spring JPA hibernate how to persist children (remove, add, or update) from @OneToMany parent column?

Time:09-17

I'm trying to solve this problem since a while and I haven't achieved a 100% solution. First of all I have to describe my problem. I'm developping a restaurant application, and amoung the Entities, I have the Entity Ingredient and as you know Ingredient can consist of other Ingredient with a specific quantity. So I created an Entity SubIngredient with an Embedded Id.

And to persist subIngredients list I tried a combinations of Cascade and orphanRemoval, each combination worked for some operation but not for the others.

I started by using CascadeType.ALL and the new subIngredient persisted successfuly from the @OneToMany propertiy, But if I try to remove an subIngredient from the subIngredients list and save this error appear.

java.lang.StackOverflowError: null at com.mysql.cj.NativeSession.execSQL(NativeSession.java:1109) ~[mysql-connector-java-8.0.23.jar:8.0.23]......

I loked in the net for a solution and I find the I have to use orphanremoval = true I tried it but it didn't work until I changed cascade from CascadeType.ALL to CascadeType.PERSIST. But this one make the persistance of new SubIngredient this error aprear

Caused by: javax.persistence.EntityNotFoundException: Unable to find com.example.Resto.domain.SubIngredient with id com.example.Resto.domain.SubIngredientKey@51b11186........

These are my Enities:

@Entity
public class Ingredient {

@Id
@GeneratedValue( strategy = GenerationType.IDENTITY)
@Column(name="ID")
private long id;


@NotNull
@Column(unique=true)
private String name;
private String photoContentType;
@Lob
private byte[] photo;

@JsonIgnoreProperties({"photoContentType","photo"})
@ManyToOne
private IngredientType ingredientType;


@OneToMany(mappedBy = "embId.ingredientId", fetch = FetchType.EAGER,   
cascade = CascadeType.ALL /*or orphanRemoval = true, cascade = CascadeType.PERSIST*/ )
private Set<SubIngredient> subIngredients =  new HashSet<SubIngredient>();

getters and setters.....

And

@Entity
@AssociationOverrides({
@AssociationOverride(name = "embId.ingredientId", 
    joinColumns = @JoinColumn(name = "ING_ID")),
@AssociationOverride(name = "embId.subIngredientId", 
    joinColumns = @JoinColumn(name = "SUB_ING_ID")) })
public class SubIngredient {


@EmbeddedId
private SubIngredientKey embId = new SubIngredientKey();

private double quantity;

getters and setters....

And

@Embeddable
public class SubIngredientKey implements Serializable{

@ManyToOne(cascade = CascadeType.ALL)
private Ingredient ingredientId;


@ManyToOne(cascade = CascadeType.ALL)
private Ingredient subIngredientId;

getters and setters...

CodePudding user response:

The stackoverflow happen because you use a Set<> with Hibernate. When Hibernate retrieves the entities from your DB, it will fill up the Set<> with each entities. In order to that, hashode/equals will be used to determine wether or not the entitie is already present in the Set<>. By default, when you call the hashcode of Ingredient, this happen:

hashcode Ingredient -> hashcode SubIngredient -> hashcode Ingredient 

which will result in an infinite call of hashcode method. That's why you have a stackoverflow error.

The same thing will happen with equals/toString.

So to avoid such an issue, it's best to override hashcode, equals and toString.

CodePudding user response:

I have solved the problem by making some changes to may Entities and override equals/hashcode methods thanks Pilpo.

@Embeddable
public class SubIngredientKey implements Serializable{


private Long ingredientId;


private Long subIngredientId;
/**
 * @return the ingredientId
 */


@Override
public int hashCode() {
    return Objects.hash(ingredientId, subIngredientId);
}
@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (!(obj instanceof SubIngredientKey)) {
        return false;
    }
    SubIngredientKey other = (SubIngredientKey) obj;
    return Objects.equals(ingredientId, other.ingredientId)
            && Objects.equals(subIngredientId, other.subIngredientId);
}

}



@Entity
public class SubIngredient {


@EmbeddedId
private SubIngredientKey embId = new SubIngredientKey();

@ManyToOne(fetch = FetchType.LAZY)
@MapsId("ingredientId")
private Ingredient ingredient;

@ManyToOne(fetch = FetchType.LAZY)
@MapsId("subIngredientId")
private Ingredient subIngredient;

private double quantity;

@JsonIgnore
public SubIngredientKey getId() {
    return embId;
}
public void setId(SubIngredientKey id) {
    this.embId = id;
}


@JsonIgnoreProperties({"subIngredients","photo","photoContentType","ingredientType"})
public Ingredient getIngredient() {
    return ingredient;
}
public void setIngredient(Ingredient ingredient) {
    this.ingredient = ingredient;
}



@JsonIgnoreProperties({"subIngredients","photo","photoContentType","ingredientType"})
public Ingredient getSubIngredient() {
    return subIngredient;
}
public void setSubIngredient(Ingredient subIngredient) {
    this.subIngredient = subIngredient;
}

public double getQuantity() {
    return quantity;
}

public void setQuantity(double quantity) {
    this.quantity = quantity;
}

@Override
public String toString() {
    return "subIngredient= "   getSubIngredient().getName()   " , quantity=  "   getQuantity();
}


@Override
public int hashCode() {
    return Objects.hash(ingredient,subIngredient);
}
@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (!(obj instanceof SubIngredient)) {
        return false;
    }
    SubIngredient other = (SubIngredient) obj;
    return Objects.equals(ingredient, other.ingredient) && Objects.equals(subIngredient, other.subIngredient);
}

}



@Entity
public class Ingredient {

@Id
@GeneratedValue( strategy = GenerationType.IDENTITY)
@Column(name="ID")
private long id;


@NotNull
@Column(unique=true)
private String name;
private String photoContentType;
@Lob
private byte[] photo;

@JsonIgnoreProperties({"photoContentType","photo"})
@ManyToOne
private IngredientType ingredientType;


@OneToMany(mappedBy = "embId.ingredientId", fetch = FetchType.EAGER, cascade = 
CascadeType.ALL, orphanRemoval = true)
private Set<SubIngredient> subIngredients =  new HashSet<SubIngredient>();



public long getId() {
    return id;
}

public void setId(long id) {
    this.id = id;
}

public String getName() {
    return this.name;
}

public void setName(String name) {
    this.name = name;
}

public String getPhotoContentType() {
    return photoContentType;
}



public void setPhotoContentType(String photoContentType) {
    this.photoContentType = photoContentType;
}

public byte[] getPhoto() {
    return photo;
}

public void setPhoto(byte[] photo) {
    this.photo = photo;
}


public IngredientType getIngredientType() {
    return this.ingredientType;
}
public void setIngredientType(IngredientType ingredientType) {
    this.ingredientType = ingredientType;
}



public Set<SubIngredient> getSubIngredients() {
    return subIngredients;
}

public void setSubIngredients(Set<SubIngredient> subIngredients) {
    this.subIngredients = subIngredients;
}

public void addSubIngredient(SubIngredient subIngredient) {
    this.subIngredients.add(subIngredient);
}

@Override
public String toString() {
     String subIngsText = "";
     for(var subIngredient:this.subIngredients) {
         subIngsText = subIngsText   ", "    subIngredient.toString();
     }
    return "{id= " id ",name="   name  ", ingredients=" subIngsText "}";
}

@Override
public int hashCode() {
    
    return Objects.hash(name);
}

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (!(obj instanceof Ingredient)) {
        return false;
    }
    Ingredient other = (Ingredient) obj;
    return Objects.equals(name, other.name);
}
}
  • Related