“detached entity passed to persist” 当我尝试持久化具有多对多关系的实体时。

huangapple 未分类评论53阅读模式
英文:

"detached entity passed to persist" When i try to persist an Entity with ManyToMany relationship

问题

我知道有很多类似的问题,但我还没有遇到过一个能解决我的问题的问题,我已经尝试了我所能做的一切 - 而且我也阅读了相关资料。

所以问题基本上是,我无法在我的代码中持久保存一个具有多对多关系的实体“Recipe”,其中一个实体“Category”在我的H2数据库中,而不在代码流程中之前持久化类别实例。

这个“Recipe”实体在其声明中已经有了许多其他实体(Notes - OneToOne,Ingredient OneToMany),而且在不事先持久化它们的情况下,一切都可以正常工作。

以下是工作的代码片段:

    categoryRepository.save(newCategory);
    recipeRepository.save(perfectGuacamole);

所以,这段代码可以正常工作,但我想避免在保存类别之前持久化它,因为对于其他实体,我已经这样做了。所以,基本上,即使没有保存其余的实体(Ingredient,Notes),这段代码也会隐式地将它们持久化。

代码示例:

    Category american = categoryRepository.findByDescription("American").get();
    Category mexican = categoryRepository.findByDescription("Mexican").get();

    // ... 其他代码 ...

    // CREATING A NEW CATEGORY
    Category newCategory = new Category();
    newCategory.setDescription("New");
    newCategory.setRecipes(newCategoryRecipes);

    // ... 其他代码 ...

    categoryRepository.save(newCategory);
    recipeRepository.save(perfectGuacamole);

Recipe实体:

@Entity
public class Recipe {
    // ... 其他属性 ...

    @ManyToMany
    @JoinTable(name = "recipe_category",
            joinColumns = @JoinColumn(name = "recipe_id"),
            inverseJoinColumns = @JoinColumn(name = "category_id")
    )
    private Set<Category> categories = new HashSet<>();

    // ... 其他方法 ...

    public Set<Category> getCategories() {
        return categories;
    }

    public void setCategories(Set<Category> categories) {
        this.categories = categories;
    }
}

Category实体:

@Entity
public class Category {
    // ... 其他属性 ...

    @ManyToMany(mappedBy = "categories")
    private Set<Recipe> recipes = new HashSet<>();

    // ... 其他方法 ...

    public Set<Recipe> getRecipes() {
        return recipes;
    }

    public void setRecipes(Set<Recipe> recipes) {
        this.recipes = recipes;
    }
}

这个问题的一个常见原因是在不同的持久化上下文中操作实体,导致它们从持久化上下文中分离。如果上述解决方案不起作用,您可以考虑在关系中使用级联操作,并确保在保存Recipe实体时,其关联的Category实体也会被持久化。

希望这可以帮助您解决问题。

英文:

I know there are a lot of similiar questions, but i not encountered a single one with a solution for my problem and i already tried everything i could - and i read.

So the problem is basically that i'm not able to persist in my code an instance of an entity "Recipe" with ManyToMany relationship with another one "Category" in my h2 database without persisting the category instance before in the code flow.

This "Recipe" entity already has in its declaration many other entities (Notes - OneToOne, Ingredient OneToMany) and everything works flawlessly without having persist them previously.

categoryRepository.save(newCategory);
recipeRepository.save(perfectGuacamole);

So, this code work perfectly, but i would like to avoid to persist before the category, as im already doing for the other entities. So, basically even without saving the remaining entities (Ingredient, Notes) this code is implicitly persisting them.

Code:

    Category american = categoryRepository.findByDescription(&quot;American&quot;).get();
    Category mexican = categoryRepository.findByDescription(&quot;Mexican&quot;).get();


    Recipe perfectGuacamole = new Recipe();
    Set&lt;Recipe&gt; newCategoryRecipes = new HashSet&lt;&gt;();
    newCategoryRecipes.add(perfectGuacamole);

    // CREATING A NEW CATEGORY
    Category newCategory = new Category();
    newCategory.setDescription(&quot;New&quot;);
    newCategory.setRecipes(newCategoryRecipes);

    // CRIATING A NEW SET OF GUACAMOLE&#39;S CATEGORIES
    Set&lt;Category&gt; categories = new HashSet&lt;&gt;();
    categories.add(newCategory);
    categories.add(american);
    categories.add(mexican);


    // CREATING GUACAMOLE&#39;S NOTES
    Notes notes = new Notes();
    notes.setRecipe(perfectGuacamole);
    notes.setRecipeNotes(&quot;Be careful handling chiles if using. Wash your hands thoroughly after handling and do not touch your eyes or the area near your eyes with your hands for several hours.&quot;);

    // CRIATING GUACAMOLE&#39;S INGREDIENT&#39;S
    Set&lt;Ingredient&gt; ingredientesGuacamole = new HashSet&lt;&gt;();
    Ingredient avocado = new Ingredient(&quot;Avocado&quot;, new BigDecimal(2), unitOfMeasureRepository.findByDescription(&quot;Unit&quot;).get(), perfectGuacamole);
    Ingredient salt = new Ingredient(&quot;Salt&quot;, new BigDecimal(0.25), unitOfMeasureRepository.findByDescription(&quot;Teaspoon&quot;).get(), perfectGuacamole);
    Ingredient lemonJuice = new Ingredient(&quot;Lemon juice&quot;, new BigDecimal(0.25), unitOfMeasureRepository.findByDescription(&quot;Tablespoon&quot;).get(), perfectGuacamole);

    ingredientesGuacamole.add(avocado);
    ingredientesGuacamole.add(salt);
    ingredientesGuacamole.add(lemonJuice);
    perfectGuacamole.setCookTime(10);
    perfectGuacamole.setDifficulty(Difficulty.MODERATE);
    perfectGuacamole.setDescription(&quot;The best guacamole keeps it simple: just ripe avocados, salt, a squeeze of lime, onions, chiles, cilantro, and some chopped tomato. Serve it as a dip at your next party or spoon it on top of tacos for an easy dinner upgrade.&quot;);
    perfectGuacamole.setCategories(categories);
    perfectGuacamole.setUrl(&quot;https://www.simplyrecipes.com/recipes/perfect_guacamole/&quot;);
    perfectGuacamole.setDirections(&quot;1 Cut the avocado, remove flesh: Cut the avocados in half. Remove the pit. Score the inside of the avocado with a blunt knife and scoop out the flesh with a spoon. (See How to Cut and Peel an Avocado.) Place in a bowl.\n&quot; +
            &quot;2 Mash with a fork: Using a fork, roughly mash the avocado. (Don&#39;t overdo it! The guacamole should be a little chunky.)\n&quot; +
            &quot;3 Add salt, lime juice, and the rest: Sprinkle with salt and lime (or lemon) juice. The acid in the lime juice will provide some balance to the richness of the avocado and will help delay the avocados from turning brown.\n&quot; +
            &quot;Add the chopped onion, cilantro, black pepper, and chiles. Chili peppers vary individually in their hotness. So, start with a half of one chili pepper and add to the guacamole to your desired degree of hotness.\n&quot; +
            &quot;Remember that much of this is done to taste because of the variability in the fresh ingredients. Start with this recipe and adjust to your taste.\n&quot; +
            &quot;Chilling tomatoes hurts their flavor, so if you want to add chopped tomato to your guacamole, add it just before serving.\n&quot; +
            &quot;4 Serve: Serve immediately, or if making a few hours ahead, place plastic wrap on the surface of the guacamole and press down to cover it and to prevent air reaching it. (The oxygen in the air causes oxidation which will turn the guacamole brown.) Refrigerate until ready to serve.&quot;);
    perfectGuacamole.setIngredients(ingredientesGuacamole);
    perfectGuacamole.setNotes(notes);
    perfectGuacamole.setServings(4);

    categoryRepository.save(newCategory);
    recipeRepository.save(perfectGuacamole);

Recipe Entity:

@Entity
public class Recipe {

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

private String description;
private Integer prepTime;
private Integer cookTime;
private Integer servings;
private String source;
private String url;

@Lob
private String directions;

@Lob
private Byte[] image;

@Enumerated(value = EnumType.STRING)
private Difficulty difficulty;

@OneToOne(cascade = CascadeType.ALL)
private Notes notes;
@OneToMany(cascade = CascadeType.ALL, mappedBy = &quot;recipe&quot;)
private Set&lt;Ingredient&gt; ingredients;


@ManyToMany
@JoinTable(name = &quot;recipe_category&quot;,
        joinColumns = @JoinColumn(name = &quot;recipe_id&quot;),
        inverseJoinColumns = @JoinColumn(name = &quot;category_id&quot;)
)
private Set&lt;Category&gt; categories = new HashSet&lt;&gt;();


public Long getId() {
    return id;
}

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

public String getDescription() {
    return description;
}

public void setDescription(String description) {
    this.description = description;
}

public Integer getPrepTime() {
    return prepTime;
}

public void setPrepTime(Integer prepTime) {
    this.prepTime = prepTime;
}

public Integer getCookTime() {
    return cookTime;
}

public void setCookTime(Integer cookTime) {
    this.cookTime = cookTime;
}

public Integer getServings() {
    return servings;
}

public void setServings(Integer servings) {
    this.servings = servings;
}

public String getSource() {
    return source;
}

public void setSource(String source) {
    this.source = source;
}

public String getUrl() {
    return url;
}

public void setUrl(String url) {
    this.url = url;
}

public String getDirections() {
    return directions;
}

public void setDirections(String directions) {
    this.directions = directions;
}

public Byte[] getImage() {
    return image;
}

public void setImage(Byte[] image) {
    this.image = image;
}

public Difficulty getDifficulty() {
    return difficulty;
}

public void setDifficulty(Difficulty difficulty) {
    this.difficulty = difficulty;
}

public Notes getNotes() {
    return notes;
}

public void setNotes(Notes notes) {
    this.notes = notes;
}

public Set&lt;Ingredient&gt; getIngredients() {
    return ingredients;
}

public void setIngredients(Set&lt;Ingredient&gt; ingredients) {
    this.ingredients = ingredients;
}


public Set&lt;Category&gt; getCategories() {
    return categories;
}

public void setCategories(Set&lt;Category&gt; categories) {
    this.categories = categories;
}

}

Category Entity :

@Entity
public class Category {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String description;


@ManyToMany(mappedBy = &quot;categories&quot;)
private Set&lt;Recipe&gt; recipes = new HashSet&lt;&gt;();

public Long getId() {
    return id;
}

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

public String getDescription() {
    return description;
}

public void setDescription(String description) {
    this.description = description;
}


public Set&lt;Recipe&gt; getRecipes() {
    return recipes;
}

public void setRecipes(Set&lt;Recipe&gt; recipes) {
    this.recipes = recipes;
}
}

Observation : i already know that to work, i should use Cascate annotation, the point is the even with it, it doesn't work.

I already tried to use Fetch.eager in every side of the relationship, and it doesn't work either, and even set the property spring.jpa.open-in-view on false.

Maybe i should implement a Custom Repository layer with a an Entity Manager inside it to handle it "manually". But i do not know how i should proceed.

I hope you guys can find a solution.
Thanks in advance.

EDIT : The detached entity is Category.

答案1

得分: 0

Sure, here is the translated code:

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "recipe_category",
        joinColumns = @JoinColumn(name = "recipe_id"),
        inverseJoinColumns = @JoinColumn(name = "category_id")
)
private Set<Category> categories = new HashSet<>();
英文:

Could you add cascade attribute to @ManyToMany annotation in Recipe model and try again? The code will look like as this:

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = &quot;recipe_category&quot;,
        joinColumns = @JoinColumn(name = &quot;recipe_id&quot;),
        inverseJoinColumns = @JoinColumn(name = &quot;category_id&quot;)
)
private Set&lt;Category&gt; categories = new HashSet&lt;&gt;();

huangapple
  • 本文由 发表于 2020年5月5日 09:53:00
  • 转载请务必保留本文链接:https://java.coder-hub.com/61604450.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定