Spring - 使用嵌套实体发布JSON正文不起作用 [英] Spring - POSTing JSON body with nested entity does not work
问题描述
$ b
TLDR
当我发送JSON时嵌套的资源,它创建子资源。即使它已经存在,导致持久性问题,你如何在Spring中停止它?
我有两个实体,Book和Shelf。一个书架可以有多本书,但一本书只能放在一个书架上。所以Shelf(1)< - >(*)Book。
@Entity
public class Book {
@Id
@GenericGenerator(name =uuid-gen,strategy =uuid2)
@GeneratedValue(generator =uuid-gen)
@Type (type =pg-uuid)
私人UUID ID;
@Column(nullable = false)
私有字符串名称;
私人字符串描述;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(foreignKey = @ForeignKey(name =shelf_id))
私有货架;
$ b public Book(){
}
public Book(UUID id,String name,String description,Shelf shelf){
this.id = ID;
this.name = name;
this.description = description;
this.shelf = shelf;
}
public UUID getId(){
return id;
}
public void setId(UUID id){
this.id = id;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public void setShelf(Shelf shelf){
this.shelf = shelf;
$ / code $ / pre
@Entity
public class Shelf {
@Id
@GenericGenerator(name =uuid-gen,strategy =uuid2)
@GeneratedValue(generator =uuid-gen)
@Type(type =pg-uuid)
private UUID id;
@Column(nullable = false)
私有字符串名称;
私人字符串描述;
@OneToMany(fetch = FetchType.LAZY,mappedBy =shelf,cascade = CascadeType.ALL)
private Set< Book>图书;
$ b $ public Dashboard(){
}
public Dashboard(UUID id,String name,String description,Set&Book> book){
this .id = id;
this.name = name;
this.description = description;
this.book = book;
}
public UUID getId(){
return id;
}
public void setId(UUID id){
this.id = id;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
public void setBooks(){
for(Book book:books)
book.setShelf(this);
}
public Set< Book> getBooks(){
返还书籍;
}
}
我有一个扩展JpaRepository的ShelfRepository。
当我向body发出请求时
{name: ShelfName,books:[{name:bookName}]}
返回创建资源书籍和书架,但不会链接它们,因为Book是首先创建的,没有书架可供参考。所以需要在架子上调用setBooks。不理想,但我不能找出另一种方式。
创建一本书并使用id作为books数组中的参考(这正是我在我的API中所期望的)如下所示:
{name:otherShelfName,books:[{id:7d9c81c2-ac25 -46ab-bc4d-5e43c595eee3}]}
这会导致存在持久性问题, / p>
org.hibernate.PersistentObjectException:传递给persist的分离实体
有没有如何在Spring中拥有像上面这样的嵌套资源,并且能够在没有持久性问题的情况下进行关联?
--------服务
@Service
公共类BookService {
$ b $ @Autowired
私有BookRepository bookRepository;
公共列表< Book> findAll(){
return bookRepository.findAll();
}
public Book create(Book book){
return bookRepository.save(book);
}
}
@Service
公共类ShelfService {
$ b $ @Autowired
私有ShelfRepository shelfRepository;
公共列表< Shelf> findAll(){
return shelfRepository.findAll();
}
public Book create(Book book){
Shelf newShelf = shelfRepository.save(shelf);
shelf.setBooks();
返回newShelf;
解决方案欢迎到痛苦的...... ehhh我的意思是令人惊叹...... ORM的世界在做简单的事情是微不足道的,但做事情有时也变得复杂起来变得越来越复杂:P
很少有指针可以帮助:
- 如果一本书必须属于一个书架,@JoinColumn应该也可以有nullable = false;这应该强制Hibernate首先持久化搁架,因为它需要ID来坚持书的shelf_id FK。
- 您有双向关系,在某种程度上可以看作是无限的循环(书架包含书籍,指的是书架,包含书籍,...);有办法来处理,使用杰克逊,你可以阅读关于它这里
回到上述两点,尽管您的书架包含JSON数据中的书籍,但相同的数据并未明确将书籍书籍指向书架AND, Hibernate透视关系是可选的,所以它可能只是没有打扰做链接。
现在,如果你幸运的话,nullable = false技巧可能会解决所有这些问题,但没有任何很容易,所以我会怀疑它;正如我之前所说的,如果你只看书的JSON,它们没有对父架子的引用,所以当Jackson把书籍转换成Java对象时,架子属性很可能保持为空(但你不能引用架子的ID因为它还没有创建...多么有趣!)。你需要做的是,在你的ShelfRepository中,当你创建书架时,你首先必须浏览它包含的所有书籍,并按照这篇文章。 最后,关于分离的实体传递给持久化异常,如果你给它一个带有其身份字段的对象,Hibernate会一直这样做,但实际的对象从来没有使用Hibernate取得;如果您只是使用ID在您的JSON中引用对象,则必须首先从Hibernate中获取实际实体,并在持久性中使用该实例。
我希望所有这些信息都能帮助你。
I'm new to spring and java, so apologises if this is obvious.
TLDR
When I send JSON with nested resources, it creates the subresource. EVEN when it already exists, causing a persistence issue, how do you stop this in Spring?
I have two entities, Book and Shelf. A shelf can have multiple books but a book can only be on one shelf. So Shelf (1) <-> (*) Book.
@Entity
public class Book {
@Id
@GenericGenerator(name = "uuid-gen", strategy = "uuid2")
@GeneratedValue(generator = "uuid-gen")
@Type(type = "pg-uuid")
private UUID id;
@Column(nullable = false)
private String name;
private String description;
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(foreignKey = @ForeignKey(name = "shelf_id"))
private Shelf shelf;
public Book() {
}
public Book(UUID id, String name, String description, Shelf shelf) {
this.id = id;
this.name = name;
this.description = description;
this.shelf = shelf;
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setShelf(Shelf shelf) {
this.shelf = shelf;
}
}
Shelf
@Entity
public class Shelf {
@Id
@GenericGenerator(name = "uuid-gen", strategy = "uuid2")
@GeneratedValue(generator = "uuid-gen")
@Type(type = "pg-uuid")
private UUID id;
@Column(nullable = false)
private String name;
private String description;
@OneToMany(fetch=FetchType.LAZY, mappedBy = "shelf", cascade = CascadeType.ALL)
private Set<Book> books;
public Dashboard() {
}
public Dashboard(UUID id, String name, String description, Set<Book> books) {
this.id = id;
this.name = name;
this.description = description;
this.book = book;
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setBooks() {
for (Book book : books)
book.setShelf(this);
}
public Set<Book> getBooks() {
return books;
}
}
I have a ShelfRepository which extends JpaRepository.
When I make a request with the body
{"name":"ShelfName", "books": [{"name": "bookName"}]}
it will return create the resource Book and Shelf but does not link them as Book is created first without Shelf to reference. So calling setBooks on the Shelf is needed. Not ideal but I cant figure out another way.
Creating a book and using the id as the reference in the books array (which is what I would like in my API) like below:
{"name":"otherShelfName", "books": [{"id": "7d9c81c2-ac25-46ab-bc4d-5e43c595eee3"}]}
This causes a persistence issue as the book already exist
org.hibernate.PersistentObjectException: detached entity passed to persist
Is there a way to have a nested resource like above in Spring and be able to associate without a persistence issue?
-------- Services
@Service
public class BookService {
@Autowired
private BookRepository bookRepository;
public List<Book> findAll() {
return bookRepository.findAll();
}
public Book create(Book book) {
return bookRepository.save(book);
}
}
@Service
public class ShelfService {
@Autowired
private ShelfRepository shelfRepository;
public List<Shelf> findAll() {
return shelfRepository.findAll();
}
public Book create(Book book) {
Shelf newShelf = shelfRepository.save(shelf);
shelf.setBooks();
return newShelf;
}
}
解决方案 Welcome to the painful ... ehhh I meant wondeful ... world of ORMs where doing simple things is trivial, but doing things even a tad complicated becomes increasingly complicated :P
Few pointers that might help:
- If a book HAS to belong to a shelf, the @JoinColumn should probably also have nullable=false; this should force Hibernate to persiste the shelf first since it will need the ID to persist the book's shelf_id FK.
- You have a bidirectional relationship which, in a way, can be seen as an infinite loop (shelf contains books, that references shelf, that contains books, that ...); there are ways to handle that using Jackson, you can read about it here.
- Going back to both points above, although your shelf contains books in your JSON data, that same data doesn't explicitly have the book point back to shelf AND, from an Hibernate perspective the relationship is optional anyway so it probably just didn't bother doing the link.
- Now the "nullable=false" trick might solve all of that if you are lucky, but nothing is ever easy so I would doubt it; as I said before, if you look only at the JSON for books, they have no references to the parent shelf so when Jackson converts the books to Java object, the "shelf" property most probably stays null (but you cannot reference the shelf's ID since it wasn't created yet ... how fun!). What you will have to do is, in your ShelfRepository, when you create the shelf, you will first have to go through all the books it contains and set the reference to the parent shelf as explained in this article.
- Finally, regarding the "detached entity passed to persist" exception, Hibernate will always do that if you give it an object with its identity field populated, but the actual object was never fetched using Hibernate; if you are merely referencing an object in your JSON using the ID, you will have to first fetch the real entity from Hibernate and use that exact instance in your persistence.
I hope all this info will help you.
这篇关于Spring - 使用嵌套实体发布JSON正文不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!