Spring - 使用嵌套实体发布JSON正文不起作用 [英] Spring - POSTing JSON body with nested entity does not work

查看:89
本文介绍了Spring - 使用嵌套实体发布JSON正文不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述


$ 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

很少有指针可以帮助:



我希望所有这些信息都能帮助你。

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆