Hibernate - JPA - OneToMany 和CascadeType.ALL

3

我有一个与@OneToMany相关的JPA问题,特别是涉及CascadeType.ALL功能。

我有两个实体,父级和子级。

以下是我目前的一个示例:

@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;
    ...
    ...
}

页面

@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;
    ...
    ...
}

因此,我只有一本书和几页内容。当我保存这本书时,由于使用了CascadeType.ALL,也会保存其中包含的页面。
实际上,Hibernate尝试保存Book对象,并意识到有Page实体需要保存,然后通过级联ALL启动查询。
我只需执行以下操作:
bookRepository.save(book);

我想要保存的图书对应的JSON如下所示:

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

然而,如果我将书的id设置为0,则会被创建。如果我在页面中将idBook设置为0,则会出现错误,如下所示:Key (id_book) = (0) is not present in table "book"。

但是,我真正不理解的是:为什么如果我在json中放置一个在数据库中存在的id_book,然后完成保存书实体后,它就能理解这个id并在数据库中正确插入它?

因此,如果我提供以下JSON。

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

让我们考虑在数据库中只有一个id=1的书。 保存后,它会生成一行id=2的书,并且与该书链接的页面的id为2?

这件事让我疯狂。 如果给了一个idBook=0来告诉他要检索/生成它,他告诉我它不存在。另一方面,如果我给他一个确实存在但不正确的idBook,他会不予考虑并且会正确地使用刚刚创建的书的idBook吗?

我只想不用使用虚假的idBook,告诉他在创建书之后要获取页面的idBook,就像他现在所做的那样。


所以你正在传递一个 Page 并保存一个 Book - Andronicus
@Andronicus 不是的,我传递了一本书,带有一页(或者可能是多页)来保存一本书和一页。通过OneToMany中的CascadeType,可以将两个不同的实体保存在一个实体中。 - Ed-Genesis
2个回答

1

Page实体中存在的id_book列可以推断出您想使用双向映射。尝试像这样做,将子实体作为关系的所有者:

@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);
    }
}

1
考虑到“我传递一本书,有一页(或多页)”,我也想分享我的解决方案。 与其他答案有些相似,我也使用了双向。我还包括了集成测试。 书籍实体。
@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() {
    }

}

页面实体
@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() {}
}

集成测试
@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
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接