0

I am very new to Hibernate and I am trying to solve an issue similar to this question, specifically the answer.

I have something very similar set up (in my case it's Author, Book, and the mapping table AuthorBook). I am using this in Spring Data JPA, so I have the following components:

  • Repositories: AuthorRepository, BookRepository, AuthorBookRepository
  • Services: AuthorService, BookService, AuthorBookRepository
  • Controllers: AuthorController, BookController, AuthorBookController

My entities are:

@Entity
public class Author {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "author")
    private Set<AuthorBook> authorBooks;

    // getters and setters
}
@Entity
public class Book {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "book")
    private Set<AuthorBook> authorBooks;

    // getters and setters
}
@Entity
public class AuthorBook {

    @Id
    @GeneratedValue
    private Integer id;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private Author author;

    @ManyToOne
    @JoinColumn(name = "book_id")
    private Book book;

    @Column(name = "isMainAuthor")
    private boolean isMainAuthor;
  
    // getters and setter
}

My understanding is that I should make the following POST requests:

Create an author:

{
    "name": "Test Author"
}

Create a book:

{
    "name": "Test Book"
}

Create the mapping:

{
    "author": {
        "id": 1
    },
    "book": {
        "id": 2
    },
    "isMainAuthor": true
}

First of all: is this the correct way to use this? If no, how should I use it instead? If yes, how is serialisation supposed to work? Because if I do it like this, and then fetch the list of books, the response will be infinitely long like so:

[
    {
        "id": 2,
        "name": "Test Book",
        "authorBooks": [
            {
                "id": 3,
                "author": {
                    "id": 1,
                    "name": "Test Author",
                    "authorBooks": [
                        {
                            "id": 3,
                            "author": {
                                "id": 1,
                                "name": "Test Author",
                                "authorBooks": [
                                    {
                                        "id": 3,
                                        "author": {
                                            "id": 1,
                                            "name": "Test Author",
                                            "authorBooks": [
    ...

I know that I could use @JsonIgnore on the Author and Book getters in AuthorBook, and @JsonProperty on the setters so that deserialisation still works. However, as someone who is unexperienced with Hibernate, this seems like a hack to me. What is the cleanest and best way to solve this?

Also, is there a way to update the mapping via the author and book endpoints directly? I might already have an author in the database and want to just add a new book he wrote by adding a new Book entity and providing the relation as part of it, but I seem not to be able to do that.

Yanick Nedderhoff
  • 1,174
  • 3
  • 18
  • 35

2 Answers2

0

This is absolutely not a hacker trick. With the @JsonIgnore annotation, this problem is solved. This problem is called infinite recursion with the release of Jackson JSON and nothing can be done with it. For this, they came up with an annotation.

ulxanxv
  • 101
  • 3
  • This is definitely a restriction though, no? Maybe I have good reason wanting to serialise that `Entity` elsewhere, i.e. in another endpoint? How would I do that then? – Yanick Nedderhoff Sep 08 '20 at 20:52
0

You probably need to set an embedded ID within the mapping table. Keeping the Author and Book table as such, create a new class

@Embeddable
public class AuthorBookId{

  @Column(name = "user_id")
  private String authorId;

  @Column(name = "book_id")
  private String bookId;

// Constructors
// Getters and Setters
}

And change the AuthorBook table as

@Entity
public class AuthorBook {

  @EmbeddedId private AuthorBookId id = new AuthorBookId();

  @ManyToOne
  @MapsId("user_id")
  @JsonIgnore
  private Author author;

  @ManyToOne
  @MapsId("book_id")
  @JsonIgnore
  private Book book;

  @Column(name = "isMainAuthor")
  private boolean isMainAuthor;

  // getters and setter
}
  • Can you explain what this does and what it does for me? – Yanick Nedderhoff Sep 08 '20 at 21:10
  • This would resolve your infinite recursion issue. Also since we are manually creating a separate table for the mapping entity, it's better to specify the ID which in this case is the Embedded ID. – Aravind Sai Vellappat Sep 08 '20 at 21:20
  • So I think what does not work is the `@JsonIgnore` as you use it. Instead, it needs to be on the getter, and `@JsonProperty` on the setter, otherwise how could I set the author and book mapping in my request? However, even with that change and making the exact same requests as before, I get `org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "USER_ID"; SQL statement: insert into author_book (is_main_author, user_id, book_id) values (?, ?, ?) [23502-200]` – Yanick Nedderhoff Sep 08 '20 at 21:31
  • `{ "id": { "user_id": 1, "book_id": 1 }, "isMainAuthor": true }` Will you be able to send the JSON like above – Aravind Sai Vellappat Sep 09 '20 at 07:40