Hibernate Reactive-在迭代父实体期间获取子关联的最佳实践



请考虑以下示例。我有两个实体:AuthorBook。他们的签名是:

@Getter
@Setter
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "author")
public class Author implements Serializable {
@Serial
private static final long serialVersionUID = 7626370553439538790L;
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false)
private String name;
@Default
@OneToMany(fetch = FetchType.LAZY, mappedBy = "author", cascade = CascadeType.ALL)
private Set<Book> books = new HashSet<>();
}

@Getter
@Setter
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "book")
public class Book implements Serializable {
@Serial
private static final long serialVersionUID = 4454993533777924839L;
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false)
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id", nullable = false)
private Author author;
}

我想查询Author并生成List<AuthorResponse>AuthorResponse包含与AuthorSet<BookResponse>类似的属性,而BookResponse具有与Book相同的属性。因此,我编写了以下代码:

public Uni<List<AuthorResponse>> getAuthors() {
// @formatter:off
return sessionFactory.withSession(
(Session session) -> {
CriteriaBuilder criteriaBuilder = sessionFactory.getCriteriaBuilder();
CriteriaQuery<Author> criteriaQuery = criteriaBuilder.createQuery(Author.class);
Root<Author> authorTable = criteriaQuery.from(Author.class);
criteriaQuery.select(authorTable);
Query<Author> query = session.createQuery(criteriaQuery);
query.setFirstResult(0);
query.setMaxResults(10);
return query.getResultList()
.onItem()
.transform(
(List<Author> authors) -> authors
.stream()
.map(
(Author author) -> AuthorResponse.builder()
.id(author.getId())
.name(author.getName())
.books(
author.getBooks()
.stream()
.map(
(Book book) -> BookResponse.builder()
.id(book.getId())
.name(book.getName())
.build()
)
.collect(Collectors.toSet())
)
.build()
)
.collect(Collectors.toList())
);
}
);
// @formatter:on
}

代码author.getBooks()显然抛出LazyInitializationException,除非我没有用session.fetch()Mutiny.fetch()显式初始化它。问题是,在上面的代码链中调用这两个方法中的任何一个都不合适,因为它返回Uni<Set<Book>>,除非我执行以下操作:

Mutiny.fetch(author.getBooks())
.onItem()
.transform(
(Set<Book> books) -> books.stream()
.map(
(Book book) -> BookResponse.builder()
.id(book.getId())
.name(book.getName())
.build()
)
.collect(Collectors.toSet()))
.await()
.indefinitely()

很明显,这是反应性的反模式(如果我的理解是正确的(。

因此,为了缓解上述情况,我使用了EntityGraph,如下所示:

EntityGraph<Author> entityGraph = session.createEntityGraph(Author.class);
entityGraph.addAttributeNodes("book");
Query<Author> query = session.createQuery(criteriaQuery);
query.setPlan(entityGraph);

之后,它开始发挥作用。

我想知道在这种情况下使用EntityGraph是否是一种好的做法。或者有更好的方法吗?

如有任何建议,我们将不胜感激。

谨致问候,Tapas

我已经在GitHub上回答了,但我想我会在这里重复一遍。

通常,在JPQL查询或EntityGraph中使用渴望获取来获取结果是一种更好的方法,因为您将使用单个查询加载关联。如果你知道你将需要相关的元素,这是有意义的。

但是您仍然可以使用Mutiny.fetch而不进行阻塞。我会将Uni<List<Author>>转换为Multi<Author>:

return query.getResultList()
// Convert the Uni<List<Author> into a Multi<Author>
.onItem().transformToMulti( Multi.createFrom()::iterable )
// For each author fetch the books
.onItem().call( author -> Mutiny.fetch( author.getBooks() ) )
// Now everything has been fetched and you can build the response
.map( this::buildAuthorResponse )
// Convert the Multi<AuthorResponse> into Uni<List<AuthorResponse>>
.collect().asList();
...

private void AuthorResponse buildAuthorResponse(Author author) {
return AuthorResponse.builder()
.id(author.getId())
.name(author.getName())
.books(
author.getBooks()
.stream()
.map(
(Book book) -> BookResponse.builder()
.id(book.getId())
.name(book.getName())
.build()
)
.collect(Collectors.toSet())
)
.build();
}

请注意,如果您使用Quarkus,可能不需要将结果收集到Uni<List<AuthorResponse>>中,只需返回Multi<AuthorReponse>即可。

无论如何,所有的方法都是有效的,您可以选择更适合您的用例的方法。请记住,每次获取列表中每个结果的关联都会导致新的查询,通常不建议这样做(N+1问题(。

相关内容

  • 没有找到相关文章

最新更新