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