From 1d3b099298c442b392a929ac20e6143b7df97330 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Thu, 20 Feb 2025 15:58:29 +0000 Subject: [PATCH 01/38] Update project version to : `3.0.0-SNAPSHOT` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 08a599ec8..603432f4e 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.0.0.Beta2 \ No newline at end of file +projectVersion=3.0.0-SNAPSHOT \ No newline at end of file From b4de2db5b31c8e3ee0ead5c2a1124e8ca1048c2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Mar 2025 03:51:47 +0000 Subject: [PATCH 02/38] Bump the workflow-actions group with 2 updates Bumps the workflow-actions group with 2 updates: [actions/cache](https://github.com/actions/cache) and [actions/upload-artifact](https://github.com/actions/upload-artifact). Updates `actions/cache` from 4.2.0 to 4.2.2 - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/1bd1e32a3bdc45362d1e726936510720a7c30a57...d4323d4df104b026a6aa633fdb11d772146be0bf) Updates `actions/upload-artifact` from 4.6.0 to 4.6.1 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08...4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-patch dependency-group: workflow-actions - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-patch dependency-group: workflow-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8f519dac8..d63e27906 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -90,7 +90,7 @@ jobs: echo "::set-output name=yearmonth::$(/bin/date -u "+%Y-%m")" shell: bash - name: Cache Gradle downloads - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 id: cache-gradle with: path: | @@ -116,7 +116,7 @@ jobs: - name: Run examples in '${{ matrix.example }}' on ${{ matrix.db }} run: ./gradlew :${{ matrix.example }}:runAllExamplesOn${{ matrix.db }} - name: Upload reports (if build failed) - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 if: failure() with: name: reports-examples-${{ matrix.db }} @@ -139,7 +139,7 @@ jobs: echo "::set-output name=yearmonth::$(/bin/date -u "+%Y-%m")" shell: bash - name: Cache Gradle downloads - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 id: cache-gradle with: path: | @@ -166,7 +166,7 @@ jobs: - name: Build and Test with ${{ matrix.db }} run: ./gradlew build -PshowStandardOutput -Pdocker -Pdb=${{ matrix.db }} - name: Upload reports (if build failed) - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 if: failure() with: name: reports-db-${{ matrix.db }} @@ -217,7 +217,7 @@ jobs: echo "buildtool-cache-key=${ROOT_CACHE_KEY}-${CURRENT_MONTH}-${CURRENT_BRANCH}-${CURRENT_DAY}" >> $GITHUB_OUTPUT - name: Cache Maven/Gradle Dependency/Dist Caches id: cache-maven - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 # if it's not a pull request, we restore and save the cache if: github.event_name != 'pull_request' with: @@ -234,7 +234,7 @@ jobs: ${{ steps.cache-key.outputs.buildtool-monthly-branch-cache-key }}- ${{ steps.cache-key.outputs.buildtool-monthly-cache-key }}- - name: Restore Maven/Gradle Dependency/Dist Caches - uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + uses: actions/cache/restore@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 # if it's a pull request, we restore the cache, but we don't save it if: github.event_name == 'pull_request' with: @@ -292,7 +292,7 @@ jobs: -Porg.gradle.java.installations.paths=${{ steps.mainjdk-exportpath.outputs.path }},${{ steps.testjdk-exportpath.outputs.path }} \ ${{ matrix.java.jvm_args && '-Ptest.jdk.launcher.args=' }}${{ matrix.java.jvm_args }} - name: Upload reports (if build failed) - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 if: failure() with: name: reports-java${{ matrix.java.name }} From 65497c7205e90742e2bef291485b67697ff227f9 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Tue, 25 Feb 2025 16:00:47 +0100 Subject: [PATCH 03/38] [#2134] Use flat badges in README to match what other projects do --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 746235200..482a0af59 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ [![Hibernate team logo](http://static.jboss.org/hibernate/images/hibernate_logo_whitebkg_200px.png)](https://hibernate.org/reactive) -[![Main branch build status](https://github.com/hibernate/hibernate-reactive/workflows/Hibernate%20Reactive%20CI/badge.svg?style=flat)](https://github.com/hibernate/hibernate-reactive/actions?query=workflow%3A%22Hibernate+Reactive+CI%22) -[![Apache 2.0 license](https://img.shields.io/badge/License-APACHE%202.0-green.svg?logo=APACHE&style=flat)](https://opensource.org/licenses/Apache-2.0) -[![Latest version on Maven Central](https://img.shields.io/maven-central/v/org.hibernate.reactive/hibernate-reactive-core.svg?label=Maven%20Central&logo=apache-maven&style=flat)](https://search.maven.org/search?q=g:org.hibernate.reactive) -[![Developers stream on Zulip](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg?logo=zulip&style=flat)](https://hibernate.zulipchat.com/#narrow/stream/205413-hibernate-reactive-dev) -[![Hibernate Reactive documentation](https://img.shields.io/badge/Hibernate-Documentation-orange.svg?logo=Hibernate&style=flat)](https://hibernate.org/reactive/documentation/) +[![Main branch build status](https://img.shields.io/github/actions/workflow/status/hibernate/hibernate-reactive/build.yml?label=Hibernate%20Reactive%20CI&style=for-the-badge)](https://github.com/hibernate/hibernate-reactive/actions?query=workflow%3A%22Hibernate+Reactive+CI%22) +[![Apache 2.0 license](https://img.shields.io/badge/License-APACHE%202.0-green.svg?logo=APACHE&style=for-the-badge)](https://opensource.org/licenses/Apache-2.0) +[![Latest version on Maven Central](https://img.shields.io/maven-central/v/org.hibernate.reactive/hibernate-reactive-core.svg?label=Maven%20Central&logo=apache-maven&style=for-the-badge)](https://search.maven.org/search?q=g:org.hibernate.reactive) +[![Developers stream on Zulip](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg?logo=zulip&style=for-the-badge)](https://hibernate.zulipchat.com/#narrow/stream/205413-hibernate-reactive-dev) +[![Hibernate Reactive documentation](https://img.shields.io/badge/Hibernate-Documentation-orange.svg?logo=Hibernate&style=for-the-badge)](https://hibernate.org/reactive/documentation/) # Hibernate Reactive From f4bd7ad4a632c575546569b9f1278287f34887aa Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Tue, 25 Feb 2025 16:03:13 +0100 Subject: [PATCH 04/38] [#2134] Add Reproducible Central badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 482a0af59..72b3a7e63 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![Latest version on Maven Central](https://img.shields.io/maven-central/v/org.hibernate.reactive/hibernate-reactive-core.svg?label=Maven%20Central&logo=apache-maven&style=for-the-badge)](https://search.maven.org/search?q=g:org.hibernate.reactive) [![Developers stream on Zulip](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg?logo=zulip&style=for-the-badge)](https://hibernate.zulipchat.com/#narrow/stream/205413-hibernate-reactive-dev) [![Hibernate Reactive documentation](https://img.shields.io/badge/Hibernate-Documentation-orange.svg?logo=Hibernate&style=for-the-badge)](https://hibernate.org/reactive/documentation/) +[![Reproducible Builds](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/jvm-repo-rebuild/reproducible-central/master/content/org/hibernate/reactive/hibernate-reactive/badge.json&style=for-the-badge)](https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/org/hibernate/reactive/hibernate-reactive/README.md) # Hibernate Reactive From 41119ba7b573f5fcb04a18ebe9be7ce6e7a7bd28 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 20 Feb 2025 17:21:01 +0100 Subject: [PATCH 05/38] [#2108] StatelessSession insertAll in batch does not do batching --- .../org/hibernate/reactive/mutiny/Mutiny.java | 12 +++++--- .../impl/MutinyStatelessSessionImpl.java | 10 +++---- .../impl/ReactiveStatelessSessionImpl.java | 30 ++++++++++++------- .../org/hibernate/reactive/stage/Stage.java | 12 +++++--- .../stage/impl/StageStatelessSessionImpl.java | 8 ++--- 5 files changed, 45 insertions(+), 27 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java index a8faefbbd..a648a7bd7 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java @@ -1779,7 +1779,8 @@ default Uni get(Class entityClass, Object id, LockModeType lockModeTyp Uni insert(Object entity); /** - * Insert multiple rows. + * Insert multiple rows, using the number of the + * given entities as the batch size. * * @param entities new transient instances * @@ -1817,7 +1818,8 @@ default Uni get(Class entityClass, Object id, LockModeType lockModeTyp Uni delete(Object entity); /** - * Delete multiple rows. + * Delete multiple rows, using the number of the + * given entities as the batch size. * * @param entities detached entity instances * @@ -1855,7 +1857,8 @@ default Uni get(Class entityClass, Object id, LockModeType lockModeTyp Uni update(Object entity); /** - * Update multiple rows. + * Update multiple rows, using the number of the + * given entities as the batch size. * * @param entities detached entity instances * @@ -1915,7 +1918,8 @@ default Uni get(Class entityClass, Object id, LockModeType lockModeTyp Uni refresh(Object entity); /** - * Refresh the entity instance state from the database. + * Refresh the entity instance state from the database, using the number of the + * given entities as the batch size. * * @param entities The entities to be refreshed. * diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java index 473196712..a80d79750 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java @@ -138,7 +138,7 @@ public Uni insert(Object entity) { @Override public Uni insertAll(Object... entities) { - return uni( () -> delegate.reactiveInsertAll( entities ) ); + return uni( () -> delegate.reactiveInsertAll( entities.length, entities ) ); } @Override @@ -158,12 +158,12 @@ public Uni delete(Object entity) { @Override public Uni deleteAll(Object... entities) { - return uni( () -> delegate.reactiveDeleteAll( entities ) ); + return uni( () -> delegate.reactiveDeleteAll( entities.length, entities ) ); } @Override public Uni deleteAll(int batchSize, Object... entities) { - return uni( () -> delegate.reactiveDeleteAll( entities ) ); + return uni( () -> delegate.reactiveDeleteAll( batchSize, entities ) ); } @Override @@ -178,7 +178,7 @@ public Uni update(Object entity) { @Override public Uni updateAll(Object... entities) { - return uni( () -> delegate.reactiveUpdateAll( entities ) ); + return uni( () -> delegate.reactiveUpdateAll( entities.length, entities ) ); } @Override @@ -208,7 +208,7 @@ public Uni upsert(String entityName, Object entity) { @Override public Uni refreshAll(Object... entities) { - return uni( () -> delegate.reactiveRefreshAll( entities ) ); + return uni( () -> delegate.reactiveRefreshAll( entities.length, entities ) ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java index 8e8e338a6..53b928903 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java @@ -128,7 +128,7 @@ public class ReactiveStatelessSessionImpl extends StatelessSessionImpl implement private final ReactiveConnection reactiveConnection; - private final ReactiveStatelessSession batchingHelperSession; + private final ReactiveStatelessSessionImpl batchingHelperSession; private final PersistenceContext persistenceContext; @@ -150,10 +150,9 @@ private ReactiveStatelessSessionImpl( PersistenceContext persistenceContext) { super( factory, options ); this.persistenceContext = persistenceContext; - Integer batchSize = getConfiguredJdbcBatchSize(); - reactiveConnection = batchSize == null || batchSize < 2 - ? connection - : new BatchingConnection( connection, batchSize ); + // Setting batch size to 0 because `StatelessSession` does not consider + // the value of `hibernate.jdbc.batch_size` + reactiveConnection = new BatchingConnection( connection, 0 ); batchingHelperSession = this; influencers = new LoadQueryInfluencers( factory ); } @@ -551,9 +550,12 @@ public CompletionStage reactiveInsertAll(Object... entities) { @Override public CompletionStage reactiveInsertAll(int batchSize, Object... entities) { + final Integer jdbcBatchSize = batchingHelperSession.getJdbcBatchSize(); + batchingHelperSession.setJdbcBatchSize( batchSize ); final ReactiveConnection connection = batchingConnection( batchSize ); return loop( entities, batchingHelperSession::reactiveInsert ) - .thenCompose( v -> connection.executeBatch() ); + .thenCompose( v -> connection.executeBatch() ) + .whenComplete( (v, throwable) -> batchingHelperSession.setJdbcBatchSize( jdbcBatchSize ) ); } @Override @@ -564,9 +566,12 @@ public CompletionStage reactiveUpdateAll(Object... entities) { @Override public CompletionStage reactiveUpdateAll(int batchSize, Object... entities) { + final Integer jdbcBatchSize = batchingHelperSession.getJdbcBatchSize(); + batchingHelperSession.setJdbcBatchSize( batchSize ); final ReactiveConnection connection = batchingConnection( batchSize ); return loop( entities, batchingHelperSession::reactiveUpdate ) - .thenCompose( v -> connection.executeBatch() ); + .thenCompose( v -> connection.executeBatch() ) + .whenComplete( (v, throwable) -> batchingHelperSession.setJdbcBatchSize( jdbcBatchSize ) ); } @Override @@ -577,9 +582,11 @@ public CompletionStage reactiveDeleteAll(Object... entities) { @Override public CompletionStage reactiveDeleteAll(int batchSize, Object... entities) { + final Integer jdbcBatchSize = batchingHelperSession.getJdbcBatchSize(); + batchingHelperSession.setJdbcBatchSize( batchSize ); final ReactiveConnection connection = batchingConnection( batchSize ); - return loop( entities, batchingHelperSession::reactiveDelete ) - .thenCompose( v -> connection.executeBatch() ); + return loop( entities, batchingHelperSession::reactiveDelete ).thenCompose( v -> connection.executeBatch() ) + .whenComplete( (v, throwable) -> batchingHelperSession.setJdbcBatchSize( jdbcBatchSize ) ); } @@ -591,9 +598,12 @@ public CompletionStage reactiveRefreshAll(Object... entities) { @Override public CompletionStage reactiveRefreshAll(int batchSize, Object... entities) { + final Integer jdbcBatchSize = batchingHelperSession.getJdbcBatchSize(); + batchingHelperSession.setJdbcBatchSize( batchSize ); final ReactiveConnection connection = batchingConnection( batchSize ); return loop( entities, batchingHelperSession::reactiveRefresh ) - .thenCompose( v -> connection.executeBatch() ); + .thenCompose( v -> connection.executeBatch() ) + .whenComplete( (v, throwable) -> batchingHelperSession.setJdbcBatchSize( jdbcBatchSize ) ); } private ReactiveConnection batchingConnection(int batchSize) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java index 711a91ab9..074f63e58 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java @@ -1836,7 +1836,8 @@ default CompletionStage get(Class entityClass, Object id, LockModeType CompletionStage insert(Object entity); /** - * Insert multiple rows. + * Insert multiple rows, using the number of the + * given entities as the batch size. * * @param entities new transient instances * @@ -1874,7 +1875,8 @@ default CompletionStage get(Class entityClass, Object id, LockModeType CompletionStage delete(Object entity); /** - * Delete multiple rows. + * Delete multiple rows, using the number of the + * given entities as the batch size. * * @param entities detached entity instances * @@ -1912,7 +1914,8 @@ default CompletionStage get(Class entityClass, Object id, LockModeType CompletionStage update(Object entity); /** - * Update multiple rows. + * Update multiple rows, using the number of the + * given entities as the batch size. * * @param entities a detached entity instance * @@ -1950,7 +1953,8 @@ default CompletionStage get(Class entityClass, Object id, LockModeType CompletionStage refresh(Object entity); /** - * Refresh the entity instance state from the database. + * Refresh the entity instance state from the database, using the number of the + * given entities as the batch size. * * @param entities The entities to be refreshed. * diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java index 7724a5cd1..c97be1a94 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java @@ -67,7 +67,7 @@ public CompletionStage insert(Object entity) { @Override public CompletionStage insert(Object... entities) { - return delegate.reactiveInsertAll( entities ); + return delegate.reactiveInsertAll( entities.length, entities ); } @Override @@ -87,7 +87,7 @@ public CompletionStage delete(Object entity) { @Override public CompletionStage delete(Object... entities) { - return delegate.reactiveDeleteAll( entities ); + return delegate.reactiveDeleteAll( entities.length, entities ); } @Override @@ -107,7 +107,7 @@ public CompletionStage update(Object entity) { @Override public CompletionStage update(Object... entities) { - return delegate.reactiveUpdateAll( entities ); + return delegate.reactiveUpdateAll( entities.length, entities ); } @Override @@ -127,7 +127,7 @@ public CompletionStage refresh(Object entity) { @Override public CompletionStage refresh(Object... entities) { - return delegate.reactiveRefreshAll( entities ); + return delegate.reactiveRefreshAll( entities.length, entities ); } @Override From de13a2784058c84147f2b991087c7e9dbb1d7bc2 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 12 Feb 2025 10:57:21 +0100 Subject: [PATCH 06/38] [#2108] Add test for StatelessSession insertAll in batch does not do batching --- ...ReactiveStatelessDefaultBatchSizeTest.java | 622 ++++++++++++++++++ .../reactive/BatchingConnectionTest.java | 69 ++ 2 files changed, 691 insertions(+) create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessDefaultBatchSizeTest.java diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessDefaultBatchSizeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessDefaultBatchSizeTest.java new file mode 100644 index 000000000..c4d74261e --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessDefaultBatchSizeTest.java @@ -0,0 +1,622 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate; + +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.reactive.BaseReactiveTest; +import org.hibernate.reactive.stage.Stage; +import org.hibernate.reactive.testing.SqlStatementTracker; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The test aims to check that methods accepting the batch size as parameter e.g. {@link Stage.StatelessSession#insert(int, Object...)} + * work when {@link AvailableSettings.STATEMENT_BATCH_SIZE} hasn't been set. + */ +@Timeout(value = 10, timeUnit = MINUTES) +public class ReactiveStatelessDefaultBatchSizeTest extends BaseReactiveTest { + private static SqlStatementTracker sqlTracker; + + private static final String PIG_ONE_NAME = "One"; + private static final String PIG_TWO_NAME = "Two"; + private static final String PIG_THREE_NAME = "Three"; + private static final String PIG_FOUR_NAME = "Four"; + private static final String PIG_FIVE_NAME = "Five"; + private static final String PIG_SIX_NAME = "Six"; + + private static final GuineaPig PIG_ONE = new GuineaPig( 11, PIG_ONE_NAME ); + private static final GuineaPig PIG_TWO = new GuineaPig( 22, PIG_TWO_NAME ); + private static final GuineaPig PIG_THREE = new GuineaPig( 33, PIG_THREE_NAME ); + private static final GuineaPig PIG_FOUR = new GuineaPig( 44, PIG_FOUR_NAME ); + private static final GuineaPig PIG_FIVE = new GuineaPig( 55, PIG_FIVE_NAME ); + private static final GuineaPig PIG_SIX = new GuineaPig( 66, PIG_SIX_NAME ); + + private static final GuineaPig[] PIGS = { PIG_ONE, PIG_TWO, PIG_THREE, PIG_FOUR, PIG_FIVE, PIG_SIX, }; + + @Override + protected Set> annotatedEntities() { + return Set.of( GuineaPig.class ); + } + + @Override + protected Configuration constructConfiguration() { + Configuration configuration = super.constructConfiguration(); + + // Construct a tracker that collects query statements via the SqlStatementLogger framework. + // Pass in configuration properties to hand off any actual logging properties + sqlTracker = new SqlStatementTracker( + ReactiveStatelessDefaultBatchSizeTest::filter, + configuration.getProperties() + ); + return configuration; + } + + @BeforeEach + public void clearTracker() { + sqlTracker.clear(); + } + + @Override + protected void addServices(StandardServiceRegistryBuilder builder) { + sqlTracker.registerService( builder ); + } + + private static boolean filter(String s) { + String[] accepted = { "insert ", "update ", "delete " }; + for ( String valid : accepted ) { + if ( s.toLowerCase().startsWith( valid ) ) { + return true; + } + } + return false; + } + + @Test + public void testMutinyBatchingInsert(VertxTestContext context) { + test( context, getMutinySessionFactory().withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) + .invoke( () -> { + // We expect only one insert query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) + .matches( "insert into pig \\(name,id\\) values (.*)" ); + } ) + ); + } + + @Test + public void testMutinyBatchingInsertMultiple(VertxTestContext context) { + test( context, getMutinySessionFactory().withStatelessTransaction( s -> s.insertMultiple( List.of( PIGS ) ) ) + .invoke( () -> { + // We expect only one insert query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) + .matches( "insert into pig \\(name,id\\) values (.*)" ); + } ) + .invoke( v -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p", GuineaPig.class ) + .getResultList() + .invoke( pigs -> assertThat( pigs ).hasSize( PIGS.length ) ) + ) + ) + ); + } + + @Test + public void testMutinyBatchingInsertAllNoBatchSizeParameter(VertxTestContext context) { + test( context, getMutinySessionFactory().withStatelessTransaction( s -> s.insertAll( PIGS ) ) + .invoke( () -> { + // We expect only one insert query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) + .matches( "insert into pig \\(name,id\\) values (.*)" ); + } ) + .invoke( v -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p", GuineaPig.class ) + .getResultList() + .invoke( pigs -> assertThat( pigs ).hasSize( PIGS.length ) ) + ) + ) + ); + } + + @Test + public void testStageBatchingInsert(VertxTestContext context) { + test( context, getSessionFactory().withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenAccept( v -> { + // We expect only one insert query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) + .matches( "insert into pig \\(name,id\\) values (.*)" ); + } ) + .thenAccept( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p", GuineaPig.class ) + .getResultList() + .thenAccept( pigs -> assertThat( pigs ).hasSize( PIGS.length ) ) + ) + ) + ); + } + + @Test + public void testStageBatchingInsertMultiple(VertxTestContext context) { + test( context, getSessionFactory().withStatelessTransaction( s -> s.insertMultiple( List.of(PIGS) ) ) + .thenAccept( v -> { + // We expect only one insert query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) + .matches( "insert into pig \\(name,id\\) values (.*)" ); + } ) + .thenAccept( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p", GuineaPig.class ) + .getResultList() + .thenAccept( pigs -> assertThat( pigs ).hasSize( PIGS.length ) ) + ) + ) + ); + } + + @Test + public void testStageBatchingInsertNoBatchSizeParameter(VertxTestContext context) { + test( context, getSessionFactory().withStatelessTransaction( s -> s.insert( PIGS ) ) + .thenAccept( v -> { + // We expect only one insert query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) + .matches( "insert into pig \\(name,id\\) values (.*)" ); + } ) + .thenAccept( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p", GuineaPig.class ) + .getResultList() + .thenAccept( pigs -> assertThat( pigs ).hasSize( PIGS.length ) ) + ) + ) + ); + } + + @Test + public void testMutinyBatchingDelete(VertxTestContext context) { + test( context, getMutinySessionFactory().withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction(s -> s + .createQuery( "select p from GuineaPig p", GuineaPig.class ).getResultList() + ) + .invoke( pigs -> sqlTracker.clear() ) + .chain( pigs -> getMutinySessionFactory().withStatelessTransaction( + s -> + s.deleteAll( 10, pigs.subList( 0, 2 ).toArray() ) + ) + ) + .invoke( () -> { + // We expect only one delete query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "delete from pig where id=.*" ); + } ) + .chain( () -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p", GuineaPig.class ) + .getResultList() + ) + .invoke( guineaPigs -> assertThat( guineaPigs.size() ).isEqualTo( 4 ) ) + ) ) + ); + } + + @Test + public void testMutinyBatchingDeleteMultiple(VertxTestContext context) { + test( context, getMutinySessionFactory().withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction(s -> s + .createQuery( "select p from GuineaPig p", GuineaPig.class ).getResultList() + ) + .invoke( pigs -> sqlTracker.clear() ) + .chain( pigs -> getMutinySessionFactory().withStatelessTransaction( + s -> s.deleteMultiple( pigs.subList( 0, 2 ) ) ) + ) + .invoke( () -> { + // We expect only one delete query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "delete from pig where id=.*" ); + } ) + .chain( () -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p", GuineaPig.class ) + .getResultList() + ) + .invoke( guineaPigs -> assertThat( guineaPigs.size() ).isEqualTo( 4 ) ) + ) ) + ); + } + + @Test + public void testMutinyBatchingDeleteAllNoBatchSizeParameter(VertxTestContext context) { + test( context, getMutinySessionFactory().withStatelessTransaction( s -> s.insertAll( PIGS ) ) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction(s -> s + .createQuery( "select p from GuineaPig p", GuineaPig.class ).getResultList() + ) + .invoke( pigs -> sqlTracker.clear() ) + .chain( pigs -> getMutinySessionFactory().withStatelessTransaction( + s -> s.deleteAll( pigs.subList( 0, 2 ).toArray() ) ) + ) + .invoke( () -> { + // We expect only one delete query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "delete from pig where id=.*" ); + } ) + .chain( () -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p", GuineaPig.class ) + .getResultList() + ) + .invoke( guineaPigs -> assertThat( guineaPigs.size() ).isEqualTo( 4 ) ) + ) ) + ); + } + + @Test + public void testStageBatchingDelete(VertxTestContext context) { + test( context, getSessionFactory().withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenAccept( v -> sqlTracker.clear() ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p", GuineaPig.class ).getResultList() + .thenCompose( pigs -> { + sqlTracker.clear(); + return s.delete( 10, pigs.subList( 0, 2 ).toArray() ); + } + ) ) + .thenAccept( vo -> { + // We expect only one delete query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) + .matches( "delete from pig where id=.*" ); + } ) + .thenCompose( vo -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p", GuineaPig.class ) + .getResultList() + ) + .thenAccept( guineaPigs -> assertThat( guineaPigs.size() ).isEqualTo( 4 ) ) + ) ) + ); + } + + @Test + public void testStageBatchingDeleteMultiple(VertxTestContext context) { + test( context, getSessionFactory().withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenAccept( v -> sqlTracker.clear() ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p", GuineaPig.class ).getResultList() + .thenCompose( pigs -> { + sqlTracker.clear(); + return s.deleteMultiple( pigs.subList( 0, 2 ) ); + } + ) ) + .thenAccept( vo -> { + // We expect only one delete query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) + .matches( "delete from pig where id=.*" ); + } ) + .thenCompose( vo -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p", GuineaPig.class ) + .getResultList() ) + .thenAccept( guineaPigs -> assertThat( guineaPigs.size() ).isEqualTo( 4 ) ) + ) ) + ); + } + + @Test + public void testStageBatchingDeleteNoBatchSizeParameter(VertxTestContext context) { + test( context, getSessionFactory().withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenAccept( v -> sqlTracker.clear() ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p", GuineaPig.class ).getResultList() + .thenCompose( pigs -> { + sqlTracker.clear(); + return s.delete( pigs.subList( 0, 2 ).toArray() ); + } + ) ) + .thenAccept( vo -> { + // We expect only one delete query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) + .matches( "delete from pig where id=.*" ); + } ) + .thenCompose( vo -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p", GuineaPig.class ) + .getResultList() ) + .thenAccept( guineaPigs -> assertThat( guineaPigs.size() ).isEqualTo( 4 ) ) + ) ) + ); + } + + @Test + public void testMutinyBatchingUpdate(VertxTestContext context) { + final String pigOneUpdatedName = "One updated"; + final String pigTwoUpdatedName = "Two updated"; + test( context, getMutinySessionFactory().withStatelessTransaction( s -> s .insertAll( 10, PIGS )) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .invoke( pigs -> sqlTracker.clear() ) + .chain( pigs -> { + GuineaPig guineaPigOne = pigs.get( 0 ); + guineaPigOne.setName( pigOneUpdatedName ); + GuineaPig guineaPigTwo = pigs.get( 1 ); + guineaPigTwo.setName( pigTwoUpdatedName ); + return s.updateAll( 10, new GuineaPig[] { guineaPigOne, guineaPigTwo } ); + } ) + ) ) + .invoke( () -> { + // We expect only one update query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "update pig set name=.* where id=.*" ); + } ) + .chain( () -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p order by id", GuineaPig.class ) + .getResultList() + .invoke( guineaPigs -> { + checkPigsAreCorrectlyUpdated( guineaPigs, pigOneUpdatedName, pigTwoUpdatedName ); + } ) ) ) + ); + } + + @Test + public void testMutinyBatchingUpdateMultiple(VertxTestContext context) { + final String pigOneUpdatedName = "One updated"; + final String pigTwoUpdatedName = "Two updated"; + test( context, getMutinySessionFactory().withStatelessTransaction( s -> s .insertAll( 10, PIGS )) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .invoke( pigs -> sqlTracker.clear() ) + .chain( pigs -> { + GuineaPig guineaPigOne = pigs.get( 0 ); + guineaPigOne.setName( pigOneUpdatedName ); + GuineaPig guineaPigTwo = pigs.get( 1 ); + guineaPigTwo.setName( pigTwoUpdatedName ); + return s.updateMultiple( List.of( guineaPigOne, guineaPigTwo ) ); + } ) + ) ) + .invoke( () -> { + // We expect only one update query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "update pig set name=.* where id=.*" ); + } ) + .chain( () -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p order by id", GuineaPig.class ) + .getResultList() + .invoke( guineaPigs -> { + checkPigsAreCorrectlyUpdated( guineaPigs, pigOneUpdatedName, pigTwoUpdatedName ); + } ) ) ) + ); + } + + @Test + public void testMutinyBatchingUpdateAllNoBatchSizeParameter(VertxTestContext context) { + final String pigOneUpdatedName = "One updated"; + final String pigTwoUpdatedName = "Two updated"; + test( context, getMutinySessionFactory().withStatelessTransaction( s -> s .insertAll( 10, PIGS )) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .invoke( pigs -> sqlTracker.clear() ) + .chain( pigs -> { + GuineaPig guineaPigOne = pigs.get( 0 ); + guineaPigOne.setName( pigOneUpdatedName ); + GuineaPig guineaPigTwo = pigs.get( 1 ); + guineaPigTwo.setName( pigTwoUpdatedName ); + return s.updateAll( guineaPigOne, guineaPigTwo ); + } ) + ) ) + .invoke( () -> { + // We expect only one update query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "update pig set name=.* where id=.*" ); + } ) + .chain( () -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p order by id", GuineaPig.class ) + .getResultList() + .invoke( guineaPigs -> { + checkPigsAreCorrectlyUpdated( guineaPigs, pigOneUpdatedName, pigTwoUpdatedName ); + } ) ) ) + ); + } + + @Test + public void testStageBatchingUpdate(VertxTestContext context) { + final String pigOneUpdatedName = "One updated"; + final String pigTwoUpdatedName = "Two updated"; + test(context, getSessionFactory().withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenAccept( v -> sqlTracker.clear() ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction(s -> s + .createQuery( "select p from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .thenApply( pigs -> { + sqlTracker.clear(); + GuineaPig guineaPigOne = pigs.get( 0 ); + guineaPigOne.setName( pigOneUpdatedName ); + GuineaPig guineaPigTwo = pigs.get( 1 ); + guineaPigTwo.setName( pigTwoUpdatedName ); + return s.update( 10, new GuineaPig[] { guineaPigOne, guineaPigTwo } ); + } ) + ) + .thenAccept( vo -> { + // We expect only one update query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "update pig set name=.* where id=.*" ); + } ) + .thenCompose( vo -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p order by id", GuineaPig.class ) + .getResultList() + .thenAccept( guineaPigs -> + checkPigsAreCorrectlyUpdated( guineaPigs, pigOneUpdatedName, pigTwoUpdatedName ) + ) + ) ) ) + ); + } + + @Test + public void testStageBatchingUpdateMultiple(VertxTestContext context) { + final String pigOneUpdatedName = "One updated"; + final String pigTwoUpdatedName = "Two updated"; + test(context, getSessionFactory().withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenAccept( v -> sqlTracker.clear() ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction(s -> s + .createQuery( "select p from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .thenApply( pigs -> { + sqlTracker.clear(); + GuineaPig guineaPigOne = pigs.get( 0 ); + guineaPigOne.setName( pigOneUpdatedName ); + GuineaPig guineaPigTwo = pigs.get( 1 ); + guineaPigTwo.setName( pigTwoUpdatedName ); + return s.updateMultiple( List.of( guineaPigOne, guineaPigTwo ) ); + } ) + ) + .thenAccept( vo -> { + // We expect only one update query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "update pig set name=.* where id=.*" ); + } ) + .thenCompose( vo -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p order by id", GuineaPig.class ) + .getResultList() + .thenAccept( guineaPigs -> + checkPigsAreCorrectlyUpdated( guineaPigs, pigOneUpdatedName, pigTwoUpdatedName ) + ) + ) ) ) + ); + } + + @Test + public void testStageBatchingUpdateNoBatchSizeParameter(VertxTestContext context) { + final String pigOneUpdatedName = "One updated"; + final String pigTwoUpdatedName = "Two updated"; + test(context, getSessionFactory().withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenAccept( v -> sqlTracker.clear() ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction(s -> s + .createQuery( "select p from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .thenApply( pigs -> { + sqlTracker.clear(); + GuineaPig guineaPigOne = pigs.get( 0 ); + guineaPigOne.setName( pigOneUpdatedName ); + GuineaPig guineaPigTwo = pigs.get( 1 ); + guineaPigTwo.setName( pigTwoUpdatedName ); + return s.update( guineaPigOne, guineaPigTwo ); + } ) + ) + .thenAccept( vo -> { + // We expect only one update query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "update pig set name=.* where id=.*" ); + } ) + .thenCompose( vo -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p order by id", GuineaPig.class ) + .getResultList() + .thenAccept( guineaPigs -> + checkPigsAreCorrectlyUpdated( guineaPigs, pigOneUpdatedName, pigTwoUpdatedName ) + ) + ) ) ) + ); + } + + private static void checkPigsAreCorrectlyUpdated(List guineaPigs, String pigOneUpdatedName, String pigTwoUpdatedName) { + assertThat( guineaPigs.get( 0 ).getName() ).isEqualTo( pigOneUpdatedName ); + assertThat( guineaPigs.get( 1 ).getName() ).isEqualTo( pigTwoUpdatedName ); + assertThat( guineaPigs.get( 2 ).getName() ).isEqualTo( PIG_THREE_NAME ); + assertThat( guineaPigs.get( 3 ).getName() ).isEqualTo( PIG_FOUR_NAME ); + assertThat( guineaPigs.get( 4 ).getName() ).isEqualTo( PIG_FIVE_NAME ); + assertThat( guineaPigs.get( 5 ).getName() ).isEqualTo( PIG_SIX_NAME ); + } + + @Entity(name = "GuineaPig") + @Table(name = "pig") + public static 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/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchingConnectionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchingConnectionTest.java index 0ce3d2e9d..75d90375e 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchingConnectionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchingConnectionTest.java @@ -145,6 +145,75 @@ public void testBatching(VertxTestContext context) { ); } + @Test + public void testBatchingWithStateless(VertxTestContext context) { + final GuineaPig[] pigs = { + new GuineaPig( 11, "One" ), + new GuineaPig( 22, "Two" ), + new GuineaPig( 33, "Three" ), + new GuineaPig( 44, "Four" ), + new GuineaPig( 55, "Five" ), + new GuineaPig( 66, "Six" ), + }; + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( 10, pigs ) ) + .invoke( () -> { + // We expect only one insert query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) + .matches("insert into pig \\(name,version,id\\) values (.*)" ); + sqlTracker.clear(); + } ) + ); + } + + @Test + public void testMutinyInsertAllWithStateless(VertxTestContext context) { + final GuineaPig[] pigs = { + new GuineaPig( 11, "One" ), + new GuineaPig( 22, "Two" ), + new GuineaPig( 33, "Three" ), + new GuineaPig( 44, "Four" ), + new GuineaPig( 55, "Five" ), + new GuineaPig( 66, "Six" ), + }; + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( pigs ) ) + .invoke( () -> { + // We expect only 1 insert query, despite hibernate.jdbc.batch_size is set to 5, insertAll by default use the pigs.length as batch size + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) + .matches("insert into pig \\(name,version,id\\) values (.*)" ); + sqlTracker.clear(); + } ) + ); + } + + @Test + public void testStageInsertWithStateless(VertxTestContext context) { + final GuineaPig[] pigs = { + new GuineaPig( 11, "One" ), + new GuineaPig( 22, "Two" ), + new GuineaPig( 33, "Three" ), + new GuineaPig( 44, "Four" ), + new GuineaPig( 55, "Five" ), + new GuineaPig( 66, "Six" ), + }; + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( pigs ) ) + .thenAccept( v -> { + // We expect only 1 insert query, despite hibernate.jdbc.batch_size is set to 5, insertAll by default use the pigs.length as batch size + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) + .matches("insert into pig \\(name,version,id\\) values (.*)" ); + sqlTracker.clear(); + } ) + ); + } + @Test public void testBatchingConnection(VertxTestContext context) { test( context, openSession() From 52f1ec7920c4c9bca6930c4bcb976ae49be5a648 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Thu, 6 Mar 2025 18:13:51 +0100 Subject: [PATCH 07/38] [#2139] Refactoring of ReactiveStatelessDefaultBatchSizeTest * Rename it to ReactiveStatelessWithBatchTest * Clean up unit tests --- ...ReactiveStatelessDefaultBatchSizeTest.java | 622 ------------------ .../ReactiveStatelessWithBatchTest.java | 422 ++++++++++++ 2 files changed, 422 insertions(+), 622 deletions(-) delete mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessDefaultBatchSizeTest.java create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessWithBatchTest.java diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessDefaultBatchSizeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessDefaultBatchSizeTest.java deleted file mode 100644 index c4d74261e..000000000 --- a/hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessDefaultBatchSizeTest.java +++ /dev/null @@ -1,622 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate; - -import org.hibernate.boot.registry.StandardServiceRegistryBuilder; -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.cfg.Configuration; -import org.hibernate.reactive.BaseReactiveTest; -import org.hibernate.reactive.stage.Stage; -import org.hibernate.reactive.testing.SqlStatementTracker; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import io.vertx.junit5.Timeout; -import io.vertx.junit5.VertxTestContext; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import java.util.List; -import java.util.Objects; -import java.util.Set; - -import static java.util.concurrent.TimeUnit.MINUTES; -import static org.assertj.core.api.Assertions.assertThat; - -/** - * The test aims to check that methods accepting the batch size as parameter e.g. {@link Stage.StatelessSession#insert(int, Object...)} - * work when {@link AvailableSettings.STATEMENT_BATCH_SIZE} hasn't been set. - */ -@Timeout(value = 10, timeUnit = MINUTES) -public class ReactiveStatelessDefaultBatchSizeTest extends BaseReactiveTest { - private static SqlStatementTracker sqlTracker; - - private static final String PIG_ONE_NAME = "One"; - private static final String PIG_TWO_NAME = "Two"; - private static final String PIG_THREE_NAME = "Three"; - private static final String PIG_FOUR_NAME = "Four"; - private static final String PIG_FIVE_NAME = "Five"; - private static final String PIG_SIX_NAME = "Six"; - - private static final GuineaPig PIG_ONE = new GuineaPig( 11, PIG_ONE_NAME ); - private static final GuineaPig PIG_TWO = new GuineaPig( 22, PIG_TWO_NAME ); - private static final GuineaPig PIG_THREE = new GuineaPig( 33, PIG_THREE_NAME ); - private static final GuineaPig PIG_FOUR = new GuineaPig( 44, PIG_FOUR_NAME ); - private static final GuineaPig PIG_FIVE = new GuineaPig( 55, PIG_FIVE_NAME ); - private static final GuineaPig PIG_SIX = new GuineaPig( 66, PIG_SIX_NAME ); - - private static final GuineaPig[] PIGS = { PIG_ONE, PIG_TWO, PIG_THREE, PIG_FOUR, PIG_FIVE, PIG_SIX, }; - - @Override - protected Set> annotatedEntities() { - return Set.of( GuineaPig.class ); - } - - @Override - protected Configuration constructConfiguration() { - Configuration configuration = super.constructConfiguration(); - - // Construct a tracker that collects query statements via the SqlStatementLogger framework. - // Pass in configuration properties to hand off any actual logging properties - sqlTracker = new SqlStatementTracker( - ReactiveStatelessDefaultBatchSizeTest::filter, - configuration.getProperties() - ); - return configuration; - } - - @BeforeEach - public void clearTracker() { - sqlTracker.clear(); - } - - @Override - protected void addServices(StandardServiceRegistryBuilder builder) { - sqlTracker.registerService( builder ); - } - - private static boolean filter(String s) { - String[] accepted = { "insert ", "update ", "delete " }; - for ( String valid : accepted ) { - if ( s.toLowerCase().startsWith( valid ) ) { - return true; - } - } - return false; - } - - @Test - public void testMutinyBatchingInsert(VertxTestContext context) { - test( context, getMutinySessionFactory().withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) - .invoke( () -> { - // We expect only one insert query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) - .matches( "insert into pig \\(name,id\\) values (.*)" ); - } ) - ); - } - - @Test - public void testMutinyBatchingInsertMultiple(VertxTestContext context) { - test( context, getMutinySessionFactory().withStatelessTransaction( s -> s.insertMultiple( List.of( PIGS ) ) ) - .invoke( () -> { - // We expect only one insert query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) - .matches( "insert into pig \\(name,id\\) values (.*)" ); - } ) - .invoke( v -> getMutinySessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p", GuineaPig.class ) - .getResultList() - .invoke( pigs -> assertThat( pigs ).hasSize( PIGS.length ) ) - ) - ) - ); - } - - @Test - public void testMutinyBatchingInsertAllNoBatchSizeParameter(VertxTestContext context) { - test( context, getMutinySessionFactory().withStatelessTransaction( s -> s.insertAll( PIGS ) ) - .invoke( () -> { - // We expect only one insert query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) - .matches( "insert into pig \\(name,id\\) values (.*)" ); - } ) - .invoke( v -> getMutinySessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p", GuineaPig.class ) - .getResultList() - .invoke( pigs -> assertThat( pigs ).hasSize( PIGS.length ) ) - ) - ) - ); - } - - @Test - public void testStageBatchingInsert(VertxTestContext context) { - test( context, getSessionFactory().withStatelessTransaction( s -> s.insert( 10, PIGS ) ) - .thenAccept( v -> { - // We expect only one insert query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) - .matches( "insert into pig \\(name,id\\) values (.*)" ); - } ) - .thenAccept( v -> getSessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p", GuineaPig.class ) - .getResultList() - .thenAccept( pigs -> assertThat( pigs ).hasSize( PIGS.length ) ) - ) - ) - ); - } - - @Test - public void testStageBatchingInsertMultiple(VertxTestContext context) { - test( context, getSessionFactory().withStatelessTransaction( s -> s.insertMultiple( List.of(PIGS) ) ) - .thenAccept( v -> { - // We expect only one insert query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) - .matches( "insert into pig \\(name,id\\) values (.*)" ); - } ) - .thenAccept( v -> getSessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p", GuineaPig.class ) - .getResultList() - .thenAccept( pigs -> assertThat( pigs ).hasSize( PIGS.length ) ) - ) - ) - ); - } - - @Test - public void testStageBatchingInsertNoBatchSizeParameter(VertxTestContext context) { - test( context, getSessionFactory().withStatelessTransaction( s -> s.insert( PIGS ) ) - .thenAccept( v -> { - // We expect only one insert query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) - .matches( "insert into pig \\(name,id\\) values (.*)" ); - } ) - .thenAccept( v -> getSessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p", GuineaPig.class ) - .getResultList() - .thenAccept( pigs -> assertThat( pigs ).hasSize( PIGS.length ) ) - ) - ) - ); - } - - @Test - public void testMutinyBatchingDelete(VertxTestContext context) { - test( context, getMutinySessionFactory().withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) - .invoke( sqlTracker::clear ) - .chain( v -> getMutinySessionFactory().withStatelessTransaction(s -> s - .createQuery( "select p from GuineaPig p", GuineaPig.class ).getResultList() - ) - .invoke( pigs -> sqlTracker.clear() ) - .chain( pigs -> getMutinySessionFactory().withStatelessTransaction( - s -> - s.deleteAll( 10, pigs.subList( 0, 2 ).toArray() ) - ) - ) - .invoke( () -> { - // We expect only one delete query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "delete from pig where id=.*" ); - } ) - .chain( () -> getMutinySessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p", GuineaPig.class ) - .getResultList() - ) - .invoke( guineaPigs -> assertThat( guineaPigs.size() ).isEqualTo( 4 ) ) - ) ) - ); - } - - @Test - public void testMutinyBatchingDeleteMultiple(VertxTestContext context) { - test( context, getMutinySessionFactory().withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) - .invoke( sqlTracker::clear ) - .chain( v -> getMutinySessionFactory().withStatelessTransaction(s -> s - .createQuery( "select p from GuineaPig p", GuineaPig.class ).getResultList() - ) - .invoke( pigs -> sqlTracker.clear() ) - .chain( pigs -> getMutinySessionFactory().withStatelessTransaction( - s -> s.deleteMultiple( pigs.subList( 0, 2 ) ) ) - ) - .invoke( () -> { - // We expect only one delete query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "delete from pig where id=.*" ); - } ) - .chain( () -> getMutinySessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p", GuineaPig.class ) - .getResultList() - ) - .invoke( guineaPigs -> assertThat( guineaPigs.size() ).isEqualTo( 4 ) ) - ) ) - ); - } - - @Test - public void testMutinyBatchingDeleteAllNoBatchSizeParameter(VertxTestContext context) { - test( context, getMutinySessionFactory().withStatelessTransaction( s -> s.insertAll( PIGS ) ) - .invoke( sqlTracker::clear ) - .chain( v -> getMutinySessionFactory().withStatelessTransaction(s -> s - .createQuery( "select p from GuineaPig p", GuineaPig.class ).getResultList() - ) - .invoke( pigs -> sqlTracker.clear() ) - .chain( pigs -> getMutinySessionFactory().withStatelessTransaction( - s -> s.deleteAll( pigs.subList( 0, 2 ).toArray() ) ) - ) - .invoke( () -> { - // We expect only one delete query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "delete from pig where id=.*" ); - } ) - .chain( () -> getMutinySessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p", GuineaPig.class ) - .getResultList() - ) - .invoke( guineaPigs -> assertThat( guineaPigs.size() ).isEqualTo( 4 ) ) - ) ) - ); - } - - @Test - public void testStageBatchingDelete(VertxTestContext context) { - test( context, getSessionFactory().withStatelessTransaction( s -> s.insert( 10, PIGS ) ) - .thenAccept( v -> sqlTracker.clear() ) - .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p", GuineaPig.class ).getResultList() - .thenCompose( pigs -> { - sqlTracker.clear(); - return s.delete( 10, pigs.subList( 0, 2 ).toArray() ); - } - ) ) - .thenAccept( vo -> { - // We expect only one delete query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) - .matches( "delete from pig where id=.*" ); - } ) - .thenCompose( vo -> getSessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p", GuineaPig.class ) - .getResultList() - ) - .thenAccept( guineaPigs -> assertThat( guineaPigs.size() ).isEqualTo( 4 ) ) - ) ) - ); - } - - @Test - public void testStageBatchingDeleteMultiple(VertxTestContext context) { - test( context, getSessionFactory().withStatelessTransaction( s -> s.insert( 10, PIGS ) ) - .thenAccept( v -> sqlTracker.clear() ) - .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p", GuineaPig.class ).getResultList() - .thenCompose( pigs -> { - sqlTracker.clear(); - return s.deleteMultiple( pigs.subList( 0, 2 ) ); - } - ) ) - .thenAccept( vo -> { - // We expect only one delete query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) - .matches( "delete from pig where id=.*" ); - } ) - .thenCompose( vo -> getSessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p", GuineaPig.class ) - .getResultList() ) - .thenAccept( guineaPigs -> assertThat( guineaPigs.size() ).isEqualTo( 4 ) ) - ) ) - ); - } - - @Test - public void testStageBatchingDeleteNoBatchSizeParameter(VertxTestContext context) { - test( context, getSessionFactory().withStatelessTransaction( s -> s.insert( 10, PIGS ) ) - .thenAccept( v -> sqlTracker.clear() ) - .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p", GuineaPig.class ).getResultList() - .thenCompose( pigs -> { - sqlTracker.clear(); - return s.delete( pigs.subList( 0, 2 ).toArray() ); - } - ) ) - .thenAccept( vo -> { - // We expect only one delete query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) - .matches( "delete from pig where id=.*" ); - } ) - .thenCompose( vo -> getSessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p", GuineaPig.class ) - .getResultList() ) - .thenAccept( guineaPigs -> assertThat( guineaPigs.size() ).isEqualTo( 4 ) ) - ) ) - ); - } - - @Test - public void testMutinyBatchingUpdate(VertxTestContext context) { - final String pigOneUpdatedName = "One updated"; - final String pigTwoUpdatedName = "Two updated"; - test( context, getMutinySessionFactory().withStatelessTransaction( s -> s .insertAll( 10, PIGS )) - .invoke( sqlTracker::clear ) - .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p order by p.id", GuineaPig.class ) - .getResultList() - .invoke( pigs -> sqlTracker.clear() ) - .chain( pigs -> { - GuineaPig guineaPigOne = pigs.get( 0 ); - guineaPigOne.setName( pigOneUpdatedName ); - GuineaPig guineaPigTwo = pigs.get( 1 ); - guineaPigTwo.setName( pigTwoUpdatedName ); - return s.updateAll( 10, new GuineaPig[] { guineaPigOne, guineaPigTwo } ); - } ) - ) ) - .invoke( () -> { - // We expect only one update query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "update pig set name=.* where id=.*" ); - } ) - .chain( () -> getMutinySessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p order by id", GuineaPig.class ) - .getResultList() - .invoke( guineaPigs -> { - checkPigsAreCorrectlyUpdated( guineaPigs, pigOneUpdatedName, pigTwoUpdatedName ); - } ) ) ) - ); - } - - @Test - public void testMutinyBatchingUpdateMultiple(VertxTestContext context) { - final String pigOneUpdatedName = "One updated"; - final String pigTwoUpdatedName = "Two updated"; - test( context, getMutinySessionFactory().withStatelessTransaction( s -> s .insertAll( 10, PIGS )) - .invoke( sqlTracker::clear ) - .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p order by p.id", GuineaPig.class ) - .getResultList() - .invoke( pigs -> sqlTracker.clear() ) - .chain( pigs -> { - GuineaPig guineaPigOne = pigs.get( 0 ); - guineaPigOne.setName( pigOneUpdatedName ); - GuineaPig guineaPigTwo = pigs.get( 1 ); - guineaPigTwo.setName( pigTwoUpdatedName ); - return s.updateMultiple( List.of( guineaPigOne, guineaPigTwo ) ); - } ) - ) ) - .invoke( () -> { - // We expect only one update query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "update pig set name=.* where id=.*" ); - } ) - .chain( () -> getMutinySessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p order by id", GuineaPig.class ) - .getResultList() - .invoke( guineaPigs -> { - checkPigsAreCorrectlyUpdated( guineaPigs, pigOneUpdatedName, pigTwoUpdatedName ); - } ) ) ) - ); - } - - @Test - public void testMutinyBatchingUpdateAllNoBatchSizeParameter(VertxTestContext context) { - final String pigOneUpdatedName = "One updated"; - final String pigTwoUpdatedName = "Two updated"; - test( context, getMutinySessionFactory().withStatelessTransaction( s -> s .insertAll( 10, PIGS )) - .invoke( sqlTracker::clear ) - .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p order by p.id", GuineaPig.class ) - .getResultList() - .invoke( pigs -> sqlTracker.clear() ) - .chain( pigs -> { - GuineaPig guineaPigOne = pigs.get( 0 ); - guineaPigOne.setName( pigOneUpdatedName ); - GuineaPig guineaPigTwo = pigs.get( 1 ); - guineaPigTwo.setName( pigTwoUpdatedName ); - return s.updateAll( guineaPigOne, guineaPigTwo ); - } ) - ) ) - .invoke( () -> { - // We expect only one update query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "update pig set name=.* where id=.*" ); - } ) - .chain( () -> getMutinySessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p order by id", GuineaPig.class ) - .getResultList() - .invoke( guineaPigs -> { - checkPigsAreCorrectlyUpdated( guineaPigs, pigOneUpdatedName, pigTwoUpdatedName ); - } ) ) ) - ); - } - - @Test - public void testStageBatchingUpdate(VertxTestContext context) { - final String pigOneUpdatedName = "One updated"; - final String pigTwoUpdatedName = "Two updated"; - test(context, getSessionFactory().withStatelessTransaction( s -> s.insert( 10, PIGS ) ) - .thenAccept( v -> sqlTracker.clear() ) - .thenCompose( v -> getSessionFactory().withStatelessTransaction(s -> s - .createQuery( "select p from GuineaPig p order by p.id", GuineaPig.class ) - .getResultList() - .thenApply( pigs -> { - sqlTracker.clear(); - GuineaPig guineaPigOne = pigs.get( 0 ); - guineaPigOne.setName( pigOneUpdatedName ); - GuineaPig guineaPigTwo = pigs.get( 1 ); - guineaPigTwo.setName( pigTwoUpdatedName ); - return s.update( 10, new GuineaPig[] { guineaPigOne, guineaPigTwo } ); - } ) - ) - .thenAccept( vo -> { - // We expect only one update query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "update pig set name=.* where id=.*" ); - } ) - .thenCompose( vo -> getSessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p order by id", GuineaPig.class ) - .getResultList() - .thenAccept( guineaPigs -> - checkPigsAreCorrectlyUpdated( guineaPigs, pigOneUpdatedName, pigTwoUpdatedName ) - ) - ) ) ) - ); - } - - @Test - public void testStageBatchingUpdateMultiple(VertxTestContext context) { - final String pigOneUpdatedName = "One updated"; - final String pigTwoUpdatedName = "Two updated"; - test(context, getSessionFactory().withStatelessTransaction( s -> s.insert( 10, PIGS ) ) - .thenAccept( v -> sqlTracker.clear() ) - .thenCompose( v -> getSessionFactory().withStatelessTransaction(s -> s - .createQuery( "select p from GuineaPig p order by p.id", GuineaPig.class ) - .getResultList() - .thenApply( pigs -> { - sqlTracker.clear(); - GuineaPig guineaPigOne = pigs.get( 0 ); - guineaPigOne.setName( pigOneUpdatedName ); - GuineaPig guineaPigTwo = pigs.get( 1 ); - guineaPigTwo.setName( pigTwoUpdatedName ); - return s.updateMultiple( List.of( guineaPigOne, guineaPigTwo ) ); - } ) - ) - .thenAccept( vo -> { - // We expect only one update query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "update pig set name=.* where id=.*" ); - } ) - .thenCompose( vo -> getSessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p order by id", GuineaPig.class ) - .getResultList() - .thenAccept( guineaPigs -> - checkPigsAreCorrectlyUpdated( guineaPigs, pigOneUpdatedName, pigTwoUpdatedName ) - ) - ) ) ) - ); - } - - @Test - public void testStageBatchingUpdateNoBatchSizeParameter(VertxTestContext context) { - final String pigOneUpdatedName = "One updated"; - final String pigTwoUpdatedName = "Two updated"; - test(context, getSessionFactory().withStatelessTransaction( s -> s.insert( 10, PIGS ) ) - .thenAccept( v -> sqlTracker.clear() ) - .thenCompose( v -> getSessionFactory().withStatelessTransaction(s -> s - .createQuery( "select p from GuineaPig p order by p.id", GuineaPig.class ) - .getResultList() - .thenApply( pigs -> { - sqlTracker.clear(); - GuineaPig guineaPigOne = pigs.get( 0 ); - guineaPigOne.setName( pigOneUpdatedName ); - GuineaPig guineaPigTwo = pigs.get( 1 ); - guineaPigTwo.setName( pigTwoUpdatedName ); - return s.update( guineaPigOne, guineaPigTwo ); - } ) - ) - .thenAccept( vo -> { - // We expect only one update query - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "update pig set name=.* where id=.*" ); - } ) - .thenCompose( vo -> getSessionFactory().withStatelessTransaction( s -> s - .createQuery( "select p from GuineaPig p order by id", GuineaPig.class ) - .getResultList() - .thenAccept( guineaPigs -> - checkPigsAreCorrectlyUpdated( guineaPigs, pigOneUpdatedName, pigTwoUpdatedName ) - ) - ) ) ) - ); - } - - private static void checkPigsAreCorrectlyUpdated(List guineaPigs, String pigOneUpdatedName, String pigTwoUpdatedName) { - assertThat( guineaPigs.get( 0 ).getName() ).isEqualTo( pigOneUpdatedName ); - assertThat( guineaPigs.get( 1 ).getName() ).isEqualTo( pigTwoUpdatedName ); - assertThat( guineaPigs.get( 2 ).getName() ).isEqualTo( PIG_THREE_NAME ); - assertThat( guineaPigs.get( 3 ).getName() ).isEqualTo( PIG_FOUR_NAME ); - assertThat( guineaPigs.get( 4 ).getName() ).isEqualTo( PIG_FIVE_NAME ); - assertThat( guineaPigs.get( 5 ).getName() ).isEqualTo( PIG_SIX_NAME ); - } - - @Entity(name = "GuineaPig") - @Table(name = "pig") - public static 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/hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessWithBatchTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessWithBatchTest.java new file mode 100644 index 000000000..f934090b8 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessWithBatchTest.java @@ -0,0 +1,422 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate; + +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Configuration; +import org.hibernate.reactive.BaseReactiveTest; +import org.hibernate.reactive.testing.SqlStatementTracker; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.smallrye.mutiny.Uni; +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletionStage; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test the stateless session actually execute the operations in batch. + */ +@Timeout(value = 10, timeUnit = MINUTES) +public class ReactiveStatelessWithBatchTest extends BaseReactiveTest { + private static SqlStatementTracker sqlTracker; + + private static final Object[] PIGS = { + new GuineaPig( 11, "One" ), + new GuineaPig( 22, "Two" ), + new GuineaPig( 33, "Three" ), + new GuineaPig( 44, "Four" ), + new GuineaPig( 55, "Five" ), + new GuineaPig( 66, "Six" ) + }; + + private static final Object[] PIGS_AFTER_DELETE = List.of( PIGS ) + .subList( 2, PIGS.length ) + .toArray(); + + private static final Object[] PIGS_AFTER_UPDATE = { + new GuineaPig( 11, "One updated" ), + new GuineaPig( 22, "Two updated" ), + new GuineaPig( 33, "Three" ), + new GuineaPig( 44, "Four" ), + new GuineaPig( 55, "Five" ), + new GuineaPig( 66, "Six" ) + }; + + @Override + protected Set> annotatedEntities() { + return Set.of( GuineaPig.class ); + } + + @Override + protected Configuration constructConfiguration() { + Configuration configuration = super.constructConfiguration(); + + // Construct a tracker that collects query statements via the SqlStatementLogger framework. + // Pass in configuration properties to hand off any actual logging properties + sqlTracker = new SqlStatementTracker( + ReactiveStatelessWithBatchTest::filter, + configuration.getProperties() + ); + return configuration; + } + + @BeforeEach + public void clearTracker() { + sqlTracker.clear(); + } + + @Override + protected void addServices(StandardServiceRegistryBuilder builder) { + sqlTracker.registerService( builder ); + } + + private static boolean filter(String s) { + String[] accepted = { "insert ", "update ", "delete " }; + for ( String valid : accepted ) { + if ( s.toLowerCase().startsWith( valid ) ) { + return true; + } + } + return false; + } + + @Test + public void testMutinyBatchingInsert(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) + .invoke( () -> assertSqlLogTracker( "insert into pig \\(name,id\\) values (.*)" ) ) + .chain( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS ) ) ) + ); + } + + @Test + public void testMutinyBatchingInsertMultiple(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertMultiple( List.of( PIGS ) ) ) + .invoke( () -> assertSqlLogTracker( "insert into pig \\(name,id\\) values (.*)" ) ) + .chain( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS ) ) ) + ); + } + + @Test + public void testMutinyBatchingInsertAllNoBatchSizeParameter(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( PIGS ) ) + .invoke( () -> assertSqlLogTracker( "insert into pig \\(name,id\\) values (.*)" ) ) + .chain( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS ) ) ) + ); + } + + @Test + public void testStageBatchingInsert(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenAccept( v -> assertSqlLogTracker( "insert into pig \\(name,id\\) values (.*)" ) ) + .thenCompose( v -> assertExpectedResult( PIGS ) ) + ); + } + + @Test + public void testStageBatchingInsertMultiple(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insertMultiple( List.of( PIGS ) ) ) + .thenAccept( v -> assertSqlLogTracker( "insert into pig \\(name,id\\) values (.*)" ) ) + .thenCompose( v -> assertExpectedResult( PIGS ) ) + ); + } + + @Test + public void testStageBatchingInsertNoBatchSizeParameter(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( PIGS ) ) + .thenAccept( v -> assertSqlLogTracker( "insert into pig \\(name,id\\) values (.*)" ) ) + .thenCompose( v -> assertExpectedResult( PIGS ) ) + ); + } + + @Test + public void testMutinyBatchingDelete(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p", GuineaPig.class ).getResultList() + ) ) + .chain( pigs -> getMutinySessionFactory().withStatelessTransaction( s -> s + .deleteAll( 10, pigs.subList( 0, 2 ).toArray() ) + ) ) + .invoke( () -> assertSqlLogTracker( "delete from pig where id=.*" ) ) + .call( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS_AFTER_DELETE ) ) ) + ); + } + + @Test + public void testMutinyBatchingDeleteMultiple(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p", GuineaPig.class ).getResultList() + ) ) + .chain( pigs -> getMutinySessionFactory().withStatelessTransaction( s -> s + .deleteMultiple( pigs.subList( 0, 2 ) ) ) + ) + .invoke( () -> assertSqlLogTracker( "delete from pig where id=.*" ) ) + .call( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS_AFTER_DELETE ) ) ) + ); + } + + @Test + public void testMutinyBatchingDeleteAllNoBatchSizeParameter(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( PIGS ) ) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p", GuineaPig.class ).getResultList() + ) ) + .chain( pigs -> getMutinySessionFactory().withStatelessTransaction( s -> s + .deleteAll( pigs.subList( 0, 2 ).toArray() ) + ) ) + .invoke( () -> assertSqlLogTracker( "delete from pig where id=.*" ) ) + .call( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS_AFTER_DELETE ) ) ) + ); + } + + @Test + public void testStageBatchingDelete(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenRun( sqlTracker::clear ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p", GuineaPig.class ).getResultList() + .thenCompose( pigs -> s.delete( 10, pigs.subList( 0, 2 ).toArray() ) ) + ) ) + .thenAccept( v -> assertSqlLogTracker( "delete from pig where id=.*" ) ) + .thenCompose( v -> assertExpectedResult( PIGS_AFTER_DELETE ) ) + ); + } + + @Test + public void testStageBatchingDeleteMultiple(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenRun( sqlTracker::clear ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p", GuineaPig.class ).getResultList() + .thenCompose( pigs -> s.deleteMultiple( pigs.subList( 0, 2 ) ) ) + ) ) + .thenAccept( v -> assertSqlLogTracker( "delete from pig where id=.*" ) ) + .thenCompose( v -> assertExpectedResult( PIGS_AFTER_DELETE ) ) + ); + } + + @Test + public void testStageBatchingDeleteNoBatchSizeParameter(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenRun( sqlTracker::clear ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p", GuineaPig.class ).getResultList() + .thenCompose( pigs -> s.delete( pigs.subList( 0, 2 ).toArray() ) ) + ) ) + .thenAccept( v -> assertSqlLogTracker( "delete from pig where id=.*" ) ) + .thenCompose( v -> assertExpectedResult( PIGS_AFTER_DELETE ) ) + ); + } + + @Test + public void testMutinyBatchingUpdate(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .chain( pigs -> { + pigs.get( 0 ).setName( "One updated" ); + pigs.get( 1 ).setName( "Two updated" ); + return s.updateAll( 10, pigs.toArray() ); + } ) + ) ) + .invoke( () -> assertSqlLogTracker( "update pig set name=.* where id=.*" ) ) + .call( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS_AFTER_UPDATE ) ) ) + ); + } + + @Test + public void testMutinyBatchingUpdateMultiple(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .chain( pigs -> { + pigs.get( 0 ).setName( "One updated" ); + pigs.get( 1 ).setName( "Two updated" ); + return s.updateMultiple( pigs.subList( 0, 2 ) ); + } ) ) + ) + .invoke( () -> assertSqlLogTracker( "update pig set name=.* where id=.*" ) ) + .call( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS_AFTER_UPDATE ) ) ) + ); + } + + @Test + public void testMutinyBatchingUpdateAllNoBatchSizeParameter(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .chain( pigs -> { + pigs.get( 0 ).setName( "One updated" ); + pigs.get( 1 ).setName( "Two updated" ); + return s.updateAll( pigs.toArray() ); + } ) ) + ) + .invoke( () -> assertSqlLogTracker( "update pig set name=.* where id=.*" ) ) + .call( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS_AFTER_UPDATE ) ) ) + ); + } + + @Test + public void testStageBatchingUpdate(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenAccept( v -> sqlTracker.clear() ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .thenApply( pigs -> { + pigs.get( 0 ).setName( "One updated" ); + pigs.get( 1 ).setName( "Two updated" ); + return s.update( 10, pigs.toArray() ); + } ) + ) ) + .thenAccept( v -> assertSqlLogTracker( "update pig set name=.* where id=.*" ) ) + .thenCompose( v -> assertExpectedResult( PIGS_AFTER_UPDATE ) ) + ); + } + + @Test + public void testStageBatchingUpdateMultiple(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenRun( sqlTracker::clear ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .thenApply( pigs -> { + pigs.get( 0 ).setName( "One updated" ); + pigs.get( 1 ).setName( "Two updated" ); + return s.updateMultiple( pigs ); + } ) + ) ) + .thenAccept( v -> assertSqlLogTracker( "update pig set name=.* where id=.*" ) ) + .thenCompose( v -> assertExpectedResult( PIGS_AFTER_UPDATE ) ) + ); + } + + @Test + public void testStageBatchingUpdateNoBatchSizeParameter(VertxTestContext context) { + test(context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenRun( sqlTracker::clear ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .thenApply( pigs -> { + pigs.get( 0 ).setName( "One updated" ); + pigs.get( 1 ).setName( "Two updated" ); + return s.update( pigs.get( 0 ), pigs.get( 1 ) ); + } ) + ) ) + .thenAccept( v -> assertSqlLogTracker( "update pig set name=.* where id=.*" ) ) + .thenCompose( v -> assertExpectedResult( PIGS_AFTER_UPDATE ) ) + ); + } + + private CompletionStage assertExpectedResult(Object[] expected) { + return getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p order by id", Object.class ) + .getResultList() + .thenAccept( pigs -> assertThat( pigs ).containsExactly( expected ) ) ); + } + + private static void assertSqlLogTracker(String queryRegex) { + // We expect only one query for each batched operations + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so the regex must keep that in consideration + assertThat( sqlTracker.getLoggedQueries() ).allMatch( s -> s.matches( queryRegex ) ); + } + + @Entity(name = "GuineaPig") + @Table(name = "pig") + public static 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 ); + } + } +} From 55ed2a116852bb5da4d00b67bb46dd61952dcac0 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 7 Mar 2025 10:48:17 +0100 Subject: [PATCH 08/38] [#2139] Add batching upsert to the StatelessSession --- .../org/hibernate/reactive/mutiny/Mutiny.java | 36 +++++++++++++++++++ .../impl/MutinyStatelessSessionImpl.java | 15 ++++++++ .../session/ReactiveStatelessSession.java | 2 ++ .../impl/ReactiveStatelessSessionImpl.java | 10 ++++++ .../org/hibernate/reactive/stage/Stage.java | 36 +++++++++++++++++++ .../stage/impl/StageStatelessSessionImpl.java | 23 +++++++++--- 6 files changed, 118 insertions(+), 4 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java index a648a7bd7..59edbb875 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java @@ -1908,6 +1908,42 @@ default Uni get(Class entityClass, Object id, LockModeType lockModeTyp @Incubating Uni upsert(String entityName, Object entity); + /** + * Use a SQL {@code merge into} statement to perform + * an upsert on multiple rows using the size of the given array + * as batch size. + * + * @param entities the entities to upsert + * + * @see org.hibernate.StatelessSession#upsert(Object) + */ + @Incubating + Uni upsertAll(Object... entities); + + /** + * Use a SQL {@code merge into} statement to perform + * an upsert on multiple rows using the specified batch size. + * + * @param batchSize the batch size + * @param entities the list of entities to upsert + * + * @see org.hibernate.StatelessSession#upsert(Object) + */ + @Incubating + Uni upsertAll(int batchSize, Object... entities); + + /** + * Use a SQL {@code merge into} statement to perform + * an upsert on multiple rows using the size of the given list + * as batch size. + * + * @param entities the entities to upsert + * + * @see org.hibernate.StatelessSession#upsert(Object) + */ + @Incubating + Uni upsertMultiple(List entities); + /** * Refresh the entity instance state from the database. * diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java index a80d79750..d6e8e984e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java @@ -206,6 +206,21 @@ public Uni upsert(String entityName, Object entity) { return uni( () -> delegate.reactiveUpsert( entityName, entity ) ); } + @Override + public Uni upsertAll(Object... entities) { + return uni( () -> delegate.reactiveUpsertAll( entities.length, entities ) ); + } + + @Override + public Uni upsertAll(int batchSize, Object... entities) { + return uni( () -> delegate.reactiveUpsertAll( batchSize, entities ) ); + } + + @Override + public Uni upsertMultiple(List entities) { + return uni( () -> delegate.reactiveUpsertAll( entities.size(), entities.toArray() ) ); + } + @Override public Uni refreshAll(Object... entities) { return uni( () -> delegate.reactiveRefreshAll( entities.length, entities ) ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveStatelessSession.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveStatelessSession.java index 33e34917b..e6d2aa1ee 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveStatelessSession.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveStatelessSession.java @@ -48,6 +48,8 @@ public interface ReactiveStatelessSession extends ReactiveQueryProducer, Reactiv CompletionStage reactiveUpsert(String entityName, Object entity); + CompletionStage reactiveUpsertAll(int batchSize, Object... entities); + CompletionStage reactiveRefresh(Object entity); CompletionStage reactiveRefresh(String entityName, Object entity); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java index 53b928903..0c018d789 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java @@ -542,6 +542,16 @@ public CompletionStage reactiveUpsert(String entityName, Object entity) { .mergeReactive( id, state, null, false, null, oldVersion, entity, null, this ); } + @Override + public CompletionStage reactiveUpsertAll(int batchSize, Object... entities) { + final Integer jdbcBatchSize = batchingHelperSession.getJdbcBatchSize(); + batchingHelperSession.setJdbcBatchSize( batchSize ); + final ReactiveConnection connection = batchingConnection( batchSize ); + return loop( entities, batchingHelperSession::reactiveUpsert ) + .thenCompose( v -> connection.executeBatch() ) + .whenComplete( (v, throwable) -> batchingHelperSession.setJdbcBatchSize( jdbcBatchSize ) ); + } + @Override public CompletionStage reactiveInsertAll(Object... entities) { return loop( entities, batchingHelperSession::reactiveInsert ) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java index 074f63e58..b490f2fb3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java @@ -2021,6 +2021,42 @@ default CompletionStage refresh(Object entity, LockModeType lockModeType) */ CompletionStage upsert(String entityName, Object entity); + /** + * Use a SQL {@code merge into} statement to perform + * an upsert on multiple rows using the size of the given array + * as batch size. + * + * @param entities the entities to upsert + * + * @see org.hibernate.StatelessSession#upsert(Object) + */ + @Incubating + CompletionStage upsertAll(Object... entities); + + /** + * Use a SQL {@code merge into} statement to perform + * an upsert on multiple rows using the specified batch size. + * + * @param batchSize the batch size + * @param entities the list of entities to upsert + * + * @see org.hibernate.StatelessSession#upsert(Object) + */ + @Incubating + CompletionStage upsertAll(int batchSize, Object... entities); + + /** + * Use a SQL {@code merge into} statement to perform + * an upsert on multiple rows using the size of the given list + * as batch size. + * + * @param entities the entities to upsert + * + * @see org.hibernate.StatelessSession#upsert(Object) + */ + @Incubating + CompletionStage upsertMultiple(List entities); + /** * Asynchronously fetch an association that's configured for lazy loading. * diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java index c97be1a94..aadd76285 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java @@ -5,10 +5,6 @@ */ package org.hibernate.reactive.stage.impl; -import jakarta.persistence.EntityGraph; -import jakarta.persistence.criteria.CriteriaDelete; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.CriteriaUpdate; import org.hibernate.LockMode; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.query.criteria.JpaCriteriaInsert; @@ -20,6 +16,10 @@ import org.hibernate.reactive.stage.Stage.Query; import org.hibernate.reactive.stage.Stage.SelectionQuery; +import jakarta.persistence.EntityGraph; +import jakarta.persistence.criteria.CriteriaDelete; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.CriteriaUpdate; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -155,6 +155,21 @@ public CompletionStage upsert(String entityName, Object entity) { return delegate.reactiveUpsert( entityName, entity ); } + @Override + public CompletionStage upsertAll(Object... entities) { + return delegate.reactiveUpsertAll( entities.length, entities ); + } + + @Override + public CompletionStage upsertAll(int batchSize, Object... entities) { + return delegate.reactiveUpsertAll( batchSize, entities ); + } + + @Override + public CompletionStage upsertMultiple(List entities) { + return delegate.reactiveUpsertAll( entities.size(), entities.toArray() ); + } + @Override public CompletionStage fetch(T association) { return delegate.reactiveFetch( association, false ); From 2acd8940365b64a9d1b306f8c8488d5b7583d2b3 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 7 Mar 2025 10:49:16 +0100 Subject: [PATCH 09/38] [#2139] Add test for upsert with batching --- .../ReactiveStatelessWithBatchTest.java | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessWithBatchTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessWithBatchTest.java index f934090b8..563fad40b 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessWithBatchTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessWithBatchTest.java @@ -8,6 +8,7 @@ import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.Configuration; import org.hibernate.reactive.BaseReactiveTest; +import org.hibernate.reactive.annotations.EnabledFor; import org.hibernate.reactive.testing.SqlStatementTracker; import org.junit.jupiter.api.BeforeEach; @@ -26,6 +27,7 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.POSTGRESQL; /** * Test the stateless session actually execute the operations in batch. @@ -85,7 +87,7 @@ protected void addServices(StandardServiceRegistryBuilder builder) { } private static boolean filter(String s) { - String[] accepted = { "insert ", "update ", "delete " }; + String[] accepted = { "merge ", "insert ", "update ", "delete " }; for ( String valid : accepted ) { if ( s.toLowerCase().startsWith( valid ) ) { return true; @@ -94,6 +96,66 @@ private static boolean filter(String s) { return false; } + @Test + @EnabledFor(POSTGRESQL) + public void testMutinyMergeUpsertAll(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.upsertAll( PIGS ) ) + .invoke( () -> assertSqlLogTracker( "merge into pig as t using (.*)" ) ) + .chain( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS ) ) ) + ); + } + + @Test + @EnabledFor(POSTGRESQL) + public void testMutinyMergeUpsertAllWithBatchSize(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.upsertAll( 10, PIGS ) ) + .invoke( () -> assertSqlLogTracker( "merge into pig as t using (.*)" ) ) + .chain( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS ) ) ) + ); + } + + @Test + @EnabledFor(POSTGRESQL) + public void testMutinyMergeUpsertMultiple(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.upsertMultiple( List.of( PIGS ) ) ) + .invoke( () -> assertSqlLogTracker( "merge into pig as t using (.*)" ) ) + .chain( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS ) ) ) + ); + } + + @Test + @EnabledFor(POSTGRESQL) + public void testStageMergeUpsertAll(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.upsertAll( PIGS ) ) + .thenRun( () -> assertSqlLogTracker( "merge into pig as t using (.*)" ) ) + .thenCompose( v -> assertExpectedResult( PIGS ) ) + ); + } + + @Test + @EnabledFor(POSTGRESQL) + public void testStageMergeUpsertAllWithBatchSize(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.upsertAll( 10, PIGS ) ) + .thenRun(() -> assertSqlLogTracker( "merge into pig as t using (.*)" ) ) + .thenCompose( v -> assertExpectedResult( PIGS ) ) + ); + } + + @Test + @EnabledFor(POSTGRESQL) + public void testStageMergeUpsertMultiple(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.upsertMultiple( List.of( PIGS ) ) ) + .thenRun( () -> assertSqlLogTracker( "merge into pig as t using (.*)" ) ) + .thenCompose( v -> assertExpectedResult( PIGS ) ) + ); + } + @Test public void testMutinyBatchingInsert(VertxTestContext context) { test( context, getMutinySessionFactory() From ede60d8e084b6a8ff1b13a94ff25318925dfcc58 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 7 Mar 2025 11:04:34 +0100 Subject: [PATCH 10/38] [#2139] Explain why we set the default batch size to 0 --- .../session/impl/ReactiveStatelessSessionImpl.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java index 0c018d789..4941dda50 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java @@ -150,8 +150,11 @@ private ReactiveStatelessSessionImpl( PersistenceContext persistenceContext) { super( factory, options ); this.persistenceContext = persistenceContext; - // Setting batch size to 0 because `StatelessSession` does not consider - // the value of `hibernate.jdbc.batch_size` + // StatelessSession should not allow JDBC batching, because that would change + // its "immediate synchronous execution" model into something more like transactional + // write-behind and be confusing. For this reason, the default batch size is always set to 0. + // When a user calls the CRUD operations for batching, we set the batch size to the same number of + // objects to process, therefore, there is no write-behind behavior. reactiveConnection = new BatchingConnection( connection, 0 ); batchingHelperSession = this; influencers = new LoadQueryInfluencers( factory ); From 71f1899bc56ccf3bc1c1e69e465fab538bd85ea6 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 14 Mar 2025 10:50:34 +0100 Subject: [PATCH 11/38] [#2143] Upgrade Testcontainers to 1.20.6 --- build.gradle | 2 +- tooling/jbang/CockroachDBReactiveTest.java.qute | 2 +- tooling/jbang/Db2ReactiveTest.java.qute | 2 +- tooling/jbang/MariaDBReactiveTest.java.qute | 2 +- tooling/jbang/MySQLReactiveTest.java.qute | 2 +- tooling/jbang/PostgreSQLReactiveTest.java.qute | 2 +- tooling/jbang/ReactiveTest.java | 10 +++++----- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index aef4fcc26..808127db4 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,7 @@ ext { vertxSqlClientVersion = '4.5.13' } - testcontainersVersion = '1.20.4' + testcontainersVersion = '1.20.6' logger.lifecycle "Vert.x SQL Client Version: " + project.vertxSqlClientVersion } diff --git a/tooling/jbang/CockroachDBReactiveTest.java.qute b/tooling/jbang/CockroachDBReactiveTest.java.qute index 9e041af06..9b713c8ea 100755 --- a/tooling/jbang/CockroachDBReactiveTest.java.qute +++ b/tooling/jbang/CockroachDBReactiveTest.java.qute @@ -10,7 +10,7 @@ //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Beta1} //DEPS org.assertj:assertj-core:3.26.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:cockroachdb:1.20.4 +//DEPS org.testcontainers:cockroachdb:1.20.6 //DEPS org.slf4j:slf4j-simple:2.0.7 //// Testcontainer needs the JDBC drivers to start the container diff --git a/tooling/jbang/Db2ReactiveTest.java.qute b/tooling/jbang/Db2ReactiveTest.java.qute index feabab3f7..a13d21b6d 100755 --- a/tooling/jbang/Db2ReactiveTest.java.qute +++ b/tooling/jbang/Db2ReactiveTest.java.qute @@ -10,7 +10,7 @@ //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Beta1} //DEPS org.assertj:assertj-core:3.26.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:db2:1.20.4 +//DEPS org.testcontainers:db2:1.20.6 //DEPS org.slf4j:slf4j-simple:2.0.7 import jakarta.persistence.Entity; diff --git a/tooling/jbang/MariaDBReactiveTest.java.qute b/tooling/jbang/MariaDBReactiveTest.java.qute index dd6ceb3c3..f6b528925 100755 --- a/tooling/jbang/MariaDBReactiveTest.java.qute +++ b/tooling/jbang/MariaDBReactiveTest.java.qute @@ -10,7 +10,7 @@ //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Beta1} //DEPS org.assertj:assertj-core:3.26.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:mariadb:1.20.4 +//DEPS org.testcontainers:mariadb:1.20.6 //DEPS org.slf4j:slf4j-simple:2.0.7 //// Testcontainer needs the JDBC drivers to start the container diff --git a/tooling/jbang/MySQLReactiveTest.java.qute b/tooling/jbang/MySQLReactiveTest.java.qute index b31d4e8d8..f8307495d 100755 --- a/tooling/jbang/MySQLReactiveTest.java.qute +++ b/tooling/jbang/MySQLReactiveTest.java.qute @@ -10,7 +10,7 @@ //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Beta1} //DEPS org.assertj:assertj-core:3.26.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:mysql:1.20.4 +//DEPS org.testcontainers:mysql:1.20.6 //DEPS org.slf4j:slf4j-simple:2.0.7 //// Testcontainer needs the JDBC drivers to start the container diff --git a/tooling/jbang/PostgreSQLReactiveTest.java.qute b/tooling/jbang/PostgreSQLReactiveTest.java.qute index ab43700ed..b49a6433b 100755 --- a/tooling/jbang/PostgreSQLReactiveTest.java.qute +++ b/tooling/jbang/PostgreSQLReactiveTest.java.qute @@ -10,7 +10,7 @@ //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Beta1} //DEPS org.assertj:assertj-core:3.26.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:postgresql:1.20.4 +//DEPS org.testcontainers:postgresql:1.20.6 //DEPS org.slf4j:slf4j-simple:2.0.7 //DESCRIPTION Allow authentication to PostgreSQL using SCRAM: //DEPS com.ongres.scram:client:2.1 diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java index 7357011ed..51ba7f267 100755 --- a/tooling/jbang/ReactiveTest.java +++ b/tooling/jbang/ReactiveTest.java @@ -13,11 +13,11 @@ //DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:3.0.0.Beta1} //DEPS org.assertj:assertj-core:3.26.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:postgresql:1.20.4 -//DEPS org.testcontainers:mysql:1.20.4 -//DEPS org.testcontainers:db2:1.20.4 -//DEPS org.testcontainers:mariadb:1.20.4 -//DEPS org.testcontainers:cockroachdb:1.20.4 +//DEPS org.testcontainers:postgresql:1.20.6 +//DEPS org.testcontainers:mysql:1.20.6 +//DEPS org.testcontainers:db2:1.20.6 +//DEPS org.testcontainers:mariadb:1.20.6 +//DEPS org.testcontainers:cockroachdb:1.20.6 // //// Testcontainer needs the JDBC drivers to start the containers //// Hibernate Reactive doesn't use them From 6a9b9f4d3f1aa37a127516fa11d0abf97aca7c48 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 14 Mar 2025 10:39:02 +0100 Subject: [PATCH 12/38] [#2064] Add CONTRIBUTING.md --- CONTRIBUTING.md | 107 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..2df00b224 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,107 @@ +# Contributing + +Contributions from the community are essential in keeping Hibernate (and any Open Source +project really) strong and successful. + +# Legal + +All original contributions to Hibernate are licensed under the +[Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0). + +The Apache 2.0 license text is included verbatim in the [LICENSE](LICENSE) file in the root directory +of the Hibernate Reactive repository. + +All contributions are subject to the [Developer Certificate of Origin (DCO)](https://developercertificate.org/). + +The DCO text is available verbatim in the [dco.txt](dco.txt) file in the root directory +of the Hibernate Reactive repository. + +## Guidelines + +While we try to keep requirements for contributing to a minimum, there are a few guidelines +we ask that you mind. + +For code contributions, these guidelines include: +* Respect the project code style - find templates for [IntelliJ IDEA](https://hibernate.org/community/contribute/intellij-idea/) or [Eclipse](https://hibernate.org/community/contribute/eclipse-ide/) +* Have a corresponding GitHub [issue](https://github.com/hibernate/hibernate-reactive/issues) and be sure to include + the key for this issue in your commit messages. +* Have a set of appropriate tests. + For your convenience, a [set of test templates](https://github.com/hibernate/hibernate-test-case-templates/tree/main/reactive) + have been made available. + + When submitting bug reports, the tests should reproduce the initially reported bug and illustrate that your solution addresses the issue. + For features/enhancements, the tests should demonstrate that the feature works as intended. + In both cases, be sure to incorporate your tests into the project to protect against possible regressions. +* If applicable, documentation should be updated to reflect the introduced changes +* The code compiles and the tests pass (`./gradlew clean build`) + +For documentation contributions, mainly to respect the project code style, especially in regard +to the use of tabs - as mentioned above, code style templates are available for both IntelliJ IDEA and Eclipse +IDEs. Ideally, these contributions would also have a corresponding issue, although this +is less necessary for documentation contributions. + +## Getting Started + +If you are just getting started with Git, GitHub, and/or contributing to Hibernate via +GitHub there are a few pre-requisite steps to follow: + +* Make sure you have a [GitHub account](https://github.com/signup/free) +* [Fork](https://help.github.com/articles/fork-a-repo) the Hibernate Reactive repository. As discussed in +the linked page, this also includes: + * [set up your local git install](https://help.github.com/articles/set-up-git) + * clone your fork +* Instruct git to ignore certain commits when using `git blame`. From the directory of your local clone, run this: `git config blame.ignoreRevsFile .git-blame-ignore-revs` +* See the wiki pages for setting up your IDE, whether you use +[IntelliJ IDEA](https://hibernate.org/community/contribute/intellij-idea/) +or [Eclipse](https://hibernate.org/community/contribute/eclipse-ide/)(1). + + +## Create the working (topic) branch + +Create a [topic branch](https://git-scm.com/book/en/Git-Branching-Branching-Workflows#Topic-Branches) +on which you will work. The convention is to incorporate the JIRA issue key in the name of this branch, +although this is more of a mnemonic strategy than a hard-and-fast rule - but doing so helps: +* Remember what each branch is for +* Isolate the work from other contributions you may be working on + +_If there is not already a GitHub issue covering the work you want to do, [create one](https://github.com/hibernate/hibernate-reactive/issues/new)._ + +Assuming you will be working from the `main` branch and working +on the GitHub issue #123 : `git checkout -b 123 main` + +## Code + +Do your thing! + + +## Commit + +* Make commits of logical units +* Be sure to start each commit message using the ** GitHub issue key **. For example: + ``` + [#1234] Fix some kind of problem + ``` +* Make sure you have added the necessary tests for your changes +* Run _all_ the tests to ensure nothing else was accidentally broken + +_Before committing, if you want to pull in the latest upstream changes (highly +appreciated btw), please use rebasing rather than merging. Merging creates +"merge commits" that invariably muck up the project timeline._ + +## Submit + +* Push your changes to the topic branch in your fork of the repository +* Initiate a [pull request](https://help.github.com/articles/creating-a-pull-request) +* Adding the sentence `Fix #123`, where `#123` is the issue key, will link the pull request to the corresponding issue + and close it accordingly, when the pull request gets merged. + +It is important that this topic branch of your fork: + +* Is isolated to just the work on this one issue, or multiple issues if they are + related and also fixed/implemented by this work. The main point is to not push commits for more than + one PR to a single branch - GitHub PRs are linked to a branch rather than specific commits +* remain until the PR is closed. Once the underlying branch is deleted the corresponding PR will be closed, + if not already, and the changes will be lost. + +# Notes +(1) Gradle `eclipse` plugin is no longer supported, so the recommended way to import the project in your IDE is with the proper IDE tools/plugins. Don't try to run `./gradlew clean eclipse --refresh-dependencies` from the command line as you'll get an error because `eclipse` no longer exists From e768bb2c0ea37f23580d374a41ef05b6f88624e6 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 18 Mar 2025 08:59:17 +0100 Subject: [PATCH 13/38] [#2148] Upgrade PostgreSQL for testing to 17.4 --- .../org/hibernate/reactive/containers/PostgreSQLDatabase.java | 2 +- .../src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java | 2 +- .../reactive/it/quarkus/qe/database/BaseReactiveIT.java | 2 +- .../java/org/hibernate/reactive/it/verticle/VertxServer.java | 2 +- podman.md | 2 +- tooling/jbang/Example.java | 2 +- tooling/jbang/PostgreSQLReactiveTest.java.qute | 2 +- tooling/jbang/ReactiveTest.java | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java index c832ae867..5b427f4e7 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java @@ -87,7 +87,7 @@ class PostgreSQLDatabase implements TestableDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final PostgreSQLContainer postgresql = new PostgreSQLContainer<>( imageName( "postgres", "16.3" ) ) + public static final PostgreSQLContainer postgresql = new PostgreSQLContainer<>( imageName( "postgres", "17.4" ) ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java index 72ad3273d..ff9f5e601 100644 --- a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java +++ b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java @@ -53,7 +53,7 @@ public abstract class BaseReactiveIT { public static final boolean USE_DOCKER = Boolean.getBoolean( "docker" ); public static final DockerImageName IMAGE_NAME = DockerImageName - .parse( "docker.io/postgres:16.3" ) + .parse( "docker.io/postgres:17.4" ) .asCompatibleSubstituteFor( "postgres" ); public static final String USERNAME = "hreact"; diff --git a/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/BaseReactiveIT.java b/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/BaseReactiveIT.java index 7272f13f2..afff092e9 100644 --- a/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/BaseReactiveIT.java +++ b/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/BaseReactiveIT.java @@ -53,7 +53,7 @@ public abstract class BaseReactiveIT { public static final boolean USE_DOCKER = Boolean.getBoolean( "docker" ); public static final DockerImageName IMAGE_NAME = DockerImageName - .parse( "docker.io/postgres:16.3" ) + .parse( "docker.io/postgres:17.4" ) .asCompatibleSubstituteFor( "postgres" ); public static final String USERNAME = "hreact"; diff --git a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java index eab23a6ff..238e5278b 100644 --- a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java +++ b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java @@ -36,7 +36,7 @@ public class VertxServer { // These properties are in DatabaseConfiguration in core public static final boolean USE_DOCKER = Boolean.getBoolean( "docker" ); - public static final String IMAGE_NAME = "postgres:16.3"; + public static final String IMAGE_NAME = "postgres:17.4"; public static final String USERNAME = "hreact"; public static final String PASSWORD = "hreact"; public static final String DB_NAME = "hreact"; diff --git a/podman.md b/podman.md index dc81fb828..3a35a715f 100644 --- a/podman.md +++ b/podman.md @@ -39,7 +39,7 @@ required credentials and schema to run the tests: podman run --rm --name HibernateTestingPGSQL \ -e POSTGRES_USER=hreact -e POSTGRES_PASSWORD=hreact -e POSTGRES_DB=hreact \ -e POSTGRES_INITDB_ARGS="-A password" \ - -p 5432:5432 docker.io/postgres:16.3 + -p 5432:5432 docker.io/postgres:17.4 ``` When the database has started, you can run the tests on PostgreSQL with: diff --git a/tooling/jbang/Example.java b/tooling/jbang/Example.java index 7c0f4cef0..7f6d383fd 100644 --- a/tooling/jbang/Example.java +++ b/tooling/jbang/Example.java @@ -59,7 +59,7 @@ *
  *                 podman run --rm --name HibernateTestingPGSQL \
  *                      -e POSTGRES_USER=hreact -e POSTGRES_PASSWORD=hreact -e POSTGRES_DB=hreact \
- *                      -p 5432:5432 postgres:16.3
+ *                      -p 5432:5432 postgres:17.4
  *              
* *
3. Run the example with JBang
diff --git a/tooling/jbang/PostgreSQLReactiveTest.java.qute b/tooling/jbang/PostgreSQLReactiveTest.java.qute index b49a6433b..682ac502b 100755 --- a/tooling/jbang/PostgreSQLReactiveTest.java.qute +++ b/tooling/jbang/PostgreSQLReactiveTest.java.qute @@ -67,7 +67,7 @@ public class {baseName} { } @ClassRule - public final static PostgreSQLContainer database = new PostgreSQLContainer( imageName( "docker.io", "postgres", "16.3" ) ); + public final static PostgreSQLContainer database = new PostgreSQLContainer( imageName( "docker.io", "postgres", "17.4" ) ); private Mutiny.SessionFactory sessionFactory; diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java index 51ba7f267..c1094f891 100755 --- a/tooling/jbang/ReactiveTest.java +++ b/tooling/jbang/ReactiveTest.java @@ -228,7 +228,7 @@ public String toString() { * It's a wrapper around the testcontainers classes. */ enum Database { - POSTGRESQL( () -> new PostgreSQLContainer( "postgres:16.3" ) ), + POSTGRESQL( () -> new PostgreSQLContainer( "postgres:17.4" ) ), MYSQL( () -> new MySQLContainer( "mysql:8.4.0" ) ), DB2( () -> new Db2Container( "docker.io/icr.io/db2_community/db2:12.1.0.0" ).acceptLicense() ), MARIADB( () -> new MariaDBContainer( "mariadb:11.4.2" ) ), From 27acfb2ee1b9af38966762b72bd50d8e2d7b3b72 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 19 Mar 2025 11:32:57 +0100 Subject: [PATCH 14/38] [#2150] Avoid creation of logger at runtime Quarkus doesn't like it when running in native mode --- .../temptable/ReactiveGlobalTemporaryTableStrategy.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableStrategy.java index 7a396753c..095230723 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableStrategy.java @@ -27,6 +27,8 @@ public interface ReactiveGlobalTemporaryTableStrategy { + Log LOG = make( Log.class, lookup() ); + static String sessionIdentifier(SharedSessionContractImplementor session) { return session.getSessionIdentifier().toString(); } @@ -60,7 +62,7 @@ default void prepare(MappingModelCreationProcess mappingModelCreationProcess, Jd tableCreatedStage.complete( null ); } else { - make( Log.class, lookup() ).debugf( "Creating global-temp ID table : %s", getTemporaryTable().getTableExpression() ); + LOG.debugf( "Creating global-temp ID table : %s", getTemporaryTable().getTableExpression() ); connectionStage() .thenCompose( this::createTable ) @@ -98,7 +100,7 @@ private CompletionStage releaseConnection(ReactiveConnection connection) { private static void logConnectionClosedError(Throwable t) { if ( t != null ) { - make( Log.class, lookup() ).debugf( "Ignoring error closing the connection: %s", t.getMessage() ); + LOG.debugf( "Ignoring error closing the connection: %s", t.getMessage() ); } } @@ -146,7 +148,7 @@ default void release( setDropIdTables( false ); final TemporaryTable temporaryTable = getTemporaryTable(); - make( Log.class, lookup() ).debugf( "Dropping global-tempk ID table : %s", temporaryTable.getTableExpression() ); + LOG.debugf( "Dropping global-temp ID table : %s", temporaryTable.getTableExpression() ); connectionStage() .thenCompose( this::dropTable ) From 779a6324255acd7eba2b32f5ccbbf4e72abdb449 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 25 Mar 2025 18:09:14 +0100 Subject: [PATCH 15/38] [#2159] Remove JDK 23 testing --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d63e27906..cd4d85000 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -191,7 +191,6 @@ jobs: # and it's useful to test that. - { name: "20", java_version_numeric: 20, jvm_args: '--enable-preview' } - { name: "21", java_version_numeric: 21, jvm_args: '--enable-preview' } - - { name: "23", java_version_numeric: 23, from: 'jdk.java.net', jvm_args: '--enable-preview' } - { name: "24-ea", java_version_numeric: 24, from: 'jdk.java.net', jvm_args: '--enable-preview' } - { name: "25-ea", java_version_numeric: 25, from: 'jdk.java.net', jvm_args: '--enable-preview' } steps: From 08e06d11ce2b79211bed8a688d8dd55891250df2 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 25 Mar 2025 18:09:42 +0100 Subject: [PATCH 16/38] [#2159] Test with JDK 24 GA --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cd4d85000..eec52d729 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -191,7 +191,7 @@ jobs: # and it's useful to test that. - { name: "20", java_version_numeric: 20, jvm_args: '--enable-preview' } - { name: "21", java_version_numeric: 21, jvm_args: '--enable-preview' } - - { name: "24-ea", java_version_numeric: 24, from: 'jdk.java.net', jvm_args: '--enable-preview' } + - { name: "24", java_version_numeric: 24, from: 'jdk.java.net', jvm_args: '--enable-preview' } - { name: "25-ea", java_version_numeric: 25, from: 'jdk.java.net', jvm_args: '--enable-preview' } steps: - name: Checkout ${{ inputs.branch }} From c4fdc9a9c0bba5695a75b572a50a37bc512acde9 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 18 Feb 2025 14:34:10 +0100 Subject: [PATCH 17/38] [#2155] Upgrade Hibernate ORM to 7.0.0.Beta5 Some of the changes: * NullabilityCheck has changed * Remove ReactiveQueryImplementor#setOptional* * Replace inline calls to BatchFetchQueueHelper (removed) --- gradle.properties | 2 +- .../DefaultReactiveDeleteEventListener.java | 7 +- .../ReactiveEntityBatchLoaderArrayParam.java | 4 +- ...ReactiveMultiIdEntityLoaderArrayParam.java | 6 +- .../mutiny/impl/MutinySessionImpl.java | 2 +- .../impl/MutinyStatelessSessionImpl.java | 2 +- .../query/ReactiveQueryImplementor.java | 7 -- .../sql/internal/ReactiveNativeQueryImpl.java | 23 ++-- .../ReactiveSimpleDeleteQueryPlan.java | 39 ++++--- .../session/ReactiveQueryProducer.java | 2 + .../session/impl/ReactiveSessionImpl.java | 101 ++++++++++++++---- .../impl/ReactiveStatelessSessionImpl.java | 90 +++++++++++++--- .../reactive/stage/impl/StageSessionImpl.java | 31 +++--- .../stage/impl/StageStatelessSessionImpl.java | 2 +- 14 files changed, 218 insertions(+), 100 deletions(-) diff --git a/gradle.properties b/gradle.properties index 299ccf3b9..be7612bc5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -35,7 +35,7 @@ org.gradle.java.installations.auto-download=false #enableMavenLocalRepo = true # The default Hibernate ORM version (override using `-PhibernateOrmVersion=the.version.you.want`) -hibernateOrmVersion = 7.0.0.Beta4 +hibernateOrmVersion = 7.0.0.Beta5 # Override default Hibernate ORM Gradle plugin version # Using the stable version because I don't know how to configure the build to download the snapshot version from diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java index 664b09da3..f490d5366 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java @@ -436,11 +436,8 @@ protected CompletionStage deleteEntity( persister ).nullifyTransientReferences( entityEntry.getDeletedState() ) .thenAccept( vv -> { - new Nullability( session ).checkNullability( - entityEntry.getDeletedState(), - persister, - Nullability.NullabilityCheckType.DELETE - ); + new Nullability( session, Nullability.NullabilityCheckType.DELETE ) + .checkNullability( entityEntry.getDeletedState(), persister ); persistenceContext.registerNullifiableEntityKey( key ); final ReactiveActionQueue actionQueue = actionQueue( session ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveEntityBatchLoaderArrayParam.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveEntityBatchLoaderArrayParam.java index ba69fd407..04f971ffd 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveEntityBatchLoaderArrayParam.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveEntityBatchLoaderArrayParam.java @@ -10,7 +10,6 @@ import java.util.concurrent.CompletionStage; import org.hibernate.LockOptions; -import org.hibernate.engine.internal.BatchFetchQueueHelper; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -160,7 +159,8 @@ private CompletionStage initializeEntities( continue; } // found or not, remove the key from the batch-fetch queue - BatchFetchQueueHelper.removeBatchLoadableEntityKey( id, getLoadable(), session ); + session.getPersistenceContextInternal().getBatchFetchQueue() + .removeBatchLoadableEntityKey( session.generateEntityKey( id, getLoadable().getEntityPersister() ) ); } } ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderArrayParam.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderArrayParam.java index 8495d115b..8239a6e15 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderArrayParam.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderArrayParam.java @@ -13,7 +13,6 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; -import org.hibernate.engine.internal.BatchFetchQueueHelper; import org.hibernate.engine.spi.BatchFetchQueue; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; @@ -206,7 +205,7 @@ protected CompletionStage> performOrderedMultiLoad( // the element value at this position in the result List should be // the EntityKey for that entity - reuse it final EntityKey entityKey = (EntityKey) result.get( resultIndex ); - BatchFetchQueueHelper.removeBatchLoadableEntityKey( entityKey, session ); + session.getPersistenceContextInternal().getBatchFetchQueue().removeBatchLoadableEntityKey( entityKey ); Object entity = persistenceContext.getEntity( entityKey ); if ( entity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() ) { // make sure it is not DELETED @@ -293,7 +292,8 @@ protected CompletionStage> performUnorderedMultiLoad( continue; } // found or not, remove the key from the batch-fetch queue - BatchFetchQueueHelper.removeBatchLoadableEntityKey( id, getLoadable(), session ); + session.getPersistenceContextInternal().getBatchFetchQueue() + .removeBatchLoadableEntityKey( session.generateEntityKey( id, getLoadable().getEntityPersister() ) ); } return result; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionImpl.java index 929987b55..87cf50bb0 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionImpl.java @@ -173,7 +173,7 @@ public MutationQuery createQuery(CriteriaDelete criteriaDelete) { @Override public Query createNamedQuery(String queryName) { - return new MutinyQueryImpl<>( delegate.createReactiveNamedQuery( queryName, null ), factory ); + return new MutinyQueryImpl<>( delegate.createReactiveNamedQuery( queryName ), factory ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java index d6e8e984e..5605a7911 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java @@ -93,7 +93,7 @@ public Mutiny.MutationQuery createMutationQuery(String queryString) { @Override public Query createNamedQuery(String queryName) { - return new MutinyQueryImpl<>( delegate.createReactiveNamedQuery( queryName, null ), factory ); + return new MutinyQueryImpl<>( delegate.createReactiveNamedQuery( queryName ), factory ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveQueryImplementor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveQueryImplementor.java index 7c7588a7c..0354a59e5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveQueryImplementor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveQueryImplementor.java @@ -5,7 +5,6 @@ */ package org.hibernate.reactive.query; -import java.io.Serializable; import java.time.Instant; import java.util.Calendar; import java.util.Collection; @@ -23,12 +22,6 @@ public interface ReactiveQueryImplementor extends ReactiveQuery { - void setOptionalId(Serializable id); - - void setOptionalEntityName(String entityName); - - void setOptionalObject(Object optionalObject); - QueryParameterBindings getParameterBindings(); @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java index 6d162882a..ac8f4e9b4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java @@ -26,7 +26,6 @@ import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.RootGraph; import org.hibernate.graph.spi.RootGraphImplementor; -import org.hibernate.internal.AbstractSharedSessionContract; import org.hibernate.metamodel.model.domain.BasicDomainType; import org.hibernate.query.BindableType; import org.hibernate.query.Order; @@ -65,8 +64,18 @@ public class ReactiveNativeQueryImpl extends NativeQueryImpl private final ReactiveAbstractSelectionQuery selectionQueryDelegate; - public ReactiveNativeQueryImpl(String memento, SharedSessionContractImplementor session) { - super( memento, session ); + public ReactiveNativeQueryImpl(String sql, SharedSessionContractImplementor session) { + super( sql, null, session ); + this.selectionQueryDelegate = createSelectionQueryDelegate( session ); + } + + public ReactiveNativeQueryImpl(String sql, Class resultClass, SharedSessionContractImplementor session) { + super( sql, resultClass, session ); + this.selectionQueryDelegate = createSelectionQueryDelegate( session ); + } + + public ReactiveNativeQueryImpl(String sql, NamedResultSetMappingMemento resultSetMappingMemento, Class resultClass, SharedSessionContractImplementor session) { + super( sql, resultSetMappingMemento, resultClass, session); this.selectionQueryDelegate = createSelectionQueryDelegate( session ); } @@ -91,14 +100,6 @@ public ReactiveNativeQueryImpl( this.selectionQueryDelegate = createSelectionQueryDelegate( session ); } - public ReactiveNativeQueryImpl( - String sqlString, - NamedResultSetMappingMemento resultSetMappingMemento, - AbstractSharedSessionContract session) { - super( sqlString, resultSetMappingMemento, session ); - this.selectionQueryDelegate = createSelectionQueryDelegate( session ); - } - // Convenient for passing parameters to ReactiveAbstractSelectionQuery using method reference private T getNull() { return null; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java index a17d1eb73..eb055a38a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java @@ -37,9 +37,7 @@ import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement; import org.hibernate.sql.ast.tree.MutationStatement; -import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcLiteral; import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper; import org.hibernate.sql.ast.tree.from.NamedTableReference; import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; @@ -95,29 +93,28 @@ protected SqlAstTranslator createTranslato return factory.getJdbcServices() .getJdbcEnvironment() .getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, mutationStatement() ); + .buildMutationTranslator( factory, createDeleteAst() ); } - private MutationStatement mutationStatement() { + // Copy and paste from superclass + private MutationStatement createDeleteAst() { + final MutationStatement ast; if ( entityDescriptor.getSoftDeleteMapping() == null ) { - return sqmInterpretation.getSqlAst(); + ast = sqmInterpretation.getSqlAst(); } - final AbstractUpdateOrDeleteStatement sqlDeleteAst = sqmInterpretation.getSqlAst(); - final NamedTableReference targetTable = sqlDeleteAst.getTargetTable(); - final SoftDeleteMapping columnMapping = getEntityDescriptor().getSoftDeleteMapping(); - final ColumnReference columnReference = new ColumnReference( targetTable, columnMapping ); - //noinspection rawtypes,unchecked - final JdbcLiteral jdbcLiteral = new JdbcLiteral( - columnMapping.getDeletedLiteralValue(), - columnMapping.getJdbcMapping() - ); - final Assignment assignment = new Assignment( columnReference, jdbcLiteral ); - - return new UpdateStatement( - targetTable, - Collections.singletonList( assignment ), - sqlDeleteAst.getRestriction() - ); + else { + final AbstractUpdateOrDeleteStatement sqlDeleteAst = sqmInterpretation.getSqlAst(); + final NamedTableReference targetTable = sqlDeleteAst.getTargetTable(); + final SoftDeleteMapping columnMapping = getEntityDescriptor().getSoftDeleteMapping(); + final Assignment assignment = columnMapping.createSoftDeleteAssignment( targetTable ); + + ast = new UpdateStatement( + targetTable, + Collections.singletonList( assignment ), + sqlDeleteAst.getRestriction() + ); + } + return ast; } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveQueryProducer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveQueryProducer.java index 25a7402ef..a3b667fea 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveQueryProducer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveQueryProducer.java @@ -57,6 +57,8 @@ public interface ReactiveQueryProducer extends ReactiveConnectionSupplier { ReactiveQuery createReactiveQuery(String queryString, Class resultType); + ReactiveQueryImplementor createReactiveNamedQuery(String queryString); + ReactiveQueryImplementor createReactiveNamedQuery(String queryString, Class resultType); ReactiveNativeQuery createReactiveNativeQuery(String sqlString); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java index fc8ee8ae6..72157e46e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java @@ -72,13 +72,16 @@ import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import org.hibernate.query.IllegalMutationQueryException; +import org.hibernate.query.UnknownNamedQueryException; import org.hibernate.query.criteria.JpaCriteriaInsert; import org.hibernate.query.hql.spi.SqmQueryImplementor; import org.hibernate.query.named.NamedResultSetMappingMemento; import org.hibernate.query.spi.HqlInterpretation; import org.hibernate.query.spi.QueryImplementor; +import org.hibernate.query.sql.spi.NamedNativeQueryMemento; import org.hibernate.query.sql.spi.NativeQueryImplementor; import org.hibernate.query.sqm.internal.SqmUtil; +import org.hibernate.query.sqm.spi.NamedSqmQueryMemento; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; @@ -131,7 +134,6 @@ import static org.hibernate.engine.spi.NaturalIdResolutions.INVALID_NATURAL_ID_REFERENCE; import static org.hibernate.event.spi.LoadEventListener.IMMEDIATE_LOAD; import static org.hibernate.internal.util.StringHelper.isEmpty; -import static org.hibernate.internal.util.StringHelper.isNotEmpty; import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; import static org.hibernate.reactive.common.InternalStateAssertions.assertUseOnEventLoop; import static org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister.forceInitialize; @@ -443,14 +445,16 @@ public ReactiveNativeQuery createReactiveNativeQuery(String sqlString, Cl @Override @Deprecated(forRemoval = true) public ReactiveNativeQuery createReactiveNativeQuery(String sqlString, String resultSetMappingName) { + if ( isEmpty( resultSetMappingName ) ) { + throw new IllegalArgumentException( "Result set mapping name was not specified" ); + } + checkOpen(); pulseTransactionCoordinator(); delayedAfterCompletion(); try { - return isNotEmpty( resultSetMappingName ) - ? new ReactiveNativeQueryImpl<>( sqlString, getResultSetMappingMemento( resultSetMappingName ), this ) - : new ReactiveNativeQueryImpl<>( sqlString, this ); + return new ReactiveNativeQueryImpl<>( sqlString, getResultSetMappingMemento( resultSetMappingName ), null, this ); //TODO: why no applyQuerySettingsAndHints( query ); ??? } catch (RuntimeException he) { @@ -497,9 +501,78 @@ private ReactiveSelectionQuery createSelectionQuery(String hql, Class return query; } + @Override + public ReactiveQueryImplementor createReactiveNamedQuery(String name) { + checksBeforeQueryCreation(); + try { + return (ReactiveQueryImplementor) buildNamedQuery( + name, + this::createSqmQueryImplementor, + this::createNativeQueryImplementor + ); + } + catch (RuntimeException e) { + throw convertNamedQueryException( e ); + } + } + @Override public ReactiveQueryImplementor createReactiveNamedQuery(String name, Class resultType) { - return (ReactiveQueryImplementor) buildNamedQuery( name, resultType ); + checksBeforeQueryCreation(); + if ( resultType == null ) { + throw new IllegalArgumentException( "Result class is null" ); + } + try { + return buildNamedQuery( + name, + memento -> createReactiveSqmQueryImplementor( resultType, memento ), + memento -> createReactiveNativeQueryImplementor( resultType, memento ) + ); + } + catch (RuntimeException e) { + throw convertNamedQueryException( e ); + } + } + + private void checksBeforeQueryCreation() { + checkOpen(); + checkTransactionSynchStatus(); + } + + protected ReactiveNativeQueryImpl createReactiveNativeQueryImplementor(Class resultType, NamedNativeQueryMemento memento) { + final NativeQueryImplementor query = memento.toQuery(this, resultType ); + if ( isEmpty( query.getComment() ) ) { + query.setComment( "dynamic native SQL query" ); + } + applyQuerySettingsAndHints( query ); + return (ReactiveNativeQueryImpl) query; + } + + protected ReactiveQuerySqmImpl createReactiveSqmQueryImplementor(Class resultType, NamedSqmQueryMemento memento) { + final SqmQueryImplementor query = memento.toQuery( this, resultType ); + if ( isEmpty( query.getComment() ) ) { + query.setComment( "dynamic query" ); + } + applyQuerySettingsAndHints( query ); + if ( memento.getLockOptions() != null ) { + query.setLockOptions( memento.getLockOptions() ); + } + return (ReactiveQuerySqmImpl) query; + } + + private RuntimeException convertNamedQueryException(RuntimeException e) { + if ( e instanceof UnknownNamedQueryException ) { + // JPA expects this to mark the transaction for rollback only + getTransactionCoordinator().getTransactionDriverControl().markRollbackOnly(); + // it also expects an IllegalArgumentException, so wrap UnknownNamedQueryException + return new IllegalArgumentException( e.getMessage(), e ); + } + else if ( e instanceof IllegalArgumentException ) { + return e; + } + else { + return getExceptionConverter().convert( e ); + } } @Override @@ -584,7 +657,7 @@ public ReactiveNativeQuery createReactiveNativeQuery(String queryString, delayedAfterCompletion(); try { - final ReactiveNativeQueryImpl query = new ReactiveNativeQueryImpl<>( queryString, this ); + final ReactiveNativeQueryImpl query = new ReactiveNativeQueryImpl<>( queryString, null, this ); addAffectedEntities( affectedEntities, query ); if ( isEmpty( query.getComment() ) ) { query.setComment( "dynamic native SQL query" ); @@ -622,12 +695,11 @@ public ReactiveNativeQueryImpl createReactiveNativeQuery(String queryStri checkOpen(); pulseTransactionCoordinator(); delayedAfterCompletion(); - + // Should we throw an exception? + NamedResultSetMappingMemento memento = resultSetMapping == null ? null : getResultSetMappingMemento( resultSetMapping.getName() ); try { // Same approach as AbstractSharedSessionContract#createNativeQuery(String, String) - final ReactiveNativeQueryImpl nativeQuery = resultSetMapping != null - ? new ReactiveNativeQueryImpl<>( queryString, getResultSetMappingMemento( resultSetMapping.getName() ), this ) - : new ReactiveNativeQueryImpl<>( queryString, this ); + final ReactiveNativeQueryImpl nativeQuery = new ReactiveNativeQueryImpl<>( queryString, memento, null, this ); applyQuerySettingsAndHints( nativeQuery ); return nativeQuery; } @@ -652,20 +724,13 @@ public ResultSetMapping getResultSetMapping(Class resultType, String m if ( mapping == null ) { throw new IllegalArgumentException( "result set mapping does not exist: " + mappingName ); } -// -// ResultSetMappingImpl resultSetMapping = new ResultSetMappingImpl( "impl" ); -// if ( resultType != null ) { -// Class mappedResultType = resultSetMapping.; -// if ( !resultType.equals( mappedResultType ) ) { -// throw new IllegalArgumentException( "incorrect result type for result set mapping: " + mappingName + " has type " + mappedResultType.getName() ); -// } -// } return new ResultSetMapping<>() { @Override public String getName() { return mappingName; } + @Override public Class getResultType() { return resultType; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java index 4941dda50..2d3efe4f5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java @@ -22,6 +22,7 @@ import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.internal.ReactivePersistenceContextAdapter; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.PersistenceContext; @@ -47,10 +48,12 @@ import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import org.hibernate.query.IllegalMutationQueryException; +import org.hibernate.query.UnknownNamedQueryException; import org.hibernate.query.criteria.JpaCriteriaInsert; import org.hibernate.query.criteria.JpaCriteriaQuery; import org.hibernate.query.criteria.JpaRoot; import org.hibernate.query.hql.spi.SqmQueryImplementor; +import org.hibernate.query.named.NamedResultSetMappingMemento; import org.hibernate.query.spi.HqlInterpretation; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.query.sql.spi.NativeQueryImplementor; @@ -64,7 +67,6 @@ import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.common.AffectedEntities; import org.hibernate.reactive.common.ResultSetMapping; -import org.hibernate.engine.internal.ReactivePersistenceContextAdapter; import org.hibernate.reactive.id.ReactiveIdentifierGenerator; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.persister.collection.impl.ReactiveCollectionPersister; @@ -905,7 +907,7 @@ public ReactiveNativeQueryImplementor createReactiveNativeQuery(String sq delayedAfterCompletion(); try { - final ReactiveNativeQueryImpl query = new ReactiveNativeQueryImpl<>( sqlString, this ); + final ReactiveNativeQueryImpl query = new ReactiveNativeQueryImpl<>( sqlString, null, this); if ( isEmpty( query.getComment() ) ) { query.setComment( "dynamic native SQL query" ); } @@ -956,9 +958,19 @@ public ReactiveNativeQuery createReactiveNativeQuery(String sqlString, St delayedAfterCompletion(); try { - return isNotEmpty( resultSetMappingName ) - ? new ReactiveNativeQueryImpl<>( sqlString, getResultSetMappingMemento( resultSetMappingName ), this ) - : new ReactiveNativeQueryImpl<>( sqlString, this ); + if ( isNotEmpty( resultSetMappingName ) ) { + final NamedResultSetMappingMemento resultSetMappingMemento = getFactory().getQueryEngine() + .getNamedObjectRepository() + .getResultSetMappingMemento( resultSetMappingName ); + + if ( resultSetMappingMemento == null ) { + throw new HibernateException( "Could not resolve specified result-set mapping name : " + resultSetMappingName ); + } + return new ReactiveNativeQueryImpl<>( sqlString, resultSetMappingMemento, null, this ); + } + else { + return new ReactiveNativeQueryImpl<>( sqlString, this ); + } //TODO: why no applyQuerySettingsAndHints( query ); ??? } catch (RuntimeException he) { @@ -1069,11 +1081,64 @@ public ReactiveMutationQuery createNamedReactiveMutationQuery(String quer ); } + @Override + public ReactiveQueryImplementor createReactiveNamedQuery(String queryName) { + checksBeforeQueryCreation(); + try { + return (ReactiveQueryImplementor) buildNamedQuery( + queryName, + this::createSqmQueryImplementor, + this::createNativeQueryImplementor + ); + } + catch (RuntimeException e) { + throw convertNamedQueryException( e ); + } + } + + @Override + public ReactiveQueryImplementor createReactiveNamedQuery(String queryName, Class resultType) { + checksBeforeQueryCreation(); + if ( resultType == null ) { + throw new IllegalArgumentException( "Result class is null" ); + } + try { + return (ReactiveQueryImplementor) buildNamedQuery( + queryName, + memento -> createSqmQueryImplementor( resultType, memento ), + memento -> createNativeQueryImplementor( resultType, memento ) + ); + } + catch (RuntimeException e) { + throw convertNamedQueryException( e ); + } + } + + private RuntimeException convertNamedQueryException(RuntimeException e) { + if ( e instanceof UnknownNamedQueryException ) { + // JPA expects this to mark the transaction for rollback only + getTransactionCoordinator().getTransactionDriverControl().markRollbackOnly(); + // it also expects an IllegalArgumentException, so wrap UnknownNamedQueryException + return new IllegalArgumentException( e.getMessage(), e ); + } + else if ( e instanceof IllegalArgumentException ) { + return e; + } + else { + return getExceptionConverter().convert( e ); + } + } + @Override public ReactiveSelectionQuery createNamedReactiveSelectionQuery(String queryName, Class expectedResultType) { return (ReactiveSelectionQuery) createNamedSelectionQuery( queryName , expectedResultType ); } + private void checksBeforeQueryCreation() { + checkOpen(); + checkTransactionSynchStatus(); + } + @Override public ReactiveMutationQuery createNativeReactiveMutationQuery(String sqlString) { final ReactiveNativeQueryImplementor query = createReactiveNativeQuery( sqlString ); @@ -1083,11 +1148,6 @@ public ReactiveMutationQuery createNativeReactiveMutationQuery(String sql return query; } - @Override - public ReactiveQueryImplementor createReactiveNamedQuery(String queryName, Class resultType) { - return (ReactiveQueryImplementor) buildNamedQuery( queryName, resultType ); - } - @Override public ReactiveNativeQuery createReactiveNativeQuery(String queryString, AffectedEntities affectedEntities) { checkOpen(); @@ -1131,11 +1191,13 @@ public ReactiveNativeQueryImpl createReactiveNativeQuery(String queryStri pulseTransactionCoordinator(); delayedAfterCompletion(); + if ( resultSetMapping == null ) { + throw new IllegalArgumentException( "Result set mapping was not specified" ); + } + try { - // Same approach as AbstractSharedSessionContract#createNativeQuery(String, String) - final ReactiveNativeQueryImpl nativeQuery = resultSetMapping != null - ? new ReactiveNativeQueryImpl<>( queryString, getResultSetMappingMemento( resultSetMapping.getName() ), this ) - : new ReactiveNativeQueryImpl<>( queryString, this ); + final NamedResultSetMappingMemento memento = getResultSetMappingMemento( resultSetMapping.getName() ); + final ReactiveNativeQueryImpl nativeQuery = new ReactiveNativeQueryImpl<>( queryString, memento, null, this ); applyQuerySettingsAndHints( nativeQuery ); return nativeQuery; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java index 78ce3c891..150c2ec96 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java @@ -5,16 +5,11 @@ */ package org.hibernate.reactive.stage.impl; -import jakarta.persistence.CacheRetrieveMode; -import jakarta.persistence.CacheStoreMode; -import jakarta.persistence.EntityGraph; -import jakarta.persistence.FlushModeType; -import jakarta.persistence.LockModeType; -import jakarta.persistence.PersistenceException; -import jakarta.persistence.criteria.CriteriaDelete; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.CriteriaUpdate; -import jakarta.persistence.metamodel.Attribute; +import java.lang.invoke.MethodHandles; +import java.util.List; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + import org.hibernate.CacheMode; import org.hibernate.Filter; import org.hibernate.FlushMode; @@ -39,10 +34,16 @@ import org.hibernate.reactive.stage.Stage.Query; import org.hibernate.reactive.stage.Stage.SelectionQuery; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.concurrent.CompletionStage; -import java.util.function.Function; +import jakarta.persistence.CacheRetrieveMode; +import jakarta.persistence.CacheStoreMode; +import jakarta.persistence.EntityGraph; +import jakarta.persistence.FlushModeType; +import jakarta.persistence.LockModeType; +import jakarta.persistence.PersistenceException; +import jakarta.persistence.criteria.CriteriaDelete; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.CriteriaUpdate; +import jakarta.persistence.metamodel.Attribute; import static org.hibernate.reactive.util.impl.CompletionStages.applyToAll; import static org.hibernate.reactive.util.impl.CompletionStages.returnOrRethrow; @@ -556,7 +557,7 @@ public MutationQuery createQuery(CriteriaDelete criteriaDelete) { @Override public Query createNamedQuery(String queryName) { - return new StageQueryImpl<>( delegate.createReactiveNamedQuery( queryName, null ) ); + return new StageQueryImpl<>( delegate.createReactiveNamedQuery( queryName ) ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java index aadd76285..c48a7eab8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java @@ -326,7 +326,7 @@ public Query createNativeQuery(String queryString) { @Override public Query createNamedQuery(String queryName) { - return new StageQueryImpl<>( delegate.createReactiveNamedQuery( queryName, null ) ); + return new StageQueryImpl<>( delegate.createReactiveNamedQuery( queryName ) ); } @Override From f7304add53e2372038e8cf7b71bfbf3b87840d09 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Mon, 17 Mar 2025 16:28:17 +0100 Subject: [PATCH 18/38] [#2155] Move ReactiveStatelessWithBatchTest to org.hibernate.reactive --- .../{ => reactive}/ReactiveStatelessWithBatchTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename hibernate-reactive-core/src/test/java/org/hibernate/{ => reactive}/ReactiveStatelessWithBatchTest.java (99%) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessWithBatchTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessWithBatchTest.java similarity index 99% rename from hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessWithBatchTest.java rename to hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessWithBatchTest.java index 563fad40b..a30cc1364 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/ReactiveStatelessWithBatchTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessWithBatchTest.java @@ -3,11 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 * Copyright: Red Hat Inc. and Hibernate Authors */ -package org.hibernate; +package org.hibernate.reactive; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.Configuration; -import org.hibernate.reactive.BaseReactiveTest; import org.hibernate.reactive.annotations.EnabledFor; import org.hibernate.reactive.testing.SqlStatementTracker; From 1b52966e4767482adc970a16fc2821e985a1ebfb Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 21 Mar 2025 13:57:12 +0100 Subject: [PATCH 19/38] [#2152] Remove experimental table support when starting CockroachDB It's not needed anymore --- .../reactive/containers/CockroachDBDatabase.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java index ae4d34d39..60dd1b997 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java @@ -51,7 +51,6 @@ private String address() { // Calling start() will start the container (if not already started) // It is required to call start() before obtaining the JDBC URL because it will contain a randomized port cockroachDb.start(); - enableTemporaryTables(); return disableSslMode( cockroachDb.getJdbcUrl() ); } @@ -62,13 +61,6 @@ private static String disableSslMode(String url) { return url + "?sslmode=disable"; } - /** - * We need temporary tables when updating entities in a hierarchy - */ - private static void enableTemporaryTables() { - runSql( "SET CLUSTER SETTING sql.defaults.experimental_temporary_tables.enabled = 'true';" ); - } - private static void runSql(String command) { Container.ExecResult execResult; try { From 8b574920acdf5df787d75d8d65eeaf0687d6f7b3 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 26 Mar 2025 22:10:12 +0100 Subject: [PATCH 20/38] eliminate dependency on useless helper class --- .../StandardReactiveSelectExecutor.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java index 43b7c1554..379a83060 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java @@ -12,6 +12,7 @@ import java.util.concurrent.TimeUnit; import org.hibernate.CacheMode; +import org.hibernate.SharedSessionContract; import org.hibernate.cache.spi.QueryKey; import org.hibernate.cache.spi.QueryResultsCache; import org.hibernate.engine.spi.PersistenceContext; @@ -20,6 +21,7 @@ import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.query.TupleTransformer; import org.hibernate.engine.internal.ReactivePersistenceContextAdapter; +import org.hibernate.query.spi.QueryOptions; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.exec.spi.ReactiveSelectExecutor; import org.hibernate.reactive.sql.exec.spi.ReactiveValuesResultSet; @@ -31,7 +33,6 @@ import org.hibernate.reactive.sql.results.spi.ReactiveRowReader; import org.hibernate.reactive.sql.results.spi.ReactiveValuesMappingProducer; import org.hibernate.sql.exec.SqlExecLogger; -import org.hibernate.sql.exec.internal.JdbcExecHelper; import org.hibernate.sql.exec.internal.StandardStatementCreator; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; @@ -50,6 +51,8 @@ import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.spi.TypeConfiguration; +import static org.hibernate.internal.util.NullnessHelper.coalesceSuppliedValues; + /** * @see org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl */ @@ -286,13 +289,13 @@ public CompletionStage resolveJdbcValuesSource( final SessionFactoryImplementor factory = session.getFactory(); final boolean queryCacheEnabled = factory.getSessionFactoryOptions().isQueryCacheEnabled(); - final List cachedResults; - final CacheMode cacheMode = JdbcExecHelper.resolveCacheMode( executionContext ); + final CacheMode cacheMode = resolveCacheMode( executionContext ); final boolean cacheable = queryCacheEnabled && canBeCached && executionContext.getQueryOptions().isResultCachingEnabled() == Boolean.TRUE; - final QueryKey queryResultsCacheKey; + final QueryKey queryResultsCacheKey; + final List cachedResults; if ( cacheable && cacheMode.isGetEnabled() ) { SqlExecLogger.SQL_EXEC_LOGGER.debugf( "Reading Query result cache data per CacheMode#isGetEnabled [%s]", cacheMode.name() ); final Set querySpaces = jdbcSelect.getAffectedTableNames(); @@ -419,6 +422,16 @@ public CompletionStage resolveJdbcValuesSource( } } + private static CacheMode resolveCacheMode(ExecutionContext executionContext) { + final QueryOptions queryOptions = executionContext.getQueryOptions(); + final SharedSessionContract session = executionContext.getSession(); + return coalesceSuppliedValues( + () -> queryOptions == null ? null : queryOptions.getCacheMode(), + session::getCacheMode, + () -> CacheMode.NORMAL + ); + } + /** * see {@link CachedJdbcValuesMetadata} */ From aea92393ac4ed165fd953c8df275e975d3dad91d Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 26 Mar 2025 23:25:44 +0100 Subject: [PATCH 21/38] cleanups to StandardReactiveSelectExecutor --- .../StandardReactiveSelectExecutor.java | 112 ++++++++---------- 1 file changed, 50 insertions(+), 62 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java index 379a83060..64c5b3af3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java @@ -15,10 +15,10 @@ import org.hibernate.SharedSessionContract; import org.hibernate.cache.spi.QueryKey; import org.hibernate.cache.spi.QueryResultsCache; +import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.query.TupleTransformer; import org.hibernate.engine.internal.ReactivePersistenceContextAdapter; import org.hibernate.query.spi.QueryOptions; @@ -43,6 +43,7 @@ import org.hibernate.sql.results.internal.RowTransformerTupleTransformerAdapter; import org.hibernate.sql.results.jdbc.internal.CachedJdbcValuesMetadata; import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; import org.hibernate.sql.results.spi.RowTransformer; @@ -52,6 +53,7 @@ import org.hibernate.type.spi.TypeConfiguration; import static org.hibernate.internal.util.NullnessHelper.coalesceSuppliedValues; +import static org.hibernate.internal.util.collections.ArrayHelper.indexOf; /** * @see org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl @@ -148,8 +150,8 @@ public CompletionStage executeQuery( ReactiveResultsConsumer resultsConsumer) { final PersistenceContext persistenceContext = executionContext.getSession().getPersistenceContext(); - boolean defaultReadOnlyOrig = persistenceContext.isDefaultReadOnly(); - Boolean readOnly = executionContext.getQueryOptions().isReadOnly(); + final boolean defaultReadOnlyOrig = persistenceContext.isDefaultReadOnly(); + final Boolean readOnly = executionContext.getQueryOptions().isReadOnly(); if ( readOnly != null ) { // The read-only/modifiable mode for the query was explicitly set. // Temporarily set the default read-only/modifiable setting to the query's setting. @@ -179,7 +181,8 @@ private CompletionStage doExecuteQuery( JdbcSelectExecutor.StatementCreator statementCreator, ReactiveResultsConsumer resultsConsumer) { - final ReactiveDeferredResultSetAccess deferredResultSetAccess = new ReactiveDeferredResultSetAccess( jdbcSelect, jdbcParameterBindings, executionContext, statementCreator, resultCountEstimate ); + final ReactiveDeferredResultSetAccess deferredResultSetAccess = + new ReactiveDeferredResultSetAccess( jdbcSelect, jdbcParameterBindings, executionContext, statementCreator, resultCountEstimate ); return resolveJdbcValuesSource( executionContext.getQueryIdentifier( deferredResultSetAccess.getFinalSql() ), @@ -216,10 +219,8 @@ public boolean shouldReturnProxies() { } }; - final JdbcValuesSourceProcessingStateStandardImpl valuesProcessingState = new JdbcValuesSourceProcessingStateStandardImpl( - executionContext, - processingOptions - ); + final JdbcValuesSourceProcessingStateStandardImpl valuesProcessingState = + new JdbcValuesSourceProcessingStateStandardImpl( executionContext, processingOptions ); final ReactiveRowReader rowReader = ReactiveResultsHelper.createRowReader( executionContext.getSession().getSessionFactory(), @@ -257,26 +258,25 @@ private static RowTransformer rowTransformer( ExecutionContext executionContext, RowTransformer transformer, ReactiveValuesResultSet jdbcValues) { - RowTransformer rowTransformer = transformer; - if ( rowTransformer == null ) { - @SuppressWarnings("unchecked") final TupleTransformer tupleTransformer = (TupleTransformer) executionContext - .getQueryOptions() - .getTupleTransformer(); - + if ( transformer == null ) { + @SuppressWarnings("unchecked") + final TupleTransformer tupleTransformer = + (TupleTransformer) executionContext.getQueryOptions().getTupleTransformer(); if ( tupleTransformer == null ) { - rowTransformer = RowTransformerStandardImpl.instance(); + return RowTransformerStandardImpl.instance(); } else { - final List> domainResults = jdbcValues.getValuesMapping() - .getDomainResults(); + final List> domainResults = jdbcValues.getValuesMapping().getDomainResults(); final String[] aliases = new String[domainResults.size()]; for ( int i = 0; i < domainResults.size(); i++ ) { aliases[i] = domainResults.get( i ).getResultVariable(); } - rowTransformer = new RowTransformerTupleTransformerAdapter<>( aliases, tupleTransformer ); + return new RowTransformerTupleTransformerAdapter<>( aliases, tupleTransformer ); } } - return rowTransformer; + else { + return transformer; + } } public CompletionStage resolveJdbcValuesSource( @@ -290,9 +290,10 @@ public CompletionStage resolveJdbcValuesSource( final boolean queryCacheEnabled = factory.getSessionFactoryOptions().isQueryCacheEnabled(); final CacheMode cacheMode = resolveCacheMode( executionContext ); + final QueryOptions queryOptions = executionContext.getQueryOptions(); final boolean cacheable = queryCacheEnabled && canBeCached - && executionContext.getQueryOptions().isResultCachingEnabled() == Boolean.TRUE; + && queryOptions.isResultCachingEnabled() == Boolean.TRUE; final QueryKey queryResultsCacheKey; final List cachedResults; @@ -307,10 +308,10 @@ public CompletionStage resolveJdbcValuesSource( } final QueryResultsCache queryCache = factory.getCache() - .getQueryResultsCache( executionContext.getQueryOptions().getResultCacheRegionName() ); + .getQueryResultsCache( queryOptions.getResultCacheRegionName() ); queryResultsCacheKey = QueryKey - .from( jdbcSelect.getSqlString(), executionContext.getQueryOptions().getLimit(), executionContext.getQueryParameterBindings(), session ); + .from( jdbcSelect.getSqlString(), queryOptions.getLimit(), executionContext.getQueryParameterBindings(), session ); cachedResults = queryCache.get( // todo (6.0) : QueryCache#get takes the `queryResultsCacheKey` see tat discussion above @@ -345,7 +346,7 @@ public CompletionStage resolveJdbcValuesSource( if ( cacheable && cacheMode.isPutEnabled() ) { queryResultsCacheKey = QueryKey.from( jdbcSelect.getSqlString(), - executionContext.getQueryOptions().getLimit(), + queryOptions.getLimit(), executionContext.getQueryParameterBindings(), session ); @@ -355,16 +356,18 @@ public CompletionStage resolveJdbcValuesSource( } } - ReactiveValuesMappingProducer mappingProducer = (ReactiveValuesMappingProducer) jdbcSelect.getJdbcValuesMappingProducer(); + final LoadQueryInfluencers loadQueryInfluencers = session.getLoadQueryInfluencers(); + + final ReactiveValuesMappingProducer mappingProducer = + (ReactiveValuesMappingProducer) jdbcSelect.getJdbcValuesMappingProducer(); if ( cachedResults == null ) { if ( queryResultsCacheKey == null ) { - return mappingProducer - .reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ) + return mappingProducer.reactiveResolve( resultSetAccess, loadQueryInfluencers, factory ) .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( resultSetAccess, null, queryIdentifier, - executionContext.getQueryOptions(), + queryOptions, resultSetAccess.usesFollowOnLocking(), jdbcValuesMapping, null, @@ -374,13 +377,12 @@ public CompletionStage resolveJdbcValuesSource( else { // If we need to put the values into the cache, we need to be able to capture the JdbcValuesMetadata final CapturingJdbcValuesMetadata capturingMetadata = new CapturingJdbcValuesMetadata( resultSetAccess ); - return mappingProducer - .reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ) + return mappingProducer.reactiveResolve( resultSetAccess, loadQueryInfluencers, factory ) .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( resultSetAccess, queryResultsCacheKey, queryIdentifier, - executionContext.getQueryOptions(), + queryOptions, resultSetAccess.usesFollowOnLocking(), jdbcValuesMapping, capturingMetadata.resolveMetadataForCache(), @@ -392,34 +394,22 @@ public CompletionStage resolveJdbcValuesSource( // TODO: Implements JdbcValuesCacheHit for reactive, see JdbcSelectExecutorStandardImpl#resolveJdbcValuesSource // If we need to put the values into the cache, we need to be able to capture the JdbcValuesMetadata final CapturingJdbcValuesMetadata capturingMetadata = new CapturingJdbcValuesMetadata( resultSetAccess ); - if ( cachedResults.isEmpty() || !( cachedResults.get( 0 ) instanceof JdbcValuesMetadata ) ) { - return mappingProducer.reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ) - .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( - resultSetAccess, - queryResultsCacheKey, - queryIdentifier, - executionContext.getQueryOptions(), - resultSetAccess.usesFollowOnLocking(), - jdbcValuesMapping, - capturingMetadata.resolveMetadataForCache(), - executionContext - ) ); - } - else { - return mappingProducer - .reactiveResolve( (JdbcValuesMetadata) cachedResults.get( 0 ), session.getLoadQueryInfluencers(), factory ) - .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( - resultSetAccess, - queryResultsCacheKey, - queryIdentifier, - executionContext.getQueryOptions(), - resultSetAccess.usesFollowOnLocking(), - jdbcValuesMapping, - capturingMetadata.resolveMetadataForCache(), - executionContext - ) ); - } - } + final CompletionStage stage = + !cachedResults.isEmpty() + && cachedResults.get(0) instanceof JdbcValuesMetadata jdbcValuesMetadata + ? mappingProducer.reactiveResolve( jdbcValuesMetadata, loadQueryInfluencers, factory ) + : mappingProducer.reactiveResolve( resultSetAccess, loadQueryInfluencers, factory ); + return stage.thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( + resultSetAccess, + queryResultsCacheKey, + queryIdentifier, + queryOptions, + resultSetAccess.usesFollowOnLocking(), + jdbcValuesMapping, + capturingMetadata.resolveMetadataForCache(), + executionContext + ) ); + } } private static CacheMode resolveCacheMode(ExecutionContext executionContext) { @@ -468,7 +458,7 @@ public int resolveColumnPosition(String columnName) { position = resultSetAccess.resolveColumnPosition( columnName ); columnNames[position - 1] = columnName; } - else if ( ( position = ArrayHelper.indexOf( columnNames, columnName ) + 1 ) == 0 ) { + else if ( ( position = indexOf( columnNames, columnName ) + 1 ) == 0 ) { position = resultSetAccess.resolveColumnPosition( columnName ); columnNames[position - 1] = columnName; } @@ -556,9 +546,7 @@ public void end(JdbcOperationQuerySelect jdbcSelect, T result) { } private int getResultSize(T result) { - return result instanceof Collection - ? ( (Collection) result ).size() - : -1; + return result instanceof Collection collection ? collection.size() : -1; } } } From c3cf2f6fc09d9031d44ae7f856c4146431ca4904 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 28 Mar 2025 17:45:26 +0100 Subject: [PATCH 22/38] use @linkplain where appropriate --- .../reactive/pool/impl/ReactiveConnectionPoolInitiator.java | 6 +++--- .../pool/impl/SqlClientPoolConfigurationInitiator.java | 2 +- .../provider/service/NoJdbcConnectionProviderInitiator.java | 4 ++-- .../provider/service/NoJdbcEnvironmentInitiator.java | 4 ++-- .../NoJdbcMultiTenantConnectionProviderInitiator.java | 4 ++-- .../reactive/provider/service/NoJtaPlatformInitiator.java | 3 ++- .../provider/service/ReactiveMarkerServiceInitiator.java | 3 ++- .../service/ReactivePersisterClassResolverInitiator.java | 5 +++-- .../service/ReactiveSessionFactoryBuilderInitiator.java | 6 +++--- 9 files changed, 20 insertions(+), 17 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/ReactiveConnectionPoolInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/ReactiveConnectionPoolInitiator.java index 15c517acc..cf57e94f0 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/ReactiveConnectionPoolInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/ReactiveConnectionPoolInitiator.java @@ -19,10 +19,10 @@ import java.util.Map; /** - * A Hibernate {@link StandardServiceInitiator service initiator} that + * A Hibernate {@linkplain StandardServiceInitiator service initiator} that * integrates our {@link ReactiveConnectionPool}. By default, the pool - * implementation is {@link DefaultSqlClientPool}. A custom implementation may - * be specified via {@link Settings#SQL_CLIENT_POOL}. + * implementation is {@link DefaultSqlClientPool}. A custom implementation + * may be specified via {@link Settings#SQL_CLIENT_POOL}. * * @see ReactiveConnectionPool * @see DefaultSqlClientPool diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientPoolConfigurationInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientPoolConfigurationInitiator.java index e23656d21..1c12cc8b6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientPoolConfigurationInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientPoolConfigurationInitiator.java @@ -16,7 +16,7 @@ import java.util.Map; /** - * A Hibernate {@link StandardServiceInitiator service initiator} that + * A Hibernate {@linkplain StandardServiceInitiator service initiator} that * allows the user to define their own {@link SqlClientPoolConfiguration} * strategy. */ diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProviderInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProviderInitiator.java index 6ecdffe2f..a57f72b16 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProviderInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProviderInitiator.java @@ -14,8 +14,8 @@ import java.util.Map; /** - * A Hibernate {@link StandardServiceInitiator service initiator} that - * wraps the Hibernate {@link ConnectionProvider} in an instance of + * A Hibernate {@linkplain StandardServiceInitiator service initiator} + * that wraps the Hibernate {@link ConnectionProvider} in an instance of * {@link NoJdbcConnectionProvider}. * * @author Gavin King diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcEnvironmentInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcEnvironmentInitiator.java index 5580c99fa..953285dd4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcEnvironmentInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcEnvironmentInitiator.java @@ -28,8 +28,8 @@ import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; /** - * A Hibernate {@link StandardServiceInitiator service initiator} that - * provides an implementation of {@link JdbcEnvironment} that infers + * A Hibernate {@linkplain StandardServiceInitiator service initiator} + * that provides an implementation of {@link JdbcEnvironment} that infers * the Hibernate {@link org.hibernate.dialect.Dialect} from the JDBC URL. */ public class NoJdbcEnvironmentInitiator implements StandardServiceInitiator { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcMultiTenantConnectionProviderInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcMultiTenantConnectionProviderInitiator.java index 60a6cf599..50e614e2e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcMultiTenantConnectionProviderInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcMultiTenantConnectionProviderInitiator.java @@ -13,8 +13,8 @@ import org.hibernate.service.spi.ServiceRegistryImplementor; /** - * A Hibernate {@link StandardServiceInitiator service initiator} for - * {@link NoJdbcMultiTenantConnectionProvider}. + * A Hibernate {@linkplain StandardServiceInitiator service initiator} + * for {@link NoJdbcMultiTenantConnectionProvider}. * * @author Gavin King */ diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJtaPlatformInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJtaPlatformInitiator.java index 30add8dae..8fd12fa10 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJtaPlatformInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJtaPlatformInitiator.java @@ -13,7 +13,8 @@ import java.util.Map; /** - * A Hibernate {@link StandardServiceInitiator service initiator} for the non-configured form of JTA platform. + * A Hibernate {@linkplain StandardServiceInitiator service initiator} + * for the non-configured form of JTA platform. * * @see NoJtaPlatform */ diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveMarkerServiceInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveMarkerServiceInitiator.java index e7e92c270..46b4549d8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveMarkerServiceInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveMarkerServiceInitiator.java @@ -11,7 +11,8 @@ import org.hibernate.service.spi.ServiceRegistryImplementor; /** - * A Hibernate {@link StandardServiceInitiator service initiator} for {@link ReactiveMarkerService}. + * A Hibernate {@linkplain StandardServiceInitiator service initiator} for + * {@link ReactiveMarkerService}. */ public final class ReactiveMarkerServiceInitiator implements StandardServiceInitiator { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactivePersisterClassResolverInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactivePersisterClassResolverInitiator.java index 8e9670860..8edd7b4b1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactivePersisterClassResolverInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactivePersisterClassResolverInitiator.java @@ -12,8 +12,9 @@ import org.hibernate.service.spi.ServiceRegistryImplementor; /** - * A Hibernate {@link StandardServiceInitiator service initiator} that creates a {@link ReactivePersisterClassResolver} to register - * the persisters Hibernate Reactive needs. + * A Hibernate {@linkplain StandardServiceInitiator service initiator} + * that creates a {@link ReactivePersisterClassResolver} to register the + * persisters needed by Hibernate Reactive. */ public class ReactivePersisterClassResolverInitiator implements StandardServiceInitiator { public static final ReactivePersisterClassResolverInitiator INSTANCE = new ReactivePersisterClassResolverInitiator(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveSessionFactoryBuilderInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveSessionFactoryBuilderInitiator.java index 768c36ec6..14805057f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveSessionFactoryBuilderInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveSessionFactoryBuilderInitiator.java @@ -12,9 +12,9 @@ import org.hibernate.service.spi.ServiceRegistryImplementor; /** - * A Hibernate {@link StandardServiceInitiator service initiator} that - * wraps the Hibernate {@link org.hibernate.engine.jdbc.connections.spi.ConnectionProvider} in an instance of - * {@link NoJdbcConnectionProvider}. + * A Hibernate {@linkplain StandardServiceInitiator service initiator} that wraps + * the Hibernate {@link org.hibernate.engine.jdbc.connections.spi.ConnectionProvider} + * in an instance of {@link NoJdbcConnectionProvider}. */ public class ReactiveSessionFactoryBuilderInitiator implements StandardServiceInitiator { From 66226f1a7be82e04ac019dc6be8dac717b0b39af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 03:16:41 +0000 Subject: [PATCH 23/38] Bump the workflow-actions group with 2 updates Bumps the workflow-actions group with 2 updates: [actions/cache](https://github.com/actions/cache) and [actions/upload-artifact](https://github.com/actions/upload-artifact). Updates `actions/cache` from 4.2.2 to 4.2.3 - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/d4323d4df104b026a6aa633fdb11d772146be0bf...5a3ec84eff668545956fd18022155c47e93e2684) Updates `actions/upload-artifact` from 4.6.1 to 4.6.2 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1...ea165f8d65b6e75b540449e92b4886f43607fa02) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 4.2.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: workflow-actions - dependency-name: actions/upload-artifact dependency-version: 4.6.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: workflow-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eec52d729..c85d9281d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -90,7 +90,7 @@ jobs: echo "::set-output name=yearmonth::$(/bin/date -u "+%Y-%m")" shell: bash - name: Cache Gradle downloads - uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 id: cache-gradle with: path: | @@ -116,7 +116,7 @@ jobs: - name: Run examples in '${{ matrix.example }}' on ${{ matrix.db }} run: ./gradlew :${{ matrix.example }}:runAllExamplesOn${{ matrix.db }} - name: Upload reports (if build failed) - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: failure() with: name: reports-examples-${{ matrix.db }} @@ -139,7 +139,7 @@ jobs: echo "::set-output name=yearmonth::$(/bin/date -u "+%Y-%m")" shell: bash - name: Cache Gradle downloads - uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 id: cache-gradle with: path: | @@ -166,7 +166,7 @@ jobs: - name: Build and Test with ${{ matrix.db }} run: ./gradlew build -PshowStandardOutput -Pdocker -Pdb=${{ matrix.db }} - name: Upload reports (if build failed) - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: failure() with: name: reports-db-${{ matrix.db }} @@ -216,7 +216,7 @@ jobs: echo "buildtool-cache-key=${ROOT_CACHE_KEY}-${CURRENT_MONTH}-${CURRENT_BRANCH}-${CURRENT_DAY}" >> $GITHUB_OUTPUT - name: Cache Maven/Gradle Dependency/Dist Caches id: cache-maven - uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 # if it's not a pull request, we restore and save the cache if: github.event_name != 'pull_request' with: @@ -233,7 +233,7 @@ jobs: ${{ steps.cache-key.outputs.buildtool-monthly-branch-cache-key }}- ${{ steps.cache-key.outputs.buildtool-monthly-cache-key }}- - name: Restore Maven/Gradle Dependency/Dist Caches - uses: actions/cache/restore@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 # if it's a pull request, we restore the cache, but we don't save it if: github.event_name == 'pull_request' with: @@ -291,7 +291,7 @@ jobs: -Porg.gradle.java.installations.paths=${{ steps.mainjdk-exportpath.outputs.path }},${{ steps.testjdk-exportpath.outputs.path }} \ ${{ matrix.java.jvm_args && '-Ptest.jdk.launcher.args=' }}${{ matrix.java.jvm_args }} - name: Upload reports (if build failed) - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: failure() with: name: reports-java${{ matrix.java.name }} From 147604a354c214252d5095b92c3d7b4f7e421934 Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Wed, 2 Apr 2025 15:10:32 +0200 Subject: [PATCH 24/38] [#2174] Prepare for Vert.x 5 upgrade in Vert.x Context Closes #2174 Local context data management methods are deprecated and will be removed in Vert.x 5. This change will make it easier to upgrade to Vert.x 5 (should be just a matter of changing imports) --- .../reactive/context/impl/VertxContext.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java index 4448c9b6c..fef172e66 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java @@ -36,10 +36,10 @@ public void injectServices(ServiceRegistryImplementor serviceRegistry) { @Override public void put(Key key, T instance) { - final io.vertx.core.Context context = Vertx.currentContext(); + final ContextInternal context = ContextInternal.current(); if ( context != null ) { if ( trace ) LOG.tracef( "Putting key,value in context: [%1$s, %2$s]", key, instance ); - context.putLocal( key, instance ); + context.localContextData().put( key, instance ); } else { if ( trace ) LOG.tracef( "Context is null for key,value: [%1$s, %2$s]", key, instance ); @@ -49,9 +49,10 @@ public void put(Key key, T instance) { @Override public T get(Key key) { - final io.vertx.core.Context context = Vertx.currentContext(); + final ContextInternal context = ContextInternal.current(); if ( context != null ) { - T local = context.getLocal( key ); + @SuppressWarnings("unchecked") + T local = (T) context.localContextData().get( key ); if ( trace ) LOG.tracef( "Getting value %2$s from context for key %1$s", key, local ); return local; } @@ -63,9 +64,9 @@ public T get(Key key) { @Override public void remove(Key key) { - final io.vertx.core.Context context = Vertx.currentContext(); + final ContextInternal context = ContextInternal.current(); if ( context != null ) { - boolean removed = context.removeLocal( key ); + boolean removed = context.localContextData().remove( key ) != null; if ( trace ) LOG.tracef( "Key %s removed from context: %s", key, removed ); } else { From 7469034f7cd5c74bf0582ae27c5dc39d8b8411ea Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 6 Mar 2025 16:39:44 +0100 Subject: [PATCH 25/38] [#1867] Error when inserting in batch with joined table inheritance --- .../ReactiveMutationExecutorStandard.java | 90 +++++++++++++++++-- 1 file changed, 83 insertions(+), 7 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorStandard.java index 4f1d62325..0a09ff3a1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorStandard.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorStandard.java @@ -5,10 +5,6 @@ */ package org.hibernate.reactive.engine.jdbc.mutation.internal; -import java.lang.invoke.MethodHandles; -import java.sql.SQLException; -import java.util.concurrent.CompletionStage; - import org.hibernate.engine.jdbc.batch.spi.Batch; import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; import org.hibernate.engine.jdbc.mutation.OperationResultChecker; @@ -25,6 +21,7 @@ import org.hibernate.persister.entity.mutation.EntityTableMapping; import org.hibernate.reactive.adaptor.impl.PrepareStatementDetailsAdaptor; import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor; +import org.hibernate.reactive.engine.jdbc.ResultsCheckerUtil; import org.hibernate.reactive.engine.jdbc.env.internal.ReactiveMutationExecutor; import org.hibernate.reactive.generator.values.ReactiveGeneratedValuesMutationDelegate; import org.hibernate.reactive.logging.impl.Log; @@ -37,9 +34,16 @@ import org.hibernate.sql.model.TableMapping; import org.hibernate.sql.model.ValuesAnalysis; +import java.lang.invoke.MethodHandles; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletionStage; + import static org.hibernate.engine.jdbc.mutation.internal.ModelMutationHelper.checkResults; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.loop; import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER; @@ -73,10 +77,64 @@ private ReactiveConnection connection(SharedSessionContractImplementor session) @Override public CompletionStage performReactiveBatchedOperations( ValuesAnalysis valuesAnalysis, - TableInclusionChecker inclusionChecker, OperationResultChecker resultChecker, + TableInclusionChecker inclusionChecker, + OperationResultChecker resultChecker, SharedSessionContractImplementor session) { - return ReactiveMutationExecutor.super - .performReactiveBatchedOperations( valuesAnalysis, inclusionChecker, resultChecker, session); + final PreparedStatementGroup batchedMutationOperationGroup = getBatchedPreparedStatementGroup(); + if ( batchedMutationOperationGroup != null ) { + final List preparedStatementDetailsList = new ArrayList<>( + batchedMutationOperationGroup.getNumberOfStatements() ); + batchedMutationOperationGroup.forEachStatement( (tableName, statementDetails) -> preparedStatementDetailsList + .add( statementDetails ) ); + return loop( preparedStatementDetailsList, statementDetails -> { + if ( statementDetails == null ) { + return voidFuture(); + } + final JdbcValueBindings valueBindings = getJdbcValueBindings(); + final TableMapping tableDetails = statementDetails.getMutatingTableDetails(); + if ( inclusionChecker != null && !inclusionChecker.include( tableDetails ) ) { + if ( MODEL_MUTATION_LOGGER.isTraceEnabled() ) { + MODEL_MUTATION_LOGGER.tracef( + "Skipping execution of secondary insert : %s", + tableDetails.getTableName() + ); + } + return voidFuture(); + } + + // If we get here the statement is needed - make sure it is resolved + final Object[] paramValues = PreparedStatementAdaptor.bind( statement -> { + PreparedStatementDetails details = new PrepareStatementDetailsAdaptor( + statementDetails, + statement, + session.getJdbcServices() + ); + valueBindings.beforeStatement( details ); + } ); + + final ReactiveConnection reactiveConnection = ( (ReactiveConnectionSupplier) session ).getReactiveConnection(); + final String sql = statementDetails.getSqlString(); + return reactiveConnection.update( + sql, + paramValues, + true, + (rowCount, batchPosition, query) -> ResultsCheckerUtil.checkResults( + session, + statementDetails, + resultChecker, + rowCount, + batchPosition + ) + ).whenComplete( (o, throwable) -> { //TODO: is this part really needed? + if ( statementDetails.getStatement() != null ) { + statementDetails.releaseStatement( session ); + } + valueBindings.afterStatement( tableDetails ); + } ); + } + ); + } + return voidFuture(); } @Override @@ -159,6 +217,23 @@ public CompletionStage performReactiveNonBatchedOperations( } } + @Override + public CompletionStage performReactiveSelfExecutingOperations( + ValuesAnalysis valuesAnalysis, + TableInclusionChecker inclusionChecker, + SharedSessionContractImplementor session) { + if ( getSelfExecutingMutations() == null || getSelfExecutingMutations().isEmpty() ) { + return voidFuture(); + } + + return loop( getSelfExecutingMutations(), operation -> { + if ( inclusionChecker.include( operation.getTableDetails() ) ) { + operation.performMutation( getJdbcValueBindings(), valuesAnalysis, session ); + } + return voidFuture(); + }); + } + private class OperationsForEach { private final Object id; @@ -210,6 +285,7 @@ public CompletionStage buildLoop() { return loop; } } + @Override public CompletionStage performReactiveNonBatchedMutation( PreparedStatementDetails statementDetails, From 751ac6d90a2233612cdf8a72894ef6863eb5985e Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 27 Feb 2024 12:59:51 +0100 Subject: [PATCH 26/38] [#1867] Test case --- .../reactive/JoinedInheritanceBatchTest.java | 130 ++++++++ .../ManyToManyWithCompositeIdTest.java | 293 ++++++++++++++++++ 2 files changed, 423 insertions(+) create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedInheritanceBatchTest.java create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToManyWithCompositeIdTest.java diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedInheritanceBatchTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedInheritanceBatchTest.java new file mode 100644 index 000000000..0c49d883e --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedInheritanceBatchTest.java @@ -0,0 +1,130 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletionStage; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +@Timeout(value = 10, timeUnit = MINUTES) +public class JoinedInheritanceBatchTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( ClientA.class, Client.class ); + } + + @Override + protected CompletionStage cleanDb() { + return voidFuture(); + } + + @Test + public void test(VertxTestContext context) { + final ClientA client1 = new ClientA("Client 1", "email@c1", "123456"); + + test( context, getMutinySessionFactory().withTransaction( session -> { + session.setBatchSize( 5 ); + return session.persist( client1 ); + } ) + .chain( () -> getMutinySessionFactory().withTransaction( session -> session + .createQuery( "select c from Client c", Client.class ) + .getResultList() + .invoke( persistedClients -> assertThat( persistedClients ) + .as( "Clients has not bee persisted" ) + .isNotEmpty() ) ) ) + ); + } + + @Entity(name = "Client") + @Table(name = "`Client`") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Client { + + @Id + @SequenceGenerator(name = "seq", sequenceName = "id_seq", allocationSize = 1) + @GeneratedValue(generator = "seq", strategy = GenerationType.SEQUENCE) + private Long id; + + private String name; + + private String email; + + private String phone; + + public Client() { + } + + public Client(String name, String email, String phone) { + this.name = name; + this.email = email; + this.phone = phone; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + } + + @Entity + @Table(name = "`ClientA`") + public static class ClientA extends Client { + + public ClientA() { + } + + public ClientA(String name, String email, String phone) { + super( name, email, phone ); + } + } + +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToManyWithCompositeIdTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToManyWithCompositeIdTest.java new file mode 100644 index 000000000..8c72c38d3 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToManyWithCompositeIdTest.java @@ -0,0 +1,293 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import io.vertx.junit5.Timeout; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import java.util.concurrent.CompletionStage; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +@Timeout(value = 10, timeUnit = MINUTES) +public class ManyToManyWithCompositeIdTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( CarsClients.class, ClientA.class, Client.class, Car.class ); + } + + @Override + protected CompletionStage cleanDb() { + return voidFuture(); + } + + @Test + public void test(VertxTestContext context) { + List clients = new ArrayList<>(); + for ( int i = 0; i < 5; i++ ) { + ClientA client = new ClientA(); + client.setName( "name" + i ); + client.setEmail( "email" + i ); + client.setPhone( "phone" + i ); + clients.add( client ); + } + + List cars = new ArrayList<>(); + for ( int i = 0; i < 2; i++ ) { + Car car = new Car(); + car.setBrand( "brand" + i ); + car.setModel( "model" + i ); + cars.add( car ); + } + + test( context, getMutinySessionFactory() + .withSession( session -> { + session.setBatchSize( 5 ); + return session.persistAll( cars.toArray() ) + .chain( () -> session + .persistAll( clients.toArray() ) + .chain( session::flush ) ) + .chain( () -> { + List carsClientsList = new ArrayList<>(); + for ( Client client : clients ) { + for ( Car car : cars ) { + CarsClients carsClients = new CarsClients( "location" ); + carsClientsList.add( carsClients ); + car.addClient( carsClients ); + client.addCar( carsClients ); + } + } + return session + .persistAll( carsClientsList.toArray() ) + .chain( session::flush ); + } ); + } ) + ); + } + + @Entity(name = "Car") + @Table(name = "Car_Table") + public static class Car { + + @Id + @SequenceGenerator(name = "seq_car", sequenceName = "id_seq_car", allocationSize = 1) + @GeneratedValue(generator = "seq_car", strategy = GenerationType.SEQUENCE) + private Long id; + + public String brand; + + + private String model; + + @OneToMany(mappedBy = "car") + private Set clients = new HashSet<>(); + + public Car() { + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getBrand() { + return brand; + } + + public void setBrand(String brand) { + this.brand = brand; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public Set getClients() { + return clients; + } + + public void setClients(Set clients) { + this.clients = clients; + } + + public void addClient(CarsClients carsClients) { + carsClients.setCar( this ); + clients.add( carsClients ); + } + } + + @Entity + @Table(name = "Client_Table") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Client { + + @Id + @SequenceGenerator(name = "seq", sequenceName = "id_seq", allocationSize = 1) + @GeneratedValue(generator = "seq", strategy = GenerationType.SEQUENCE) + private Long id; + + private String name; + + private String email; + + private String phone; + + @OneToMany(mappedBy = "client") + private Set cars = new HashSet<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public Set getCars() { + return cars; + } + + public void setCars(Set cars) { + this.cars = cars; + } + + public void addCar(CarsClients carsClients) { + carsClients.setClient( this ); + cars.add( carsClients ); + } + } + + @Entity + @Table(name = "ClientA_Table") + public static class ClientA extends Client { + + public ClientA() { + } + } + + @Entity + @IdClass(CarsClientsId.class) + @Table(name = "cars_clients_table") + public static class CarsClients { + + @Id + @ManyToOne + private Car car; + + @Id + @ManyToOne + private Client client; + + private String location; + + public CarsClients() { + } + + public CarsClients(String location) { + this.location = location; + } + + public Car getCar() { + return car; + } + + public void setCar(Car car) { + this.car = car; + } + + public Client getClient() { + return client; + } + + public void setClient(Client client) { + this.client = client; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + } + + public static class CarsClientsId { + private Car car; + + private Client client; + + public CarsClientsId() { + } + + public Car getCar() { + return car; + } + + public void setCar(Car car) { + this.car = car; + } + + public Client getClient() { + return client; + } + + public void setClient(Client client) { + this.client = client; + } + } +} From 4495a1f8d88656f2b2b6056f0c4194232a25b2e5 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 4 Apr 2025 21:33:04 +0200 Subject: [PATCH 27/38] clean up a method --- ...SingleIdEntityLoaderProvidedQueryImpl.java | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderProvidedQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderProvidedQueryImpl.java index b2ec82649..a2b3a384e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderProvidedQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderProvidedQueryImpl.java @@ -7,17 +7,18 @@ import java.util.concurrent.CompletionStage; -import org.hibernate.FlushMode; import org.hibernate.LockOptions; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.query.Query; +import org.hibernate.query.QueryFlushMode; import org.hibernate.query.named.NamedQueryMemento; -import org.hibernate.query.spi.QueryImplementor; import org.hibernate.reactive.loader.ast.spi.ReactiveSingleIdEntityLoader; import org.hibernate.reactive.query.ReactiveSelectionQuery; import jakarta.persistence.Parameter; +import org.hibernate.type.descriptor.java.JavaType; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; @@ -30,9 +31,9 @@ public class ReactiveSingleIdEntityLoaderProvidedQueryImpl implements Reactiv private static final CompletionStage EMPTY_ARRAY_STAGE = completedFuture( ArrayHelper.EMPTY_OBJECT_ARRAY ); private final EntityMappingType entityDescriptor; - private final NamedQueryMemento namedQueryMemento; + private final NamedQueryMemento namedQueryMemento; - public ReactiveSingleIdEntityLoaderProvidedQueryImpl(EntityMappingType entityDescriptor, NamedQueryMemento namedQueryMemento) { + public ReactiveSingleIdEntityLoaderProvidedQueryImpl(EntityMappingType entityDescriptor, NamedQueryMemento namedQueryMemento) { this.entityDescriptor = entityDescriptor; this.namedQueryMemento = namedQueryMemento; } @@ -42,17 +43,13 @@ public EntityMappingType getLoadable() { return entityDescriptor; } - @Override + @Override @SuppressWarnings("unchecked") public CompletionStage load(Object pkValue, LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session) { - // noinspection unchecked - final QueryImplementor query = namedQueryMemento - .toQuery( session, entityDescriptor.getMappedJavaType().getJavaTypeClass() ); - - //noinspection unchecked + final JavaType mappedJavaType = (JavaType) entityDescriptor.getMappedJavaType(); + final Query query = namedQueryMemento.toQuery( session, mappedJavaType.getJavaTypeClass() ); query.setParameter( (Parameter) query.getParameters().iterator().next(), pkValue ); - query.setHibernateFlushMode( FlushMode.MANUAL ); - - return ( (ReactiveSelectionQuery) query ).reactiveUnique(); + query.setQueryFlushMode( QueryFlushMode.NO_FLUSH ); + return ( (ReactiveSelectionQuery) query ).reactiveUnique(); } @Override From 95ad44ca45ded244b9031dc4015089cd0064c204 Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Fri, 4 Apr 2025 15:38:04 +0200 Subject: [PATCH 28/38] [#2176] Create custom Vert.x contextual data storage Closes #2176 In Vert.x 5, the local context data map is deprecated. Hibernate Reactive can have its own Vert.x contextual data storage (requires creating a io.vertx.core.spi.context.storage.ContextLocal key). --- .../context/impl/ContextualDataStorage.java | 25 +++++++++++++++++++ .../reactive/context/impl/VertxContext.java | 18 ++++++++++--- .../io.vertx.core.spi.VertxServiceProvider | 1 + 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/ContextualDataStorage.java create mode 100644 hibernate-reactive-core/src/main/resources/META-INF/services/io.vertx.core.spi.VertxServiceProvider diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/ContextualDataStorage.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/ContextualDataStorage.java new file mode 100644 index 000000000..295b92f4d --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/ContextualDataStorage.java @@ -0,0 +1,25 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.context.impl; + +import java.util.concurrent.ConcurrentMap; + +import io.vertx.core.impl.VertxBuilder; +import io.vertx.core.spi.VertxServiceProvider; +import io.vertx.core.spi.context.storage.ContextLocal; + +/** + * SPI Implementation for {@link ContextLocal} storage. + */ +public class ContextualDataStorage implements VertxServiceProvider { + + @SuppressWarnings("rawtypes") + static ContextLocal CONTEXTUAL_DATA_KEY = ContextLocal.registerLocal( ConcurrentMap.class ); + + @Override + public void init(VertxBuilder vertxBuilder) { + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java index fef172e66..937dc3b1b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java @@ -6,9 +6,12 @@ package org.hibernate.reactive.context.impl; import java.lang.invoke.MethodHandles; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import io.vertx.core.Vertx; import io.vertx.core.impl.ContextInternal; +import io.vertx.core.spi.context.storage.AccessMode; import org.hibernate.reactive.context.Context; import org.hibernate.reactive.logging.impl.Log; @@ -39,7 +42,7 @@ public void put(Key key, T instance) { final ContextInternal context = ContextInternal.current(); if ( context != null ) { if ( trace ) LOG.tracef( "Putting key,value in context: [%1$s, %2$s]", key, instance ); - context.localContextData().put( key, instance ); + VertxContext.contextualDataMap( context ).put( key, instance ); } else { if ( trace ) LOG.tracef( "Context is null for key,value: [%1$s, %2$s]", key, instance ); @@ -51,8 +54,7 @@ public void put(Key key, T instance) { public T get(Key key) { final ContextInternal context = ContextInternal.current(); if ( context != null ) { - @SuppressWarnings("unchecked") - T local = (T) context.localContextData().get( key ); + T local = VertxContext.contextualDataMap( context ).get( key ); if ( trace ) LOG.tracef( "Getting value %2$s from context for key %1$s", key, local ); return local; } @@ -66,7 +68,7 @@ public T get(Key key) { public void remove(Key key) { final ContextInternal context = ContextInternal.current(); if ( context != null ) { - boolean removed = context.localContextData().remove( key ) != null; + boolean removed = contextualDataMap( context ).remove( key ) != null; if ( trace ) LOG.tracef( "Key %s removed from context: %s", key, removed ); } else { @@ -93,4 +95,12 @@ public void execute(Runnable runnable) { } } + @SuppressWarnings({ "unchecked" }) + private static ConcurrentMap, T> contextualDataMap(ContextInternal vertxContext) { + return vertxContext.getLocal( + ContextualDataStorage.CONTEXTUAL_DATA_KEY, + AccessMode.CONCURRENT, + ConcurrentHashMap::new + ); + } } diff --git a/hibernate-reactive-core/src/main/resources/META-INF/services/io.vertx.core.spi.VertxServiceProvider b/hibernate-reactive-core/src/main/resources/META-INF/services/io.vertx.core.spi.VertxServiceProvider new file mode 100644 index 000000000..80ffcb31c --- /dev/null +++ b/hibernate-reactive-core/src/main/resources/META-INF/services/io.vertx.core.spi.VertxServiceProvider @@ -0,0 +1 @@ +org.hibernate.reactive.context.impl.ContextualDataStorage \ No newline at end of file From 3d6cc0fb74c729788132ce35da948705ac03ab2d Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Thu, 27 Mar 2025 21:45:44 +0100 Subject: [PATCH 29/38] [#2166] Hibernate Reactive eager fetching the wrong record for ManyToOne GHQUARKUS-305 --- .../ReactiveEmbeddableInitializerImpl.java | 10 + .../ReactiveEntitySelectFetchInitializer.java | 10 + .../reactive/EmbeddedIdWithManyEagerTest.java | 228 ++++++++++++++++++ 3 files changed, 248 insertions(+) create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyEagerTest.java diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java index 69ae34a73..a1d4a890e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java @@ -33,6 +33,16 @@ public ReactiveEmbeddableInitializerData( super( initializer, rowProcessingState ); } + @Override + public void setState(State state) { + super.setState( state ); + if ( State.UNINITIALIZED == state ) { + // reset instance to null as otherwise EmbeddableInitializerImpl#prepareCompositeInstance + // will never create a new instance after the "first row with a non-null instance" gets processed + setInstance( null ); + } + } + public EmbeddableMappingType.ConcreteEmbeddableType getConcreteEmbeddableType() { return super.concreteEmbeddableType; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java index b27c0cc93..8f848d01b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java @@ -56,6 +56,16 @@ public Object getEntityIdentifier() { public void setEntityIdentifier(Object entityIdentifier) { super.entityIdentifier = entityIdentifier; } + + @Override + public void setState(State state) { + super.setState( state ); + if ( State.UNINITIALIZED == state ) { + // reset instance to null as otherwise EmbeddableInitializerImpl#prepareCompositeInstance + // will never create a new instance after the "first row with a non-null instance" gets processed + setInstance( null ); + } + } } private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyEagerTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyEagerTest.java new file mode 100644 index 000000000..0ceeaef18 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyEagerTest.java @@ -0,0 +1,228 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +public class EmbeddedIdWithManyEagerTest extends BaseReactiveTest { + + Fruit cherry; + Fruit apple; + Fruit banana; + + Flower sunflower; + Flower chrysanthemum; + Flower rose; + + @Override + protected Collection> annotatedEntities() { + return List.of( Flower.class, Fruit.class ); + } + + @BeforeEach + public void populateDb(VertxTestContext context) { + Seed seed1 = new Seed( 1 ); + rose = new Flower( seed1, "Rose" ); + + cherry = new Fruit( seed1, "Cherry" ); + cherry.addFriend( rose ); + + Seed seed2 = new Seed( 2 ); + sunflower = new Flower( seed2, "Sunflower" ); + + apple = new Fruit( seed2, "Apple" ); + apple.addFriend( sunflower ); + + Seed seed3 = new Seed( 3 ); + chrysanthemum = new Flower( seed3, "Chrysanthemum" ); + + banana = new Fruit( seed3, "Banana" ); + banana.addFriend( chrysanthemum ); + + test( + context, + getMutinySessionFactory().withTransaction( s -> s + .persistAll( cherry, rose, sunflower, apple, chrysanthemum, banana ) + ) + ); + } + + @Test + public void testFindWithEmbeddedId(VertxTestContext context) { + test( + context, getMutinySessionFactory().withTransaction( s -> s + .find( Flower.class, chrysanthemum.getSeed() ) + .invoke( flower -> assertThat( flower.getName() ).isEqualTo( chrysanthemum.getName() ) ) + ) + ); + } + + @Test + public void testSelectQueryWithEmbeddedId(VertxTestContext context) { + test( + context, getMutinySessionFactory().withTransaction( s -> s + .createSelectionQuery( "from Flower", Flower.class ) + .getResultList() + .invoke( list -> assertThat( list.stream().map( Flower::getName ) ) + .containsExactlyInAnyOrder( + sunflower.getName(), + chrysanthemum.getName(), + rose.getName() + ) + ) + ) + ); + } + + @Test + public void testSelectQueryWithEmbeddedIdAndEagerRelation(VertxTestContext context) { + test( + context, getMutinySessionFactory().withTransaction( s -> s + .createSelectionQuery( "from Flower f ", Flower.class ) + .getResultList() + .invoke( list -> assertThat( list.stream().map( Flower::getFriend ).map( Plant::getName ) ) + .containsExactlyInAnyOrder( + cherry.getName(), + apple.getName(), + banana.getName() + ) + ) + ) + ); + } + + @Embeddable + public static class Seed { + + @Column(nullable = false, updatable = false) + private Integer id; + + public Seed() { + } + + public Seed(Integer id) { + this.id = id; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + } + + @MappedSuperclass + public static abstract class Plant { + + @EmbeddedId + private Seed seed; + + @Column(length = 40, unique = true) + private String name; + + protected Plant() { + } + + protected Plant(Seed seed, String name) { + this.seed = seed; + this.name = name; + } + + public Seed getSeed() { + return seed; + } + + public void setSeed(Seed seed) { + this.seed = seed; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Fruit") + @Table(name = "known_fruits") + public static class Fruit extends Plant { + + @OneToMany(mappedBy = "friend", fetch = FetchType.LAZY) + private List friends = new ArrayList<>(); + + public Fruit() { + } + + public Fruit(Seed seed, String name) { + super( seed, name ); + } + + public void addFriend(Flower flower) { + this.friends.add( flower ); + flower.friend = this; + } + + public List getFriends() { + return friends; + } + + @Override + public String toString() { + return "Fruit{" + getSeed().getId() + "," + getName() + '}'; + } + + } + + @Entity(name = "Flower") + @Table(name = "known_flowers") + public static class Flower extends Plant { + + @ManyToOne(fetch = FetchType.EAGER, optional = false) + @JoinColumn(name = "friend", referencedColumnName = "id", nullable = false) + private Fruit friend; + + public Flower() { + } + + public Flower(Seed seed, String name) { + super( seed, name ); + } + + public Fruit getFriend() { + return friend; + } + + @Override + public String toString() { + return "Flower{" + getSeed().getId() + "," + getName() + '}'; + } + + } + +} From 45010e1f0f0012561d743fdf36c71f39444a7478 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Mon, 7 Apr 2025 15:39:07 +0200 Subject: [PATCH 30/38] [#2187] Use adjustToDefaultPrecision in UTCNormalizedZonedTest Replace `DateTimeUtils.roundToDefaultPrecision` with `DateTimeUtils.adjustToDefaultPrecision` This should prevent the failures we sometimes have on CI. --- .../reactive/timezones/UTCNormalizedZonedTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/UTCNormalizedZonedTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/UTCNormalizedZonedTest.java index 9943a0f97..474f5df7b 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/UTCNormalizedZonedTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/UTCNormalizedZonedTest.java @@ -29,7 +29,7 @@ import static org.hibernate.cfg.AvailableSettings.TIMEZONE_DEFAULT_STORAGE; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.testing.ReactiveAssertions.assertWithTruncationThat; -import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision; +import static org.hibernate.type.descriptor.DateTimeUtils.adjustToDefaultPrecision; @Timeout(value = 10, timeUnit = MINUTES) @DisabledFor(value = DB2, reason = "Exception: IllegalStateException: Needed to have 6 in buffer but only had 0") @@ -60,10 +60,10 @@ public void test(VertxTestContext context) { .thenCompose( zid -> openSession() .thenCompose( s -> s.find( Zoned.class, zid ) .thenAccept( z -> { - assertWithTruncationThat( roundToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) ) - .isEqualTo( roundToDefaultPrecision( nowZoned.toInstant(), getDialect() ) ); - assertWithTruncationThat( roundToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) ) - .isEqualTo( roundToDefaultPrecision( nowOffset.toInstant(), getDialect() ) ); + assertWithTruncationThat( adjustToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) ) + .isEqualTo( adjustToDefaultPrecision( nowZoned.toInstant(), getDialect() ) ); + assertWithTruncationThat( adjustToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) ) + .isEqualTo( adjustToDefaultPrecision( nowOffset.toInstant(), getDialect() ) ); assertThat( z.offsetDateTime.getOffset() ).isEqualTo( ZoneId.of( "Z" ) ); assertThat( z.zonedDateTime.getZone() ).isEqualTo( ZoneOffset.ofHours( 0 ) ); } ) From b9fa84fa36b9cd2dcb5bec4c41ec45f8dbc70bc9 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 8 Apr 2025 14:01:41 +0200 Subject: [PATCH 31/38] [#2190] Upgrade MariaDB to 11.7.2 --- .../java/org/hibernate/reactive/containers/MariaDatabase.java | 2 +- podman.md | 2 +- tooling/jbang/MariaDBReactiveTest.java.qute | 2 +- tooling/jbang/ReactiveTest.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java index ba8e12f42..16a5f97ff 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java @@ -36,7 +36,7 @@ class MariaDatabase extends MySQLDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final MariaDBContainer maria = new MariaDBContainer<>( imageName( "mariadb", "11.4.2" ) ) + public static final MariaDBContainer maria = new MariaDBContainer<>( imageName( "mariadb", "11.7.2" ) ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/podman.md b/podman.md index 3a35a715f..f9db3e091 100644 --- a/podman.md +++ b/podman.md @@ -67,7 +67,7 @@ and schema to run the tests: ``` podman run --rm --name HibernateTestingMariaDB \ -e MYSQL_ROOT_PASSWORD=hreact -e MYSQL_DATABASE=hreact -e MYSQL_USER=hreact -e MYSQL_PASSWORD=hreact \ - -p 3306:3306 docker.io/mariadb:11.4.2 + -p 3306:3306 docker.io/mariadb:11.7.2 ``` When the database has started, you can run the tests on MariaDB with: diff --git a/tooling/jbang/MariaDBReactiveTest.java.qute b/tooling/jbang/MariaDBReactiveTest.java.qute index f6b528925..42195d326 100755 --- a/tooling/jbang/MariaDBReactiveTest.java.qute +++ b/tooling/jbang/MariaDBReactiveTest.java.qute @@ -69,7 +69,7 @@ public class {baseName} { } @ClassRule - public final static MariaDBContainer database = new MariaDBContainer<>( imageName( "docker.io", "mariadb", "11.4.2" ) ); + public final static MariaDBContainer database = new MariaDBContainer<>( imageName( "docker.io", "mariadb", "11.7.2" ) ); private Mutiny.SessionFactory sessionFactory; diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java index c1094f891..5cc278087 100755 --- a/tooling/jbang/ReactiveTest.java +++ b/tooling/jbang/ReactiveTest.java @@ -231,7 +231,7 @@ enum Database { POSTGRESQL( () -> new PostgreSQLContainer( "postgres:17.4" ) ), MYSQL( () -> new MySQLContainer( "mysql:8.4.0" ) ), DB2( () -> new Db2Container( "docker.io/icr.io/db2_community/db2:12.1.0.0" ).acceptLicense() ), - MARIADB( () -> new MariaDBContainer( "mariadb:11.4.2" ) ), + MARIADB( () -> new MariaDBContainer( "mariadb:11.7.2" ) ), COCKROACHDB( () -> new CockroachContainer( "cockroachdb/cockroach:v24.1.0" ) ); private final Supplier> containerSupplier; From 741af5e89bf547e008f0f273afdf49c82fcfc493 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 8 Apr 2025 14:07:40 +0200 Subject: [PATCH 32/38] [#2190] Upgrade MySQL to 9.2.0 --- .github/workflows/build.yml | 2 +- README.md | 2 +- .../java/org/hibernate/reactive/containers/MySQLDatabase.java | 2 +- podman.md | 2 +- tooling/jbang/MySQLReactiveTest.java.qute | 2 +- tooling/jbang/ReactiveTest.java | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c85d9281d..00a2518dc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,7 +50,7 @@ jobs: # Label used to access the service container mysql: # Docker Hub image - image: mysql:8.4.0 + image: mysql:9.2.0 env: MYSQL_ROOT_PASSWORD: hreact MYSQL_DATABASE: hreact diff --git a/README.md b/README.md index 72b3a7e63..e9b179491 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Hibernate Reactive has been tested with: - Java 11, 17, 21, 23 - PostgreSQL 16 -- MySQL 8 +- MySQL 9 - MariaDB 11 - Db2 11 - CockroachDB v24 diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java index e753da93d..e0aa9ef3a 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java @@ -87,7 +87,7 @@ class MySQLDatabase implements TestableDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final MySQLContainer mysql = new MySQLContainer<>( imageName( "mysql", "8.4.0") ) + public static final MySQLContainer mysql = new MySQLContainer<>( imageName( "mysql", "9.2.0") ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/podman.md b/podman.md index f9db3e091..ea9280709 100644 --- a/podman.md +++ b/podman.md @@ -94,7 +94,7 @@ and schema to run the tests: ``` podman run --rm --name HibernateTestingMySQL \ -e MYSQL_ROOT_PASSWORD=hreact -e MYSQL_DATABASE=hreact -e MYSQL_USER=hreact -e MYSQL_PASSWORD=hreact \ - -p 3306:3306 docker.io/mysql:8.4.0 + -p 3306:3306 docker.io/mysql:9.2.0 ``` When the database has started, you can run the tests on MySQL with: diff --git a/tooling/jbang/MySQLReactiveTest.java.qute b/tooling/jbang/MySQLReactiveTest.java.qute index f8307495d..e568bd6b0 100755 --- a/tooling/jbang/MySQLReactiveTest.java.qute +++ b/tooling/jbang/MySQLReactiveTest.java.qute @@ -72,7 +72,7 @@ public class {baseName} { } @ClassRule - public final static MySQLContainer database = new MySQLContainer<>( imageName( "docker.io", "mysql", "8.4.0" ) ); + public final static MySQLContainer database = new MySQLContainer<>( imageName( "docker.io", "mysql", "9.2.0" ) ); private Mutiny.SessionFactory sessionFactory; diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java index 5cc278087..9ae2cad49 100755 --- a/tooling/jbang/ReactiveTest.java +++ b/tooling/jbang/ReactiveTest.java @@ -229,7 +229,7 @@ public String toString() { */ enum Database { POSTGRESQL( () -> new PostgreSQLContainer( "postgres:17.4" ) ), - MYSQL( () -> new MySQLContainer( "mysql:8.4.0" ) ), + MYSQL( () -> new MySQLContainer( "mysql:9.2.0" ) ), DB2( () -> new Db2Container( "docker.io/icr.io/db2_community/db2:12.1.0.0" ).acceptLicense() ), MARIADB( () -> new MariaDBContainer( "mariadb:11.7.2" ) ), COCKROACHDB( () -> new CockroachContainer( "cockroachdb/cockroach:v24.1.0" ) ); From 59bff98400b6755cb589dad9e948ab91e28184dc Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 8 Apr 2025 14:12:14 +0200 Subject: [PATCH 33/38] [#2190] Upgrade PostgreSQL to 17.4 The version wasn't aligned everywhere --- .github/workflows/build.yml | 2 +- podman.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00a2518dc..2718f729f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -66,7 +66,7 @@ jobs: - 3306:3306 postgres: # Docker Hub image - image: postgres:16.3 + image: postgres:17.4 env: POSTGRES_DB: hreact POSTGRES_USER: hreact diff --git a/podman.md b/podman.md index ea9280709..b74425d8d 100644 --- a/podman.md +++ b/podman.md @@ -14,13 +14,13 @@ If you replace `podman` with `sudo docker`, they will also work with [Docker][do Example: ``` -podman run --rm --name HibernateTestingPGSQL postgres:14.0 +podman run --rm --name HibernateTestingPGSQL postgres:17.4 ``` becomes for Docker: ``` -sudo docker run --rm --name HibernateTestingPGSQL postgres:14.0 +sudo docker run --rm --name HibernateTestingPGSQL postgres:17.4 ``` --- From 9e917ecff0f78634baf39f522d7f058aa154b1e5 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 8 Apr 2025 14:35:57 +0200 Subject: [PATCH 34/38] [#2190] Upgrade CockroachDB to 24.1.15 --- .../org/hibernate/reactive/containers/CockroachDBDatabase.java | 2 +- podman.md | 2 +- tooling/jbang/CockroachDBReactiveTest.java.qute | 2 +- tooling/jbang/ReactiveTest.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java index 60dd1b997..3ec042a38 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java @@ -25,7 +25,7 @@ class CockroachDBDatabase extends PostgreSQLDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final CockroachContainer cockroachDb = new CockroachContainer( imageName( "cockroachdb/cockroach", "v24.1.0" ) ) + public static final CockroachContainer cockroachDb = new CockroachContainer( imageName( "cockroachdb/cockroach", "v24.1.15" ) ) // Username, password and database are not supported by test container at the moment // Testcontainers will use a database named 'postgres' and the 'root' user .withReuse( true ); diff --git a/podman.md b/podman.md index b74425d8d..5d7d9476b 100644 --- a/podman.md +++ b/podman.md @@ -121,7 +121,7 @@ configured to run the tests: ``` podman run --rm --name=HibernateTestingCockroachDB \ --hostname=roachrr1 -p 26257:26257 -p 8080:8080 \ - docker.io/cockroachdb/cockroach:v24.1.0 start-single-node --insecure + docker.io/cockroachdb/cockroach:v24.1.15 start-single-node --insecure ``` Some of tests needs temporary tables and because this is an experimental feature in diff --git a/tooling/jbang/CockroachDBReactiveTest.java.qute b/tooling/jbang/CockroachDBReactiveTest.java.qute index 9b713c8ea..a2437ebfe 100755 --- a/tooling/jbang/CockroachDBReactiveTest.java.qute +++ b/tooling/jbang/CockroachDBReactiveTest.java.qute @@ -70,7 +70,7 @@ public class {baseName} { } @ClassRule - public final static CockroachContainer database = new CockroachContainer( imageName( "cockroachdb", "cockroach", "v24.1.0" ) ); + public final static CockroachContainer database = new CockroachContainer( imageName( "cockroachdb", "cockroach", "v24.1.15" ) ); private Mutiny.SessionFactory sessionFactory; diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java index 9ae2cad49..3677fe873 100755 --- a/tooling/jbang/ReactiveTest.java +++ b/tooling/jbang/ReactiveTest.java @@ -232,7 +232,7 @@ enum Database { MYSQL( () -> new MySQLContainer( "mysql:9.2.0" ) ), DB2( () -> new Db2Container( "docker.io/icr.io/db2_community/db2:12.1.0.0" ).acceptLicense() ), MARIADB( () -> new MariaDBContainer( "mariadb:11.7.2" ) ), - COCKROACHDB( () -> new CockroachContainer( "cockroachdb/cockroach:v24.1.0" ) ); + COCKROACHDB( () -> new CockroachContainer( "cockroachdb/cockroach:v24.1.15" ) ); private final Supplier> containerSupplier; From a5c574038949527ed1d92c090640aa80fbffdd06 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 8 Apr 2025 14:54:11 +0200 Subject: [PATCH 35/38] [#2190] Update Db2 version in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e9b179491..01996c250 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Hibernate Reactive has been tested with: - PostgreSQL 16 - MySQL 9 - MariaDB 11 -- Db2 11 +- Db2 12 - CockroachDB v24 - MS SQL Server 2022 - Oracle 23 From 770b58fc5af7bad8340e0e133387fadfd6f65e07 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 8 Apr 2025 14:55:00 +0200 Subject: [PATCH 36/38] [#2190] Update JDKs in the README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 01996c250..3dcba2ae7 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Learn more at . Hibernate Reactive has been tested with: -- Java 11, 17, 21, 23 +- Java 17, 21, 24 - PostgreSQL 16 - MySQL 9 - MariaDB 11 From f972da439c6ef1d8bc3ef6797ebf4541e095ca36 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 8 Apr 2025 15:03:24 +0200 Subject: [PATCH 37/38] [#2192] Upgrade Vert.x SQL client to 4.5.14 --- README.md | 10 +++++----- build.gradle | 2 +- gradle.properties | 6 +++--- tooling/jbang/CockroachDBReactiveTest.java.qute | 4 ++-- tooling/jbang/Db2ReactiveTest.java.qute | 4 ++-- tooling/jbang/Example.java | 6 +++--- tooling/jbang/MariaDBReactiveTest.java.qute | 4 ++-- tooling/jbang/MySQLReactiveTest.java.qute | 4 ++-- tooling/jbang/PostgreSQLReactiveTest.java.qute | 4 ++-- tooling/jbang/ReactiveTest.java | 8 ++++---- 10 files changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 3dcba2ae7..c01979d65 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,11 @@ Hibernate Reactive has been tested with: - MS SQL Server 2022 - Oracle 23 - [Hibernate ORM][] 7.0.0.Beta4 -- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.13 -- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.13 -- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.13 -- [Vert.x Reactive MS SQL Server Client](https://vertx.io/docs/vertx-mssql-client/java/) 4.5.13 -- [Vert.x Reactive Oracle Client](https://vertx.io/docs/vertx-oracle-client/java/) 4.5.13 +- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.14 +- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.14 +- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.14 +- [Vert.x Reactive MS SQL Server Client](https://vertx.io/docs/vertx-mssql-client/java/) 4.5.14 +- [Vert.x Reactive Oracle Client](https://vertx.io/docs/vertx-oracle-client/java/) 4.5.14 - [Quarkus][Quarkus] via the Hibernate Reactive extension [PostgreSQL]: https://www.postgresql.org diff --git a/build.gradle b/build.gradle index 808127db4..c7ad6918c 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ ext { // Example: // ./gradlew build -PvertxSqlClientVersion=4.0.0-SNAPSHOT if ( !project.hasProperty( 'vertxSqlClientVersion' ) ) { - vertxSqlClientVersion = '4.5.13' + vertxSqlClientVersion = '4.5.14' } testcontainersVersion = '1.20.6' diff --git a/gradle.properties b/gradle.properties index be7612bc5..3576439d6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -47,9 +47,9 @@ hibernateOrmVersion = 7.0.0.Beta5 #skipOrmVersionParsing = true # Override default Vert.x Sql client version -#vertxSqlClientVersion = 4.5.13-SNAPSHOT +#vertxSqlClientVersion = 4.5.14-SNAPSHOT # Override default Vert.x Web client and server versions. For integration tests, both default to vertxSqlClientVersion -#vertxWebVersion = 4.5.13 -#vertxWebtClientVersion = 4.5.13 +#vertxWebVersion = 4.5.14 +#vertxWebtClientVersion = 4.5.14 diff --git a/tooling/jbang/CockroachDBReactiveTest.java.qute b/tooling/jbang/CockroachDBReactiveTest.java.qute index a2437ebfe..81b79ee5d 100755 --- a/tooling/jbang/CockroachDBReactiveTest.java.qute +++ b/tooling/jbang/CockroachDBReactiveTest.java.qute @@ -5,8 +5,8 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.13} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.13} +//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.14} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.14} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Beta1} //DEPS org.assertj:assertj-core:3.26.3 //DEPS junit:junit:4.13.2 diff --git a/tooling/jbang/Db2ReactiveTest.java.qute b/tooling/jbang/Db2ReactiveTest.java.qute index a13d21b6d..6aeeae23a 100755 --- a/tooling/jbang/Db2ReactiveTest.java.qute +++ b/tooling/jbang/Db2ReactiveTest.java.qute @@ -5,8 +5,8 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-db2-client:$\{vertx.version:4.5.13} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.13} +//DEPS io.vertx:vertx-db2-client:$\{vertx.version:4.5.14} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.14} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Beta1} //DEPS org.assertj:assertj-core:3.26.3 //DEPS junit:junit:4.13.2 diff --git a/tooling/jbang/Example.java b/tooling/jbang/Example.java index 7f6d383fd..893b5c80f 100644 --- a/tooling/jbang/Example.java +++ b/tooling/jbang/Example.java @@ -6,9 +6,9 @@ */ //DEPS com.ongres.scram:client:2.1 -//DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.13} -//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.13} -//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.13} +//DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.14} +//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.14} +//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.14} //DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:3.0.0.Beta1} //DEPS org.slf4j:slf4j-simple:2.0.7 //DESCRIPTION Allow authentication to PostgreSQL using SCRAM: diff --git a/tooling/jbang/MariaDBReactiveTest.java.qute b/tooling/jbang/MariaDBReactiveTest.java.qute index 42195d326..682238370 100755 --- a/tooling/jbang/MariaDBReactiveTest.java.qute +++ b/tooling/jbang/MariaDBReactiveTest.java.qute @@ -5,8 +5,8 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.13} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.13} +//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.14} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.14} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Beta1} //DEPS org.assertj:assertj-core:3.26.3 //DEPS junit:junit:4.13.2 diff --git a/tooling/jbang/MySQLReactiveTest.java.qute b/tooling/jbang/MySQLReactiveTest.java.qute index e568bd6b0..b252459d1 100755 --- a/tooling/jbang/MySQLReactiveTest.java.qute +++ b/tooling/jbang/MySQLReactiveTest.java.qute @@ -5,8 +5,8 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.13} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.13} +//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.14} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.14} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Beta1} //DEPS org.assertj:assertj-core:3.26.3 //DEPS junit:junit:4.13.2 diff --git a/tooling/jbang/PostgreSQLReactiveTest.java.qute b/tooling/jbang/PostgreSQLReactiveTest.java.qute index 682ac502b..04a0272e8 100755 --- a/tooling/jbang/PostgreSQLReactiveTest.java.qute +++ b/tooling/jbang/PostgreSQLReactiveTest.java.qute @@ -5,8 +5,8 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.13} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.13} +//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.14} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.14} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Beta1} //DEPS org.assertj:assertj-core:3.26.3 //DEPS junit:junit:4.13.2 diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java index 3677fe873..c6b116321 100755 --- a/tooling/jbang/ReactiveTest.java +++ b/tooling/jbang/ReactiveTest.java @@ -5,11 +5,11 @@ */ ///usr/bin/env jbang "$0" "$@" ; exit $? -//DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.13} +//DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.14} //DEPS com.ongres.scram:client:2.1 -//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.13} -//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.13} -//DEPS io.vertx:vertx-unit:${vertx.version:4.5.13} +//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.14} +//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.14} +//DEPS io.vertx:vertx-unit:${vertx.version:4.5.14} //DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:3.0.0.Beta1} //DEPS org.assertj:assertj-core:3.26.3 //DEPS junit:junit:4.13.2 From 2a5cbb1602a3431977879769b2410de6707f0778 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Tue, 8 Apr 2025 15:52:24 +0000 Subject: [PATCH 38/38] Update project version to : `3.0.0.Beta3` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 603432f4e..ebfc91836 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.0.0-SNAPSHOT \ No newline at end of file +projectVersion=3.0.0.Beta3 \ No newline at end of file