Spring - 使用嵌套实体发布JSON主体不起作用

时间:2018-01-27 01:20:12

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

我是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"}]}

它将返回创建资源Book and Shelf但不链接它们,因为Book是先创建的而没有Shelf引用。因此需要在架子上调用setBooks。不理想,但我无法找到另一种方式。

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

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

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

org.hibernate.PersistentObjectException:传递给persist的分离实体

有没有办法在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;
    }
}

1 个答案:

答案 0 :(得分:-1)

欢迎来到痛苦的......呃,我的意思很棒...... ORM的世界在做简单的事情是微不足道的,但做得太复杂也变得越来越复杂了:P

可能有帮助的几个指针:

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

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