Florian Florian - 3 months ago 16
Java Question

Why does an object saved by my Spring CRUD repository lose it's fields which are lists?

I am having trouble with a CRUD repository here. I am trying to save a Recipe object, which has two fields which are lists, into a database.

Recipe recipe = new Recipe("http://abc.com.jpg", "Ham and Eggs", "Tasty", Category.Breakfast, 20, 10, ingredients, steps, false);

recipes.save(recipe); // recipes being a CrudRepository<Recipe, Long>


This seems to work fine, but if I do "recipes.findOne(1L)" now, the recipe is returned with all field but the List(Ingredient) ingredients and the List(Step) steps Lists are empty.

What could be the cause of this behaviour?

My recipe class looks like this:

@Entity
public class Recipe extends BaseEntity {
@Size(min = 3, max = 250)
private String photo;
@Size(min = 3, max = 250)
private String name;
@Size(min = 5, max = 250)
private String description;
@NotNull
private Category category;
@Range(min = 1, max = 2000)
private Integer prepTime;
@Range(min = 1, max = 2000)
private Integer cookTime;
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Ingredient> ingredients;
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Step> steps;
private boolean isFavorite;

public Recipe() {
}

public Recipe(String photo, String name, String description, Category category, Integer prepTime, Integer cookTime,
List<Ingredient> ingredients, List<Step> steps, boolean isFavorite) {
this.photo = photo;
this.name = name;
this.description = description;
this.category = category;
this.prepTime = prepTime;
this.cookTime = cookTime;
this.ingredients = ingredients;
this.steps = steps;
this.isFavorite = isFavorite;
}

public String getPhoto() {
return photo;
}

public void setPhoto(String photo) {
this.photo = photo;
}

public String getName() {
return name;
}

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

public String getDescription() {
return description;
}

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

public Category getCategory() {
return category;
}

public void setCategory(Category category) {
this.category = category;
}

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 List<Ingredient> getIngredients() {
return ingredients;
}

public void addIngredient(Ingredient ingredient) {
ingredient.setRecipe(this);
ingredients.add(ingredient);
}

public List<Step> getSteps() {
return steps;
}

public void addStep(Step step) {
step.setRecipe(this);
steps.add(step);
}

public boolean isFavorite() {
return isFavorite;
}

public void setFavorite(boolean favorite) {
isFavorite = favorite;
}

public void setIngredients(List<Ingredient> ingredients) {
this.ingredients = ingredients;
}

public void setSteps(List<Step> steps) {
this.steps = steps;
}
}


Here comes the Ingredient class:

@Entity
public class Ingredient extends BaseEntity {
private String name;
private String condition;
private Integer quantity;
@ManyToOne
private Recipe recipe;

public Ingredient(){}

public Ingredient(String name, String condition, Integer quantity) {
this.name = name;
this.condition = condition;
this.quantity = quantity;
}

public String getName() {
return name;
}

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

public String getCondition() {
return condition;
}

public void setCondition(String condition) {
this.condition = condition;
}

public Integer getQuantity() {
return quantity;
}

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

public Recipe getRecipe() {
return recipe;
}

public void setRecipe(Recipe recipe) {
this.recipe = recipe;
}
}


Kind Regards,
Florian

Answer

I would guess that the associations are saved with a null FK in the database because you have not set the back reference from Ingredient and Step to Recipe. I note that you have encapsulated these operations in your addIngredient() and add addStep() methods however your are passing collections straight into the constructor rather than calling these. I would fix by updating your entity as below:

  • if you are having your JPA provider create your schema then explicitly defining a join column as non-nullable will help prevent such issues
  • force collection modifications to go through your addXXX() methods to ensure all relationships are set correctly:

Entity:

  @Entity
    public class Recipe extends BaseEntity {
        @Size(min = 3, max = 250)
        private String photo;
        @Size(min = 3, max = 250)
        private String name;
        @Size(min = 5, max = 250)
        private String description;
        @NotNull
        private Category category;
        @Range(min = 1, max = 2000)
        private Integer prepTime;
        @Range(min = 1, max = 2000)
        private Integer cookTime;

        @OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
        @JoinColumn(name="recipe_id", nullable = false)
        private List<Ingredient> ingredients;

        @OneToMany(mappedBy = "recipe",  cascade = CascadeType.ALL, fetch = FetchType.EAGER)
        @JoinColumn(name="recipe_id", nullable = false)
        private List<Step> steps;

        private boolean isFavorite;

        public Recipe() {
           ingredients = new ArrayList<>();
           steps = new ArrayList<>();
        }

        public Recipe(String photo, String name, String description, Category category, Integer prepTime, Integer cookTime, boolean isFavorite) {
            this();
            this.photo = photo;
            this.name = name;
            this.description = description;
            this.category = category;
            this.prepTime = prepTime;
            this.cookTime = cookTime;
            this.isFavorite = isFavorite;
        }

        public List<Ingredient> getIngredients() {
            return Collections.unmodifiableList(ingredients);
        }

        public void addIngredient(Ingredient ingredient) {
            ingredient.setRecipe(this);
            ingredients.add(ingredient);
        }

        public List<Step> getSteps() {
            return  Collections.unmodifiableList(steps);
        }

        public void addStep(Step step) {
            step.setRecipe(this);
            steps.add(step);
        }
    }

To Construct and Save:

Recipe recipe = new Recipe(...);
Ingredient i1 = new Ingredient(...);
Ingredient i2 = new Ingredient(...);
recipe.addIngredient(i1);
recipe.addIngredient(i2);
repository.save(receipe);
Comments