From 76072c53f7b9471cc8125e332ffc94e21504684f Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 17 Mar 2021 16:15:54 +0000 Subject: [PATCH 1/2] [#15814] Set enableCollectionInDefaultFetchGroup to true for HR The default for ORM is false, but when we create the SessionFactory for Hibernate Reactive this value has to be true. For more details, see: https://github.com/hibernate/hibernate-reactive/issues/663 --- .../boot/FastBootReactiveEntityManagerFactoryBuilder.java | 1 + 1 file changed, 1 insertion(+) 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(); From bf9e86df7cbd7c6e65675c15b86ce3feed567120 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 17 Mar 2021 17:15:57 +0000 Subject: [PATCH 2/2] [#15814] Test lazy fetching with Hibernate Reactive --- ...ibernateReactiveTestEndpointFetchLazy.java | 79 +++++++++++++++++ .../reactive/postgresql/lazy/Author.java | 73 ++++++++++++++++ .../reactive/postgresql/lazy/Book.java | 87 +++++++++++++++++++ .../HibernateReactiveFetchLazyInGraalIT.java | 7 ++ .../HibernateReactiveFetchLazyTest.java | 53 +++++++++++ 5 files changed, 299 insertions(+) create mode 100644 integration-tests/hibernate-reactive-postgresql/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveTestEndpointFetchLazy.java create mode 100644 integration-tests/hibernate-reactive-postgresql/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/lazy/Author.java create mode 100644 integration-tests/hibernate-reactive-postgresql/src/main/java/io/quarkus/it/hibernate/reactive/postgresql/lazy/Book.java create mode 100644 integration-tests/hibernate-reactive-postgresql/src/test/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveFetchLazyInGraalIT.java create mode 100644 integration-tests/hibernate-reactive-postgresql/src/test/java/io/quarkus/it/hibernate/reactive/postgresql/HibernateReactiveFetchLazyTest.java 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