java 如何维护与 Spring Data REST 和 JPA 的双向关系?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30464782/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 17:10:43  来源:igfitidea点击:

How to maintain bi-directional relationships with Spring Data REST and JPA?

javaspring-bootspring-dataspring-data-jpaspring-data-rest

提问by keaplogik

Working with Spring Data REST, if you have a OneToManyor ManyToOnerelationship, the PUT operation returns 200 on the "non-owning" entity but does not actually persist the joined resource.

使用 Spring Data REST,如果您有一个OneToManyorManyToOne关系,PUT 操作会在“非拥有”实体上返回 200,但实际上并不持久化加入的资源。

Example Entities:

示例实体:

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

If you back them with a PagingAndSortingRepository, you can GET a Book, follow the authorslink on the book and do a PUT with the URI of a author to associate with. You cannot go the other way.

如果你用 a 支持它们PagingAndSortingRepository,你可以 GET a Book,点击authors书上的链接并使用要关联的作者的 URI 执行 PUT。你不能走另一条路。

If you do a GET on an Author and do a PUT on its bookslink, the response returns 200, but the relationship is never persisted.

如果您对作者执行 GET 并对其books链接执行 PUT ,则响应将返回 200,但该关系永远不会持久化。

Is this the expected behavior?

这是预期的行为吗?

回答by Oliver Drotbohm

tl;dr

tl;博士

The key to that is not so much anything in Spring Data REST - as you can easily get it to work in your scenario - but making sure that your model keeps both ends of the association in sync.

关键不在于 Spring Data REST 中的任何内容——因为您可以轻松地让它在您的场景中工作——但要确保您的模型使关联的两端保持同步。

The problem

问题

The problem you see here arises from the fact that Spring Data REST basically modifies the booksproperty of your AuthorEntity. That itself doesn't reflect this update in the authorsproperty of the BookEntity. This has to be worked around manually, which is not a constraint that Spring Data REST makes up but the way that JPA works in general. You will be able to reproduce the erroneous behavior by simply invoking setters manually and trying to persist the result.

你在这里看到的问题源于一个事实,即春季数据REST基本上改变了books你的财产AuthorEntity。这本身并没有authorsBookEntity. 这必须手动解决,这不是 Spring Data REST 构成的约束,而是 JPA 的一般工作方式。您将能够通过简单地手动调用 setter 并尝试保留结果来重现错误行为。

How to solve this?

如何解决这个问题?

If removing the bi-directional association is not an option (see below on why I'd recommend this) the only way to make this work is to make sure changes to the association are reflected on both sides. Usually people take care of this by manually adding the author to the BookEntitywhen a book is added:

如果删除双向关联不是一个选项(请参阅下文,了解我为什么推荐此操作),使这项工作起作用的唯一方法是确保对关联的更改反映在双方上。通常人们通过在添加BookEntity书籍时手动将作者添加到 来解决这个问题:

class AuthorEntity {

  void add(BookEntity book) {

    this.books.add(book);

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

The additional if clause would've to be added on the BookEntityside as well if you want to make sure that changes from the other side are propagated, too. The ifis basically required as otherwise the two methods would constantly call themselves.

BookEntity如果您想确保也传播来自另一端的更改,则还必须在侧面添加额外的 if 子句。该if否则这两种方法将不断称自己基本上是必需的。

Spring Data REST, by default uses field access so that theres actually no method that you can put this logic into. One option would be to switch to property access and put the logic into the setters. Another option is to use a method annotated with @PreUpdate/@PrePersistthat iterates over the entities and makes sure the modifications are reflected on both sides.

Spring Data REST,默认情况下使用字段访问,因此实际上没有可以将此逻辑放入的方法。一种选择是切换到属性访问并将逻辑放入设置器中。另一种选择是使用带有@PreUpdate/注释的方法,该方法@PrePersist迭代实体并确保修改反映在双方。

Removing the root cause of the issue

消除问题的根本原因

As you can see, this adds quite a lot of complexity to the domain model. As I joked on Twitter yesterday:

如您所见,这给域模型增加了相当多的复杂性。正如我昨天在 Twitter 上开玩笑说的:

#1 rule of bi-directional associations: don't use them… :)

#1 双向关联规则:不要使用它们...... :)

It usually simplifies the matter if you try not to use bi-directional relationship whenever possible and rather fall back to a repository to obtain all the entities that make up the backside of the association.

如果您尽量不使用双向关系,而是回退到存储库以获取构成关联背面的所有实体,则通常会简化问题。

A good heuristics to determine which side to cut is to think about which side of the association is really core and crucial to the domain you're modeling. In your case I'd argue that it's perfectly fine for an author to exist with no books written by her. On the flip side, a book without an author doesn't make too much sense at all. So I'd keep the authorsproperty in BookEntitybut introduce the following method on the BookRepository:

确定要削减哪一侧的一个很好的启发式方法是考虑关联的哪一侧对您正在建模的领域真正是核心和关键的。在你的情况下,我认为作者没有她写的书而存在是完全没问题的。另一方面,一本没有作者的书根本没有多大意义。所以我会保留该authors属性,BookEntity但在 上引入以下方法BookRepository

interface BookRepository extends Repository<Book, Long> {

  List<Book> findByAuthor(Author author);
}

Yes, that requires all clients that previously could just have invoked author.getBooks()to now work with a repository. But on the positive side you've removed all the cruft from your domain objects and created a clear dependency direction from book to author along the way. Books depend on authors but not the other way round.

是的,这需要所有以前可以调用的客户端author.getBooks()现在使用存储库。但从积极的方面来说,您已经从域对象中删除了所有杂物,并在此过程中创建了从书籍到作者的清晰依赖方向。书籍依赖于作者,但反之则不然。

回答by Vidhi Gupta

I faced a similar problem, while sending my POJO(containing bi-directional mapping @OneToMany and @ManyToOne) as JSON via REST api, the data was persisted in both the parent and child entities but the foreign key relation was not established. This happens because bidirectional associations need to be manually maintained.

我遇到了类似的问题,当我通过 REST api 将我的 POJO(包含双向映射 @OneToMany 和 @ManyToOne)作为 JSON 发送时,数据同时保留在父实体和子实体中,但未建立外键关系。这是因为双向关联需要手动维护。

JPA provides an annotation @PrePersistwhich can be used to make sure that the method annotated with it is executed before the entity is persisted. Since, JPA first inserts the parent entity to the database followed by the child entity, I included a method annotated with @PrePersistwhich would iterate through the list of child entities and manually set the parent entity to it.

JPA 提供了一个注解@PrePersist,可用于确保在实体持久化之前执行注解的方法。因为,JPA 首先将父实体插入数据库,然后是子实体,所以我包含了一个注释方法,@PrePersist该方法将遍历子实体列表并手动将父实体设置为它。

In your case it would be something like this:

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

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

After this you might get an infinite recursion error, to avoid that annotate your parent class with @JsonManagedReferenceand your child class with @JsonBackReference. This solution worked for me, hopefully it will work for you too.

在此之后,您可能会遇到无限递归错误,以避免@JsonManagedReference使用@JsonBackReference. 这个解决方案对我有用,希望它也对你有用。