1

To explain the problem I'm dealing with I will first provide the code.

RecipeController

@RequestMapping(path = "/addrecipe")
public void addNewRecipe(@RequestBody AddRecipeDto addRecipeDto){
    Recipe newRecipe = new Recipe();
    EvaUser user = evaUserRepository.findOne(addRecipeDto.getUserId());
    for(Ingredient ingredient: addRecipeDto.getIngredients()){
        ingredientRepository.save(ingredient);
    }
    newRecipe.setTitle(addRecipeDto.getTitle());
    newRecipe.setAuthor(user);
    newRecipe.setDescription(addRecipeDto.getDescription());
    newRecipe.setIngredients(addRecipeDto.getIngredients());
    recipeRepository.save(newRecipe);
    user.getMyRecipes().add(newRecipe);
    evaUserRepository.save(user);
}

UserController

@RequestMapping("/getusers")
public Iterable<EvaUser> getAllUsers() {
    return evaUserRepository.findAll();
}

EvaUser

@OneToMany
private List<Recipe> myRecipes;

@ManyToMany
private List<Recipe> favoriteRecipes;

Recipe

@ManyToOne
private EvaUser author;

Exception

Failed to write HTTP message: 
org.springframework.http.converter.HttpMessageNotWritableException: Could 
not write content: Infinite recursion

Problem

So when I call the method to add a recipe, I want the database to know that there is a new recipe and that the new recipe is linked to the user who added it. When I drop the part where I save the user-entity, the mapping isn't made at all. But when I use the userRepository to tell the database that there has been made a change (adding the recipe to their list) it seems like there is an infinite loop of adding new users.

Freki
  • 167
  • 1
  • 1
  • 7
  • 1
    Possible duplicate : https://stackoverflow.com/questions/3325387/infinite-recursion-with-jackson-json-and-hibernate-jpa-issue – Mehraj Malik Aug 02 '17 at 13:03
  • @MehrajMalik The question and the answers seem a bit outdated. I don't think this one can help me. Thanks anyway. – Freki Aug 02 '17 at 13:09
  • 1
    https://stackoverflow.com/questions/45362300/java-hibernate-json-infinite-recursion-with-self-referencing-class/45362568#45362568, all tricks will work also for current task – J-Alex Aug 02 '17 at 13:22
  • @J-Alex Thanks, I will try. – Freki Aug 02 '17 at 13:38
  • @J-Alex The Jsonview annotation seems to avoid the exception, but when iIcall the method to get all users the array of "myRecipes" is empty although both id's are filled in the "eva_user_my_recipes table". – Freki Aug 02 '17 at 13:46
  • 1
    Is this even caused by the save action? To me it looks like Jackson is trying to serialize your `EvaUser` in your `getAllUsers()` and due to it having a reference to `Recipe` and `Recipe` having a reference back to `EvaUser`, it can't properly serialize it and end up going into an infinite loop mapping that chain. – g00glen00b Aug 02 '17 at 14:08
  • 2
    The answer @MehrajMalik provided to break the chain by adding `@JsonIgnore` is actually a proper solution and **isn't** outdated. – g00glen00b Aug 02 '17 at 14:09
  • @g00glen00b Yes I tried the JsonIgnore and it does break the chain, but when I call to get all users. the "my recipes" field returns empty. – Freki Aug 02 '17 at 14:13
  • Possible duplicate of [Infinite Recursion with Jackson JSON and Hibernate JPA issue](https://stackoverflow.com/questions/3325387/infinite-recursion-with-jackson-json-and-hibernate-jpa-issue) – Stanislav Aug 02 '17 at 14:59
  • @Freki you can always put the `@Jsonignore` on the `author` field, and if you don't want that, you should check out the other answers in that question, providing more smooth solutions. – g00glen00b Aug 02 '17 at 15:28

1 Answers1

1

Answering to your question and including the last requirements from your comments.

If you want to break the loop, but some somehow want to keep also nested objects, I would recommend to write a custom serializer and replace the the object which causes the endless recursion with some other field (I used author username which is String instead of Author object in the example below).

To reproduce the case I created a mock model which is similar to yours.

Recipe:

public class Recipe {

    private EvaUser author;
    private String name = "test";
    private String ingridients = "carrots, tomatos";

    public EvaUser getAuthor() {
        return author;
    }

    public void setAuthor(EvaUser author) {
        this.author = author;
    }

    public String getName() {
        return name;
    }

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

    public String getIngridients() {
        return ingridients;
    }

    public void setIngridients(String ingridients) {
        this.ingridients = ingridients;
    }
}

EvaUser:

public class EvaUser {

    private List<Recipe> myRecipes = new ArrayList<>();
    private List<Recipe> favoriteRecipes = new ArrayList<>();
    private String username;

    public List<Recipe> getMyRecipes() {
        return myRecipes;
    }

    public void setMyRecipes(List<Recipe> myRecipes) {
        this.myRecipes = myRecipes;
    }

    public List<Recipe> getFavoriteRecipes() {
        return favoriteRecipes;
    }

    public void setFavoriteRecipes(List<Recipe> favoriteRecipes) {
        this.favoriteRecipes = favoriteRecipes;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

Creating a custom serializer:

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

import java.io.IOException;
import java.util.Optional;

public class RecipeSerializer extends StdSerializer<Recipe> {

    protected RecipeSerializer() {
        this(null);
    }

    protected RecipeSerializer(Class<Recipe> t) {
        super(t);
    }

    @Override
    public void serialize(Recipe recipe, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeStartObject();

        gen.writeStringField("name", recipe.getName());
        gen.writeStringField("author", Optional.ofNullable(recipe.getAuthor().getUsername()).orElse("null"));
        gen.writeStringField("ingridients", recipe.getIngridients());

        gen.writeEndObject();
    }
}

Applying serializer:

@JsonSerialize(using = RecipeSerializer.class)
public class Recipe {
    // model entity
}

JSON response body of EvaUser from controller (previous one was StackOverflowError):

{
    "myRecipes": [
        {
            "name": "soup",
            "author": "user1",
            "ingridients": "carrots, tomatos"
        },
        {
            "name": "steak",
            "author": "user1",
            "ingridients": "meat, salt"
        }
    ],
    "favoriteRecipes": [
        {
            "name": "soup",
            "author": "user1",
            "ingridients": "carrots, tomatos"
        },
        {
            "name": "steak",
            "author": "user1",
            "ingridients": "meat, salt"
        }
    ],
    "username": "user1"
}
J-Alex
  • 6,881
  • 10
  • 46
  • 64
  • Just stumbled upon the answer too. I indeed made the mistake of using author as an object which contained recipe aswell, resulting in a loop. Thank you. – Freki Aug 03 '17 at 07:54