Linked Questions

Popular Questions

Posting an entity with manyToMany, ManyToOne

Asked by At

I've always avoided @xToX relationships in my APIs but finally wanted to give it a go, and I'm struggling with it. I've managed to make the get methods returning me the right informations with any recursiveness (at least I made a part), and now I want to post data to my API with those relationships in place (and seems working so far).

So I have a few objects a book, a book style, a book category and an author. A book has only one author (even if it's false sometimes but as I only read Sci-Fi it will do the trick) and an author have many books (same for category). And a style can have many books, also books can have many styles.

So I went with a @ManyToOne on the book's author property and with @OneToMany on the author's books property (same for category). And between the style and the bok @ManyToMany

I want to be able to post a book to my API like follows, using the author and style ids :

{
    "isbn": "15867jhg8",
    "title": "Le portrait de Dorian Gray",
    "author": 1,
    "category":1,
    "style": [1,2,3]
}

I've annotated my properties with @JsonBackReference, @JsonManagedReference, @JsonSerialize, @JsonIdentityInfo but I truly think I might have made it too much ...

I'm not sure about the way I used the @JsonSerialize, nor the @JsonIdentityInfo. I've omitted the useless properties to keep the example 'simple'.

So let's dive in.

The Book class first :

@Entity
@JsonIdentityInfo(
    generator = ObjectIdGenerators.PropertyGenerator.class,
    property = "id"
)
public class Book implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(updatable = false, nullable = false)
    private Integer id;

    private String isbn;

    private String title;

    @ManyToOne
    @JoinColumn(name="category_id", nullable = false)
    @JsonBackReference(value = "category")
    private BookCategory category;

    @ManyToOne
    @JoinColumn(name="author_id", nullable = false)
    @JsonBackReference(value = "author")
    private Author author;

    @ManyToMany
    @JsonManagedReference(value = "styles")
    @JsonSerialize(using = BookStyleListSerializer.class)
    private List <BookStyle> styles;
}

Then the author class :

@Entity
@JsonIdentityInfo (
    generator = ObjectIdGenerators.PropertyGenerator.class,
    property = "id"
)
public class Author implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(updatable = false, nullable = false)
    private Integer id;

    @OneToMany(mappedBy = "author")
    @JsonManagedReference(value = "authorBooks")
    @JsonSerialize(using = BookListSerializer.class)
    private List <Book> books;
}

As the category is quite the same, I'm only pasting the books property of it :

@OneToMany(mappedBy = "category")
@JsonManagedReference(value = "categoryBooks")
@JsonSerialize(using = BookListSerializer.class)
private List <Book> books;

And finally my bookStyle class :

@Entity
@JsonIdentityInfo(
    generator = ObjectIdGenerators.PropertyGenerator.class,
    property = "id"
)
public class BookStyle implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(updatable = false, nullable = false)
    private Integer id;

    @ManyToMany
    @JsonManagedReference(value = "styleBooks")
    @JsonSerialize(using = BookListSerializer.class)
    private List <Book> books;
}

The serializer are the same, it's only the type that changes :

public class BookStyleListSerializer extends StdSerializer <List <BookStyle>> {

public BookStyleListSerializer() {
    this(null);
}

public BookStyleListSerializer(Class<List<BookStyle>> t) {
    super(t);
}

@Override
public void serialize(List <BookStyle> bookStyles, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {

    List<Integer> ids = new ArrayList <> (  );
    for (BookStyle style: bookStyles){
        ids.add(style.getId ());
    }
    jsonGenerator.writeObject ( ids );
}
}

Of what I understood (and as english is not my native language i might misunderstood a few things here and there while coding) :

  • @JsonBackReference is to be used for the property we don't want to be serialized as opposite to @JsonManagedReference
  • @JsonSerialize is the custom serializer so that the elements will be serialized as we want them to (in this case, only using IDs)

And as it might be obvious to some of you : none of what I've coded works for posting data, here's the exception as i received it when i post something via API (and it's doubled, not a copy paste error) :

.c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class com.rz.librarian.domain.entity.Author]]: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot handle managed/back reference 'categoryBooks': no back reference property found from type [collection type; class java.util.List, contains [simple type, class com.rz.librarian.domain.entity.Book]] .c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class com.rz.librarian.domain.entity.Author]]: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot handle managed/back reference 'categoryBooks': no back reference property found from type [collection type; class java.util.List, contains [simple type, class com.rz.librarian.domain.entity.Book]]

I've tried many things but after three days on this issue i wanted to ask (cry?) for help.

Thank you guys and sorry for the long post!

Related Questions