1

I have a problem when I POST from a single REST call to create a Library and associate Books for the library. The library record is created, but the associated books are not. Library and Book has oneToMany relationship. My POST request and response is as below -

POST - http://localhost:8080/libraries/

REQUEST BODY
{
    "name":"My Library",
    "books": [
        {"title": "Effective Java", "isbn": "1234"},
        {"title": "Head First Java", "isbn": "5678"}
        ]
}
REPOSNSE 
1

GET Libraries after POST - http://localhost:8080/libraries/

[
    {
        "id": 1,
        "name": "My Library",
        "books": [],
        "address": null
    }
]

POST to create Library and add Books GET REQUEST for Libraries

MODELs

package com.publiclibrary.domain;

import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;

import org.springframework.data.rest.core.annotation.RestResource;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@Entity
public class Library {

    @Id
    @GeneratedValue
    private long id;

    @Column
    private String name;

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

    @OneToOne
    @JoinColumn(name = "address_id")
    @RestResource(path = "libraryAddress", rel="address")
    private Address address;

    // standard constructor, getters, setters
    public Library(String name) {
        super();
        this.name = name;
    }

}
package com.publiclibrary.domain;

import java.util.List;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotNull;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
//@Builder
//@AllArgsConstructor
@Entity
public class Book {

    @Id
    @GeneratedValue
    private long id;

    @NotNull
    private String title, isbn;

    @ManyToOne
    @JoinColumn(name="library_id")
    private Library library;    


    @ManyToMany(mappedBy = "books")
    private List<Author> authors;
}

REPOSITORY

package com.publiclibrary.repo;

import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

import com.publiclibrary.domain.Book;

@RepositoryRestResource(path = "books", collectionResourceRel = "books")
public interface BookRepository extends PagingAndSortingRepository<Book, Long> {

}
package com.publiclibrary.repo;

import org.springframework.data.repository.CrudRepository;

import com.publiclibrary.domain.Library;

public interface LibraryRepository extends CrudRepository<Library, Long> {
}

Service

package com.publiclibrary.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.publiclibrary.domain.Library;
import com.publiclibrary.repo.LibraryRepository;

@Service
public class LibraryService {

    @Autowired
    LibraryRepository libraryRepository;

    public List<Library> getAllLibrarys() {
        List<Library> librarys = new ArrayList<Library>();
        libraryRepository.findAll().forEach(library -> librarys.add(library));
        return librarys;
    }

    public Library getLibraryById(long id) {
        return libraryRepository.findById(id).get();
    }

    public void saveOrUpdate(Library library) {
        libraryRepository.save(library);
    }

    public void delete(long id) {
        libraryRepository.deleteById(id);
    }
}

RESTCONTROLLER

package com.publiclibrary.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.publiclibrary.domain.Library;
import com.publiclibrary.service.LibraryService;

@RestController
public class LibraryController {

    @Autowired
    LibraryService libraryService;

    @GetMapping("/libraries")
    private List<Library> getAllLibrarys() {
        return libraryService.getAllLibrarys();
    }

    @GetMapping("/libraries/{id}")
    private Library getLibrary(@PathVariable("id") int id) {
        return libraryService.getLibraryById(id);
    }

    @DeleteMapping("/libraries/{id}")
    private void deleteLibrary(@PathVariable("id") int id) {
        libraryService.delete(id);
    }

    @PostMapping("/libraries")
    private long saveLibrary(@RequestBody Library library) { 
        libraryService.saveOrUpdate(library);
        return library.getId(); 
    }

}

How can I create a Library and add Books as I intend? Appreciate any help!

am101
  • 31
  • 2
  • 7

2 Answers2

0

Try adding cascade persist (or better just cascade all) on books collection in Library class. E.g.

@OneToMany(fetch = FetchType.LAZY, mappedBy = "library", cascade = CascadeType.ALL)
private List<Book> books;
mvmn
  • 3,717
  • 27
  • 30
  • Thanks that created the records in Book but didn't established the association with Library (library_id is null for both books). – am101 Mar 26 '19 at 03:46
  • Try adding this line before libraryService.saveOrUpdate(library): "if(library.getBooks()!=null) library.getBooks.stream().forEach(b->{ b.setLibrary(library) });" – mvmn Mar 26 '19 at 12:43
0

I followed this article and resolved the problem. I explicitly handled parsing the JSON and creating my data objects. Additionally, I added add and remove methods in parent (Library) class and defined the equals and hashcode for reasons explained in the link above.

My code changes as below -

Library -

    @OneToMany(mappedBy = "library", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnoreProperties("library")
private List<Book> books = new ArrayList<>();

public void addBook(Book book) {
    books.add(book);
    book.setLibrary(this);
}

public void removeBook(Book book) {
    books.remove(book);
    book.setLibrary(null);
}

Book -

@JsonIgnoreProperties("books")
private Library library;    

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Book )) return false;
    return id != null && id.equals(((Book) o).id);
}
@Override
public int hashCode() {
    return 31;
}

LibraryController -

    @PostMapping("/libraries")
private long saveLibrary(@RequestBody Map<String, Object> payload) {
    Library library = new Library();
    library.setName(payload.get("name").toString());

    @SuppressWarnings("unchecked")
    List<Map<String, Object>> books = (List<Map<String, Object>>) payload.get("books");
    for (Map<String, Object> bookObj : books) {
        Book book = new Book();
        book.setTitle(bookObj.get("title").toString());
        book.setIsbn(bookObj.get("isbn").toString());
        library.addBook(book);
    }

    libraryService.saveOrUpdate(library);

    return library.getId(); 
}
am101
  • 31
  • 2
  • 7