PersistenObjectException: отдельный объект передан для сохранения: при попытке добавить более одного дочернего элемента - PullRequest
0 голосов
/ 07 мая 2020

У меня есть двунаправленное соединение в Hibernate, когда я добавляю родителя с его детьми и удаляю их, все работает с CascadeType.All, но я не могу правильно редактировать свои объекты. Я имею отношение ко многим к средней таблице, поэтому мне нужно отредактировать ее с помощью одной операции. Я использую Spring Data CrudRepository метод save(). Странно то, что я могу удалить всех детей и добавить только одного, и это тоже работает. Но когда я хочу добавить более одного дочернего элемента, он выдает исключение detached entity passed to persist: com.entity.Recipe Ниже показано, как выглядит моя структура.

Родитель

@Entity
public class Recipe {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "recipe_generator")
@SequenceGenerator(name="recipe_generator", sequenceName = "recipe_seq")
@Column(name = "id", nullable = false)
private Long id;
@NaturalId
@Column
private String name;

@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true)
private List<RecipeIngredients> ingredients;

public void addIngredient(RecipeIngredients recipeIngredients) {
    ingredients.add(recipeIngredients);
    recipeIngredients.setRecipe(this);
}

public void removeIngredient(RecipeIngredients recipeIngredients) {
     ingredients.remove(recipeIngredients);
        recipeIngredients.setRecipe(null);
    }

}

Дети

@Entity
public class RecipeIngredients implements Serializable {

    @EmbeddedId
    private RecipeIngredientsId recipeIngredientsId;

    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("recipeId")
    private Recipe recipe;

    @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @MapsId("ingredientId")
    private Ingredient ingredient;

    public RecipeIngredients(Recipe recipe, Ingredient ingredient) {
        this.recipe = recipe;
        this.ingredient = ingredient;
        this.recipeIngredientsId = new RecipeIngredientsId(recipe.getId(), ingredient.getId());
    }
}

@Entity
public class Ingredient {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ingredient_generator")
    @SequenceGenerator(name = "ingredient_generator", sequenceName = "ingredient_seq")
    @Column(name = "id", updatable = false, nullable = true)
    private Long id;

    @NaturalId
    @Column(unique = true)
    private String name;

}

И это то, что я хочу сделать: Сервис

@Transactional
public RecipeDto updateRecipe(Long id, RecipeDto recipe) {
    recipe.setId(id);
    return recipeDtoToEntityMapper
            .recipeEntityToDto(recipeRepository
                    .save(recipeDtoToEntityMapper.recipeDtoToEntity(recipe)));
}

@Mapper(componentModel = "spring", uses = RecipeIngredientsDtoToEntityMapper.class)
public abstract class RecipeDtoToEntityMapper {

    public Recipe recipeDtoToEntity(RecipeDto recipeDto) {
    if (recipeDto == null) {
        return null;
    } else {
        Recipe recipe = new Recipe();
        recipe.setId(recipeDto.getId());
        recipe.setName(recipeDto.getName());

      //First I need to pull existing ingredients because when I try to simple add new(edited) childrens
      //I have conflict on persistance object because Hibernate see that other Recipe has diffrent children
      //"A different object with the same identifier value was already associated with the session"

         Optional<List<RecipeIngredients>> existedRecipeIngredients = pullExistedIngredients(recipeDto.getId());
         if (existedRecipeIngredients.isPresent()) {
                recipe.setIngredients(existedRecipeIngredients.get());
                updateIngredients(recipe, recipeDto);
         }
}
...
    //Then I have method to add new ingredients 
    private void addNewIngredientsToRecipe(Recipe recipe, RecipeDto recipeDto, List<String> existingIngredientsNames) {
        for (RecipeIngredientsDto newIngredient : recipeDto.getIngredients()) {
            String ingredientName = newIngredient.getIngredient().getName();
            if (!existingIngredientsNames.contains(ingredientName)) {

     //And here everything is allright for the first time dto is mapped to entity and ingredient is added       
recipe.addIngredient(this.recipeIngredientsDtoToEntityMapper.recipeIngredientsToEntity(newIngredient, recipe));`
                        }
                    }
                }

И все маги c происходит ниже, когда я хочу вытащить ингредиенты, он выдает мне исключение, что рецепт отсоединен. Зачем??? Я хочу только вытащить его детей и даже не манипулировать данными родителей.

  public RecipeIngredients recipeIngredientsToEntity(RecipeIngredientsDto recipeIngredientsDto, Recipe recipe) {
        if ( recipeIngredientsDto == null || recipe == null ) {
            return null;
        }

        RecipeIngredients recipeIngredients = new RecipeIngredients();
        recipeIngredients.setRecipe(recipe);
        recipeIngredients.setIngredient( pullIngredient(ingredientsDtoToEntityMapper.ingredientsToEntity( recipeIngredientsDto.getIngredient() )) );
        recipeIngredients.setRecipeIngredientsId(new RecipeIngredientsId());

        return recipeIngredients;
    }
//On this method I've got an error. *detached entity passed to persist: com.entity.Recipe*
    private Ingredient pullIngredient(Ingredient ingredient) {
        return ingredientRepository.findByName(ingredient.getName()).orElse(ingredient);
    }
...