consider the following sample. I have two entities: Author
and Book
. Their signatures are:
@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<>();
}
and
@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;
}
I want to query Author
and produce List<AuthorResponse>
. The AuthorResponse
contains attributes similar to the Author
and Set<BookResponse>
, whereas the BookResponse
has attributes identical to the Book
. Hence I have written the following code:
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
}
The code author.getBooks()
clearly throws LazyInitializationException
, unless I don't initialize it explicitly either with session.fetch()
or Mutiny.fetch()
. The problem is that invoking either of these two methods within the above code chain doesn't fit, because it returns Uni<Set<Book>>
, unless I do the following:
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()
and clearly, it is the anti-patten of reactiveness (if my understanding is correct).
So to mitigate the above situation I have used EntityGraph
as follows:
EntityGraph<Author> entityGraph = session.createEntityGraph(Author.class);
entityGraph.addAttributeNodes("book");
Query<Author> query = session.createQuery(criteriaQuery);
query.setPlan(entityGraph);
Afterward, it is working.
I am wondering if using the EntityGraph
in such situations is a good practice or not. Or is there any better way?
Any suggestions would be appreciated.
Regards, Tapas