在 JPA 中的两个相关实体中出现一些急切的 n 到 n 关系中进行更新

发布于 2024-10-24 19:50:44 字数 1604 浏览 1 评论 0原文

更改 JPA 中两个实体之间的 n 对 n 关系的最佳方法是什么?

让我用一个例子来解释:假设我有一个 Author 和一个 Book 实体,其中 Book 可以有不同的 Author< /code> 和作者 可以在各种书籍 上书写。该模型表示为 n 到 n 的热切关系:

@Entity
public class Author {
    @ManyToMany(fetch=FetchType.EAGER)
    private List<Book> books;
    // ...
}

另外:

@Entity
public class Book {
    @ManyToMany(fetch=FetchType.EAGER)
    private List<Author> authors;
    // ...
}

我想在这些实体中执行一系列操作 - 例如,将 Author 标记为 Book 的作者

public class SomeService {
    public void addAuthor(Book book, Author author)  {
        book.getAuthors().add(author);
        em.persist(book);
    }
}

或从作者的书籍中删除书籍

public class SomeOtherService {
    public void removeBook(Author author, Book book) {
        author.getBooks().remove(book);
        em.persist(author);
    }
}

但是,我希望在不更新的实体中注册更新。当我调用 addAuthor() 时,我希望更新的 Book 也出现在 Author 的图书列表中。当我调用 removeBook() 时,我希望将 AuthorBook 的作者列表中删除。

如果代码如上面的那样,则不会发生。我当前的解决方案是在两个实体中执行相应的操作,或多或少如下所示:

public class SomeService {
    public void addAuthor(Book book, Author author)  {
        book.getAuthors().add(author);
        author.getBooks().add(book);
        em.persist(book);
        em.persist(author);
    }
}

不过,对我来说,这看起来是一个蹩脚的解决方案。所以,我的问题是:有更好的方法吗?

提前致谢?

What is the best way to change n-to-n relationships between two entities in JPA.

Let me explain with an example: suppose I have both an Author and a Book entity, where a Book can have various Authors and an Author can write on various Books. This model is represented as a n-to-n eager relationship:

@Entity
public class Author {
    @ManyToMany(fetch=FetchType.EAGER)
    private List<Book> books;
    // ...
}

Also:

@Entity
public class Book {
    @ManyToMany(fetch=FetchType.EAGER)
    private List<Author> authors;
    // ...
}

I want to execute a series of operations in those entities - for example, mark an Author as the author of a Book:

public class SomeService {
    public void addAuthor(Book book, Author author)  {
        book.getAuthors().add(author);
        em.persist(book);
    }
}

or remove a Book from the books of an Author:

public class SomeOtherService {
    public void removeBook(Author author, Book book) {
        author.getBooks().remove(book);
        em.persist(author);
    }
}

However, I would like to have the update registered in the entity not being updated too. When I call addAuthor(), I expect the updated Book to be also present in the list of books of the Author. I want the Author to be removed of the Book's list of authors when I call removeBook().

If the code is such as the above ones it does not happen. My current solution is to execute de corresponding operation in both entities, more or less like shown below:

public class SomeService {
    public void addAuthor(Book book, Author author)  {
        book.getAuthors().add(author);
        author.getBooks().add(book);
        em.persist(book);
        em.persist(author);
    }
}

It looks like a lame solution to me, though. So, my question is: is there a better way to do it?

Thanks in advance?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

踏雪无痕 2024-10-31 19:50:45

对我来说这并不是一个蹩脚的解决方案,因为它对正在发生的事情非常明确,一个优点是你有一个非常明确的代码。但是,如果这对您来说过于明确,那么您有多种选择。

正如 Jay 建议的那样,您可以编写一个 @PreUpdate 回调方法来执行以下操作:

author.getBooks().add(book);

为此,您必须使用 @ManyToMany 注释的级联属性将更改级联到作者

@ManyToMany(cascade=CascadeType.ALL)

,或者您可以增加 CascadeType 的粒度。

还有一种替代方法是通过委托方法处理集合。
这样做的优点是您可以完全控制 addAuthor 方法,但对于使用您的实体的人来说不太直观。
它可以写成:

public void addAuthor(Author A) {
this.getAuthors().add(a);
a.addBook(this);
}

然后您需要实现 addBook。以及级联变化。

你的选择也不错。您可以使用辅助类来实现此目的,但这会增加复杂性,因此请记住这一点。

It's not really a lame solution to me as it is very explicit on what's going on, an advantage is that you have a very explicit code. However, if to you that is excessively explicit then you have several alternatives.

As Jay suggested you could write a @PreUpdate callback method that does the:

author.getBooks().add(book);

for this to work however you have to cascade changes to the authors using the cascade attribute of the @ManyToMany annotation

@ManyToMany(cascade=CascadeType.ALL)

for instance, or you can increase the granularity of the CascadeType.

There's an alternative that is handling the collections through delegate methods.
This has the advantage that you have a total control over the addAuthor method but is less intuitive to people using your entity.
It could be written as:

public void addAuthor(Author A) {
this.getAuthors().add(a);
a.addBook(this);
}

you would then need to implement addBook. And cascade changes.

Your option isn't bad either. You can use helper classes to achieve this but this increases complexity so bare that in mind.

[旋木] 2024-10-31 19:50:45

维持双向关系状态可能具有挑战性。最后两者都

book.getAuthors().add(author);
author.getBooks().add(book);

需要被调用。

在我工作的地方,我们使用aspectJ和一些内部元数据来处理关联双方的设置。对于小型项目来说,这有点矫枉过正,但你这样做的方式很好。

Maintaining bidirectional relation state can be challenging. In the end both

book.getAuthors().add(author);
author.getBooks().add(book);

need to be called.

Where I work we use aspectJ with some internal metadata to handle the setting both sides of the association. For small project this is a bit overkill, and the way you do it is fine.

风柔一江水 2024-10-31 19:50:45

Vincent Partington once wrote a blog post about bidirectional assocations which i believe you will find interesting. Essentially, he suggests having two relationship-adding methods on each partner in the relationship: an inner (package-scoped, presumably) one which simply adds an item to the collection, and an outer one which calls the inner methods on both partners. The same pattern can be applied to removal methods.

鹤舞 2024-10-31 19:50:45

看看@PreXXXX & @PostXXXX 注释。根据您使用的数据库,您最好编写自己的触发器。

如果您正在寻找最不可知的方法,那么您提出的解决方案应该可以解决问题。

编辑:
另一种方法是保存触发器代码并在 CRUD 之后将其作为本机 SQL 运行(假设您在测试中创建并删除)。

Look into @PreXXXX & @PostXXXX annotations. Depending on the DB you're using, you might be better off going in and writing your own triggers.

If you're looking for the most agnostic approach, the solution you put down should do the trick.

Edit:
Other approach is to save your trigger code and run it as native SQL after the CRUD (assuming you create & drop in your tests).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文