3

I have a simple Spring Boot Application for a Spring Data Rest implementation.

This is the main class:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

I have two simple Entities: Book and Author. The relationship between each other is 1 Author -> N Books

This is the Author.class:

@Entity
@Table
public class Author {

    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    @Column(columnDefinition = "BINARY(16)", length = 16)
    private UUID id;

    @Column
    private String name;

    @OneToMany(fetch = FetchType.LAZY, targetEntity = Book.class, mappedBy = "author")
    private List<Book> books;

    // getters and setters

}

This is the Book.class:

@Entity
@Table
public class Book {

    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    @Column(columnDefinition = "BINARY(16)", length = 16)
    private UUID id;

    @Column
    private String title;

    @Column
    private String language;

    @ManyToOne(fetch = FetchType.EAGER, targetEntity = Author.class)
    private Author author;

    // getters and setters

}

This is the "AuthorRepository":

@RestResource(path = "authors", rel = "authors")
public interface AuthorRepository extends JpaRepository<Author, UUID> {
}

And this is the "BookRepository":

@RestResource(path = "books", rel = "books")
public interface BookRepository extends JpaRepository<Book, UUID> {
}

The application runs perfectly, at the url http://localhost:8080/ I have this response page:

{
  "_links" : {
    "authors" : {
      "href" : "http://localhost:8080/authors{?page,size,sort}",
      "templated" : true
    },
    "books" : {
      "href" : "http://localhost:8080/books{?page,size,sort}",
      "templated" : true
    },
    "profile" : {
      "href" : "http://localhost:8080/profile"
    }
  }
}

The url http://localhost:8080/authors returns this page:

{
  "_embedded" : {
    "authors" : [ ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/authors"
    },
    "profile" : {
      "href" : "http://localhost:8080/profile/authors"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 0,
    "totalPages" : 0,
    "number" : 0
  }
}

And the url http://localhost:8080/books returns this page:

{
  "_embedded" : {
    "books" : [ ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/books"
    },
    "profile" : {
      "href" : "http://localhost:8080/profile/books"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 0,
    "totalPages" : 0,
    "number" : 0
  }
}

I'm trying to make some HTTP POST starting by the Book class.

HTTP POST
url: http://localhost:8080/books
header: Content-Type:application/json
payload: { "title": "Book Title" }

Status: 201: Created
Location: http://localhost:8080/books/61311c9b-b33a-463c-9e6e-8e5efc0a7ad1

In fact, the url http://localhost:8080/books/61311c9b-b33a-463c-9e6e-8e5efc0a7ad1 returns this page:

{
  "title" : "Book Title",
  "language" : null,
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/books/61311c9b-b33a-463c-9e6e-8e5efc0a7ad1"
    },
    "book" : {
      "href" : "http://localhost:8080/books/61311c9b-b33a-463c-9e6e-8e5efc0a7ad1"
    },
    "author" : {
      "href" : "http://localhost:8080/books/61311c9b-b33a-463c-9e6e-8e5efc0a7ad1/author"
    }
  }
}

I make the same thing for the Author.

HTTP POST
url: http://localhost:8080/authors
header: Content-Type:application/json
payload: { "name": "Author Name" }

Status: 201: Created
Location: http://localhost:8080/authors/634d3bd8-abe6-472b-97cd-04a455bdfb11

And this is the response page for that url:

{
  "name" : "Author Name",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/authors/634d3bd8-abe6-472b-97cd-04a455bdfb11"
    },
    "author" : {
      "href" : "http://localhost:8080/authors/634d3bd8-abe6-472b-97cd-04a455bdfb11"
    },
    "books" : {
      "href" : "http://localhost:8080/authors/634d3bd8-abe6-472b-97cd-04a455bdfb11/books"
    }
  }
}

Notice that the relation between author and book does not yet exists, so I try to send a PUT request.

HTTP PUT
url: http://localhost:8080/authors/634d3bd8-abe6-472b-97cd-04a455bdfb11
header: Content-Type:application/json
payload: { "books": ["http://localhost:8080/books/61311c9b-b33a-463c-9e6e-8e5efc0a7ad1"] }

Status: 204: No Content
Location: http://localhost:8080/authors/634d3bd8-abe6-472b-97cd-04a455bdfb11

I have as a response code an HTTP 204 (No Content). If i go to the url http://localhost:8080/authors/634d3bd8-abe6-472b-97cd-04a455bdfb11/books I would expect the book as a result, but I have this result instead:

{
  "_embedded" : {
    "books" : [ ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/authors/634d3bd8-abe6-472b-97cd-04a455bdfb11/books"
    }
  }
}

The "books" property is still EMPTY. Why?

Furthermore, this URL returns an empty page: http://localhost:8080/books/61311c9b-b33a-463c-9e6e-8e5efc0a7ad1/author

The relation has not been processed as I were expected.

If I make the same process starting with the Author BEFORE inserting the Book, the relation exist.

How can I save a relation between two entities starting from the Book entity?

Thanks

Alessandro C
  • 3,310
  • 9
  • 46
  • 82
  • Facing the same issue, described here https://stackoverflow.com/questions/39229472/how-to-create-a-reference-between-entities-in-spring-data-rest-application – Anatoly Antonov Aug 30 '16 at 13:56

2 Answers2

0

To make this happen reverse, your @OneToMany(fetch = FetchType.LAZY, targetEntity = Book.class, mappedBy = "author") should have cascade option in it to actually persist the changes in the book entity.

Try this:

@OneToMany(fetch = FetchType.LAZY, targetEntity = Book.class, mappedBy = "author", cascade = CascadeType.ALL)

Ankit Bhatnagar
  • 745
  • 5
  • 16
0

You are using bidirectional OneToMany so you have to use 'utilities methods' that synchronize both ends whenever a child element is added or removed (see manual).

But you can do almost the same just modifying books setter, like this:

@Entity
public class Author {

    //...

    @OneToMany(mappedBy = "author")
    private List<Book> books;

    public void setBooks(List<Book> books) {
        books.forEach(book -> book.setAuthor(this));
        this.books = books;
    }


    //...
}

See my example.

P.S. Don't use cascade = CascadeType.ALL if your entities are independent, use cascade = {CascadeType.PERSIST, CascadeType.MERGE} instead (or don't use cascading at all).

Cepr0
  • 28,144
  • 8
  • 75
  • 101