Reactive panache: 使用 Mutiny fetch 停止获取双向关联实体

huangapple go评论42阅读模式
英文:

Reactive panache: stop fetching bi-directional related entity with Mutiny fetch

问题

当使用 Java Quarkus Reactive Panache 获取对象时,遇到栈溢出的示例:

@Table(name = "author")
public class Author extends PanacheEntityBase {
private Long id;
private String authorName;
@OneToMany(fetch = FetchType.LAZY)
private Set<Book> books;
}

---------
@Table(name = "book")
public class Book extends PanacheEntityBase {
private Long id;
private String bookName;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id")
private Author author;
@ManyToMany(fetch = FetchType.LAZY)
private Set<Publisher> publishers;
}

---------
@Table(name = "publisher")
public class Publisher extends PanacheEntityBase {
private Long id;
private String publisherName;
@ManyToMany(fetch = FetchType.LAZY)
@JoinColumn(...)
private Set<Book> books;
}

我使用 find 方法查找作者时遇到了 LazyInitializationException 错误。

> return find("authorId", id).firstResult()
> .onItem().call(au -> Mutiny.fetch(au.books()));
根本原因:作者和出版商之间的双向关系可能导致相关实体的无限循环。

当然,如果我将其映射到没有关系的 DTO 对象,问题就解决了。
参考链接:https://github.com/quarkusio/quarkus/issues/23757
问题是我想继续使用 Author,而不是 AuthorDTO...

英文:

An example of stack overflow when get an object using java quarkus reactive panache:

@Table(name = "author")
public class Author extends PanacheEntityBase {
private Long id;
private String authorName;
@OneToMany(fetch = FetchType.LAZY)
private Set<Book> books;
}

---------
@Table(name = "book")
public class Book extends PanacheEntityBase {
private Long id;
private String bookName;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id")
private Author author;
@ManyToMany(fetch = FetchType.LAZY)
private Set<Publisher> publishers;
}

---------
@Table(name = "publisher")
public class Publisher extends PanacheEntityBase {
private Long id;
private String publisherName;
@ManyToMany(fetch = FetchType.LAZY)
@JoinColumn(...)
private Set<Book> books;
}

I do find an author and got error with LazyInitializationException.

> return find("authorId", id).firstResult()
> .onItem().call(au -> Mutiny.fetch(au.books()));
Root cause: Bi-directional relationships between authors and publishers can result in an infinite loop of related entities.

Of course, if I map it to a DTO object with no relationships, the problem is solved.
Reffered link: https://github.com/quarkusio/quarkus/issues/23757
The problem is that I want to keep working something with Author, not AuthorDTO...

答案1

得分: 0

在你的项目中,有多个关联关系:

  • 作者(Author)和书籍(Book)之间是双向的一对多关系。
  • 书籍(Book)和出版商(Publisher)之间是单向的多对多关系。

首先,问题之一是你没有告诉 Jackson 作者(Author)和书籍(Book)之间的关联是双向的,这可能会导致堆栈溢出异常。你可以使用注解 @JsonManagedReference@JsonBackReference 来解决这个问题:

class Author {

    ...

    @JsonManagedReference
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "author")
    public Set<Book> books = new HashSet<>();
}

class Book {

    ...
    @JsonBackReference
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id")
    public Author author;
}

LazyInitializationException 的出现是因为你获取了 books 关联,但没有获取 publishers 关联。另外,一般来说,你不应该使用 Mutiny.fetch 来获取关联,因为这可能会导致 N+1 问题。你可以使用 HQL 查询来解决这个问题:

@ApplicationScoped
public class AuthorRepo implements PanacheRepository<Author> {

    public Uni<Author> findAndFetch(Long id) {
        return find("select a from Author a left join fetch a.books b left join fetch b.publishers where a.id = ?1", id)
                .firstResult();
    }

    ...
}

这样,你可以通过一次查询获取所有需要的信息。

我已经分叉了你的项目并添加了使其快速运行所需的所有更改,可以在此提交中找到。

我尚未进行测试,但将关联与出版商作为 SUBSELECT 类型进行映射应该也适用于此情况。这样,你只需要获取第一个关联(Mutiny.fetch(book.publishers)),每本书的所有出版商都将被获取。

英文:

In your project you have multiple associations:

  • A bidirectional one-to-many between Author and Book
  • A unidirectional many-to-many between Book and Publisher

The first problem is that you are not telling Jackson that the association between Author and Book is bidirectional, this might result in a StackOverflow exception. You can solve it using the annotations @JsonManagedReference and @JsonBackReference:

class Author {

    ...

    @JsonManagedReference
    @OneToMany(fetch = FetchType.LAZY, mappedBy = &quot;author&quot;)
    public Set&lt;Book&gt; books = new HashSet&lt;&gt;();
}

class Book {

    ...
    @JsonBackReference
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;author_id&quot;)
    public Author author;

}

The LazyInitializationException happens because you are fetching the books association, but not the publishers one. Also, in general, you shouldn't use Mutiny.fetch to fetch the associations. The problem with it is that you will end up with a N+1 issue.
You can solve the problem using a HQL query:


@ApplicationScoped
public class AuthorRepo implements PanacheRepository&lt;Author&gt; {

    public Uni&lt;Author&gt; findAndFetch(Long id) {
        return find( &quot;select a from Author a left join fetch a.books b left join fetch b.publishers where a.id = ?1&quot;, id )
                .firstResult();
    }

    ...
}

This way you can get everything you need with one query.

I've forked your project and added all the changes needed to make it work quickly in this commit.

I haven't tested it, but mapping the association with publishers as a SUBSELECT type, should also work well for this scenario.
This way, you just need to fetch the first association (Mutiny.fetch(book.publishers)), and all the publishers for each book are going to be fetched.

huangapple
  • 本文由 发表于 2023年4月11日 13:02:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/75982542.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定