2015-05-26 42 views
13

使用Spring Data REST。如果您有oneToMany或ManyToOne关系,则PUT操作在“非拥有”实体上返回200,但实际上并未持续连接的资源。如何与Spring Data REST和JPA保持双向关系?

示例实体。

@Entity(name = 'author') 
@ToString 
class AuthorEntity implements Author { 

    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    Long id 

    String fullName 

    @ManyToMany(mappedBy = 'authors') 
    Set<BookEntity> books 
} 


@Entity(name = 'book') 
@EqualsAndHashCode 
class BookEntity implements Book { 

    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    Long id 

    @Column(nullable = false) 
    String title 

    @Column(nullable = false) 
    String isbn 

    @Column(nullable = false) 
    String publisher 

    @ManyToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL]) 
    Set<AuthorEntity> authors 
} 

如果您有PagingAndSortingRepository支持他们,你可以得到一本书,跟随作者的书链接,做一个PUT与URI一个作者与相关联的。你不能走另一条路。

如果你做一个作者一个GET,做一个穿上它的图书链接,响应返回200,但关系永远不会持久。

这是预期的行为?

+2

双向关联必须手动维护在对象模型中,通常在setters内​​完成。 Spring Data REST默认使用字段访问。这意味着协会的变化不会在定义的协会的另一面反映出来。你有没有试过,在'@ PrePersist' /'@ PreUpdate'方法中触发同步?或切换到财产访问? –

+1

我也投票解决双向关联。一个'Author'可以存在,没有'Book'。可以使用存储库方法'设置 BookRepository.findByAuthor(作者作者)'来检索'作者'的书籍。这样可以简化模型,因为您不必手动维护引用。 –

+0

我可以看到一个库界面,您需要找到作者,然后查看它的书籍。如果没有双向关联,这将不被支持。不是管理实体关联的粉丝,如果它不是DATA REST的意图。感谢您的参与,对书籍作者进行过滤似乎是最好的方法。不要在任何可能的情况下保持单向。 :) – keaplogik

回答

24

TL;博士

的关键,不在春季数据REST这么多的东西 - 因为你可以很容易地得到它在方案中的工作 - 但要确保你的模型保持协会的两端同步。

问题

你在这里看到的问题的事实,春季数据REST基本修改您的AuthorEntitybooks属性出现。这本身并不反映BookEntityauthors属性中的此更新。这需要手动解决,这不是Spring Data REST所构成的限制,而是JPA通常工作的方式。只需手动调用设置器并尝试保留结果,就可以重现错误的行为。

如何解决这个问题?

如果删除双向关联是不是一种选择(见下文为什么我建议这个),使这项工作的唯一方法是使该协会确定的变化在两侧体现。通常人们通过在添加了一本书手动添加笔者以BookEntity照顾这一点:

class AuthorEntity { 

    void add(BookEntity book) { 

    this.books.add(book); 

    if (!book.getAuthors().contains(this)) { 
     book.add(this); 
    } 
    } 
} 

如果子句会一直要在BookEntity侧增加,以及如果你想确保附加另一方面的变化也会传播。基本上需要if,否则这两种方法会不断地自行调用。

春季数据REST,默认使用字段访问,这样实际上世界上没有其他方法,你可以把这个逻辑之中。一种选择是切换到财产访问并将逻辑放入设置者。另一种选择是使用注释为​​/@PrePersist的方法来迭代实体,并确保修改反映在两边。

拆除问题

正如你所看到的根本原因,这增加了不少复杂的域模型。

#双向协会1规则:正如我在Twitter上开玩笑说昨天没有使用这些... :)

通常,如果你尽量不要使用双向简化了此事只要有可能,就退回到一个存储库,以获得组成协会背后的所有实体。

一个很好的启发式方法可以确定哪一边是关联关系对于你建模的领域真正的核心和关键。在你的情况下,我认为,对于作者来说,没有她写的书是完美的。另一方面,没有作者的书根本没有太多意义。所以我想保持authors财产BookEntity但引进的BookRepository下面的方法:

interface BookRepository extends Repository<Book, Long> { 

    List<Book> findByAuthor(Author author); 
} 

是的,这需要以前可能只是援引author.getBooks()所有客户端现已与仓库工作。但从积极的一面来看,你已经从你的域对象中删除了所有的东西,并创建了一个明确的从书到作者的依赖方向。书籍取决于作者,而不是相反。

+4

您的答案的“模板”应该以某种方式标准化为SO:P!好东西! – geoand

+0

如果我很好地读到你,你建议删除所有OneToMany关系,并只保留他们的ManyToOne吊坠? 这将是我对实体进行建模的真正重大改变...... –

+0

不,我不推荐任何基于附注的基础。我建议不要使用双向关联。实际上,我甚至没有提到任何注释,所以我不完全明白你是如何得出这种解释的。您削减的协会的哪一方很大程度上取决于用例。请重新阅读提及启发式的部分进行论证。 –

1

我遇到了一个类似的问题,虽然通过REST API将我的POJO(包含双向映射@OneToMany和@ManyToOne)作为JSON发送,但数据在父实体和子实体中都保留,但外键关系不是成立。发生这种情况是因为需要手动维护双向关联。

JPA提供了一个注释@PrePersist,它可以用来确保在实体持久化之前执行用它注释的方法。由于JPA首先将父实体插入到子实体后面的数据库中,我添加了一个注释为@PrePersist的方法,该方法将迭代子实体列表并手动将父实体设置为它。

在你的情况下,它会是这样的:

class AuthorEntitiy { 
    @PrePersist 
    public void populateBooks { 
     for(BookEntity book : books) 
      book.addToAuthorList(this); 
    } 
} 

class BookEntity { 
    @PrePersist 
    public void populateAuthors { 
     for(AuthorEntity author : authors) 
      author.addToBookList(this); 
    } 
} 

此之后,你可能会得到一个无限递归误差,避免注释父类@JsonManagedReference@JsonBackReference你的子类。这个解决方案为我工作,希望它也适用于你。

相关问题