Issue
I have two Entities: Meal and Product. Each Meal has a couple of products, and each Product can exist in each meal, so the relation is @ManyToMany, where Meal is a parent.
I would like to save Meal with Products, but the problem is that products are duplicating in DB.
How to archive a case where if the Product exists in DB, do not save it, but just wire with existing?
(Application parse products from external API (Nutritionix), collecting them together, and then is saving separately products, and Meal as a Parent with calculated data)
I tried to insert
if(!productService.ifExists(food.getFoodName())) productService.save(food);
into saving function and get rid of cascadeType, but when product already exists I'm getting an error:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing:
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@JsonBackReference
@ManyToMany(mappedBy = "foodList")
private Set<Meal> meals = new HashSet<>();
@Column(unique = false, nullable = false)
private String foodName;
...}
...
@Entity
public class Meal {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@JsonManagedReference
@ManyToMany(cascade ={ CascadeType.PERSIST, CascadeType.MERGE})
private Set<Product> foodList = new HashSet<>();
@NaturalId
@Column(unique = true, nullable = false)
private String mealName;
...}
...
public Meal saveMeal(List<Food> foodList, String mealName){
Meal newMeal = new Meal();
newMeal.setMealName(mealName);
List<Product> productList = parseFoodToProduct(foodList);
productList.stream().forEach(y -> newMeal.getFoodList().add(y));
for(Product food : productList) {
newMeal.setNfCalories(newMeal.getNfCalories() + food.getNfCalories());
newMeal.setNfCholesterol(newMeal.getNfCholesterol() + food.getNfCholesterol());
newMeal.setNfDietaryFiber(newMeal.getNfDietaryFiber() + food.getNfDietaryFiber());
newMeal.setNfP(newMeal.getNfP() + food.getNfP());
newMeal.setNfPotassium(newMeal.getNfPotassium() + food.getNfPotassium());
newMeal.setNfProtein(newMeal.getNfProtein() + food.getNfProtein());
newMeal.setNfSaturatedFat(newMeal.getNfSaturatedFat() + food.getNfSaturatedFat());
newMeal.setNfSodium(newMeal.getNfSodium() + food.getNfSodium());
newMeal.setNfSugars(newMeal.getNfSugars() + food.getNfSugars());
newMeal.setNfTotalCarbohydrate(newMeal.getNfTotalCarbohydrate() + food.getNfTotalCarbohydrate());
newMeal.setNfTotalFat(newMeal.getNfTotalFat() + food.getNfTotalFat());
newMeal.setServingWeightGrams(newMeal.getServingWeightGrams() + food.getServingWeightGrams());
}
return mealService.save(newMeal);
}
Solution
First off, maybe call the ingredients Ingredient
s and not Product
s.
Second, the problem probably lies in the code of method parseFoodToProduct(foodList);
that we cannot see, in connection with the directive
@JsonManagedReference
@ManyToMany(cascade ={ CascadeType.PERSIST, CascadeType.MERGE})
private Set<Product> foodList = new HashSet<>();
If you create new Product
s (xxx = new Product(...)
) in parseFoodToProduct(foodList);
, instead of loading them from the database, this surely will backfire.
Leave out the CascadeType
, and always create/retrieve/update/store the Product
s on their own so they're completely independent of where they are used/referenced.
Answered By - JayC667