diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/FastBootReactiveEntityManagerFactoryBuilder.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/FastBootReactiveEntityManagerFactoryBuilder.java index 68cd314d36e7a..68943e0324968 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/FastBootReactiveEntityManagerFactoryBuilder.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/FastBootReactiveEntityManagerFactoryBuilder.java @@ -25,6 +25,7 @@ public FastBootReactiveEntityManagerFactoryBuilder(PrevalidatedQuarkusMetadata m @Override public EntityManagerFactory build() { final SessionFactoryOptionsBuilder optionsBuilder = metadata.buildSessionFactoryOptionsBuilder(); + optionsBuilder.enableCollectionInDefaultFetchGroup(true); populate(PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME, optionsBuilder, standardServiceRegistry, multiTenancyStrategy); SessionFactoryOptions options = optionsBuilder.buildOptions(); diff --git a/integration-tests/hibernate-reactive-postgresql/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveTestEndpointFetchLazy.java b/integration-tests/hibernate-reactive-postgresql/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveTestEndpointFetchLazy.java new file mode 100644 index 0000000000000..8f8fdc87f9fd0 --- /dev/null +++ b/integration-tests/hibernate-reactive-postgresql/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveTestEndpointFetchLazy.java @@ -0,0 +1,79 @@ +package io.quarkus.it.hibernate.reactive.postgresql; + +import java.util.Collection; +import java.util.concurrent.CompletionStage; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; + +import org.hibernate.reactive.mutiny.Mutiny; +import org.hibernate.reactive.stage.Stage; + +import io.quarkus.it.hibernate.reactive.postgresql.lazy.Author; +import io.quarkus.it.hibernate.reactive.postgresql.lazy.Book; +import io.smallrye.mutiny.Uni; +import io.vertx.mutiny.pgclient.PgPool; +import io.vertx.mutiny.sqlclient.Tuple; + +@Path("/hr-fetch") +public class HibernateReactiveTestEndpointFetchLazy { + + @Inject + Mutiny.Session mutinySession; + + // I'm using the factory because there are some issues with an injected Stage.Session + // See issue https://github.com/quarkusio/quarkus/issues/14812 + @Inject + Stage.SessionFactory stageSessionFactory; + + // Injecting a Vert.x Pool is not required, It's used to + // independently validate the contents of the database for the test + @Inject + PgPool pgPool; + + @GET + @Path("/findBooksWithMutiny/{authorId}") + public Uni> findBooksWithMutiny(@PathParam("authorId") Integer authorId) { + return mutinySession.find(Author.class, authorId) + .chain(author -> Mutiny.fetch(author.getBooks())); + } + + @GET + @Path("/findBooksWithStage/{authorId}") + public CompletionStage> findBooksWithStage(@PathParam("authorId") Integer authorId) { + return stageSessionFactory.withTransaction((session, tx) -> session + .find(Author.class, authorId) + .thenCompose(author -> Stage.fetch(author.getBooks()))); + } + + @POST + @Path("/prepareDb") + public Uni prepareDb() { + final Author author = new Author(567, "Neal Stephenson"); + final Book book1 = new Book("0-380-97346-4", "Cryptonomicon", author); + final Book book2 = new Book("0-553-08853-X", "Snow Crash", author); + author.getBooks().add(book1); + author.getBooks().add(book2); + + return mutinySession.createQuery(" delete from Book").executeUpdate() + .call(() -> mutinySession.createQuery("delete from Author").executeUpdate()) + .call(() -> mutinySession.persist(author)) + .chain(mutinySession::flush) + .chain(() -> selectNameFromId(author.getId())); + } + + private Uni selectNameFromId(Integer id) { + return pgPool.preparedQuery("SELECT name FROM Author WHERE id = $1").execute(Tuple.of(id)).map(rowSet -> { + if (rowSet.size() == 1) { + return rowSet.iterator().next().getString(0); + } else if (rowSet.size() > 1) { + throw new AssertionError("More than one result returned: " + rowSet.size()); + } else { + return null; // Size 0 + } + }); + } +} diff --git a/integration-tests/hibernate-reactive-postgresql/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/lazy/Author.java b/integration-tests/hibernate-reactive-postgresql/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/lazy/Author.java new file mode 100644 index 0000000000000..ab67fc157c138 --- /dev/null +++ b/integration-tests/hibernate-reactive-postgresql/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/lazy/Author.java @@ -0,0 +1,73 @@ +package io.quarkus.it.hibernate.reactive.postgresql.lazy; + +import static javax.persistence.CascadeType.PERSIST; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Objects; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +@Entity +@Table +public class Author { + @Id + private Integer id; + + private String name; + + @OneToMany(mappedBy = "author", cascade = PERSIST) + private Collection books = new ArrayList<>(); + + public Author() { + } + + public Author(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Collection getBooks() { + return books; + } + + public void setBooks(Collection books) { + this.books = books; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Author author = (Author) o; + return Objects.equals(name, author.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} diff --git a/integration-tests/hibernate-reactive-postgresql/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/lazy/Book.java b/integration-tests/hibernate-reactive-postgresql/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/lazy/Book.java new file mode 100644 index 0000000000000..b0162376261b1 --- /dev/null +++ b/integration-tests/hibernate-reactive-postgresql/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/lazy/Book.java @@ -0,0 +1,87 @@ +package io.quarkus.it.hibernate.reactive.postgresql.lazy; + +import static javax.persistence.FetchType.LAZY; + +import java.util.Objects; + +import javax.json.bind.annotation.JsonbTransient; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table +public class Book { + @Id + @GeneratedValue + private Integer id; + + private String isbn; + + private String title; + + @JsonbTransient + @ManyToOne(fetch = LAZY) + private Author author; + + public Book(String isbn, String title, Author author) { + this.title = title; + this.isbn = isbn; + this.author = author; + } + + public Book() { + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getIsbn() { + return isbn; + } + + public void setIsbn(String isbn) { + this.isbn = isbn; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Book book = (Book) o; + return Objects.equals(isbn, book.isbn) + && Objects.equals(title, book.title); + } + + @Override + public int hashCode() { + return Objects.hash(isbn, title); + } +} diff --git a/integration-tests/hibernate-reactive-postgresql/src/test/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveFetchLazyInGraalIT.java b/integration-tests/hibernate-reactive-postgresql/src/test/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveFetchLazyInGraalIT.java new file mode 100644 index 0000000000000..d62be581c51ca --- /dev/null +++ b/integration-tests/hibernate-reactive-postgresql/src/test/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveFetchLazyInGraalIT.java @@ -0,0 +1,7 @@ +package io.quarkus.it.hibernate.reactive.postgresql; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +public class HibernateReactiveFetchLazyInGraalIT extends HibernateReactiveFetchLazyTest { +} diff --git a/integration-tests/hibernate-reactive-postgresql/src/test/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveFetchLazyTest.java b/integration-tests/hibernate-reactive-postgresql/src/test/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveFetchLazyTest.java new file mode 100644 index 0000000000000..38cd5a0e82f3b --- /dev/null +++ b/integration-tests/hibernate-reactive-postgresql/src/test/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveFetchLazyTest.java @@ -0,0 +1,53 @@ +package io.quarkus.it.hibernate.reactive.postgresql; + +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import io.restassured.response.Response; + +@QuarkusTest +@TestHTTPEndpoint(HibernateReactiveTestEndpointFetchLazy.class) +public class HibernateReactiveFetchLazyTest { + + @Test + public void fetchAfterFindWithMutiny() { + RestAssured.when() + .post("/prepareDb") + .then() + .body(is("Neal Stephenson")); + + Response response = RestAssured.when() + .get("/findBooksWithMutiny/567") + .then() + .extract().response(); + assertTitles(response, "Cryptonomicon", "Snow Crash"); + } + + @Test + public void fetchAfterFindWithWithStage() { + RestAssured.when() + .post("/prepareDb") + .then() + .body(is("Neal Stephenson")); + + Response response = RestAssured.when() + .get("/findBooksWithStage/567") + .then() + .extract().response(); + assertTitles(response, "Cryptonomicon", "Snow Crash"); + } + + private void assertTitles(Response response, String... expectedTitles) { + List titles = response.jsonPath().getList("title").stream().sorted().collect(toList()); + assertIterableEquals(asList(expectedTitles), titles); + } +} \ No newline at end of file