请考虑以下示例。我有两个实体:Author
和Book
。他们的签名是:
@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
包含与Author
和Set<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问题(。