简体   繁体   中英

Hibernate - JPA - OneToMany and CascadeType.ALL

I have a JPA problem with @OneToMany and specifically with the functionality of CascadeType.ALL. I have two entities, a father and a child.

I show you an example of what I have: BOOK

@Entity
@Table(name = "books")
public class Book implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
    @JoinColumn(name="id_page")
    private List<Page> pages;
    ...
    ...
}

PAGE

@Entity
@Table(name = "pages")
public class Page implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "id")
    private Integer id;

    @Column(name = "id_book")
    private int idBook;
    ...
    ...
}

So, I just have a book and several pages. When I save the book, I also save the pages contained within it thanks to the CascadeType.ALL. Which works, actually Hibernate tries to save the Book object, realizes with OneToMany that there is also the Page entity to save and starts the query thanks to Cascade ALL.

I simply do:

bookRepository.save(book);

The json representing the book I want to save is something like this:

{
  "id": 0,
  "page": [
    {
      "id": 0,
      "idBook": 0
    }
  ]
}

But, if I put 0 to the book id, it is created. If I put 0 on the idBook into the page, it gives me an error like this: Key (id_book) = (0) is not present in table "book".

But, the thing I really don't understand is: why if I put an id_book in the json that exists on the DB, then finished saving the book entity, it understands what the id is and inserts it correctly on the DB?

So, if I give this JSON.

{
  "id": 0,
  "page": [
    {
      "id": 0,
      "idBook": 1
    }
  ]
}

And let's consider that on the DB there is only one book with id=1. After saving it generates me a line that has id=2 for the book, and a page linked to the book with id=2?

This thing drives me crazy. If give an idBook = 0 to tell him he has to retrieve / generate it, he tells me that it doesn't exist. If, on the other hand, I give him an idBook that really exists (but it's not correct), does he not even consider it and correctly use the idBook of the Book he just created?

I would simply like not to have to use bogus idBook, tell him he has to take the page idBook after creating the book, exactly like he actually does.

From the presence of id_book column in Page entity I conclude that you'd like to use bidirectional mapping. Try to do it like this, with a child as the owner of the relationship:

@Entity
@Table(name = "pages")
public class Page implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "id")
    private Integer id;

    @ManyToOne(fetch = FetchType.LAZY) // EAGER by default
    @JoinColumn(name = "id_book")
    private Book book;
    ...
    ...
}
@Entity
@Table(name = "books")
public class Book implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

    @OneToMany(mappedBy = "book", cascade = CascadeType.ALL) // Lazy fetch type by default
    private List<Page> pages = new ArrayList<>();
    ...
    ...
    
    // obligatory synchronization methods
    public void addPage(Page page) {
        pages.add(page);
        page.setBook(this);
    }

    public void removePage(Page page) {
        pages.remove(this);
        page.setBook(null);
    }
}

Considering "i pass a Book, with a Page (or maybe multiple pages)" I'd like to share my solution as well.

A bit similar to other answers, I'm also using bi-directional. I've included integration test as well.

Book Entity

@Entity
public class Book {

    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private String id;

    @OneToMany(mappedBy = "book", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
    private List<Page> pages;

    public Book(String id, List<Page> pages) {
        this.id = id;
        this.pages = pages;
        this.pages.stream().forEach(page -> page.attachedTo(this));
    }

    public String getId() {
        return id;
    }

    public List<Page> getPages() {
        return pages;
    }

    // Required by JPA
    protected Book() {
    }

}

Page Entity

@Entity
public class Page {

    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private String id;

    private String content;

    @ManyToOne
    private Book book;

    public Page(String content) {
        this.content = content;
    }

    void attachedTo(Book book) {
        this.book = book;
    }

    public String getId() {
        return id;
    }

    public Book getBook() {
        return book;
    }

    // Required by JPA
    protected Page() {}
}

Integration Test

@Test
@Transactional
public void saveBookWithPages() {
    List<Page> firstBookPages = new ArrayList<>();
    firstBookPages.add(new Page("content-0001"));
    firstBookPages.add(new Page("content-0002"));
    firstBookPages.add(new Page("content-0003"));

    Book firstBook = new Book("first-book", firstBookPages);
    firstBook = bookRepository.save(firstBook);

    List<Page> secondBookPages = new ArrayList<>();
    secondBookPages.add(new Page("content-0004"));
    secondBookPages.add(new Page("content-0005"));

    Book secondBook = new Book("second-book", secondBookPages);
    secondBook = bookRepository.save(secondBook);

    // query the first and second book
    firstBook = bookRepository.findById(firstBook.getId()).orElseThrow(() -> new IllegalArgumentException("book id not found."));
    secondBook = bookRepository.findById(secondBook.getId()).orElseThrow(() -> new IllegalArgumentException("book id not found"));

    Assert.assertEquals(3, firstBook.getPages().size());  // check if first book has 3 pages
    Assert.assertEquals(2, secondBook.getPages().size()); // check if second book has 2 pages

    // query the first page of second book
    Page secondBookFirstPage = pageRepository.findById(secondBook.getPages().get(0).getId()).orElseThrow(() -> new IllegalArgumentException(""));

    Assert.assertEquals(secondBook.getId(), secondBookFirstPage.getBook().getId());  // check if the page is correctly associated to the book
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM