【问题标题】:Spring - POSTing JSON body with nested entity does not workSpring - 使用嵌套实体发布 JSON 主体不起作用
【发布时间】:2018-07-06 09:40:12
【问题描述】:

我是 spring 和 java 的新手,如果这很明显,请道歉。

TLDR 当我发送带有嵌套资源的 JSON 时,它会创建子资源。即使它已经存在并导致持久性问题,您如何在 Spring 中阻止它?

我有两个实体,书和书架。一个书架可以有多本书,但一本书只能在一个书架上。所以书架 (1) (*) 书。

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

货架

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

我有一个扩展 JpaRepository 的 ShelfRepository。

当我向正文提出请求时

{"name":"ShelfName", "books": [{"name": "bookName"}]}

它将返回创建资源书和书架,但不会链接它们,因为书是首先创建的,书架没有参考。所以需要调用 setBooks on the Shelf。不理想,但我想不出另一种方法。

创建一本书并使用 id 作为 books 数组中的引用(这是我希望在我的 API 中使用的),如下所示:

{"name":"otherShelfName", "books": [{"id": "7d9c81c2-ac25-46ab-bc4d-5e43c595eee3"}]}

这会导致持久性问题,因为这本书已经存在

org.hibernate.PersistentObjectException: 分离的实体传递给持久化

有没有办法在 Spring 中拥有像上面这样的嵌套资源并且能够在没有持久性问题的情况下进行关联?

-------- 服务

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

【问题讨论】:

    标签: java json spring hibernate spring-data-jpa


    【解决方案1】:

    欢迎来到痛苦的... ehhh 我的意思是美妙的... ORM 的世界,做简单的事情是微不足道的,但做一些复杂的事情会变得越来越复杂:P

    一些可能有帮助的指针:

    • 如果一本书必须属于一个书架,@JoinColumn 也应该有 nullable=false;这应该会强制 Hibernate 首先持久化书架,因为它需要 ID 来持久化图书的架子 ID FK。
    • 你有一个双向关系,在某种程度上,可以看作是一个无限循环(书架包含书籍,引用书架,包含书籍,...);有一些方法可以使用 Jackson 来处理,你可以阅读它here
    • 回到上面的两点,虽然您的书架包含 JSON 数据中的书籍,但相同的数据没有明确地将书指向书架,并且从 Hibernate 的角度来看,这种关系是可选的,所以它可能只是没有不要费心做链接。
    • 如果幸运的话,现在“nullable=false”技巧可能会解决所有这些问题,但没有什么是容易的,所以我会怀疑它;正如我之前所说,如果您只查看书籍的 JSON,它们没有对父书架的引用,因此当杰克逊将书籍转换为 Java 对象时,“架子”属性很可能保持为空(但您不能引用书架的 ID因为它还没有被创建......多么有趣!)。您需要做的是,在您的 ShelfRepository 中,当您创建书架时,您首先必须浏览其中包含的所有书籍并设置对父书架的引用,如 this article 中所述。
    • 最后,关于“传递给持久化的分离实体”异常,如果您给它一个填充了其标识字段的对象,Hibernate 将始终这样做,但实际对象从未使用 Hibernate 获取;如果您只是使用 ID 引用 JSON 中的对象,则必须首先从 Hibernate 获取真实实体并在持久性中使用该确切实例。

    我希望所有这些信息对您有所帮助。

    【讨论】:

      猜你喜欢
      • 2014-08-25
      • 1970-01-01
      • 2019-06-14
      • 2018-03-11
      • 1970-01-01
      • 2011-08-12
      • 1970-01-01
      • 2017-09-24
      相关资源
      最近更新 更多