From f9e9611b0ea03e58957759eb7bfef1ee2dc7f5ff Mon Sep 17 00:00:00 2001 From: tom Date: Sat, 9 Sep 2023 10:55:38 +0300 Subject: [PATCH 1/2] When Hibernate Reactive is enabled don't create an ImpliedBlockingPersistenceUnit even when a blocking datasource is configured. Add integration test to validate co-existence of hibernate reactive and agroal. --- .../orm/deployment/HibernateOrmProcessor.java | 2 +- .../hibernate-reactive-mysql-agroal/pom.xml | 305 ++++++++++++++++++ .../hibernate/reactive/mysql/GuineaPig.java | 62 ++++ .../HibernateReactiveMySQLTestEndpoint.java | 133 ++++++++ .../src/main/resources/application.properties | 18 ++ .../HibernateReactiveMySQLInGraalIT.java | 7 + .../mysql/HibernateReactiveMySQLTest.java | 63 ++++ integration-tests/pom.xml | 1 + 8 files changed, 590 insertions(+), 1 deletion(-) create mode 100644 integration-tests/hibernate-reactive-mysql-agroal/pom.xml create mode 100644 integration-tests/hibernate-reactive-mysql-agroal/src/main/java/io/quarkus/it/hibernate/reactive/mysql/GuineaPig.java create mode 100644 integration-tests/hibernate-reactive-mysql-agroal/src/main/java/io/quarkus/it/hibernate/reactive/mysql/HibernateReactiveMySQLTestEndpoint.java create mode 100644 integration-tests/hibernate-reactive-mysql-agroal/src/main/resources/application.properties create mode 100644 integration-tests/hibernate-reactive-mysql-agroal/src/test/java/io/quarkus/it/hibernate/reactive/mysql/HibernateReactiveMySQLInGraalIT.java create mode 100644 integration-tests/hibernate-reactive-mysql-agroal/src/test/java/io/quarkus/it/hibernate/reactive/mysql/HibernateReactiveMySQLTest.java diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java index 55d6ebe006545..8a45d4ceca3c5 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java @@ -272,7 +272,7 @@ public ImpliedBlockingPersistenceUnitTypeBuildItem defineTypeOfImpliedPU( List jdbcDataSourcesBuildItem, //This is from Agroal SPI: safe to use even for Hibernate Reactive Capabilities capabilities) { // If we have some blocking datasources defined, we can have an implied PU - if (jdbcDataSourcesBuildItem.size() == 0 && capabilities.isPresent(Capability.HIBERNATE_REACTIVE)) { + if (capabilities.isPresent(Capability.HIBERNATE_REACTIVE)) { // if we don't have any blocking datasources and Hibernate Reactive is present, // we don't want a blocking persistence unit return ImpliedBlockingPersistenceUnitTypeBuildItem.none(); diff --git a/integration-tests/hibernate-reactive-mysql-agroal/pom.xml b/integration-tests/hibernate-reactive-mysql-agroal/pom.xml new file mode 100644 index 0000000000000..ff5e0c37a30b1 --- /dev/null +++ b/integration-tests/hibernate-reactive-mysql-agroal/pom.xml @@ -0,0 +1,305 @@ + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-integration-test-hibernate-reactive-mysql-agroal + Quarkus - Integration Tests - Hibernate Reactive - MySQL + Hibernate Reactive related tests running with the MySQL database + + + vertx-reactive:mysql://localhost:3306/hibernate_orm_test + jdbc:mysql://localhost:3306/hibernate_orm_test + + + + + io.quarkus + quarkus-agroal + + + io.quarkus + quarkus-jdbc-mysql + + + io.quarkus + quarkus-hibernate-reactive + + + io.quarkus + quarkus-reactive-mysql-client + + + io.quarkus + quarkus-resteasy-reactive-jsonb + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + io.quarkus + quarkus-agroal-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-jdbc-mysql-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-hibernate-reactive-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-reactive-mysql-client-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-resteasy-reactive-jsonb-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + src/main/resources + true + + + + + maven-surefire-plugin + + true + + true + + + + + maven-failsafe-plugin + + true + + true + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + + + test-mariadb + + + test-containers + + + + + + maven-surefire-plugin + + false + + + + maven-failsafe-plugin + + false + + + + + + + + docker-mariadb + + + start-containers + + + + vertx-reactive:mysql://localhost:3308/hibernate_orm_test + jdbc:mysql://localhost:3308/hibernate_orm_test + + + + + io.fabric8 + docker-maven-plugin + + + + healthcheck-${mariadb.image} + quarkus-test-mariadb + + ${mariadb.image} + + + 5s + 3s + 5s + 5 + + + mysqladmin ping -h localhost -u root -psecret|| exit 1 + + + + + + bridge + + + 3308:3306 + + + hibernate_orm_test + hibernate_orm_test + hibernate_orm_test + true + + + MariaDB: + default + cyan + + + /var/lib/mysql + + + + true + + + + + ${project.basedir}/custom-mariadbconfig:/etc/mysql/conf.d${volume.access.modifier} + + + + + + + + true + + + + docker-start + compile + + stop + build + start + + + + docker-stop + post-integration-test + + stop + + + + + + org.codehaus.mojo + exec-maven-plugin + + + docker-prune + generate-resources + + exec + + + ${docker-prune.location} + + + + + + + + + + + diff --git a/integration-tests/hibernate-reactive-mysql-agroal/src/main/java/io/quarkus/it/hibernate/reactive/mysql/GuineaPig.java b/integration-tests/hibernate-reactive-mysql-agroal/src/main/java/io/quarkus/it/hibernate/reactive/mysql/GuineaPig.java new file mode 100644 index 0000000000000..96309df01c236 --- /dev/null +++ b/integration-tests/hibernate-reactive-mysql-agroal/src/main/java/io/quarkus/it/hibernate/reactive/mysql/GuineaPig.java @@ -0,0 +1,62 @@ +package io.quarkus.it.hibernate.reactive.mysql; + +import java.util.Objects; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Entity +@Table(name = "Pig") +public class GuineaPig { + + @Id + private Integer id; + private String name; + + public GuineaPig() { + } + + public GuineaPig(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; + } + + @Override + public String toString() { + return id + ": " + name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + GuineaPig guineaPig = (GuineaPig) o; + return Objects.equals(name, guineaPig.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} diff --git a/integration-tests/hibernate-reactive-mysql-agroal/src/main/java/io/quarkus/it/hibernate/reactive/mysql/HibernateReactiveMySQLTestEndpoint.java b/integration-tests/hibernate-reactive-mysql-agroal/src/main/java/io/quarkus/it/hibernate/reactive/mysql/HibernateReactiveMySQLTestEndpoint.java new file mode 100644 index 0000000000000..bbf7070d9cf79 --- /dev/null +++ b/integration-tests/hibernate-reactive-mysql-agroal/src/main/java/io/quarkus/it/hibernate/reactive/mysql/HibernateReactiveMySQLTestEndpoint.java @@ -0,0 +1,133 @@ +package io.quarkus.it.hibernate.reactive.mysql; + +import java.sql.*; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import org.hibernate.reactive.mutiny.Mutiny; + +import io.agroal.api.AgroalDataSource; +import io.quarkus.agroal.DataSource; +import io.smallrye.mutiny.Uni; +import io.vertx.mutiny.mysqlclient.MySQLPool; +import io.vertx.mutiny.sqlclient.Row; +import io.vertx.mutiny.sqlclient.RowSet; +import io.vertx.mutiny.sqlclient.Tuple; + +@Path("/tests") +public class HibernateReactiveMySQLTestEndpoint { + + @Inject + Mutiny.SessionFactory sessionFactory; + + // Injecting a Vert.x Pool is not required, it us only used to + // independently validate the contents of the database for the test + @Inject + MySQLPool mysqlPool; + + @Inject + @DataSource("blocking") + AgroalDataSource blockingDS; + + @GET + @Path("/blockingFind") + public GuineaPig blockingFind() throws SQLException { + final GuineaPig expectedPig = new GuineaPig(6, "Iola"); + populateDBBlocking(); + return selectBlocking(6); + } + + @GET + @Path("/reactiveFindMutiny") + public Uni reactiveFindMutiny() { + final GuineaPig expectedPig = new GuineaPig(5, "Aloi"); + return populateDB() + .chain(() -> sessionFactory.withSession(s -> s.find(GuineaPig.class, expectedPig.getId()))); + } + + @GET + @Path("/reactivePersist") + public Uni reactivePersist() { + return sessionFactory.withTransaction(s -> s.persist(new GuineaPig(10, "Tulip"))) + .chain(() -> selectNameFromId(10)); + } + + @GET + @Path("/reactiveRemoveTransientEntity") + public Uni reactiveRemoveTransientEntity() { + return populateDB() + .chain(() -> selectNameFromId(5)) + .map(name -> { + if (name == null) { + throw new AssertionError("Database was not populated properly"); + } + return name; + }) + .chain(() -> sessionFactory + .withTransaction(s -> s.merge(new GuineaPig(5, "Aloi")).chain(s::remove))) + .chain(() -> selectNameFromId(5)) + .onItem().ifNotNull().transform(result -> result) + .onItem().ifNull().continueWith("OK"); + } + + @GET + @Path("/reactiveRemoveManagedEntity") + public Uni reactiveRemoveManagedEntity() { + return populateDB() + .chain(() -> sessionFactory.withTransaction(s -> s.find(GuineaPig.class, 5).chain(s::remove))) + .chain(() -> selectNameFromId(5)) + .onItem().ifNotNull().transform(result -> result) + .onItem().ifNull().continueWith("OK"); + } + + @GET + @Path("/reactiveUpdate") + public Uni reactiveUpdate() { + final String NEW_NAME = "Tina"; + return populateDB() + .chain(() -> sessionFactory.withTransaction(s -> s.find(GuineaPig.class, 5) + .invoke(pig -> { + if (NEW_NAME.equals(pig.getName())) { + throw new AssertionError("Pig already had name " + NEW_NAME); + } + pig.setName(NEW_NAME); + }))) + .chain(() -> selectNameFromId(5)); + } + + private Uni> populateDB() { + return mysqlPool.query("DELETE FROM Pig").execute() + .flatMap(junk -> mysqlPool.preparedQuery("INSERT INTO Pig (id, name) VALUES (5, 'Aloi')").execute()); + } + + private void populateDBBlocking() throws SQLException { + Connection connection = blockingDS.getConnection(); + connection.prepareStatement("DELETE FROM Pig").execute(); + connection.prepareStatement("INSERT INTO Pig (id, name) VALUES (6, 'Iola')").execute(); + connection.close(); + } + + private Uni selectNameFromId(Integer id) { + return mysqlPool.preparedQuery("SELECT name FROM Pig WHERE id = ?").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 + } + }); + } + + private GuineaPig selectBlocking(Integer id) throws SQLException { + Connection connection = blockingDS.getConnection(); + PreparedStatement statement = connection.prepareStatement("SELECT id, name FROM Pig WHERE id = ?"); + statement.setInt(1, id); + ResultSet rowSet = statement.executeQuery(); + rowSet.next(); + return new GuineaPig(rowSet.getInt("id"), rowSet.getString("name")); + } + +} diff --git a/integration-tests/hibernate-reactive-mysql-agroal/src/main/resources/application.properties b/integration-tests/hibernate-reactive-mysql-agroal/src/main/resources/application.properties new file mode 100644 index 0000000000000..97751eb944d68 --- /dev/null +++ b/integration-tests/hibernate-reactive-mysql-agroal/src/main/resources/application.properties @@ -0,0 +1,18 @@ +quarkus.datasource.db-kind=mysql +quarkus.datasource.username=hibernate_orm_test +quarkus.datasource.password=hibernate_orm_test + +# Hibernate config +#quarkus.hibernate-orm.log.sql=true +quarkus.hibernate-orm.database.generation=drop-and-create + +# Reactive config +quarkus.datasource.reactive=true +quarkus.datasource.reactive.url=${reactive-mysql.url} + +quarkus.datasource.blocking.db-kind=mysql +quarkus.datasource.blocking.username=hibernate_orm_test +quarkus.datasource.blocking.password=hibernate_orm_test +quarkus.datasource.blocking.jdbc.url=${mysql.jdbc.url} +quarkus.datasource.blocking.jdbc=true +quarkus.datasource.blocking.jdbc.max-size=1 diff --git a/integration-tests/hibernate-reactive-mysql-agroal/src/test/java/io/quarkus/it/hibernate/reactive/mysql/HibernateReactiveMySQLInGraalIT.java b/integration-tests/hibernate-reactive-mysql-agroal/src/test/java/io/quarkus/it/hibernate/reactive/mysql/HibernateReactiveMySQLInGraalIT.java new file mode 100644 index 0000000000000..ce752f2847df7 --- /dev/null +++ b/integration-tests/hibernate-reactive-mysql-agroal/src/test/java/io/quarkus/it/hibernate/reactive/mysql/HibernateReactiveMySQLInGraalIT.java @@ -0,0 +1,7 @@ +package io.quarkus.it.hibernate.reactive.mysql; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class HibernateReactiveMySQLInGraalIT extends HibernateReactiveMySQLTest { +} diff --git a/integration-tests/hibernate-reactive-mysql-agroal/src/test/java/io/quarkus/it/hibernate/reactive/mysql/HibernateReactiveMySQLTest.java b/integration-tests/hibernate-reactive-mysql-agroal/src/test/java/io/quarkus/it/hibernate/reactive/mysql/HibernateReactiveMySQLTest.java new file mode 100644 index 0000000000000..da83bb2d53c97 --- /dev/null +++ b/integration-tests/hibernate-reactive-mysql-agroal/src/test/java/io/quarkus/it/hibernate/reactive/mysql/HibernateReactiveMySQLTest.java @@ -0,0 +1,63 @@ +package io.quarkus.it.hibernate.reactive.mysql; + +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; + +/** + * Test various Reactive Hibernate operations running in Quarkus using MySQL + */ +@QuarkusTest +public class HibernateReactiveMySQLTest { + + @Test + public void reactiveFindMutiny() { + RestAssured.when() + .get("/tests/reactiveFindMutiny") + .then() + .body(is("{\"id\":5,\"name\":\"Aloi\"}")); + } + + @Test + public void blockingFind() { + RestAssured.when() + .get("/tests/blockingFind") + .then() + .body(is("{\"id\":6,\"name\":\"Iola\"}")); + } + + @Test + public void reactivePersist() { + RestAssured.when() + .get("/tests/reactivePersist") + .then() + .body(is("Tulip")); + } + + @Test + public void reactiveRemoveTransientEntity() { + RestAssured.when() + .get("/tests/reactiveRemoveTransientEntity") + .then() + .body(is("OK")); + } + + @Test + public void reactiveRemoveManagedEntity() { + RestAssured.when() + .get("/tests/reactiveRemoveManagedEntity") + .then() + .body(is("OK")); + } + + @Test + public void reactiveUpdate() { + RestAssured.when() + .get("/tests/reactiveUpdate") + .then() + .body(is("Tina")); + } +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 2a276c31810e8..076ee8143c1e6 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -226,6 +226,7 @@ hibernate-orm-panache-kotlin hibernate-reactive-db2 hibernate-reactive-mysql + hibernate-reactive-mysql-agroal hibernate-reactive-postgresql hibernate-reactive-panache hibernate-reactive-panache-kotlin From 2a76b08a05c2f39f596b0e34460071ae3c5f1390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Sun, 10 Sep 2023 17:16:19 +0200 Subject: [PATCH 2/2] Run hibernate-reactive-mysql-agroal on CI --- .github/native-tests.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/native-tests.json b/.github/native-tests.json index 4debb9920952b..c68511a1e63c6 100644 --- a/.github/native-tests.json +++ b/.github/native-tests.json @@ -45,7 +45,7 @@ { "category": "Data7", "timeout": 85, - "test-modules": "reactive-oracle-client, reactive-mysql-client, reactive-db2-client, hibernate-reactive-db2, hibernate-reactive-mysql, hibernate-reactive-panache, hibernate-reactive-panache-kotlin", + "test-modules": "reactive-oracle-client, reactive-mysql-client, reactive-db2-client, hibernate-reactive-db2, hibernate-reactive-mysql, hibernate-reactive-mysql-agroal, hibernate-reactive-panache, hibernate-reactive-panache-kotlin", "os-name": "ubuntu-latest" }, {