英文:
Hibernate - JPA - OneToMany and CascadeType.ALL
问题
我有一个关于 @OneToMany 的 JPA 问题,特别是涉及 CascadeType.ALL 功能的问题。
我有两个实体,一个父实体和一个子实体。
我给你展示一个我所拥有的示例:
书籍(BOOK)
```java
@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;
// ...
// ...
}
所以,我有一个书籍和多个页面。当我保存书籍时,由于 CascadeType.ALL 的存在,也会保存其中包含的页面。
这实际上是有效的,Hibernate 会尝试保存书籍对象,通过 OneToMany 发现还有页面实体需要保存,然后启动查询以实现级联保存。
我只需要执行:
bookRepository.save(book);
表示要保存的书籍的 JSON 如下所示:
{
"id": 0,
"pages": [
{
"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,
"pages": [
{
"id": 0,
"idBook": 1
}
]
}
并且假设数据库中只有一个 id 为 1 的书籍。
在保存后,它会生成一行 id=2 的书籍,以及一个链接到该书籍的页面,其 id 为 2。
这真的让我很困惑。
如果我给一个 idBook = 0 告诉它它必须检索/生成它,它告诉我该项不存在。另一方面,如果我给它一个实际存在的 idBook(但不正确),它甚至不会考虑它,并且会正确地使用刚刚创建的书籍的 idBook?
我只是希望不必使用虚假的 idBook,告诉它在创建书籍之后要使用页面的 idBook,就像它现在实际上所做的那样。
<details>
<summary>英文:</summary>
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.
</details>
# 答案1
**得分**: 1
根据```Page```实体中的```id_book```列的存在,我推断您想要使用双向映射。尝试按照以下方式进行操作,其中子实体作为关系的拥有者:
```java
@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
@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类型
private List<Page> pages = new ArrayList<>();
...
...
// 必要的同步方法
public void addPage(Page page) {
pages.add(page);
page.setBook(this);
}
public void removePage(Page page) {
pages.remove(this);
page.setBook(null);
}
}
英文:
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);
}
}
答案2
得分: 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;
}
// 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;
}
// 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);
// 查询第一本书和第二本书
firstBook = bookRepository.findById(firstBook.getId()).orElseThrow(() -> new IllegalArgumentException("未找到书籍ID。"));
secondBook = bookRepository.findById(secondBook.getId()).orElseThrow(() -> new IllegalArgumentException("未找到书籍ID。"));
Assert.assertEquals(3, firstBook.getPages().size()); // 检查第一本书是否有3页
Assert.assertEquals(2, secondBook.getPages().size()); // 检查第二本书是否有2页
// 查询第二本书的第一页
Page secondBookFirstPage = pageRepository.findById(secondBook.getPages().get(0).getId()).orElseThrow(() -> new IllegalArgumentException(""));
Assert.assertEquals(secondBook.getId(), secondBookFirstPage.getBook().getId()); // 检查页面是否正确关联到书籍
}
请注意,这只是翻译,不包括解释或其他内容。
英文:
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
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论