diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2718f729f..f818b94e8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,15 +7,18 @@ on: - 'wip/**' - '2.*' - '3.*' + - '4.*' tags: - '2.*' - '3.*' + - '4.*' pull_request: branches: - 'main' - 'wip/**' - '2.*' - '3.*' + - '4.*' # For building snapshots workflow_call: inputs: @@ -101,13 +104,13 @@ jobs: key: gradle-examples-${{ matrix.db }}-${{ steps.get-date.outputs.yearmonth }} - name: Set up JDK 11 if: ${{ startsWith( inputs.branch, 'wip/2' ) }} - uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: distribution: 'temurin' java-version: 11 - name: Set up JDK 17 if: ${{ !startsWith( inputs.branch, 'wip/2' ) }} - uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: distribution: 'temurin' java-version: 17 @@ -150,13 +153,13 @@ jobs: key: gradle-db-${{ matrix.db }}-${{ steps.get-date.outputs.yearmonth }} - name: Set up JDK 11 if: ${{ startsWith( inputs.branch, 'wip/2' ) }} - uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: distribution: 'temurin' java-version: 11 - name: Set up JDK 17 if: ${{ !startsWith( inputs.branch, 'wip/2' ) }} - uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: distribution: 'temurin' @@ -255,7 +258,7 @@ jobs: release: ${{ matrix.java.java_version_numeric }} - name: Set up latest JDK ${{ matrix.java.name }} from Adoptium if: matrix.java.from == '' || matrix.java.from == 'adoptium.net' - uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: distribution: 'temurin' java-version: ${{ matrix.java.java_version_numeric }} @@ -265,14 +268,14 @@ jobs: run: echo "::set-output name=path::${JAVA_HOME}" - name: Set up JDK 11 if: ${{ startsWith( inputs.branch, 'wip/2' ) }} - uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: distribution: 'temurin' java-version: 11 check-latest: true - name: Set up JDK 17 if: ${{ !startsWith( inputs.branch, 'wip/2' ) }} - uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: distribution: 'temurin' java-version: 17 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 13d87abe0..0be7d261f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,7 +2,7 @@ name: "CodeQL" on: push: - branches: [ "main", "1.0", "jakarta/main" ] + branches: [ "main", "4.0", "3.0", "2.4" ] pull_request: branches: [ "main" ] schedule: @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Java - uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: distribution: temurin java-version: 17 diff --git a/.github/workflows/scheduler.yml b/.github/workflows/scheduler.yml index 2a25cf60a..24299c8a7 100644 --- a/.github/workflows/scheduler.yml +++ b/.github/workflows/scheduler.yml @@ -11,7 +11,7 @@ jobs: build-snapshots: strategy: matrix: - branch: [ 'wip/2.3', 'wip/2.4', 'wip/3.0' ] + branch: [ 'wip/2.4', 'wip/3.0', 'wip/4.0' ] uses: ./.github/workflows/build.yml with: branch: ${{ matrix.branch }} diff --git a/.gitignore b/.gitignore index 8016117ce..750319d47 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,3 @@ bin # Vim *.swp *.swo - -# Release scripts downloaded from hibernate/hibernate-release-scripts -.release diff --git a/.release/.gitignore b/.release/.gitignore new file mode 100644 index 000000000..cdd9a17d6 --- /dev/null +++ b/.release/.gitignore @@ -0,0 +1,3 @@ +# The folder into which we checkout our release scripts into +* +!.gitignore \ No newline at end of file diff --git a/AUTHORS.txt b/AUTHORS.txt new file mode 100644 index 000000000..caf0817d1 --- /dev/null +++ b/AUTHORS.txt @@ -0,0 +1,39 @@ +# This file lists copyright owners of the project. +# The list is not exhaustive: other copyright owners exist. +# See CONTRIBUTING.md for instructions regarding how to be added to this list. + +# Corporate contributors + +Red Hat, Inc. + +# Individual contributors + +Andrea Boriero +Andrew Guibert +Barry LaFond +Thinking Chen (cdmikechen) +Coding Xu (codingxu97) +Conor Farrell +Davide D'Alto +Derick Hermanson +Eric Dalquist +Eric Deandrea +Gail Badner +Gavin King +George Gastaldi +Georgios Andrianakis +Jay Erb +Julien Ponge +Marko Bekhta +Max Rydahl Andersen +(meepown) +Nesrin Aşan +Ravi Khadiwala +Renar Narubin +Sanne Grinovero +Scott Marlow +Stephane Epardaud +Steve Ebersole +Stuart Douglas +Thomas Segismont +Yoann Rodière diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2df00b224..bb825420f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,6 +16,12 @@ All contributions are subject to the [Developer Certificate of Origin (DCO)](htt The DCO text is available verbatim in the [dco.txt](dco.txt) file in the root directory of the Hibernate Reactive repository. +Copyright owners are listed in [AUTHORS.txt](AUTHORS.txt). +Contributors with a valid copyright claim can request to be added to that list +by sending a pull request to the project's GitHub repository, +listing at least one relevant contribution in the pull request description. +Note: one-liner or repetitive patches may not be sufficient to claim copyright. + ## Guidelines While we try to keep requirements for contributing to a minimum, there are a few guidelines diff --git a/README.md b/README.md index c01979d65..743540d6c 100644 --- a/README.md +++ b/README.md @@ -38,12 +38,12 @@ Hibernate Reactive has been tested with: - CockroachDB v24 - 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.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 +- [Hibernate ORM][] 7.0.0.Final +- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 5.0.0 +- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 5.0.0 +- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 5.0.0 +- [Vert.x Reactive MS SQL Server Client](https://vertx.io/docs/vertx-mssql-client/java/) 5.0.0 +- [Vert.x Reactive Oracle Client](https://vertx.io/docs/vertx-oracle-client/java/) 5.0.0 - [Quarkus][Quarkus] via the Hibernate Reactive extension [PostgreSQL]: https://www.postgresql.org diff --git a/build.gradle b/build.gradle index c7ad6918c..f927371ef 100644 --- a/build.gradle +++ b/build.gradle @@ -22,21 +22,14 @@ ext { // Example: // ./gradlew build -PvertxSqlClientVersion=4.0.0-SNAPSHOT if ( !project.hasProperty( 'vertxSqlClientVersion' ) ) { - vertxSqlClientVersion = '4.5.14' + vertxSqlClientVersion = '5.0.0' } - testcontainersVersion = '1.20.6' + testcontainersVersion = '1.21.0' logger.lifecycle "Vert.x SQL Client Version: " + project.vertxSqlClientVersion } -// Publishing to Sonatype (Maven Central): -nexusPublishing { - repositories { - sonatype() - } -} - subprojects { apply plugin: 'java-library' apply plugin: 'com.diffplug.spotless' diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index d365436ab..8627950a1 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -119,8 +119,10 @@ pipeline { echo "Release was triggered automatically" // Avoid doing an automatic release for commits from a release - def lastCommitter = sh(script: 'git show -s --format=\'%an\'', returnStdout: true) - def secondLastCommitter = sh(script: 'git show -s --format=\'%an\' HEAD~1', returnStdout: true) + def lastCommitter = sh(script: 'git show -s --format=\'%an\'', returnStdout: true).trim() + def secondLastCommitter = sh(script: 'git show -s --format=\'%an\' HEAD~1', returnStdout: true).trim() + echo "Last two commits were performed by '${lastCommitter}'/'${secondLastCommitter}'." + if (lastCommitter == 'Hibernate-CI' && secondLastCommitter == 'Hibernate-CI') { print "INFO: Automatic release skipped because last commits were for the previous release" currentBuild.result = 'ABORTED' @@ -149,6 +151,7 @@ pipeline { env.DEVELOPMENT_VERSION = developmentVersion.toString() // Dry run is not supported at the moment env.SCRIPT_OPTIONS = params.RELEASE_DRY_RUN ? "-d" : "" + env.JRELEASER_DRY_RUN = params.RELEASE_DRY_RUN // Determine version id to check if Jira version exists // This step doesn't work for Hibernate Reactive (the project has been created with a different type on JIRA) @@ -172,11 +175,10 @@ pipeline { // tags the version // changes the version to the provided development version withEnv([ - "BRANCH=${env.GIT_BRANCH}", // Increase the amount of memory for this part since asciidoctor doc rendering consumes a lot of metaspace "GRADLE_OPTS=-Dorg.gradle.jvmargs='-Dlog4j2.disableJmx -Xmx4g -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError -Duser.language=en -Duser.country=US -Duser.timezone=UTC -Dfile.encoding=UTF-8'" ]) { - sh ".release/scripts/prepare-release.sh ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION}" + sh ".release/scripts/prepare-release.sh -j -b ${env.GIT_BRANCH} -v ${env.DEVELOPMENT_VERSION} ${env.PROJECT} ${env.RELEASE_VERSION}" } } } @@ -193,16 +195,22 @@ pipeline { configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts") ]) { withCredentials([ - // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh - usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'ORG_GRADLE_PROJECT_sonatypePassword', usernameVariable: 'ORG_GRADLE_PROJECT_sonatypeUsername'), - file(credentialsId: 'release.gpg.private-key', variable: 'SIGNING_GPG_PRIVATE_KEY_PATH'), - string(credentialsId: 'release.gpg.passphrase', variable: 'SIGNING_GPG_PASSPHRASE'), - gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default') + // TODO: Once we switch to maven-central publishing (from nexus2) we need to add a new credentials + // to use the following env variable names to set the user/password: + // - JRELEASER_MAVENCENTRAL_USERNAME + // - JRELEASER_MAVENCENTRAL_TOKEN + // Also use the new `credentialsId` for Maven Central, e.g.: + // usernamePassword(credentialsId: '???????', passwordVariable: 'JRELEASER_MAVENCENTRAL_TOKEN', usernameVariable: 'JRELEASER_MAVENCENTRAL_USERNAME'), + usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'JRELEASER_NEXUS2_PASSWORD', usernameVariable: 'JRELEASER_NEXUS2_USERNAME'), + gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default'), + file(credentialsId: 'release.gpg.private-key', variable: 'RELEASE_GPG_PRIVATE_KEY_PATH'), + string(credentialsId: 'release.gpg.passphrase', variable: 'JRELEASER_GPG_PASSPHRASE'), + string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN') ]) { sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) { // performs documentation upload and Sonatype release // push to github - sh ".release/scripts/publish.sh ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION} ${env.GIT_BRANCH}" + sh ".release/scripts/publish.sh -j ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION} ${env.GIT_BRANCH}" } } } @@ -217,4 +225,4 @@ pipeline { } } } -} \ No newline at end of file +} diff --git a/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile index 07fa844c6..fea160b23 100644 --- a/ci/snapshot-publish.Jenkinsfile +++ b/ci/snapshot-publish.Jenkinsfile @@ -10,6 +10,14 @@ if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) { return } +def checkoutReleaseScripts() { + dir('.release/scripts') { + checkout scmGit(branches: [[name: '*/main']], extensions: [], + userRemoteConfigs: [[credentialsId: 'ed25519.Hibernate-CI.github.com', + url: 'https://github.com/hibernate/hibernate-release-scripts.git']]) + } +} + pipeline { agent { label 'Release' @@ -30,13 +38,23 @@ pipeline { } stage('Publish') { steps { - withCredentials([ - // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh - usernamePassword(credentialsId: 'ossrh.sonatype.org', usernameVariable: 'ORG_GRADLE_PROJECT_sonatypeUsername', passwordVariable: 'ORG_GRADLE_PROJECT_sonatypePassword'), - file(credentialsId: 'release.gpg.private-key', variable: 'SIGNING_GPG_PRIVATE_KEY_PATH'), - string(credentialsId: 'release.gpg.passphrase', variable: 'SIGNING_GPG_PASSPHRASE') - ]) { - sh "./gradlew clean publish --no-scan" + script { + withCredentials([ + // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh + // TODO: Once we switch to maven-central publishing (from nexus2) we need to update credentialsId: + // https://docs.gradle.org/current/samples/sample_publishing_credentials.html#:~:text=via%20environment%20variables + usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'ORG_GRADLE_PROJECT_snapshotsPassword', usernameVariable: 'ORG_GRADLE_PROJECT_snapshotsUsername'), + gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default'), + string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN') + ]) { + checkoutReleaseScripts() + def version = sh( + script: ".release/scripts/determine-current-version.sh reactive", + returnStdout: true + ).trim() + echo "Current version: '${version}'" + sh "bash -xe .release/scripts/snapshot-deploy.sh reactive ${version}" + } } } } @@ -48,4 +66,4 @@ pipeline { } } } -} \ No newline at end of file +} diff --git a/documentation/src/main/asciidoc/reference/introduction.adoc b/documentation/src/main/asciidoc/reference/introduction.adoc index 4f2237edb..ee5090c21 100644 --- a/documentation/src/main/asciidoc/reference/introduction.adoc +++ b/documentation/src/main/asciidoc/reference/introduction.adoc @@ -89,7 +89,7 @@ Optionally, you might also add any of the following additional features: | Hibernate Validator | `org.hibernate.validator:hibernate-validator` and `org.glassfish:jakarta.el` | Compile-time checking for your HQL queries | `org.hibernate:query-validator` | Second-level cache support via JCache and EHCache | `org.hibernate.orm:hibernate-jcache` along with `org.ehcache:ehcache` -| SCRAM authentication support for PostgreSQL | `com.ongres.scram:client:2.1` +| SCRAM authentication support for PostgreSQL | `com.ongres.scram:scram-client:3.1` |=== You might also add the Hibernate {enhancer}[bytecode enhancer] to your diff --git a/examples/native-sql-example/build.gradle b/examples/native-sql-example/build.gradle index c79ec8984..3b650d34a 100644 --- a/examples/native-sql-example/build.gradle +++ b/examples/native-sql-example/build.gradle @@ -40,7 +40,7 @@ dependencies { runtimeOnly "org.apache.logging.log4j:log4j-core:2.20.0" // Allow authentication to PostgreSQL using SCRAM: - runtimeOnly 'com.ongres.scram:client:2.1' + runtimeOnly 'com.ongres.scram:scram-client:3.1' } // Optional: enable the bytecode enhancements diff --git a/examples/session-example/build.gradle b/examples/session-example/build.gradle index 4da40ba69..a001112d8 100644 --- a/examples/session-example/build.gradle +++ b/examples/session-example/build.gradle @@ -41,7 +41,7 @@ dependencies { runtimeOnly "org.apache.logging.log4j:log4j-core:2.20.0" // Allow authentication to PostgreSQL using SCRAM: - runtimeOnly 'com.ongres.scram:client:2.1' + runtimeOnly 'com.ongres.scram:scram-client:3.1' } // Optional: enable the bytecode enhancements diff --git a/gradle.properties b/gradle.properties index 3576439d6..36bc0c5ac 100644 --- a/gradle.properties +++ b/gradle.properties @@ -35,21 +35,21 @@ 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.Beta5 +hibernateOrmVersion = 7.0.0.Final # 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 # a remote repository -#hibernateOrmGradlePluginVersion = 7.0.0.Beta4 +#hibernateOrmGradlePluginVersion = 7.0.0.Final # If set to true, skip Hibernate ORM version parsing (default is true, if set to null) # this is required when using intervals or weird versions or the build will fail #skipOrmVersionParsing = true # Override default Vert.x Sql client version -#vertxSqlClientVersion = 4.5.14-SNAPSHOT +#vertxSqlClientVersion = 5.0.0-SNAPSHOT # Override default Vert.x Web client and server versions. For integration tests, both default to vertxSqlClientVersion -#vertxWebVersion = 4.5.14 -#vertxWebtClientVersion = 4.5.14 +#vertxWebVersion = 5.0.0 +#vertxWebtClientVersion = 5.0.0 diff --git a/gradle/version.properties b/gradle/version.properties index ebfc91836..618fb2d72 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.0.0.Beta3 \ No newline at end of file +projectVersion=4.0.0-SNAPSHOT \ No newline at end of file diff --git a/hibernate-reactive-core/build.gradle b/hibernate-reactive-core/build.gradle index fca12935f..26a4de751 100644 --- a/hibernate-reactive-core/build.gradle +++ b/hibernate-reactive-core/build.gradle @@ -10,22 +10,22 @@ dependencies { api "org.hibernate.orm:hibernate-core:${hibernateOrmVersion}" - api 'io.smallrye.reactive:mutiny:2.7.0' + api 'io.smallrye.reactive:mutiny:2.9.0' //Logging implementation 'org.jboss.logging:jboss-logging:3.6.1.Final' annotationProcessor 'org.jboss.logging:jboss-logging:3.6.1.Final' - compileOnly 'org.jboss.logging:jboss-logging-annotations:3.0.3.Final' - annotationProcessor 'org.jboss.logging:jboss-logging-annotations:3.0.3.Final' - annotationProcessor 'org.jboss.logging:jboss-logging-processor:3.0.3.Final' + compileOnly 'org.jboss.logging:jboss-logging-annotations:3.0.4.Final' + annotationProcessor 'org.jboss.logging:jboss-logging-annotations:3.0.4.Final' + annotationProcessor 'org.jboss.logging:jboss-logging-processor:3.0.4.Final' //Specific implementation details of Hibernate Reactive: implementation "io.vertx:vertx-sql-client:${vertxSqlClientVersion}" // Testing - testImplementation 'org.assertj:assertj-core:3.26.3' + testImplementation 'org.assertj:assertj-core:3.27.3' testImplementation "io.vertx:vertx-junit5:${vertxSqlClientVersion}" // Drivers @@ -39,23 +39,23 @@ dependencies { testImplementation "io.vertx:vertx-micrometer-metrics:${vertxSqlClientVersion}" // Optional dependency of vertx-pg-client, essential when connecting via SASL SCRAM - testImplementation 'com.ongres.scram:client:2.1' + testImplementation 'com.ongres.scram:scram-client:3.1' // JUnit Jupiter testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.3' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.11.3' // JDBC driver to test with ORM and PostgreSQL - testRuntimeOnly "org.postgresql:postgresql:42.7.4" + testRuntimeOnly "org.postgresql:postgresql:42.7.5" // JDBC driver for Testcontainers with MS SQL Server - testRuntimeOnly "com.microsoft.sqlserver:mssql-jdbc:12.8.1.jre11" + testRuntimeOnly "com.microsoft.sqlserver:mssql-jdbc:12.10.0.jre11" // JDBC driver for Testcontainers with MariaDB Server - testRuntimeOnly "org.mariadb.jdbc:mariadb-java-client:3.5.1" + testRuntimeOnly "org.mariadb.jdbc:mariadb-java-client:3.5.3" // JDBC driver for Testcontainers with MYSQL Server - testRuntimeOnly "com.mysql:mysql-connector-j:9.1.0" + testRuntimeOnly "com.mysql:mysql-connector-j:9.3.0" // JDBC driver for Db2 server, for testing testRuntimeOnly "com.ibm.db2:jcc:12.1.0.0" diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/engine/internal/ReactivePersistenceContextAdapter.java b/hibernate-reactive-core/src/main/java/org/hibernate/engine/internal/ReactivePersistenceContextAdapter.java index 3dfc8dcd0..6d87416b4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/engine/internal/ReactivePersistenceContextAdapter.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/engine/internal/ReactivePersistenceContextAdapter.java @@ -37,9 +37,9 @@ import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.engine.impl.ReactiveCallbackImpl; +import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; -import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.sql.exec.spi.Callback; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState; @@ -79,8 +79,9 @@ private class NonLazyCollectionInitializer implements Consumer nonLazyCollection) { if ( !nonLazyCollection.wasInitialized() ) { - stage = stage.thenCompose( v -> ( (ReactiveSession) getSession() ) - .reactiveInitializeCollection( nonLazyCollection, false ) ); + stage = stage.thenCompose( v -> + ( (ReactiveSharedSessionContractImplementor) getSession() ) + .reactiveInitializeCollection( nonLazyCollection, false ) ); } } } 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 deleted file mode 100644 index 295b92f4d..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/ContextualDataStorage.java +++ /dev/null @@ -1,25 +0,0 @@ -/* 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 937dc3b1b..02c28af5f 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,12 +6,9 @@ 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 io.vertx.core.internal.ContextInternal; import org.hibernate.reactive.context.Context; import org.hibernate.reactive.logging.impl.Log; @@ -39,10 +36,10 @@ public void injectServices(ServiceRegistryImplementor serviceRegistry) { @Override public void put(Key key, T instance) { - final ContextInternal context = ContextInternal.current(); + final ContextInternal context = currentContext(); if ( context != null ) { if ( trace ) LOG.tracef( "Putting key,value in context: [%1$s, %2$s]", key, instance ); - VertxContext.contextualDataMap( context ).put( key, instance ); + context.putLocal( key, instance ); } else { if ( trace ) LOG.tracef( "Context is null for key,value: [%1$s, %2$s]", key, instance ); @@ -50,11 +47,15 @@ public void put(Key key, T instance) { } } + private static ContextInternal currentContext() { + return (ContextInternal) Vertx.currentContext(); + } + @Override public T get(Key key) { - final ContextInternal context = ContextInternal.current(); + final ContextInternal context = currentContext(); if ( context != null ) { - T local = VertxContext.contextualDataMap( context ).get( key ); + T local = context.getLocal( key ); if ( trace ) LOG.tracef( "Getting value %2$s from context for key %1$s", key, local ); return local; } @@ -66,9 +67,9 @@ public T get(Key key) { @Override public void remove(Key key) { - final ContextInternal context = ContextInternal.current(); + final ContextInternal context = currentContext(); if ( context != null ) { - boolean removed = contextualDataMap( context ).remove( key ) != null; + boolean removed = context.removeLocal( key ); if ( trace ) LOG.tracef( "Key %s removed from context: %s", key, removed ); } else { @@ -78,7 +79,7 @@ public void remove(Key key) { @Override public void execute(Runnable runnable) { - final io.vertx.core.Context currentContext = Vertx.currentContext(); + final io.vertx.core.Context currentContext = currentContext(); if ( currentContext == null ) { if ( trace ) LOG.tracef( "Not in a Vert.x context, checking the VertxInstance service" ); final io.vertx.core.Context newContext = vertxInstance.getVertx().getOrCreateContext(); @@ -86,6 +87,7 @@ public void execute(Runnable runnable) { // that could lead to unintentionally share the same session with other streams. ContextInternal newContextInternal = (ContextInternal) newContext; final ContextInternal duplicate = newContextInternal.duplicate(); + if ( trace ) LOG.tracef( "Using duplicated context from VertxInstance: %s", duplicate ); duplicate.runOnContext( x -> runnable.run() ); } @@ -95,12 +97,4 @@ 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/java/org/hibernate/reactive/engine/impl/CollectionTypes.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java index 4bc488bb6..9ce6a7c5b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java @@ -23,7 +23,6 @@ import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.PersistenceContext; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.persister.collection.CollectionPersister; @@ -58,7 +57,7 @@ public static CompletionStage replace( CollectionType type, Object original, Object target, - SessionImplementor session, + SharedSessionContractImplementor session, Object owner, Map copyCache, ForeignKeyDirection foreignKeyDirection) @@ -76,7 +75,7 @@ public static CompletionStage replace( CollectionType type, Object original, Object target, - SessionImplementor session, + SharedSessionContractImplementor session, Object owner, Map copyCache) throws HibernateException { if ( original == null ) { @@ -96,7 +95,7 @@ else if ( !Hibernate.isInitialized( original ) ) { */ private static Object replaceNullOriginal( Object target, - SessionImplementor session) { + SharedSessionContractImplementor session) { if ( target == null ) { return null; } @@ -110,20 +109,18 @@ else if ( target instanceof Map map ) { } else { final PersistenceContext persistenceContext = session.getPersistenceContext(); - final PersistentCollection collectionHolder = persistenceContext.getCollectionHolder( target ); - if ( collectionHolder != null ) { - if ( collectionHolder instanceof PersistentArrayHolder arrayHolder ) { - persistenceContext.removeCollectionHolder( target ); - arrayHolder.beginRead(); - final PluralAttributeMapping attributeMapping = - persistenceContext.getCollectionEntry( collectionHolder ) - .getLoadedPersister().getAttributeMapping(); - arrayHolder.injectLoadedState( attributeMapping, null ); - arrayHolder.endRead(); - arrayHolder.dirty(); - persistenceContext.addCollectionHolder( collectionHolder ); - return arrayHolder.getArray(); - } + if ( persistenceContext.getCollectionHolder( target ) + instanceof PersistentArrayHolder arrayHolder ) { + persistenceContext.removeCollectionHolder( target ); + arrayHolder.beginRead(); + final PluralAttributeMapping attributeMapping = + persistenceContext.getCollectionEntry( arrayHolder ) + .getLoadedPersister().getAttributeMapping(); + arrayHolder.injectLoadedState( attributeMapping, null ); + arrayHolder.endRead(); + arrayHolder.dirty(); + persistenceContext.addCollectionHolder( arrayHolder ); + return arrayHolder.getArray(); } } return null; @@ -134,7 +131,7 @@ private static Object replaceUninitializedOriginal( CollectionType type, Object original, Object target, - SessionImplementor session, + SharedSessionContractImplementor session, Map copyCache) { final PersistentCollection persistentCollection = (PersistentCollection) original; if ( persistentCollection.hasQueuedOperations() ) { @@ -142,8 +139,9 @@ private static Object replaceUninitializedOriginal( // A managed entity with an uninitialized collection is being merged, // We need to replace any detached entities in the queued operations // with managed copies. - final AbstractPersistentCollection pc = (AbstractPersistentCollection) original; - pc.replaceQueuedOperationValues( + final AbstractPersistentCollection collection = + (AbstractPersistentCollection) original; + collection.replaceQueuedOperationValues( session.getFactory() .getMappingMetamodel() .getCollectionDescriptor( type.getRole() ), copyCache @@ -166,7 +164,7 @@ private static CompletionStage replaceOriginal( CollectionType type, Object original, Object target, - SessionImplementor session, + SharedSessionContractImplementor session, Object owner, Map copyCache) { @@ -182,18 +180,20 @@ private static CompletionStage replaceOriginal( ).thenCompose( result -> { if ( original == target ) { // get the elements back into the target making sure to handle dirty flag - final boolean wasClean = - target instanceof PersistentCollection collection - && !collection.isDirty(); //TODO: this is a little inefficient, don't need to do a whole // deep replaceElements() call - return replaceElements( type, result, target, owner, copyCache, session ) - .thenApply( unused -> { - if ( wasClean ) { - ( (PersistentCollection) target ).clearDirty(); - } - return target; - } ); + final CompletionStage replaced = + replaceElements( type, result, target, owner, copyCache, session ); + if ( target instanceof PersistentCollection collection + && !collection.isDirty() ) { + return replaced.thenApply( unused -> { + collection.clearDirty(); + return target; + } ); + } + else { + return replaced.thenApply( unused -> target ); + } } else { return completedFuture( result ); @@ -210,7 +210,7 @@ private static CompletionStage replaceElements( Object target, Object owner, Map copyCache, - SessionImplementor session) { + SharedSessionContractImplementor session) { if ( type instanceof ArrayType ) { return replaceArrayTypeElements( type, original, target, owner, copyCache, session ); } @@ -220,7 +220,7 @@ else if ( type instanceof CustomCollectionType ) { else if ( type instanceof MapType ) { return replaceMapTypeElements( type, - (Map) original, + (Map) original, (Map) target, owner, copyCache, @@ -245,55 +245,61 @@ private static CompletionStage replaceCollectionTypeElements( final Collection result, Object owner, Map copyCache, - SessionImplementor session) { + SharedSessionContractImplementor session) { result.clear(); - // copy elements into newly empty target collection final Type elemType = type.getElementType( session.getFactory() ); - return loop( - (Collection) original, o -> getReplace( elemType, o, owner, session, copyCache ) - .thenAccept( result::add ) - ).thenCompose( unused -> { - // if the original is a PersistentCollection, and that original - // was not flagged as dirty, then reset the target's dirty flag - // here after the copy operation. - //

- // One thing to be careful of here is a "bare" original collection - // in which case we should never ever ever reset the dirty flag - // on the target because we simply do not know... - if ( original instanceof PersistentCollection originalPersistentCollection - && result instanceof PersistentCollection resultPersistentCollection ) { - return preserveSnapshot( - originalPersistentCollection, resultPersistentCollection, - elemType, owner, copyCache, session - ).thenApply( v -> { - if ( !originalPersistentCollection.isDirty() ) { - resultPersistentCollection.clearDirty(); - } - return result; - } ); - } - else { - return completedFuture( result ); - } - } ); + return loop( (Collection) original, + o -> getReplace( elemType, o, owner, session, copyCache ) + .thenAccept( result::add ) ) + .thenCompose( v -> preserveSnapshotIfNecessary( original, result, owner, copyCache, session, elemType ) ); + } + + private static CompletionStage preserveSnapshotIfNecessary( + Object original, + Collection result, + Object owner, + Map copyCache, + SharedSessionContractImplementor session, + Type elemType) { + // if the original is a PersistentCollection, and that original + // was not flagged as dirty, then reset the target's dirty flag + // here after the copy operation. + //

+ // One thing to be careful of here is a "bare" original collection + // in which case we should never ever ever reset the dirty flag + // on the target because we simply do not know... + if ( original instanceof PersistentCollection originalCollection + && result instanceof PersistentCollection resultCollection ) { + return preserveSnapshot( + originalCollection, resultCollection, + elemType, owner, copyCache, session + ).thenApply( v -> { + if ( !originalCollection.isDirty() ) { + resultCollection.clearDirty(); + } + return result; + } ); + } + else { + return completedFuture( result ); + } } private static CompletionStage replaceMapTypeElements( CollectionType type, - Map original, + Map original, Map target, Object owner, Map copyCache, - SessionImplementor session) { - final CollectionPersister persister = session.getFactory().getRuntimeMetamodels() - .getMappingMetamodel().getCollectionDescriptor( type.getRole() ); - final Map result = target; - result.clear(); - + SharedSessionContractImplementor session) { + final CollectionPersister persister = + session.getFactory().getRuntimeMetamodels().getMappingMetamodel() + .getCollectionDescriptor( type.getRole() ); + target.clear(); return loop( original.entrySet(), entry -> { - final Map.Entry me = entry; + final Map.Entry me = entry; return getReplace( persister.getIndexType(), me.getKey(), owner, session, copyCache ) .thenCompose( key -> getReplace( persister.getElementType(), @@ -301,10 +307,10 @@ private static CompletionStage replaceMapTypeElements( owner, session, copyCache - ).thenAccept( value -> result.put( key, value ) ) + ).thenAccept( value -> target.put( key, value ) ) ); } - ).thenApply( unused -> result ); + ).thenApply( unused -> target); } private static CompletionStage replaceArrayTypeElements( @@ -313,7 +319,7 @@ private static CompletionStage replaceArrayTypeElements( Object target, Object owner, Map copyCache, - SessionImplementor session) { + SharedSessionContractImplementor session) { final Object result; final int length = Array.getLength( original ); if ( length != Array.getLength( target ) ) { @@ -325,8 +331,8 @@ private static CompletionStage replaceArrayTypeElements( } final Type elemType = type.getElementType( session.getFactory() ); - return loop( - 0, length, i -> getReplace( elemType, Array.get( original, i ), owner, session, copyCache ) + return loop( 0, length, + i -> getReplace( elemType, Array.get( original, i ), owner, session, copyCache ) .thenApply( o -> { Array.set( result, i, o ); return result; @@ -338,25 +344,21 @@ private static CompletionStage getReplace( Type elemType, Object o, Object owner, - SessionImplementor session, + SharedSessionContractImplementor session, Map copyCache) { return getReplace( elemType, o, null, owner, session, copyCache ); } private static CompletionStage getReplace( Type elemType, - Object o, + Object object, Object target, Object owner, - SessionImplementor session, + SharedSessionContractImplementor session, Map copyCache) { - if ( elemType instanceof EntityType ) { - return EntityTypes.replace( (EntityType) elemType, o, target, session, owner, copyCache ); - } - else { - final Object replace = elemType.replace( o, target, session, owner, copyCache ); - return completedFuture( replace ); - } + return elemType instanceof EntityType entityType + ? EntityTypes.replace( entityType, object, target, session, owner, copyCache ) + : completedFuture( elemType.replace( object, target, session, owner, copyCache) ); } /** @@ -368,13 +370,15 @@ private static CompletionStage preserveSnapshot( Type elemType, Object owner, Map copyCache, - SessionImplementor session) { + SharedSessionContractImplementor session) { final CollectionEntry ce = session.getPersistenceContextInternal().getCollectionEntry( result ); if ( ce != null ) { return createSnapshot( original, result, elemType, owner, copyCache, session ) .thenAccept( serializable -> ce.resetStoredSnapshot( result, serializable ) ); } - return voidFuture(); + else { + return voidFuture(); + } } /** @@ -386,7 +390,7 @@ private static CompletionStage createSnapshot( Type elemType, Object owner, Map copyCache, - SessionImplementor session) { + SharedSessionContractImplementor session) { final Serializable originalSnapshot = original.getStoredSnapshot(); if ( originalSnapshot instanceof List list ) { return createListSnapshot( list, elemType, owner, copyCache, session ); @@ -411,9 +415,9 @@ private static CompletionStage createArraySnapshot( Type elemType, Object owner, Map copyCache, - SessionImplementor session) { - return loop( - 0, array.length, i -> getReplace( elemType, array[i], owner, session, copyCache ) + SharedSessionContractImplementor session) { + return loop( 0, array.length, + i -> getReplace( elemType, array[i], owner, session, copyCache ) .thenAccept( o -> array[i] = o ) ).thenApply( unused -> array ); } @@ -421,29 +425,26 @@ private static CompletionStage createArraySnapshot( /** * @see CollectionType#createMapSnapshot(Map, PersistentCollection, Type, Object, Map, SharedSessionContractImplementor) */ - private static CompletionStage createMapSnapshot( - Map map, + private static CompletionStage createMapSnapshot( + Map map, PersistentCollection result, Type elemType, Object owner, Map copyCache, - SessionImplementor session) { + SharedSessionContractImplementor session) { final Map resultSnapshot = (Map) result.getStoredSnapshot(); - final Map targetMap; - if ( map instanceof SortedMap sortedMap ) { - //noinspection unchecked, rawtypes - targetMap = new TreeMap( sortedMap.comparator() ); - } - else { - targetMap = mapOfSize( map.size() ); - } - return loop( - map.entrySet(), entry -> - getReplace( elemType, entry.getValue(), resultSnapshot, owner, session, copyCache ) - .thenAccept( newValue -> { - final Object key = entry.getKey(); - targetMap.put( key == entry.getValue() ? newValue : key, newValue ); - } ) + final Map targetMap = + map instanceof SortedMap sortedMap + ? new TreeMap<>( sortedMap.comparator() ) + : mapOfSize(map.size()); + return loop( map.entrySet(), + entry -> getReplace( elemType, entry.getValue(), resultSnapshot, owner, session, copyCache ) + .thenAccept( newValue -> { + final K key = entry.getKey(); + final V value = entry.getValue(); + //noinspection unchecked + targetMap.put( key == value ? (K) newValue : key, (V) newValue ); + } ) ).thenApply( v -> (Serializable) targetMap ); } @@ -455,10 +456,10 @@ private static CompletionStage createListSnapshot( Type elemType, Object owner, Map copyCache, - SessionImplementor session) { + SharedSessionContractImplementor session) { final ArrayList targetList = new ArrayList<>( list.size() ); - return loop( - list, obj -> getReplace( elemType, obj, owner, session, copyCache ) + return loop( list, + obj -> getReplace( elemType, obj, owner, session, copyCache ) .thenAccept( targetList::add ) ).thenApply( unused -> targetList ); } @@ -472,9 +473,9 @@ private static Object instantiateResultIfNecessary(CollectionType type, Object o // by default just use an unanticipated capacity since we don't // know how to extract the capacity to use from original here... return target == null - || target == original - || target == UNFETCHED_PROPERTY - || target instanceof PersistentCollection collection && collection.isWrapper( original ) + || target == original + || target == UNFETCHED_PROPERTY + || target instanceof PersistentCollection collection && collection.isWrapper( original ) ? type.instantiate( -1 ) : target; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java index 0bb0c75ae..504830150 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java @@ -16,15 +16,15 @@ import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; +import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; +import org.hibernate.reactive.session.ReactiveQueryProducer; import org.hibernate.reactive.session.impl.ReactiveQueryExecutorLookup; -import org.hibernate.reactive.session.impl.ReactiveSessionImpl; import org.hibernate.type.CollectionType; import org.hibernate.type.EntityType; import org.hibernate.type.ForeignKeyDirection; @@ -154,12 +154,12 @@ public static CompletionStage replace( final Object[] original, final Object[] target, final Type[] types, - final SessionImplementor session, + final SharedSessionContractImplementor session, final Object owner, final Map copyCache) { Object[] copied = new Object[original.length]; - return loop( - 0, types.length, i -> replace( original, target, types, session, owner, copyCache, i, copied ) + return loop( 0, types.length, + i -> replace( original, target, types, session, owner, copyCache, i, copied ) ).thenApply( v -> copied ); } @@ -170,13 +170,12 @@ public static CompletionStage replace( final Object[] original, final Object[] target, final Type[] types, - final SessionImplementor session, + final SharedSessionContractImplementor session, final Object owner, final Map copyCache, final ForeignKeyDirection foreignKeyDirection) { Object[] copied = new Object[original.length]; - return loop( - 0, types.length, + return loop( 0, types.length, i -> replace( original, target, types, session, owner, copyCache, foreignKeyDirection, i, copied ) ).thenApply( v -> copied ); } @@ -188,7 +187,7 @@ private static CompletionStage replace( EntityType entityType, Object original, Object target, - SessionImplementor session, + SharedSessionContractImplementor session, Object owner, Map copyCache, ForeignKeyDirection foreignKeyDirection) @@ -208,7 +207,7 @@ protected static CompletionStage replace( EntityType entityType, Object original, Object target, - SessionImplementor session, + SharedSessionContractImplementor session, Object owner, Map copyCache) { if ( original == null ) { @@ -254,7 +253,7 @@ protected static CompletionStage replace( private static CompletionStage resolveIdOrUniqueKey( EntityType entityType, Object original, - SessionImplementor session, + SharedSessionContractImplementor session, Object owner, Map copyCache) { return getIdentifier( entityType, original, session ) @@ -268,7 +267,7 @@ private static CompletionStage resolveIdOrUniqueKey( // as a ComponentType. In the case that the entity is unfetched, we need to // explicitly fetch it here before calling replace(). (Note that in Hibernate // ORM this is unnecessary due to transparent lazy fetching.) - return ( (ReactiveSessionImpl) session ) + return ( (ReactiveQueryProducer) session ) .reactiveFetch( id, true ) .thenCompose( fetched -> { Object idOrUniqueKey = entityType @@ -289,7 +288,7 @@ private static CompletionStage resolveIdOrUniqueKey( private static CompletionStage getIdentifier( EntityType entityType, Object value, - SessionImplementor session) { + SharedSessionContractImplementor session) { if ( entityType.isReferenceToIdentifierProperty() ) { // tolerates nulls return getEntityIdentifierIfNotUnsaved( entityType.getAssociatedEntityName(), value, session ); @@ -340,10 +339,11 @@ private static CompletionStage getIdentifierFromHibernateProxy( EntityType entityType, HibernateProxy proxy, SharedSessionContractImplementor session) { - LazyInitializer initializer = proxy.getHibernateLazyInitializer(); + final LazyInitializer initializer = proxy.getHibernateLazyInitializer(); final String entityName = initializer.getEntityName(); final Object identifier = initializer.getIdentifier(); - return ( (ReactiveSessionImpl) session ).reactiveImmediateLoad( entityName, identifier ) + return ( (ReactiveSharedSessionContractImplementor) session ) + .reactiveImmediateLoad( entityName, identifier ) .thenApply( entity -> { checkEntityFound( session, entityName, identifier, entity ); initializer.setSession( session ); @@ -357,7 +357,7 @@ private static CompletionStage getIdentifierFromHibernateProxy( // an entity type, in which case we need to resolve its identifier final AttributeMapping type = entityPersister.findAttributeMapping( uniqueKeyPropertyName ); if ( type.isEntityIdentifierMapping() ) { - propertyValue = getIdentifier( (EntityType) type, propertyValue, (SessionImplementor) session ); + propertyValue = getIdentifier( (EntityType) type, propertyValue, session ); } return propertyValue; } @@ -372,7 +372,8 @@ private static CompletionStage loadHibernateProxyEntity( LazyInitializer initializer = ( (HibernateProxy) entity ).getHibernateLazyInitializer(); final String entityName = initializer.getEntityName(); final Object identifier = initializer.getIdentifier(); - return ( (ReactiveSessionImpl) session ).reactiveImmediateLoad( entityName, identifier ) + return ( (ReactiveSharedSessionContractImplementor) session ) + .reactiveImmediateLoad( entityName, identifier ) .thenApply( result -> { checkEntityFound( session, entityName, identifier, result ); return result; @@ -387,7 +388,7 @@ private static CompletionStage replace( Object[] original, Object[] target, Type[] types, - SessionImplementor session, + SharedSessionContractImplementor session, Object owner, Map copyCache, int i, @@ -433,7 +434,7 @@ private static CompletionStage replace( Object[] original, Object[] target, Type[] types, - SessionImplementor session, + SharedSessionContractImplementor session, Object owner, Map copyCache, ForeignKeyDirection foreignKeyDirection, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ForeignKeys.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ForeignKeys.java index 087623e44..87078f87f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ForeignKeys.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ForeignKeys.java @@ -15,7 +15,6 @@ import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SelfDirtinessTracker; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.StringHelper; import org.hibernate.persister.entity.EntityPersister; @@ -48,7 +47,7 @@ public final class ForeignKeys { public static class Nullifier { private final boolean isDelete; private final boolean isEarlyInsert; - private final SessionImplementor session; + private final SharedSessionContractImplementor session; private final Object self; private final EntityPersister persister; @@ -65,7 +64,7 @@ public Nullifier( final Object self, final boolean isDelete, final boolean isEarlyInsert, - final SessionImplementor session, + final SharedSessionContractImplementor session, final EntityPersister persister) { this.isDelete = isDelete; this.isEarlyInsert = isEarlyInsert; @@ -270,7 +269,7 @@ private CompletionStage isNullifiable(final String entityName, Object o * * @return {@code true} if the given entity is not transient (meaning it is either detached/persistent) */ - public static CompletionStage isNotTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session) { + public static CompletionStage isNotTransient(String entityName, Object entity, Boolean assumed, SharedSessionContractImplementor session) { if ( isHibernateProxy( entity ) ) { return trueFuture(); } @@ -297,7 +296,7 @@ public static CompletionStage isNotTransient(String entityName, Object * * @return {@code true} if the given entity is transient (unsaved) */ - public static CompletionStage isTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session) { + public static CompletionStage isTransient(String entityName, Object entity, Boolean assumed, SharedSessionContractImplementor session) { if ( entity == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { // an unfetched association can only point to // an entity that already exists in the db @@ -350,7 +349,7 @@ public static CompletionStage isTransient(String entityName, Object ent public static CompletionStage getEntityIdentifierIfNotUnsaved( final String entityName, final Object object, - final SessionImplementor session) throws TransientObjectException { + final SharedSessionContractImplementor session) throws TransientObjectException { if ( object == null ) { return nullFuture(); } @@ -387,7 +386,7 @@ public static CompletionStage findNonNullableT final EntityPersister persister = session.getEntityPersister( entityName, entity ); final Type[] types = persister.getPropertyTypes(); - final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, (SessionImplementor) session, persister ); + final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session, persister ); final String[] propertyNames = persister.getPropertyNames(); final boolean[] nullability = persister.getPropertyNullability(); final NonNullableTransientDependencies nonNullableTransientEntities = new NonNullableTransientDependencies(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java index 4838e6971..3f40cdb86 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java @@ -16,7 +16,6 @@ import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.persister.entity.EntityPersister; @@ -64,7 +63,7 @@ public interface ReactiveEntityInsertAction extends ReactiveExecutable, Comparab // @see org.hibernate.action.internal.AbstractEntityInsertAction#nullifyTransientReferencesIfNotAlready() default CompletionStage reactiveNullifyTransientReferencesIfNotAlready() { if ( !areTransientReferencesNullified() ) { - return new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), (SessionImplementor) getSession(), getPersister() ) + return new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), getSession(), getPersister() ) .nullifyTransientReferences( getState() ) .thenAccept( v -> { new Nullability( getSession() ).checkNullability( getState(), getPersister(), false ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/dialect/internal/ReactiveStandardDialectResolver.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/dialect/internal/ReactiveStandardDialectResolver.java index 1b24e567d..d12791e5f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/dialect/internal/ReactiveStandardDialectResolver.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/dialect/internal/ReactiveStandardDialectResolver.java @@ -11,24 +11,22 @@ import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.engine.jdbc.dialect.spi.DialectResolver; -import static org.hibernate.dialect.CockroachDialect.parseVersion; public class ReactiveStandardDialectResolver implements DialectResolver { @Override public Dialect resolveDialect(DialectResolutionInfo info) { - // Hibernate ORM runs an extra query to recognize CockroachDB from PostgreSQL - // We've already done it, so we are trying to skip that step + // Hibernate ORM runs an extra query to recognize CockroachDB from PostgresSQL + // We already did it when we created the DialectResolutionInfo in NoJdbcEnvironmentInitiator, + // so we can skip that step here. if ( info.getDatabaseName().startsWith( "Cockroach" ) ) { - return new CockroachDialect( parseVersion( info.getDatabaseVersion() ) ); + return new CockroachDialect( info ); } - for ( Database database : Database.values() ) { if ( database.matchesResolutionInfo( info ) ) { return database.createDialect( info ); } } - return null; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/spi/ReactiveSharedSessionContractImplementor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/spi/ReactiveSharedSessionContractImplementor.java index 1ab340380..9bdc5742a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/spi/ReactiveSharedSessionContractImplementor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/spi/ReactiveSharedSessionContractImplementor.java @@ -9,6 +9,7 @@ import java.util.concurrent.CompletionStage; +import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.PersistenceContext; import static org.hibernate.reactive.util.impl.CompletionStages.falseFuture; @@ -22,5 +23,9 @@ default CompletionStage reactiveAutoFlushIfRequired(Set querySp return falseFuture(); } + CompletionStage reactiveImmediateLoad(String entityName, Object id); + + CompletionStage reactiveInitializeCollection(PersistentCollection collection, boolean writing); + PersistenceContext getPersistenceContext(); } 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 f490d5366..1dbf6db02 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 @@ -47,6 +47,7 @@ import org.hibernate.reactive.event.ReactiveDeleteEventListener; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.session.ReactiveQueryProducer; import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.type.CollectionType; import org.hibernate.type.CompositeType; @@ -198,7 +199,7 @@ private CompletionStage fetchAndDelete(DeleteEvent event, DeleteContext tr } //Object entity = persistenceContext.unproxyAndReassociate( event.getObject() ); - return ( (ReactiveSession) source ) + return ( (ReactiveQueryProducer) source ) .reactiveFetch( objectEvent, true ) .thenCompose( entity -> delete( event, transientEntities, entity ) ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java index 20466abd2..5547fcc74 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java @@ -23,9 +23,9 @@ import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.persister.collection.impl.ReactiveCollectionPersister; -import org.hibernate.sql.results.internal.ResultsHelper; import org.hibernate.stat.spi.StatisticsImplementor; +import static org.hibernate.event.internal.DefaultInitializeCollectionEventListener.handlePotentiallyEmptyCollection; import static org.hibernate.pretty.MessageHelper.collectionInfoString; import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @@ -59,7 +59,8 @@ public CompletionStage onReactiveInitializeCollection(InitializeCollection final CollectionPersister loadedPersister = ce.getLoadedPersister(); final Object loadedKey = ce.getLoadedKey(); if ( LOG.isTraceEnabled() ) { - LOG.tracev( "Initializing collection {0}", collectionInfoString( loadedPersister, collection, loadedKey, source ) ); + LOG.tracev( "Initializing collection {0}", + collectionInfoString( loadedPersister, collection, loadedKey, source ) ); LOG.trace( "Checking second-level cache" ); } @@ -76,11 +77,8 @@ public CompletionStage onReactiveInitializeCollection(InitializeCollection } return ( (ReactiveCollectionPersister) loadedPersister ) .reactiveInitialize( loadedKey, source ) - .thenApply( list -> { - handlePotentiallyEmptyCollection( collection, source, ce, loadedPersister ); - return list; - } ) - .thenAccept( list -> { + .thenAccept( v -> { + handlePotentiallyEmptyCollection( collection, source.getPersistenceContext(), ce, loadedPersister ); if ( LOG.isTraceEnabled() ) { LOG.trace( "Collection initialized" ); } @@ -93,23 +91,6 @@ public CompletionStage onReactiveInitializeCollection(InitializeCollection } } - private void handlePotentiallyEmptyCollection( - PersistentCollection collection, - SessionImplementor source, - CollectionEntry ce, - CollectionPersister loadedPersister) { - if ( !collection.wasInitialized() ) { - collection.initializeEmptyCollection( loadedPersister ); - ResultsHelper.finalizeCollectionLoading( - source.getPersistenceContext(), - loadedPersister, - collection, - ce.getLoadedKey(), - true - ); - } - } - /** * Try to initialize a collection from the cache * diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java index 2df247665..ffd0fcfde 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java @@ -33,6 +33,7 @@ import org.hibernate.reactive.event.ReactiveLockEventListener; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; +import org.hibernate.reactive.session.ReactiveQueryProducer; import org.hibernate.reactive.session.ReactiveSession; import static java.lang.invoke.MethodHandles.lookup; @@ -80,7 +81,7 @@ public CompletionStage reactiveOnLock(LockEvent event) throws HibernateExc //TODO: if object was an uninitialized proxy, this is inefficient, // resulting in two SQL selects - return ( (ReactiveSession) source ).reactiveFetch( event.getObject(), true ) + return ( (ReactiveQueryProducer) source ).reactiveFetch( event.getObject(), true ) .thenCompose( entity -> reactiveOnLock( event, entity ) ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java index f5e5eb1b4..ba3edda4a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java @@ -37,7 +37,7 @@ import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.persister.entity.impl.ReactiveAbstractEntityPersister; -import org.hibernate.reactive.session.ReactiveSession; +import org.hibernate.reactive.session.ReactiveQueryProducer; import org.hibernate.type.CollectionType; import org.hibernate.type.CompositeType; import org.hibernate.type.Type; @@ -84,7 +84,7 @@ public CompletionStage reactiveOnRefresh(RefreshEvent event, RefreshContex // Hibernate Reactive doesn't support detached instances in refresh() throw new IllegalArgumentException( "Unmanaged instance passed to refresh()" ); } - return ( (ReactiveSession) source ) + return ( (ReactiveQueryProducer) source ) .reactiveFetch( event.getObject(), true ) .thenCompose( entity -> reactiveOnRefresh( event, refreshedAlready, entity ) ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java index 70495ccea..e842adf11 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java @@ -5,13 +5,6 @@ */ package org.hibernate.reactive.generator.values.internal; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletionStage; - import org.hibernate.HibernateException; import org.hibernate.Internal; import org.hibernate.dialect.Dialect; @@ -48,8 +41,15 @@ import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; import org.hibernate.type.descriptor.WrapperOptions; -import static org.hibernate.generator.internal.NaturalIdHelper.getNaturalIdPropertyNames; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletionStage; + import static org.hibernate.generator.values.internal.GeneratedValuesHelper.noCustomSql; +import static org.hibernate.internal.NaturalIdHelper.getNaturalIdPropertyNames; import static org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer.UniqueSemantic.NONE; /** diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java index c820f513f..329792f28 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java @@ -14,9 +14,9 @@ import io.vertx.core.Context; import io.vertx.core.Vertx; -import io.vertx.core.net.impl.pool.CombinerExecutor; -import io.vertx.core.net.impl.pool.Executor; -import io.vertx.core.net.impl.pool.Task; +import io.vertx.core.internal.pool.CombinerExecutor; +import io.vertx.core.internal.pool.Executor; +import io.vertx.core.internal.pool.Task; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; @@ -44,7 +44,7 @@ public abstract class BlockingIdentifierGenerator implements ReactiveIdentifierG //modification access. //This replaces the synchronization blocks one would see in a similar //service in Hibernate ORM, but using a non-blocking cooperative design. - private final CombinerExecutor executor = new CombinerExecutor( state ); + private final CombinerExecutor executor = new CombinerExecutor<>( state ); /** * Allocate a new block, by obtaining the next "hi" value from the database diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveAbstractMultiIdEntityLoader.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveAbstractMultiIdEntityLoader.java index 527d59326..9b7c16395 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveAbstractMultiIdEntityLoader.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveAbstractMultiIdEntityLoader.java @@ -10,7 +10,7 @@ import java.util.concurrent.CompletionStage; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.event.spi.EventSource; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; @@ -50,7 +50,7 @@ public EntityMappingType getLoadable() { } @Override - public final CompletionStage> reactiveLoad(K[] ids, MultiIdLoadOptions loadOptions, EventSource session) { + public final CompletionStage> reactiveLoad(K[] ids, MultiIdLoadOptions loadOptions, SharedSessionContractImplementor session) { Objects.requireNonNull( ids ); return loadOptions.isOrderReturnEnabled() @@ -58,8 +58,11 @@ public final CompletionStage> reactiveLoad(K[] ids, MultiIdLoadOptio : performUnorderedMultiLoad( ids, loadOptions, session ); } - protected abstract CompletionStage> performOrderedMultiLoad(K[] ids, MultiIdLoadOptions loadOptions, EventSource session); + protected abstract CompletionStage> performOrderedMultiLoad(K[] ids, MultiIdLoadOptions loadOptions, SharedSessionContractImplementor session); - protected abstract CompletionStage> performUnorderedMultiLoad(K[] ids, MultiIdLoadOptions loadOptions, EventSource session); + protected abstract CompletionStage> performUnorderedMultiLoad(K[] ids, MultiIdLoadOptions loadOptions, SharedSessionContractImplementor session); + protected boolean isIdCoercionEnabled() { + return !getSessionFactory().getSessionFactoryOptions().getJpaCompliance().isLoadByIdComplianceEnabled(); + } } 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 8239a6e15..ef81c828e 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 @@ -18,8 +18,8 @@ import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SubselectFetch; -import org.hibernate.event.spi.EventSource; import org.hibernate.loader.ast.internal.LoaderSelectBuilder; import org.hibernate.loader.ast.internal.MultiIdEntityLoaderArrayParam; import org.hibernate.loader.ast.internal.MultiKeyLoadLogging; @@ -82,7 +82,7 @@ public BasicEntityIdentifierMapping getIdentifierMapping() { protected CompletionStage> performOrderedMultiLoad( K[] ids, MultiIdLoadOptions loadOptions, - EventSource session) { + SharedSessionContractImplementor session) { if ( MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) { MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.tracef( "ReactiveMultiIdEntityLoaderArrayParam#performOrderedMultiLoad - %s", @@ -90,7 +90,7 @@ protected CompletionStage> performOrderedMultiLoad( ); } - final boolean coerce = !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled(); + final boolean coerce = isIdCoercionEnabled(); final LockOptions lockOptions = ( loadOptions.getLockOptions() == null ) ? new LockOptions( LockMode.NONE ) : loadOptions.getLockOptions(); @@ -227,7 +227,7 @@ protected CompletionStage> performOrderedMultiLoad( protected CompletionStage> performUnorderedMultiLoad( K[] ids, MultiIdLoadOptions loadOptions, - EventSource session) { + SharedSessionContractImplementor session) { if ( MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) { MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.tracef( "ReactiveMultiIdEntityLoaderArrayParam#performUnorderedMultiLoad - %s", @@ -305,14 +305,14 @@ protected final K[] processResolvableEntities( MultiIdEntityLoaderArrayParam.ResolutionConsumer resolutionConsumer, MultiIdLoadOptions loadOptions, LockOptions lockOptions, - EventSource session) { + SharedSessionContractImplementor session) { if ( !loadOptions.isSessionCheckingEnabled() && !loadOptions.isSecondLevelCacheCheckingEnabled() ) { // we'll load all of them from the database return ids; } - final boolean coerce = !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled(); + final boolean coerce = isIdCoercionEnabled(); boolean foundAnyResolvedEntities = false; List nonResolvedIds = null; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderStandard.java index 92f4e996a..5661c3493 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderStandard.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderStandard.java @@ -24,7 +24,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SubselectFetch; -import org.hibernate.event.spi.EventSource; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.loader.ast.internal.LoaderSelectBuilder; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; @@ -75,7 +74,7 @@ public ReactiveMultiIdEntityLoaderStandard( protected CompletionStage> performOrderedMultiLoad( Object[] ids, MultiIdLoadOptions loadOptions, - EventSource session) { + SharedSessionContractImplementor session) { if ( LOG.isTraceEnabled() ) { LOG.tracef( "#performOrderedMultiLoad(`%s`, ..)", getEntityDescriptor().getEntityName() ); } @@ -104,7 +103,7 @@ protected CompletionStage> performOrderedMultiLoad( final List idsInBatch = new ArrayList<>(); final List elementPositionsLoadedByBatch = new ArrayList<>(); - final boolean coerce = !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled(); + final boolean coerce = isIdCoercionEnabled(); return loop( 0, ids.length, i -> { final Object id = coerce ? getEntityDescriptor().getIdentifierMapping().getJavaType().coerce( ids[i], session ) @@ -289,7 +288,7 @@ private static List singletonList(Object loaded) { protected CompletionStage> performUnorderedMultiLoad( Object[] ids, MultiIdLoadOptions loadOptions, - EventSource session) { + SharedSessionContractImplementor session) { assert !loadOptions.isOrderReturnEnabled(); assert ids != null; @@ -312,7 +311,7 @@ protected CompletionStage> performUnorderedMultiLoad( boolean foundAnyManagedEntities = false; final List nonManagedIds = new ArrayList<>(); - final boolean coerce = !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled(); + final boolean coerce = isIdCoercionEnabled(); for ( Object o : ids ) { final Object id = coerce ? getEntityDescriptor().getIdentifierMapping().getJavaType().coerce( o, session ) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderStandardImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderStandardImpl.java index 684894cf2..c23a00983 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderStandardImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderStandardImpl.java @@ -55,8 +55,7 @@ public CompletionStage load( SharedSessionContractImplementor session) { final ReactiveSingleIdLoadPlan loadPlan = (ReactiveSingleIdLoadPlan) resolveLoadPlan( lockOptions, - session.getLoadQueryInfluencers(), - session.getFactory() + session.getLoadQueryInfluencers() ); return loadPlan.load( key, readOnly, true, session ); } @@ -68,7 +67,7 @@ public CompletionStage load( LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session) { - final ReactiveSingleIdLoadPlan loadPlan = (ReactiveSingleIdLoadPlan) resolveLoadPlan( lockOptions, session.getLoadQueryInfluencers(), session.getFactory() ); + final ReactiveSingleIdLoadPlan loadPlan = (ReactiveSingleIdLoadPlan) resolveLoadPlan( lockOptions, session.getLoadQueryInfluencers() ); return loadPlan.load( key, entityInstance, readOnly, false, session ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/spi/ReactiveMultiIdEntityLoader.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/spi/ReactiveMultiIdEntityLoader.java index 47050b139..e0236e361 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/spi/ReactiveMultiIdEntityLoader.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/spi/ReactiveMultiIdEntityLoader.java @@ -8,7 +8,7 @@ import java.util.List; import java.util.concurrent.CompletionStage; -import org.hibernate.event.spi.EventSource; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.ast.spi.MultiIdEntityLoader; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.reactive.logging.impl.Log; @@ -22,9 +22,9 @@ public interface ReactiveMultiIdEntityLoader extends MultiIdEntityLoader { @Override - default List load(K[] ids, MultiIdLoadOptions options, EventSource session) { + default List load(K[] ids, MultiIdLoadOptions options, SharedSessionContractImplementor session) { throw make( Log.class, lookup() ).nonReactiveMethodCall( "reactiveLoad" ); } - CompletionStage> reactiveLoad(K[] ids, MultiIdLoadOptions options, EventSource session); + CompletionStage> reactiveLoad(K[] ids, MultiIdLoadOptions options, SharedSessionContractImplementor session); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java index 75287e5be..fa29cd8b5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java @@ -7,11 +7,13 @@ +import java.sql.SQLException; import java.sql.SQLWarning; import jakarta.persistence.PersistenceException; import org.hibernate.HibernateException; +import org.hibernate.JDBCException; import org.hibernate.LazyInitializationException; import org.hibernate.cache.CacheException; import org.hibernate.dialect.Dialect; @@ -24,6 +26,7 @@ import org.jboss.logging.annotations.Message; import org.jboss.logging.annotations.MessageLogger; +import static org.jboss.logging.Logger.Level.DEBUG; import static org.jboss.logging.Logger.Level.ERROR; import static org.jboss.logging.Logger.Level.INFO; import static org.jboss.logging.Logger.Level.WARN; @@ -32,8 +35,8 @@ public interface Log extends BasicLogger { @LogMessage(level = INFO) - @Message(id = 1, value = "Hibernate Reactive") - void startHibernateReactive(); + @Message(id = 1, value = "Hibernate Reactive version %s") + void startHibernateReactive(String version); @LogMessage(level = INFO) @Message(id = 2, value = "Vert.x not detected, creating a new instance") @@ -264,6 +267,9 @@ public interface Log extends BasicLogger { @Message(id = 83, value = "Unexpected request of a non reactive connection") HibernateException unexpectedConnectionRequest(); + @Message(id = 84, value = "The application requested a JDBC connection, but Hibernate Reactive doesn't use JDBC. This could be caused by a bug or the use of an unsupported feature in Hibernate Reactive") + SQLException notUsingJdbc(); + // Same method that exists in CoreMessageLogger @LogMessage(level = WARN) @Message(id = 104, value = "firstResult/maxResults specified with collection fetch; applying in memory!" ) @@ -315,4 +321,12 @@ public interface Log extends BasicLogger { @LogMessage(level = WARN) @Message( id= 494, value = "Attempt to merge an uninitialized collection with queued operations; queued operations will be ignored: %s") void ignoreQueuedOperationsOnMerge(String collectionInfoString); + + // Same method in ORM + @LogMessage(level = DEBUG) + @Message(value = "JDBCException was thrown for a transaction marked for rollback. " + + " This is probably due to an operation failing fast due to the transaction being marked for rollback.", + id = 520) + void jdbcExceptionThrownWithTransactionRolledBack(@Cause JDBCException e); + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Version.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Version.java new file mode 100644 index 000000000..2ed6d6b9d --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Version.java @@ -0,0 +1,32 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.logging.impl; + +/** + * Information about the version of Hibernate Reactive. + * + * @author Steve Ebersole + */ +public final class Version { + + private static final String VERSION; + static { + final String version = Version.class.getPackage().getImplementationVersion(); + VERSION = version != null ? version : "[WORKING]"; + } + + private Version() { + } + + /** + * Access to the Hibernate Reactive version. + * + * @return The version + */ + public static String getVersionString() { + return VERSION; + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveEmbeddedIdentifierMappingImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveEmbeddedIdentifierMappingImpl.java index 3f5244fa9..204a23d3a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveEmbeddedIdentifierMappingImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveEmbeddedIdentifierMappingImpl.java @@ -13,6 +13,7 @@ import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.internal.EmbeddedIdentifierMappingImpl; +import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableFetchImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.spi.SqlSelection; @@ -20,6 +21,7 @@ import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.Fetchable; public class ReactiveEmbeddedIdentifierMappingImpl extends AbstractCompositeIdentifierMapping { @@ -104,4 +106,13 @@ public String getSqlAliasStem() { public String getFetchableName() { return delegate.getFetchableName(); } + + @Override + public Fetchable getFetchable(int position) { + Fetchable fetchable = delegate.getFetchable( position ); + if ( fetchable instanceof ToOneAttributeMapping ) { + return new ReactiveToOneAttributeMapping( (ToOneAttributeMapping) fetchable ); + } + return fetchable; + } } 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 59edbb875..bee3a5c1f 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 @@ -5,11 +5,6 @@ */ package org.hibernate.reactive.mutiny; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.function.BiFunction; -import java.util.function.Function; - import org.hibernate.Cache; import org.hibernate.CacheMode; import org.hibernate.Filter; @@ -23,7 +18,6 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.jpa.internal.util.FlushModeTypeHelper; import org.hibernate.proxy.HibernateProxy; -import org.hibernate.query.Order; import org.hibernate.query.Page; import org.hibernate.query.criteria.HibernateCriteriaBuilder; import org.hibernate.query.criteria.JpaCriteriaInsert; @@ -43,12 +37,17 @@ import jakarta.persistence.FlushModeType; import jakarta.persistence.LockModeType; import jakarta.persistence.Parameter; +import jakarta.persistence.TypedQueryReference; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaDelete; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.CriteriaUpdate; import jakarta.persistence.metamodel.Attribute; import jakarta.persistence.metamodel.Metamodel; +import java.lang.invoke.MethodHandles; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; @@ -360,37 +359,6 @@ default SelectionQuery setLockMode(String alias, LockModeType lockModeType) { return setLockMode( alias, convertToLockMode(lockModeType) ); } -// /** -// * Set the {@link LockOptions} to use for the whole query. -// * -// * @see org.hibernate.query.Query#setLockOptions(LockOptions) -// */ -// Query setLockOptions(LockOptions lockOptions); - - /** - * If the result type of this query is an entity class, add one or more - * {@linkplain Order rules} for ordering the query results. - * - * @param orderList one or more instances of {@link Order} - * - * @see Order - * - * @see org.hibernate.query.Query#setOrder(List) - */ - SelectionQuery setOrder(List> orderList); - - /** - * If the result type of this query is an entity class, add a - * {@linkplain Order rule} for ordering the query results. - * - * @param order an instance of {@link Order} - * - * @see Order - * - * @see org.hibernate.query.Query#setOrder(Order) - */ - SelectionQuery setOrder(Order order); - /** * Set the {@link EntityGraph} that will be used as a fetch plan for * the root entity returned by this query. @@ -517,235 +485,554 @@ default Query setLockMode(String alias, LockModeType lockModeType) { Query enableFetchProfile(String profileName); } - /** - * A non-blocking counterpart to the Hibernate {@link org.hibernate.Session} - * interface, allowing a reactive style of interaction with the database. - *

- * The semantics of operations on this interface are identical to the - * semantics of the similarly-named operations of {@code Session}, except - * that the operations are performed asynchronously, returning a {@link Uni} - * without blocking the calling thread. - *

- * Entities associated with an {@code Session} do not support transparent - * lazy association fetching. Instead, {@link #fetch(Object)} should be used - * to explicitly request asynchronous fetching of an association, or the - * association should be fetched eagerly when the entity is first retrieved, - * for example, by: - * - *

    - *
  • {@link #enableFetchProfile(String) enabling a fetch profile}, - *
  • using an {@link EntityGraph}, or - *
  • writing a {@code join fetch} clause in a HQL query. - *
+ * Operations common to objects which act as factories for instances of + * {@link Query}. This is a common supertype of {@link Session} and + * {@link StatelessSession}. * - * @see org.hibernate.Session + * @since 3.0 */ - interface Session extends Closeable { - + interface QueryProducer { /** - * Asynchronously return the persistent instance of the given entity - * class with the given identifier, or {@code null} if there is no such - * persistent instance. If the instance is already associated with - * the session, return the associated instance. This method never - * returns an uninitialized instance. - * - *
-		 * {@code session.find(Book.class, id).map(book -> print(book.getTitle()));}
-		 * 
+ * Create an instance of {@link SelectionQuery} for the given HQL/JPQL + * query string. * - * @param entityClass The entity type - * @param id an identifier + * @param queryString The HQL/JPQL query * - * @return a persistent instance or null via a {@code Uni} + * @return The {@link SelectionQuery} instance for manipulation and execution * - * @see jakarta.persistence.EntityManager#find(Class, Object) + * @see jakarta.persistence.EntityManager#createQuery(String, Class) */ - Uni find(Class entityClass, Object id); + SelectionQuery createSelectionQuery(String queryString, Class resultType); /** - * Asynchronously return the persistent instance of the given entity - * class with the given identifier, requesting the given {@link LockMode}. + * Create a typed {@link org.hibernate.query.Query} instance for the given typed query reference. * - * @param entityClass The entity type - * @param id an identifier - * @param lockMode the requested {@link LockMode} + * @param typedQueryReference the type query reference * - * @return a persistent instance or null via a {@code Uni} + * @return The {@link org.hibernate.query.Query} instance for execution * - * @see #find(Class, Object) - * @see #lock(Object, LockMode) this discussion of lock modes + * @throws IllegalArgumentException if a query has not been + * defined with the name of the typed query reference or if + * the query result is found to not be assignable to + * result class of the typed query reference + * + * @see org.hibernate.query.QueryProducer#createQuery(TypedQueryReference) */ - Uni find(Class entityClass, Object id, LockMode lockMode); + Query createQuery(TypedQueryReference typedQueryReference); /** - * Asynchronously return the persistent instance of the given entity - * class with the given identifier, requesting the given {@link LockModeType}. + * Create an instance of {@link MutationQuery} for the given HQL/JPQL + * update or delete statement. * - * @param entityClass The entity type - * @param id an identifier - * @param lockModeType the requested {@link LockModeType} + * @param queryString The HQL/JPQL query, update or delete statement * - * @return a persistent instance or null via a {@code Uni} + * @return The {@link MutationQuery} instance for manipulation and execution * - * @see #find(Class, Object) - * @see #lock(Object, LockMode) this discussion of lock modes + * @see jakarta.persistence.EntityManager#createQuery(String) */ - default Uni find(Class entityClass, Object id, LockModeType lockModeType) { - return find( entityClass, id, convertToLockMode( lockModeType ) ); - } + MutationQuery createMutationQuery(String queryString); /** - * Asynchronously return the persistent instance with the given - * identifier of an entity class, using the given {@link EntityGraph} - * as a fetch plan. + * Create an instance of {@link MutationQuery} for the given update tree. * - * @param entityGraph an {@link EntityGraph} specifying the entity - * and associations to be fetched - * @param id an identifier + * @param updateQuery the update criteria query * - * @see #find(Class, Object) + * @return The {@link MutationQuery} instance for manipulation and execution + * + * @see org.hibernate.query.QueryProducer#createMutationQuery(CriteriaUpdate) */ - Uni find(EntityGraph entityGraph, Object id); + MutationQuery createMutationQuery(CriteriaUpdate updateQuery); /** - * Asynchronously return the persistent instances of the given entity - * class with the given identifiers, or null if there is no such - * persistent instance. + * Create an instance of {@link MutationQuery} for the given delete tree. * - * @param entityClass The entity type - * @param ids the identifiers + * @param deleteQuery the delete criteria query * - * @return a list of persistent instances and nulls via a {@code Uni} + * @return The {@link MutationQuery} instance for manipulation and execution * - * @see org.hibernate.Session#findMultiple(Class, List, FindOption...) + * @see org.hibernate.query.QueryProducer#createMutationQuery(CriteriaDelete) */ - Uni> find(Class entityClass, Object... ids); + MutationQuery createMutationQuery(CriteriaDelete deleteQuery); /** - * Asynchronously return the persistent instance of the given entity - * class with the given natural identifier, or null if there is no - * such persistent instance. + * Create a {@link MutationQuery} from the given insert select criteria tree * - * @param entityClass The entity type - * @param naturalId the natural identifier + * @param insert the insert select criteria query * - * @return a persistent instance or null via a {@code Uni} + * @return The {@link MutationQuery} instance for manipulation and execution + * + * @see org.hibernate.query.QueryProducer#createMutationQuery(JpaCriteriaInsert) */ - @Incubating - Uni find(Class entityClass, Identifier naturalId); + MutationQuery createMutationQuery(JpaCriteriaInsert insert); /** - * Return the persistent instance of the given entity class with the - * given identifier, assuming that the instance exists. This method - * never results in access to the underlying data store, and thus - * might return a proxied instance that must be initialized explicitly - * using {@link #fetch(Object)}. - *

- * You should not use this method to determine if an instance exists - * (use {@link #find} instead). Use this only to retrieve an instance - * which you safely assume exists, where non-existence would be an - * actual error. + * Create an instance of {@link Query} for the given HQL/JPQL query + * string or HQL/JPQL update or delete statement. In the case of an + * update or delete, the returned {@link Query} must be executed using + * {@link Query#executeUpdate()} which returns an affected row count. * - * @param entityClass a persistent class - * @param id a valid identifier of an existing persistent instance of the class + * @param queryString The HQL/JPQL query, update or delete statement * - * @return the persistent instance or proxy + * @return The {@link Query} instance for manipulation and execution * - * @see jakarta.persistence.EntityManager#getReference(Class, Object) + * @deprecated See explanation in + * {@link org.hibernate.query.QueryProducer#createSelectionQuery(String)} + * + * @see jakarta.persistence.EntityManager#createQuery(String) */ - T getReference(Class entityClass, Object id); + @Deprecated + Query createQuery(String queryString); /** - * Return the persistent instance with the same identity as the given - * instance, which might be detached, assuming that the instance is - * still persistent in the database. This method never results in - * access to the underlying data store, and thus might return a proxy - * that must be initialized explicitly using {@link #fetch(Object)}. + * Create an instance of {@link SelectionQuery} for the given HQL/JPQL + * query string and query result type. * - * @param entity a detached persistent instance + * @param queryString The HQL/JPQL query + * @param resultType the Java type returned in each row of query results * - * @return the persistent instance or proxy + * @return The {@link SelectionQuery} instance for manipulation and execution + * + * @see jakarta.persistence.EntityManager#createQuery(String, Class) */ - T getReference(T entity); + SelectionQuery createQuery(String queryString, Class resultType); /** - * Asynchronously persist the given transient instance, first assigning - * a generated identifier. (Or using the current value of the identifier - * property if the entity has assigned identifiers.) - *

- * This operation cascades to associated instances if the association is - * mapped with {@link jakarta.persistence.CascadeType#PERSIST}. + * Create an instance of {@link SelectionQuery} for the given criteria + * query. * - *

-		 * {@code session.persist(newBook).map(v -> session.flush());}
-		 * 
+ * @param criteriaQuery The {@link CriteriaQuery} * - * @param object a transient instance of a persistent class + * @return The {@link SelectionQuery} instance for manipulation and execution * - * @see jakarta.persistence.EntityManager#persist(Object) + * @see jakarta.persistence.EntityManager#createQuery(String) */ - Uni persist(Object object); + SelectionQuery createQuery(CriteriaQuery criteriaQuery); /** - * Make a transient instance persistent and mark it for later insertion in the - * database. This operation cascades to associated instances if the association - * is mapped with {@link jakarta.persistence.CascadeType#PERSIST}. - *

- * For entities with a {@link jakarta.persistence.GeneratedValue generated id}, - * {@code persist()} ultimately results in generation of an identifier for the - * given instance. But this may happen asynchronously, when the session is - * {@linkplain #flush() flushed}, depending on the identifier generation strategy. + * Create an instance of {@link MutationQuery} for the given criteria update. * - * @param entityName the entity name - * @param object a transient instance to be made persistent - * @see #persist(Object) + * @param criteriaUpdate The {@link CriteriaUpdate} + * + * @return The {@link MutationQuery} instance for manipulation and execution */ - Uni persist(String entityName, Object object); + MutationQuery createQuery(CriteriaUpdate criteriaUpdate); /** - * Persist multiple transient entity instances at once. + * Create an instance of {@link MutationQuery} for the given criteria delete. * - * @see #persist(Object) + * @param criteriaDelete The {@link CriteriaDelete} + * + * @return The {@link MutationQuery} instance for manipulation and execution */ - Uni persistAll(Object... entities); + MutationQuery createQuery(CriteriaDelete criteriaDelete); /** - * Asynchronously remove a persistent instance from the datastore. The - * argument may be an instance associated with the receiving session or - * a transient instance with an identifier associated with existing - * persistent state. - *

- * This operation cascades to associated instances if the association is - * mapped with {@link jakarta.persistence.CascadeType#REMOVE}. + * Create an instance of {@link Query} for the named query. * - *

-		 * {@code session.delete(book).thenAccept(v -> session.flush());}
-		 * 
+ * @param queryName The name of the query * - * @param entity the managed persistent instance to be removed + * @return The {@link Query} instance for manipulation and execution * - * @throws IllegalArgumentException if the given instance is not managed - * @see jakarta.persistence.EntityManager#remove(Object) + * @see jakarta.persistence.EntityManager#createQuery(String) */ - Uni remove(Object entity); + Query createNamedQuery(String queryName); /** - * Remove multiple entity instances at once. + * Create an instance of {@link SelectionQuery} for the named query. * - * @see #remove(Object) + * @param queryName The name of the query + * @param resultType the Java type returned in each row of query results + * + * @return The {@link SelectionQuery} instance for manipulation and execution + * + * @see jakarta.persistence.EntityManager#createQuery(String, Class) */ - Uni removeAll(Object... entities); + SelectionQuery createNamedQuery(String queryName, Class resultType); /** - * Copy the state of the given object onto the persistent instance with - * the same identifier. If there is no such persistent instance currently - * associated with the session, it will be loaded. Return the persistent - * instance. Or, if the given instance is transient, save a copy of it - * and return the copy as a newly persistent instance. The given instance - * does not become associated with the session. - *

- * This operation cascades to associated instances if the association is + * Create an instance of {@link Query} for the given SQL query string, + * or SQL update, insert, or delete statement. In the case of an update, + * insert, or delete, the returned {@link Query} must be executed using + * {@link Query#executeUpdate()} which returns an affected row count. + * In the case of a query: + * + *

    + *
  • If the result set has a single column, the results will be returned + * as scalars.
  • + *
  • Otherwise, if the result set has multiple columns, the results will + * be returned as elements of arrays of type {@code Object[]}.
  • + *
+ * + * @param queryString The SQL select, update, insert, or delete statement + */ + Query createNativeQuery(String queryString); + + /** + * Create an instance of {@link Query} for the given SQL query string, + * or SQL update, insert, or delete statement. In the case of an update, + * insert, or delete, the returned {@link Query} must be executed using + * {@link Query#executeUpdate()} which returns an affected row count. + * In the case of a query: + * + *
    + *
  • If the result set has a single column, the results will be returned + * as scalars.
  • + *
  • Otherwise, if the result set has multiple columns, the results will + * be returned as elements of arrays of type {@code Object[]}.
  • + *
+ *

+ * Any {@link AffectedEntities affected entities} are synchronized with + * the database before execution of the statement. + * + * @param queryString The SQL select, update, insert, or delete statement + * @param affectedEntities The entities which are affected by the statement + */ + Query createNativeQuery(String queryString, AffectedEntities affectedEntities); + + /** + * Create an instance of {@link SelectionQuery} for the given SQL query + * string, using the given {@code resultType} to interpret the results. + * + *

    + *
  • If the given result type is {@link Object}, or a built-in type + * such as {@link String} or {@link Integer}, the result set must + * have a single column, which will be returned as a scalar. + *
  • If the given result type is {@code Object[]}, then the result set + * must have multiple columns, which will be returned in arrays. + *
  • Otherwise, the given result type must be an entity class, in which + * case the result set column aliases must map to the fields of the + * entity, and the query will return instances of the entity. + *
+ * + * @param queryString The SQL query + * @param resultType the Java type returned in each row of query results + * + * @return The {@link SelectionQuery} instance for manipulation and execution + * + * @see jakarta.persistence.EntityManager#createNativeQuery(String, Class) + */ + SelectionQuery createNativeQuery(String queryString, Class resultType); + + /** + * Create an instance of {@link SelectionQuery} for the given SQL query + * string, using the given {@code resultType} to interpret the results. + * + *
    + *
  • If the given result type is {@link Object}, or a built-in type + * such as {@link String} or {@link Integer}, the result set must + * have a single column, which will be returned as a scalar. + *
  • If the given result type is {@code Object[]}, then the result set + * must have multiple columns, which will be returned in arrays. + *
  • Otherwise, the given result type must be an entity class, in which + * case the result set column aliases must map to the fields of the + * entity, and the query will return instances of the entity. + *
+ *

+ * Any {@link AffectedEntities affected entities} are synchronized with + * the database before execution of the query. + * + * @param queryString The SQL query + * @param resultType the Java type returned in each row of query results + * @param affectedEntities The entities which are affected by the query + * + * @return The {@link Query} instance for manipulation and execution + * + * @see jakarta.persistence.EntityManager#createNativeQuery(String, Class) + */ + SelectionQuery createNativeQuery(String queryString, Class resultType, AffectedEntities affectedEntities); + + /** + * Create an instance of {@link SelectionQuery} for the given SQL query + * string, using the given {@link ResultSetMapping} to interpret the + * result set. + * + * @param queryString The SQL query + * @param resultSetMapping the result set mapping + * + * @return The {@link Query} instance for manipulation and execution + * + * @see #getResultSetMapping(Class, String) + * @see jakarta.persistence.EntityManager#createNativeQuery(String, String) + */ + SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping); + + /** + * Create an instance of {@link SelectionQuery} for the given SQL query + * string, using the given {@link ResultSetMapping} to interpret the + * result set. + *

+ * Any {@link AffectedEntities affected entities} are synchronized with the + * database before execution of the query. + * + * @param queryString The SQL query + * @param resultSetMapping the result set mapping + * @param affectedEntities The entities which are affected by the query + * + * @return The {@link SelectionQuery} instance for manipulation and execution + * + * @see #getResultSetMapping(Class, String) + * @see jakarta.persistence.EntityManager#createNativeQuery(String, String) + */ + SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping, AffectedEntities affectedEntities); + + /** + * Obtain a native SQL result set mapping defined via the annotation + * {@link jakarta.persistence.SqlResultSetMapping}. + */ + ResultSetMapping getResultSetMapping(Class resultType, String mappingName); + + /** + * Obtain a named {@link EntityGraph} + */ + EntityGraph getEntityGraph(Class rootType, String graphName); + + /** + * Create a new mutable {@link EntityGraph} + */ + EntityGraph createEntityGraph(Class rootType); + + /** + * Create a new mutable copy of a named {@link EntityGraph} + */ + EntityGraph createEntityGraph(Class rootType, String graphName); + + /** + * Convenience method to obtain the {@link CriteriaBuilder}. + * + * @since 3 + */ + CriteriaBuilder getCriteriaBuilder(); + } + + /** + * A non-blocking counterpart to the Hibernate {@link org.hibernate.Session} + * interface, allowing a reactive style of interaction with the database. + *

+ * The semantics of operations on this interface are identical to the + * semantics of the similarly-named operations of {@code Session}, except + * that the operations are performed asynchronously, returning a {@link Uni} + * without blocking the calling thread. + *

+ * Entities associated with an {@code Session} do not support transparent + * lazy association fetching. Instead, {@link #fetch(Object)} should be used + * to explicitly request asynchronous fetching of an association, or the + * association should be fetched eagerly when the entity is first retrieved, + * for example, by: + * + *

    + *
  • {@link #enableFetchProfile(String) enabling a fetch profile}, + *
  • using an {@link EntityGraph}, or + *
  • writing a {@code join fetch} clause in a HQL query. + *
+ * + * @see org.hibernate.Session + */ + interface Session extends QueryProducer, Closeable { + + /** + * Asynchronously return the persistent instance of the given entity + * class with the given identifier, or {@code null} if there is no such + * persistent instance. If the instance is already associated with + * the session, return the associated instance. This method never + * returns an uninitialized instance. + * + *
+		 * {@code session.find(Book.class, id).map(book -> print(book.getTitle()));}
+		 * 
+ * + * @param entityClass The entity type + * @param id an identifier + * + * @return a persistent instance or null via a {@code Uni} + * + * @see jakarta.persistence.EntityManager#find(Class, Object) + */ + Uni find(Class entityClass, Object id); + + /** + * Asynchronously return the persistent instance of the given entity + * class with the given identifier, requesting the given {@link LockMode}. + * + * @param entityClass The entity type + * @param id an identifier + * @param lockMode the requested {@link LockMode} + * + * @return a persistent instance or null via a {@code Uni} + * + * @see #find(Class, Object) + * @see #lock(Object, LockMode) this discussion of lock modes + */ + Uni find(Class entityClass, Object id, LockMode lockMode); + + /** + * Asynchronously return the persistent instance of the given entity + * class with the given identifier, requesting the given {@link LockModeType}. + * + * @param entityClass The entity type + * @param id an identifier + * @param lockModeType the requested {@link LockModeType} + * + * @return a persistent instance or null via a {@code Uni} + * + * @see #find(Class, Object) + * @see #lock(Object, LockMode) this discussion of lock modes + */ + default Uni find(Class entityClass, Object id, LockModeType lockModeType) { + return find( entityClass, id, convertToLockMode( lockModeType ) ); + } + + /** + * Asynchronously return the persistent instance with the given + * identifier of an entity class, using the given {@link EntityGraph} + * as a fetch plan. + * + * @param entityGraph an {@link EntityGraph} specifying the entity + * and associations to be fetched + * @param id an identifier + * + * @see #find(Class, Object) + */ + Uni find(EntityGraph entityGraph, Object id); + + /** + * Asynchronously return the persistent instances of the given entity + * class with the given identifiers, or null if there is no such + * persistent instance. + * + * @param entityClass The entity type + * @param ids the identifiers + * + * @return a list of persistent instances and nulls via a {@code Uni} + * + * @see org.hibernate.Session#findMultiple(Class, List, FindOption...) + */ + Uni> find(Class entityClass, Object... ids); + + /** + * Asynchronously return the persistent instance of the given entity + * class with the given natural identifier, or null if there is no + * such persistent instance. + * + * @param entityClass The entity type + * @param naturalId the natural identifier + * + * @return a persistent instance or null via a {@code Uni} + */ + @Incubating + Uni find(Class entityClass, Identifier naturalId); + + /** + * Return the persistent instance of the given entity class with the + * given identifier, assuming that the instance exists. This method + * never results in access to the underlying data store, and thus + * might return a proxied instance that must be initialized explicitly + * using {@link #fetch(Object)}. + *

+ * You should not use this method to determine if an instance exists + * (use {@link #find} instead). Use this only to retrieve an instance + * which you safely assume exists, where non-existence would be an + * actual error. + * + * @param entityClass a persistent class + * @param id a valid identifier of an existing persistent instance of the class + * + * @return the persistent instance or proxy + * + * @see jakarta.persistence.EntityManager#getReference(Class, Object) + */ + T getReference(Class entityClass, Object id); + + /** + * Return the persistent instance with the same identity as the given + * instance, which might be detached, assuming that the instance is + * still persistent in the database. This method never results in + * access to the underlying data store, and thus might return a proxy + * that must be initialized explicitly using {@link #fetch(Object)}. + * + * @param entity a detached persistent instance + * + * @return the persistent instance or proxy + */ + T getReference(T entity); + + /** + * Asynchronously persist the given transient instance, first assigning + * a generated identifier. (Or using the current value of the identifier + * property if the entity has assigned identifiers.) + *

+ * This operation cascades to associated instances if the association is + * mapped with {@link jakarta.persistence.CascadeType#PERSIST}. + * + *

+		 * {@code session.persist(newBook).map(v -> session.flush());}
+		 * 
+ * + * @param object a transient instance of a persistent class + * + * @see jakarta.persistence.EntityManager#persist(Object) + */ + Uni persist(Object object); + + /** + * Make a transient instance persistent and mark it for later insertion in the + * database. This operation cascades to associated instances if the association + * is mapped with {@link jakarta.persistence.CascadeType#PERSIST}. + *

+ * For entities with a {@link jakarta.persistence.GeneratedValue generated id}, + * {@code persist()} ultimately results in generation of an identifier for the + * given instance. But this may happen asynchronously, when the session is + * {@linkplain #flush() flushed}, depending on the identifier generation strategy. + * + * @param entityName the entity name + * @param object a transient instance to be made persistent + * @see #persist(Object) + */ + Uni persist(String entityName, Object object); + + /** + * Persist multiple transient entity instances at once. + * + * @see #persist(Object) + */ + Uni persistAll(Object... entities); + + /** + * Asynchronously remove a persistent instance from the datastore. The + * argument may be an instance associated with the receiving session or + * a transient instance with an identifier associated with existing + * persistent state. + *

+ * This operation cascades to associated instances if the association is + * mapped with {@link jakarta.persistence.CascadeType#REMOVE}. + * + *

+		 * {@code session.delete(book).thenAccept(v -> session.flush());}
+		 * 
+ * + * @param entity the managed persistent instance to be removed + * + * @throws IllegalArgumentException if the given instance is not managed + * @see jakarta.persistence.EntityManager#remove(Object) + */ + Uni remove(Object entity); + + /** + * Remove multiple entity instances at once. + * + * @see #remove(Object) + */ + Uni removeAll(Object... entities); + + /** + * Copy the state of the given object onto the persistent instance with + * the same identifier. If there is no such persistent instance currently + * associated with the session, it will be loaded. Return the persistent + * instance. Or, if the given instance is transient, save a copy of it + * and return the copy as a newly persistent instance. The given instance + * does not become associated with the session. + *

+ * This operation cascades to associated instances if the association is * mapped with {@link jakarta.persistence.CascadeType#MERGE}. * * @param entity a detached instance with state to be copied @@ -878,14 +1165,16 @@ default Uni lock(Object entity, LockModeType lockModeType) { Uni flush(); /** - * Asynchronously fetch an association that's configured for lazy loading. + * Asynchronously fetch an association configured for lazy loading. *

*

 		 * {@code session.fetch(author.getBook()).thenAccept(book -> print(book.getTitle()));}
 		 * 
*

*

- * It can also initialize proxys. For example: + * This operation may be even be used to initialize a reference returned by + * {@link #getReference(Class, Object)}. + *

*

 		 * {@code session.fetch(session.getReference(Author.class, authorId))}
 		 * 
@@ -919,295 +1208,23 @@ default Uni lock(Object entity, LockModeType lockModeType) { * {@code session.unproxy(author.getBook()).thenAccept(book -> print(book.getTitle()));} * * - * @param association a lazy-loaded association - * - * @return the fetched association, via a {@code Uni} - * - * @see org.hibernate.Hibernate#unproxy(Object) - */ - Uni unproxy(T association); - - /** - * Determine the current lock mode of the given entity. - */ - LockMode getLockMode(Object entity); - - /** - * Determine if the given instance belongs to this persistence context. - */ - boolean contains(Object entity); - - /** - * Create an instance of {@link SelectionQuery} for the given HQL/JPQL - * query string. - * - * @param queryString The HQL/JPQL query - * - * @return The {@link SelectionQuery} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String, Class) - */ - SelectionQuery createSelectionQuery(String queryString, Class resultType); - - /** - * Create an instance of {@link MutationQuery} for the given HQL/JPQL - * update or delete statement. - * - * @param queryString The HQL/JPQL query, update or delete statement - * - * @return The {@link MutationQuery} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String) - */ - MutationQuery createMutationQuery(String queryString); - - /** - * Create an instance of {@link MutationQuery} for the given update tree. - * - * @param updateQuery the update criteria query - * - * @return The {@link MutationQuery} instance for manipulation and execution - * - * @see org.hibernate.query.QueryProducer#createMutationQuery(CriteriaUpdate) - */ - MutationQuery createMutationQuery(CriteriaUpdate updateQuery); - - /** - * Create an instance of {@link MutationQuery} for the given delete tree. - * - * @param deleteQuery the delete criteria query - * - * @return The {@link MutationQuery} instance for manipulation and execution - * - * @see org.hibernate.query.QueryProducer#createMutationQuery(CriteriaDelete) - */ - MutationQuery createMutationQuery(CriteriaDelete deleteQuery); - - /** - * Create a {@link MutationQuery} from the given insert select criteria tree - * - * @param insert the insert select criteria query - * - * @return The {@link MutationQuery} instance for manipulation and execution - * - * @see org.hibernate.query.QueryProducer#createMutationQuery(JpaCriteriaInsert) - */ - MutationQuery createMutationQuery(JpaCriteriaInsert insert); - - /** - * Create an instance of {@link Query} for the given HQL/JPQL query - * string or HQL/JPQL update or delete statement. In the case of an - * update or delete, the returned {@link Query} must be executed using - * {@link Query#executeUpdate()} which returns an affected row count. - * - * @param queryString The HQL/JPQL query, update or delete statement - * - * @return The {@link Query} instance for manipulation and execution - * - * @deprecated See explanation in - * {@link org.hibernate.query.QueryProducer#createSelectionQuery(String)} - * - * @see jakarta.persistence.EntityManager#createQuery(String) - */ - @Deprecated - Query createQuery(String queryString); - - /** - * Create an instance of {@link SelectionQuery} for the given HQL/JPQL - * query string and query result type. - * - * @param queryString The HQL/JPQL query - * @param resultType the Java type returned in each row of query results - * - * @return The {@link SelectionQuery} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String, Class) - */ - SelectionQuery createQuery(String queryString, Class resultType); - - /** - * Create an instance of {@link SelectionQuery} for the given criteria - * query. - * - * @param criteriaQuery The {@link CriteriaQuery} - * - * @return The {@link SelectionQuery} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String) - */ - SelectionQuery createQuery(CriteriaQuery criteriaQuery); - - /** - * Create an instance of {@link MutationQuery} for the given criteria update. - * - * @param criteriaUpdate The {@link CriteriaUpdate} - * - * @return The {@link MutationQuery} instance for manipulation and execution - */ - MutationQuery createQuery(CriteriaUpdate criteriaUpdate); - - /** - * Create an instance of {@link MutationQuery} for the given criteria delete. - * - * @param criteriaDelete The {@link CriteriaDelete} - * - * @return The {@link MutationQuery} instance for manipulation and execution - */ - MutationQuery createQuery(CriteriaDelete criteriaDelete); - - /** - * Create an instance of {@link Query} for the named query. - * - * @param queryName The name of the query - * - * @return The {@link Query} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String) - */ - Query createNamedQuery(String queryName); - - /** - * Create an instance of {@link SelectionQuery} for the named query. - * - * @param queryName The name of the query - * @param resultType the Java type returned in each row of query results - * - * @return The {@link SelectionQuery} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String, Class) - */ - SelectionQuery createNamedQuery(String queryName, Class resultType); - - /** - * Create an instance of {@link Query} for the given SQL query string, - * or SQL update, insert, or delete statement. In the case of an update, - * insert, or delete, the returned {@link Query} must be executed using - * {@link Query#executeUpdate()} which returns an affected row count. - * In the case of a query: - * - *
    - *
  • If the result set has a single column, the results will be returned - * as scalars.
  • - *
  • Otherwise, if the result set has multiple columns, the results will - * be returned as elements of arrays of type {@code Object[]}.
  • - *
- * - * @param queryString The SQL select, update, insert, or delete statement - */ - Query createNativeQuery(String queryString); - - /** - * Create an instance of {@link Query} for the given SQL query string, - * or SQL update, insert, or delete statement. In the case of an update, - * insert, or delete, the returned {@link Query} must be executed using - * {@link Query#executeUpdate()} which returns an affected row count. - * In the case of a query: - * - *
    - *
  • If the result set has a single column, the results will be returned - * as scalars.
  • - *
  • Otherwise, if the result set has multiple columns, the results will - * be returned as elements of arrays of type {@code Object[]}.
  • - *
- *

- * Any {@link AffectedEntities affected entities} are synchronized with - * the database before execution of the statement. - * - * @param queryString The SQL select, update, insert, or delete statement - * @param affectedEntities The entities which are affected by the statement - */ - Query createNativeQuery(String queryString, AffectedEntities affectedEntities); - - /** - * Create an instance of {@link SelectionQuery} for the given SQL query - * string, using the given {@code resultType} to interpret the results. - * - *

    - *
  • If the given result type is {@link Object}, or a built-in type - * such as {@link String} or {@link Integer}, the result set must - * have a single column, which will be returned as a scalar. - *
  • If the given result type is {@code Object[]}, then the result set - * must have multiple columns, which will be returned in arrays. - *
  • Otherwise, the given result type must be an entity class, in which - * case the result set column aliases must map to the fields of the - * entity, and the query will return instances of the entity. - *
- * - * @param queryString The SQL query - * @param resultType the Java type returned in each row of query results - * - * @return The {@link SelectionQuery} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createNativeQuery(String, Class) - */ - SelectionQuery createNativeQuery(String queryString, Class resultType); - - /** - * Create an instance of {@link SelectionQuery} for the given SQL query - * string, using the given {@code resultType} to interpret the results. - * - *
    - *
  • If the given result type is {@link Object}, or a built-in type - * such as {@link String} or {@link Integer}, the result set must - * have a single column, which will be returned as a scalar. - *
  • If the given result type is {@code Object[]}, then the result set - * must have multiple columns, which will be returned in arrays. - *
  • Otherwise, the given result type must be an entity class, in which - * case the result set column aliases must map to the fields of the - * entity, and the query will return instances of the entity. - *
- *

- * Any {@link AffectedEntities affected entities} are synchronized with - * the database before execution of the query. - * - * @param queryString The SQL query - * @param resultType the Java type returned in each row of query results - * @param affectedEntities The entities which are affected by the query + * @param association a lazy-loaded association * - * @return The {@link Query} instance for manipulation and execution + * @return the fetched association, via a {@code Uni} * - * @see jakarta.persistence.EntityManager#createNativeQuery(String, Class) + * @see org.hibernate.Hibernate#unproxy(Object) */ - SelectionQuery createNativeQuery( - String queryString, Class resultType, - AffectedEntities affectedEntities); + Uni unproxy(T association); /** - * Create an instance of {@link SelectionQuery} for the given SQL query - * string, using the given {@link ResultSetMapping} to interpret the - * result set. - * - * @param queryString The SQL query - * @param resultSetMapping the result set mapping - * - * @return The {@link Query} instance for manipulation and execution - * - * @see #getResultSetMapping(Class, String) - * @see jakarta.persistence.EntityManager#createNativeQuery(String, String) + * Determine the current lock mode of the given entity. */ - SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping); + LockMode getLockMode(Object entity); /** - * Create an instance of {@link SelectionQuery} for the given SQL query - * string, using the given {@link ResultSetMapping} to interpret the - * result set. - *

- * Any {@link AffectedEntities affected entities} are synchronized with the - * database before execution of the query. - * - * @param queryString The SQL query - * @param resultSetMapping the result set mapping - * @param affectedEntities The entities which are affected by the query - * - * @return The {@link SelectionQuery} instance for manipulation and execution - * - * @see #getResultSetMapping(Class, String) - * @see jakarta.persistence.EntityManager#createNativeQuery(String, String) + * Determine if the given instance belongs to this persistence context. */ - SelectionQuery createNativeQuery( - String queryString, - ResultSetMapping resultSetMapping, - AffectedEntities affectedEntities); + boolean contains(Object entity); /** * Set the {@link FlushMode flush mode} for this session. @@ -1279,27 +1296,6 @@ default Session setFlushMode(FlushModeType flushModeType) { */ Session enableFetchProfile(String name); - /** - * Obtain a native SQL result set mapping defined via the annotation - * {@link jakarta.persistence.SqlResultSetMapping}. - */ - ResultSetMapping getResultSetMapping(Class resultType, String mappingName); - - /** - * Obtain a named {@link EntityGraph} - */ - EntityGraph getEntityGraph(Class rootType, String graphName); - - /** - * Create a new mutable {@link EntityGraph} - */ - EntityGraph createEntityGraph(Class rootType); - - /** - * Create a new mutable copy of a named {@link EntityGraph} - */ - EntityGraph createEntityGraph(Class rootType, String graphName); - /** * Disable a particular fetch profile on this session, or do nothing if * the requested fetch profile is not enabled. @@ -1557,7 +1553,7 @@ default Session setCacheRetrieveMode(CacheRetrieveMode cacheRetrieveMode) { * * @see org.hibernate.StatelessSession */ - interface StatelessSession extends Closeable { + interface StatelessSession extends QueryProducer, Closeable { /** * Retrieve a row. @@ -1622,153 +1618,6 @@ default Uni get(Class entityClass, Object id, LockModeType lockModeTyp */ Uni get(EntityGraph entityGraph, Object id); - /** - * Create an instance of {@link Query} for the given HQL/JPQL query - * string or HQL/JPQL update or delete statement. In the case of an - * update or delete, the returned {@link Query} must be executed using - * {@link Query#executeUpdate()} which returns an affected row count. - * - * @param queryString The HQL/JPQL query, update or delete statement - * - * @return The {@link Query} instance for manipulation and execution - * - * @deprecated See explanation in - * {@link org.hibernate.query.QueryProducer#createSelectionQuery(String)} - * - * @see Session#createQuery(String) - */ - @Deprecated - Query createQuery(String queryString); - - /** - * Create an instance of {@link SelectionQuery} for the given HQL/JPQL - * query string and query result type. - * - * @param queryString The HQL/JPQL query - * @param resultType the Java type returned in each row of query results - * - * @return The {@link Query} instance for manipulation and execution - * - * @see Session#createQuery(String, Class) - */ - SelectionQuery createQuery(String queryString, Class resultType); - - /** - * Create an instance of {@link SelectionQuery} for the given HQL/JPQL - * query string. - * - * @param queryString The HQL/JPQL query - * - * @return The {@link SelectionQuery} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String, Class) - */ - SelectionQuery createSelectionQuery(String queryString, Class resultType); - - /** - * Create an instance of {@link MutationQuery} for the given HQL/JPQL - * update or delete statement. - * - * @param queryString The HQL/JPQL query, update or delete statement - * - * @return The {@link MutationQuery} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String) - */ - MutationQuery createMutationQuery(String queryString); - - /** - * Create an instance of {@link Query} for the named query. - * - * @param queryName The name of the query - * - * @return The {@link Query} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String) - */ - Query createNamedQuery(String queryName); - - /** - * Create an instance of {@link SelectionQuery} for the named query. - * - * @param queryName The name of the query - * @param resultType the Java type returned in each row of query results - * - * @return The {@link SelectionQuery} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String, Class) - */ - SelectionQuery createNamedQuery(String queryName, Class resultType); - - /** - * Create an instance of {@link Query} for the given SQL query string, - * or SQL update, insert, or delete statement. In the case of an update, - * insert, or delete, the returned {@link Query} must be executed using - * {@link Query#executeUpdate()} which returns an affected row count. - * - * @param queryString The SQL select, update, insert, or delete statement - * - * @see Session#createNativeQuery(String) - */ - Query createNativeQuery(String queryString); - - /** - * Create an instance of {@link SelectionQuery} for the given SQL query - * string, using the given {@code resultType} to interpret the results. - * - * @param queryString The SQL query - * @param resultType the Java type returned in each row of query results - * - * @return The {@link Query} instance for manipulation and execution - * - * @see Session#createNativeQuery(String, Class) - */ - SelectionQuery createNativeQuery(String queryString, Class resultType); - - /** - * Create an instance of {@link SelectionQuery} for the given SQL query - * string, using the given {@link ResultSetMapping} to interpret the - * result set. - * - * @param queryString The SQL query - * @param resultSetMapping the result set mapping - * - * @return The {@link Query} instance for manipulation and execution - * - * @see Session#createNativeQuery(String, ResultSetMapping) - */ - SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping); - - /** - * Create an instance of {@link SelectionQuery} for the given criteria - * query. - * - * @param criteriaQuery The {@link CriteriaQuery} - * - * @return The {@link SelectionQuery} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String) - */ - SelectionQuery createQuery(CriteriaQuery criteriaQuery); - - /** - * Create an instance of {@link MutationQuery} for the given criteria update. - * - * @param criteriaUpdate The {@link CriteriaUpdate} - * - * @return The {@link MutationQuery} instance for manipulation and execution - */ - MutationQuery createQuery(CriteriaUpdate criteriaUpdate); - - /** - * Create an instance of {@link MutationQuery} for the given criteria delete. - * - * @param criteriaDelete The {@link CriteriaDelete} - * - * @return The {@link MutationQuery} instance for manipulation and execution - */ - MutationQuery createQuery(CriteriaDelete criteriaDelete); - /** * Insert a row. * @@ -1896,18 +1745,6 @@ default Uni get(Class entityClass, Object id, LockModeType lockModeTyp @Incubating Uni upsert(Object entity); - /** - * Use a SQL {@code merge into} statement to perform an upsert. - * - * @param entityName The entityName for the entity to be merged - * @param entity a detached entity instance - * @throws org.hibernate.TransientObjectException is the entity is transient - * - * @see org.hibernate.StatelessSession#upsert(String, Object) - */ - @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 @@ -2035,27 +1872,6 @@ default Uni refresh(Object entity, LockModeType lockModeType) { */ Object getIdentifier(Object entity); - /** - * Obtain a native SQL result set mapping defined via the annotation - * {@link jakarta.persistence.SqlResultSetMapping}. - */ - ResultSetMapping getResultSetMapping(Class resultType, String mappingName); - - /** - * Obtain a named {@link EntityGraph} - */ - EntityGraph getEntityGraph(Class rootType, String graphName); - - /** - * Create a new mutable {@link EntityGraph} - */ - EntityGraph createEntityGraph(Class rootType); - - /** - * Create a new mutable copy of a named {@link EntityGraph} - */ - EntityGraph createEntityGraph(Class rootType, String graphName); - /** * Performs the given work within the scope of a database transaction, * automatically flushing the session. The transaction will be rolled @@ -2214,7 +2030,7 @@ interface SessionFactory extends AutoCloseable { /** - * Perform work using a {@link Session reactive session}. + * Perform work using a {@linkplain Session reactive session}. *

* *

  • If there is already a session associated with the current @@ -2233,7 +2049,7 @@ interface SessionFactory extends AutoCloseable { Uni withSession(Function> work); /** - * Perform work using a {@link Session reactive session} for + * Perform work using a {@linkplain Session reactive session} for * a specified tenant. *

    * @@ -2253,12 +2069,12 @@ interface SessionFactory extends AutoCloseable { Uni withSession(String tenantId, Function> work); /** - * Perform work using a {@link Session reactive session} within an - * associated {@link Transaction transaction}. + * Perform work using a {@linkplain Session reactive session} + * within an associated {@link Transaction transaction}. *

    * - *

  • If there is already a session associated with the - * current reactive stream, then the work will be executed using that + *
  • If there is already a session associated with the current + * reactive stream, then the work will be executed using that * session. *
  • Otherwise, if there is no session associated with the * current stream, a new session will be created. @@ -2276,12 +2092,12 @@ interface SessionFactory extends AutoCloseable { Uni withTransaction(BiFunction> work); /** - * Perform work using a {@link Session reactive session} within an - * associated transaction. + * Perform work using a {@linkplain Session reactive session} + * within an associated transaction. *

    * - *

  • If there is already a session associated with the - * current reactive stream, then the work will be executed using that + *
  • If there is already a session associated with the current + * reactive stream, then the work will be executed using that * session. *
  • Otherwise, if there is no session associated with the * current stream, a new session will be created. @@ -2301,8 +2117,8 @@ default Uni withTransaction(Function> work) { } /** - * Perform work using a {@link StatelessSession reactive session} within an - * associated {@link Transaction transaction}. + * Perform work using a {@linkplain StatelessSession reactive session} + * within an associated {@link Transaction transaction}. *

    * *

  • If there is already a stateless session associated with the @@ -2312,10 +2128,11 @@ default Uni withTransaction(Function> work) { * current stream, a new stateless session will be created. * *

    - * The session will be closed automatically and the transaction committed automatically. + * The session will be closed automatically and the transaction committed + * automatically. * - * @param work a function which accepts the stateless session and returns - * the result of the work as a {@link Uni}. + * @param work a function which accepts the stateless session and + * returns the result of the work as a {@link Uni}. * * @see #withStatelessSession(Function) * @see StatelessSession#withTransaction(Function) @@ -2325,8 +2142,8 @@ default Uni withStatelessTransaction(Function> w } /** - * Perform work using a {@link StatelessSession reactive session} within an - * associated {@link Transaction transaction}. + * Perform work using a {@linkplain StatelessSession reactive session} + * within an associated {@link Transaction transaction}. *

    * *

  • If there is already a stateless session associated with the @@ -2336,7 +2153,8 @@ default Uni withStatelessTransaction(Function> w * current stream, a new stateless session will be created. * *

    - * The session will be closed automatically and the transaction committed automatically. + * The session will be closed automatically and the transaction committed + * automatically. * * @param work a function which accepts the stateless session and returns * the result of the work as a {@link Uni}. @@ -2347,7 +2165,7 @@ default Uni withStatelessTransaction(Function> w Uni withStatelessTransaction(BiFunction> work); /** - * Perform work using a {@link StatelessSession stateless session}. + * Perform work using a {@linkplain StatelessSession stateless session}. *

    * *

  • If there is already a stateless session associated with the @@ -2365,14 +2183,15 @@ default Uni withStatelessTransaction(Function> w Uni withStatelessSession(Function> work); /** - * Perform work using a {@link StatelessSession stateless session}. + * Perform work using a {@linkplain StatelessSession stateless session}. *

    * *

  • If there is already a stateless session associated with the - * current reactive stream and given tenant id, then the work will be executed using that - * session. + * current reactive stream and given tenant id, then the work will be + * executed using that session. *
  • Otherwise, if there is no stateless session associated with the - * current stream and given tenant id, a new stateless session will be created. + * current stream and given tenant id, a new stateless session will be + * created. * *

    * The session will be closed automatically. @@ -2384,15 +2203,17 @@ default Uni withStatelessTransaction(Function> w Uni withStatelessSession(String tenantId, Function> work); /** - * Perform work using a {@link Session reactive session} for a - * specified tenant within an associated {@link Transaction transaction}. + * Perform work using a {@linkplain Session reactive session} for + * the tenant with the specified tenant id within an associated + * {@link Transaction transaction}. *

    * - *

  • If there is already a session associated with the - * current reactive stream and given tenant id, then the work will be executed using that - * session. + *
  • If there is already a session associated with the current + * reactive stream and given tenant id, then the work will be + * executed using that session. *
  • Otherwise, if there is no session associated with the - * current stream and given tenant id, a new stateless session will be created. + * current stream and given tenant id, a new stateless session + * will be created. * *

    * The session will be {@link Session#flush() flushed} and closed @@ -2408,18 +2229,21 @@ default Uni withStatelessTransaction(Function> w Uni withTransaction(String tenantId, BiFunction> work); /** - * Perform work using a {@link StatelessSession reactive session} for a - * specified tenant within an associated {@link Transaction transaction}. + * Perform work using a {@linkplain StatelessSession reactive session} + * for the tenant with the specified tenant id within an associated + * {@link Transaction transaction}. *

    * *

  • If there is already a stateless session associated with the - * current reactive stream and given tenant id, then the work will be executed using that - * session. + * current reactive stream and given tenant id, then the work will be + * executed using that session. *
  • Otherwise, if there is no stateless session associated with the - * current stream and given tenant id, a new stateless session will be created. + * current stream and given tenant id, a new stateless session will be + * created. * *

    - * The session will be closed automatically and the transaction committed automatically. + * The session will be closed automatically and the transaction committed + * automatically. * * @param tenantId the id of the tenant * @param work a function which accepts the stateless session and returns @@ -2453,6 +2277,31 @@ default Uni withStatelessTransaction(Function> w */ Statistics getStatistics(); + /** + * Return the current instance of {@link Session}, if any. + * A current session exists only when this method is called + * from within an invocation of {@link #withSession(Function)} + * or {@link #withTransaction(Function)}. + * + * @return the current instance, if any, or {@code null} + * + * @since 3.0 + */ + Session getCurrentSession(); + + /** + * Return the current instance of {@link Session}, if any. + * A current session exists only when this method is called + * from within an invocation of + * {@link #withStatelessSession(Function)} or + * {@link #withStatelessTransaction(Function)}. + * + * @return the current instance, if any, or {@code null} + * + * @since 3.0 + */ + StatelessSession getCurrentStatelessSession(); + /** * Destroy the session factory and clean up its connection pool. */ diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinySessionDelegator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinySessionDelegator.java new file mode 100644 index 000000000..82f5ccd18 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinySessionDelegator.java @@ -0,0 +1,364 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.mutiny.delegation; + +import io.smallrye.mutiny.Uni; +import jakarta.persistence.CacheRetrieveMode; +import jakarta.persistence.CacheStoreMode; +import jakarta.persistence.EntityGraph; +import jakarta.persistence.FlushModeType; +import jakarta.persistence.LockModeType; +import jakarta.persistence.TypedQueryReference; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaDelete; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.CriteriaUpdate; +import jakarta.persistence.metamodel.Attribute; +import org.hibernate.*; +import org.hibernate.query.criteria.JpaCriteriaInsert; +import org.hibernate.reactive.common.AffectedEntities; +import org.hibernate.reactive.common.Identifier; +import org.hibernate.reactive.common.ResultSetMapping; +import org.hibernate.reactive.mutiny.Mutiny; + +import java.util.List; +import java.util.function.Function; + +/** + * Wraps a {@linkplain #delegate} session. + * + * @author Gavin King + */ +public abstract class MutinySessionDelegator implements Mutiny.Session { + + public abstract Mutiny.Session delegate(); + + public Uni find(Class entityClass, Object id) { + return delegate().find(entityClass, id); + } + + public Uni find(Class entityClass, Object id, LockModeType lockModeType) { + return delegate().find(entityClass, id, lockModeType); + } + + public Uni find(Class entityClass, Object id, LockMode lockMode) { + return delegate().find(entityClass, id, lockMode); + } + + public Uni find(EntityGraph entityGraph, Object id) { + return delegate().find(entityGraph, id); + } + + public Uni> find(Class entityClass, Object... ids) { + return delegate().find(entityClass, ids); + } + + public Mutiny.SelectionQuery createNamedQuery(String queryName, Class resultType) { + return delegate().createNamedQuery(queryName, resultType); + } + + public Mutiny.SelectionQuery createQuery(String queryString, Class resultType) { + return delegate().createQuery(queryString, resultType); + } + + public boolean isReadOnly(Object entityOrProxy) { + return delegate().isReadOnly(entityOrProxy); + } + + public Mutiny.SelectionQuery createNativeQuery(String queryString, Class resultType, AffectedEntities affectedEntities) { + return delegate().createNativeQuery(queryString, resultType, affectedEntities); + } + + public boolean isDefaultReadOnly() { + return delegate().isDefaultReadOnly(); + } + + public Uni unproxy(T association) { + return delegate().unproxy(association); + } + + public Mutiny.MutationQuery createMutationQuery(String queryString) { + return delegate().createMutationQuery(queryString); + } + + public Uni close() { + return delegate().close(); + } + + public Mutiny.Session disableFetchProfile(String name) { + return delegate().disableFetchProfile(name); + } + + public EntityGraph getEntityGraph(Class rootType, String graphName) { + return delegate().getEntityGraph(rootType, graphName); + } + + public Mutiny.SelectionQuery createSelectionQuery(String queryString, Class resultType) { + return delegate().createSelectionQuery(queryString, resultType); + } + + public Uni refresh(Object entity, LockModeType lockModeType) { + return delegate().refresh(entity, lockModeType); + } + + public Uni lock(Object entity, LockModeType lockModeType) { + return delegate().lock(entity, lockModeType); + } + + public Mutiny.Query createQuery(TypedQueryReference typedQueryReference) { + return delegate().createQuery(typedQueryReference); + } + + public Mutiny.SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping, AffectedEntities affectedEntities) { + return delegate().createNativeQuery(queryString, resultSetMapping, affectedEntities); + } + + public Uni lock(Object entity, LockMode lockMode) { + return delegate().lock(entity, lockMode); + } + + @Incubating + public Uni find(Class entityClass, Identifier naturalId) { + return delegate().find(entityClass, naturalId); + } + + public Uni withTransaction(Function> work) { + return delegate().withTransaction(work); + } + + public Mutiny.SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping) { + return delegate().createNativeQuery(queryString, resultSetMapping); + } + + public EntityGraph createEntityGraph(Class rootType, String graphName) { + return delegate().createEntityGraph(rootType, graphName); + } + + public Mutiny.Transaction currentTransaction() { + return delegate().currentTransaction(); + } + + public Mutiny.Session detach(Object entity) { + return delegate().detach(entity); + } + + public Mutiny.Session setCacheStoreMode(CacheStoreMode cacheStoreMode) { + return delegate().setCacheStoreMode(cacheStoreMode); + } + + public FlushMode getFlushMode() { + return delegate().getFlushMode(); + } + + public LockMode getLockMode(Object entity) { + return delegate().getLockMode(entity); + } + + public Mutiny.Query createNamedQuery(String queryName) { + return delegate().createNamedQuery(queryName); + } + + public CriteriaBuilder getCriteriaBuilder() { + return delegate().getCriteriaBuilder(); + } + + public Mutiny.SessionFactory getFactory() { + return delegate().getFactory(); + } + + public Mutiny.SelectionQuery createNativeQuery(String queryString, Class resultType) { + return delegate().createNativeQuery(queryString, resultType); + } + + public Mutiny.Session setSubselectFetchingEnabled(boolean enabled) { + return delegate().setSubselectFetchingEnabled(enabled); + } + + public Mutiny.Session setFlushMode(FlushMode flushMode) { + return delegate().setFlushMode(flushMode); + } + + public Uni remove(Object entity) { + return delegate().remove(entity); + } + + public Mutiny.Session setCacheMode(CacheMode cacheMode) { + return delegate().setCacheMode(cacheMode); + } + + public Filter enableFilter(String filterName) { + return delegate().enableFilter(filterName); + } + + public Mutiny.MutationQuery createMutationQuery(JpaCriteriaInsert insert) { + return delegate().createMutationQuery(insert); + } + + public Mutiny.Query createNativeQuery(String queryString, AffectedEntities affectedEntities) { + return delegate().createNativeQuery(queryString, affectedEntities); + } + + public Mutiny.Session setReadOnly(Object entityOrProxy, boolean readOnly) { + return delegate().setReadOnly(entityOrProxy, readOnly); + } + + public EntityGraph createEntityGraph(Class rootType) { + return delegate().createEntityGraph(rootType); + } + + public Uni refreshAll(Object... entities) { + return delegate().refreshAll(entities); + } + + public Mutiny.MutationQuery createMutationQuery(CriteriaDelete deleteQuery) { + return delegate().createMutationQuery(deleteQuery); + } + + public Integer getBatchSize() { + return delegate().getBatchSize(); + } + + public Uni refresh(Object entity, LockMode lockMode) { + return delegate().refresh(entity, lockMode); + } + + public T getReference(Class entityClass, Object id) { + return delegate().getReference(entityClass, id); + } + + public T getReference(T entity) { + return delegate().getReference(entity); + } + + public Mutiny.Session setBatchSize(Integer batchSize) { + return delegate().setBatchSize(batchSize); + } + + public Uni refresh(Object entity) { + return delegate().refresh(entity); + } + + public CacheMode getCacheMode() { + return delegate().getCacheMode(); + } + + public Uni mergeAll(Object... entities) { + return delegate().mergeAll(entities); + } + + public Uni persist(Object object) { + return delegate().persist(object); + } + + public boolean contains(Object entity) { + return delegate().contains(entity); + } + + public Mutiny.MutationQuery createMutationQuery(CriteriaUpdate updateQuery) { + return delegate().createMutationQuery(updateQuery); + } + + public int getFetchBatchSize() { + return delegate().getFetchBatchSize(); + } + + public Mutiny.Session setDefaultReadOnly(boolean readOnly) { + return delegate().setDefaultReadOnly(readOnly); + } + + public Mutiny.Session clear() { + return delegate().clear(); + } + + public Uni fetch(E entity, Attribute field) { + return delegate().fetch(entity, field); + } + + public Mutiny.SelectionQuery createQuery(CriteriaQuery criteriaQuery) { + return delegate().createQuery(criteriaQuery); + } + + public Mutiny.Session setCacheRetrieveMode(CacheRetrieveMode cacheRetrieveMode) { + return delegate().setCacheRetrieveMode(cacheRetrieveMode); + } + + public Uni removeAll(Object... entities) { + return delegate().removeAll(entities); + } + + public Filter getEnabledFilter(String filterName) { + return delegate().getEnabledFilter(filterName); + } + + public void disableFilter(String filterName) { + delegate().disableFilter(filterName); + } + + public Mutiny.MutationQuery createQuery(CriteriaDelete criteriaDelete) { + return delegate().createQuery(criteriaDelete); + } + + public Mutiny.Session enableFetchProfile(String name) { + return delegate().enableFetchProfile(name); + } + + public ResultSetMapping getResultSetMapping(Class resultType, String mappingName) { + return delegate().getResultSetMapping(resultType, mappingName); + } + + public Uni flush() { + return delegate().flush(); + } + + public Uni persist(String entityName, Object object) { + return delegate().persist(entityName, object); + } + + public Mutiny.Query createNativeQuery(String queryString) { + return delegate().createNativeQuery(queryString); + } + + public boolean isFetchProfileEnabled(String name) { + return delegate().isFetchProfileEnabled(name); + } + + public Uni merge(T entity) { + return delegate().merge(entity); + } + + public boolean isSubselectFetchingEnabled() { + return delegate().isSubselectFetchingEnabled(); + } + + public Mutiny.MutationQuery createQuery(CriteriaUpdate criteriaUpdate) { + return delegate().createQuery(criteriaUpdate); + } + + public Mutiny.Session setFetchBatchSize(int batchSize) { + return delegate().setFetchBatchSize(batchSize); + } + + public Uni fetch(T association) { + return delegate().fetch(association); + } + + public Uni persistAll(Object... entities) { + return delegate().persistAll(entities); + } + + @Deprecated + public Mutiny.Query createQuery(String queryString) { + return delegate().createQuery(queryString); + } + + public Mutiny.Session setFlushMode(FlushModeType flushModeType) { + return delegate().setFlushMode(flushModeType); + } + + public boolean isOpen() { + return delegate().isOpen(); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinyStatelessSessionDelegator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinyStatelessSessionDelegator.java new file mode 100644 index 000000000..ca19b71e7 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinyStatelessSessionDelegator.java @@ -0,0 +1,277 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.mutiny.delegation; + +import io.smallrye.mutiny.Uni; +import jakarta.persistence.EntityGraph; +import jakarta.persistence.LockModeType; +import jakarta.persistence.TypedQueryReference; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaDelete; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.CriteriaUpdate; +import org.hibernate.Incubating; +import org.hibernate.LockMode; +import org.hibernate.query.criteria.JpaCriteriaInsert; +import org.hibernate.reactive.common.AffectedEntities; +import org.hibernate.reactive.common.ResultSetMapping; +import org.hibernate.reactive.mutiny.Mutiny; + +import java.util.List; +import java.util.function.Function; + +/** + * Wraps a {@linkplain #delegate} stateless session. + * + * @author Gavin King + */ +public abstract class MutinyStatelessSessionDelegator implements Mutiny.StatelessSession { + + public abstract Mutiny.StatelessSession delegate(); + + public Uni get(Class entityClass, Object id) { + return delegate().get(entityClass, id); + } + + @Deprecated + public Mutiny.Query createQuery(String queryString) { + return delegate().createQuery(queryString); + } + + public Uni get(EntityGraph entityGraph, Object id) { + return delegate().get(entityGraph, id); + } + + public Uni> get(Class entityClass, Object... ids) { + return delegate().get(entityClass, ids); + } + + public Mutiny.SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping) { + return delegate().createNativeQuery(queryString, resultSetMapping); + } + + public Object getIdentifier(Object entity) { + return delegate().getIdentifier(entity); + } + + public Uni get(Class entityClass, Object id, LockMode lockMode) { + return delegate().get(entityClass, id, lockMode); + } + + public boolean isOpen() { + return delegate().isOpen(); + } + + public CriteriaBuilder getCriteriaBuilder() { + return delegate().getCriteriaBuilder(); + } + + public Uni insertAll(Object... entities) { + return delegate().insertAll(entities); + } + + public Uni updateAll(int batchSize, Object... entities) { + return delegate().updateAll(batchSize, entities); + } + + public EntityGraph createEntityGraph(Class rootType, String graphName) { + return delegate().createEntityGraph(rootType, graphName); + } + + public EntityGraph getEntityGraph(Class rootType, String graphName) { + return delegate().getEntityGraph(rootType, graphName); + } + + public Uni get(Class entityClass, Object id, LockModeType lockModeType) { + return delegate().get(entityClass, id, lockModeType); + } + + public Uni update(Object entity) { + return delegate().update(entity); + } + + public Uni refreshAll(int batchSize, Object... entities) { + return delegate().refreshAll(batchSize, entities); + } + + public Mutiny.Query createQuery(TypedQueryReference typedQueryReference) { + return delegate().createQuery(typedQueryReference); + } + + public Mutiny.SelectionQuery createQuery(String queryString, Class resultType) { + return delegate().createQuery(queryString, resultType); + } + + public Uni delete(Object entity) { + return delegate().delete(entity); + } + + public Uni refresh(Object entity, LockModeType lockModeType) { + return delegate().refresh(entity, lockModeType); + } + + public Mutiny.SessionFactory getFactory() { + return delegate().getFactory(); + } + + public Mutiny.SelectionQuery createNativeQuery(String queryString, Class resultType) { + return delegate().createNativeQuery(queryString, resultType); + } + + public Uni deleteMultiple(List entities) { + return delegate().deleteMultiple(entities); + } + + public Uni deleteAll(Object... entities) { + return delegate().deleteAll(entities); + } + + public Uni updateMultiple(List entities) { + return delegate().updateMultiple(entities); + } + + public Mutiny.SelectionQuery createSelectionQuery(String queryString, Class resultType) { + return delegate().createSelectionQuery(queryString, resultType); + } + + @Incubating + public Uni upsert(Object entity) { + return delegate().upsert(entity); + } + + public Mutiny.MutationQuery createMutationQuery(String queryString) { + return delegate().createMutationQuery(queryString); + } + + public Uni refresh(Object entity) { + return delegate().refresh(entity); + } + + public ResultSetMapping getResultSetMapping(Class resultType, String mappingName) { + return delegate().getResultSetMapping(resultType, mappingName); + } + + @Incubating + public Uni upsertMultiple(List entities) { + return delegate().upsertMultiple(entities); + } + + public Mutiny.MutationQuery createQuery(CriteriaUpdate criteriaUpdate) { + return delegate().createQuery(criteriaUpdate); + } + + public Uni insertAll(int batchSize, Object... entities) { + return delegate().insertAll(batchSize, entities); + } + + public Mutiny.Query createNativeQuery(String queryString) { + return delegate().createNativeQuery(queryString); + } + + public Mutiny.Query createNamedQuery(String queryName) { + return delegate().createNamedQuery(queryName); + } + + public Mutiny.SelectionQuery createNamedQuery(String queryName, Class resultType) { + return delegate().createNamedQuery(queryName, resultType); + } + + public Uni refreshAll(Object... entities) { + return delegate().refreshAll(entities); + } + + public Uni close() { + return delegate().close(); + } + + public Uni updateAll(Object... entities) { + return delegate().updateAll(entities); + } + + public Mutiny.SelectionQuery createQuery(CriteriaQuery criteriaQuery) { + return delegate().createQuery(criteriaQuery); + } + + public Mutiny.MutationQuery createQuery(CriteriaDelete criteriaDelete) { + return delegate().createQuery(criteriaDelete); + } + + public Uni withTransaction(Function> work) { + return delegate().withTransaction(work); + } + + public Uni refreshMultiple(List entities) { + return delegate().refreshMultiple(entities); + } + + public Uni fetch(T association) { + return delegate().fetch(association); + } + + @Incubating + public Uni upsertAll(Object... entities) { + return delegate().upsertAll(entities); + } + + public Mutiny.Transaction currentTransaction() { + return delegate().currentTransaction(); + } + + @Incubating + public Uni upsertAll(int batchSize, Object... entities) { + return delegate().upsertAll(batchSize, entities); + } + + public EntityGraph createEntityGraph(Class rootType) { + return delegate().createEntityGraph(rootType); + } + + public Uni insert(Object entity) { + return delegate().insert(entity); + } + + public Uni insertMultiple(List entities) { + return delegate().insertMultiple(entities); + } + + public Uni refresh(Object entity, LockMode lockMode) { + return delegate().refresh(entity, lockMode); + } + + public Uni deleteAll(int batchSize, Object... entities) { + return delegate().deleteAll(batchSize, entities); + } + + @Override + public Mutiny.MutationQuery createMutationQuery(CriteriaUpdate updateQuery) { + return delegate().createMutationQuery(updateQuery); + } + + @Override + public Mutiny.MutationQuery createMutationQuery(CriteriaDelete deleteQuery) { + return delegate().createMutationQuery(deleteQuery); + } + + @Override + public Mutiny.MutationQuery createMutationQuery(JpaCriteriaInsert insert) { + return delegate().createMutationQuery(insert); + } + + @Override + public Mutiny.Query createNativeQuery(String queryString, AffectedEntities affectedEntities) { + return delegate().createNativeQuery(queryString, affectedEntities); + } + + @Override + public Mutiny.SelectionQuery createNativeQuery(String queryString, Class resultType, AffectedEntities affectedEntities) { + return delegate().createNativeQuery(queryString, resultType, affectedEntities); + } + + @Override + public Mutiny.SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping, AffectedEntities affectedEntities) { + return delegate().createNativeQuery(queryString, resultSetMapping, affectedEntities); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyQueryImpl.java index d0608ba5f..88243f77d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyQueryImpl.java @@ -5,16 +5,11 @@ */ package org.hibernate.reactive.mutiny.impl; -import java.util.List; -import java.util.concurrent.CompletionStage; -import java.util.function.Supplier; - import org.hibernate.CacheMode; import org.hibernate.FlushMode; import org.hibernate.LockMode; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.spi.RootGraphImplementor; -import org.hibernate.query.Order; import org.hibernate.query.Page; import org.hibernate.reactive.mutiny.Mutiny; import org.hibernate.reactive.mutiny.Mutiny.Query; @@ -27,6 +22,9 @@ import jakarta.persistence.FlushModeType; import jakarta.persistence.LockModeType; import jakarta.persistence.Parameter; +import java.util.List; +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; public class MutinyQueryImpl implements Query { @@ -74,18 +72,6 @@ public Query setLockMode(LockMode lockMode) { return this; } - @Override - public Mutiny.SelectionQuery setOrder(List> orders) { - delegate.setOrder( orders ); - return this; - } - - @Override - public Mutiny.SelectionQuery setOrder(Order order) { - delegate.setOrder( (List>) order ); - return this; - } - @Override public Query setPlan(EntityGraph entityGraph) { delegate.applyGraph( (RootGraphImplementor) entityGraph, GraphSemantic.FETCH ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySelectionQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySelectionQueryImpl.java index b001e618a..fb4fe2ad2 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySelectionQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySelectionQueryImpl.java @@ -5,16 +5,11 @@ */ package org.hibernate.reactive.mutiny.impl; -import java.util.List; -import java.util.concurrent.CompletionStage; -import java.util.function.Supplier; - import org.hibernate.CacheMode; import org.hibernate.FlushMode; import org.hibernate.LockMode; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.spi.RootGraphImplementor; -import org.hibernate.query.Order; import org.hibernate.query.Page; import org.hibernate.reactive.mutiny.Mutiny.SelectionQuery; import org.hibernate.reactive.query.ReactiveSelectionQuery; @@ -26,6 +21,9 @@ import jakarta.persistence.FlushModeType; import jakarta.persistence.LockModeType; import jakarta.persistence.Parameter; +import java.util.List; +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; public class MutinySelectionQueryImpl implements SelectionQuery { private final MutinySessionFactoryImpl factory; @@ -196,19 +194,6 @@ public SelectionQuery setLockMode(String alias, LockMode lockMode) { return this; } - @Override - public SelectionQuery setOrder(List> orders) { - delegate.setOrder( orders ); - return this; - } - - @Override - public SelectionQuery setOrder(Order order) { - delegate.setOrder( order ); - return this; - } - - @Override public SelectionQuery setParameter(String name, Object value) { delegate.setParameter( name, value ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java index f21bed53b..46a71b0b8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java @@ -148,6 +148,16 @@ private CompletionStage connection(String tenantId) { : connectionPool.getConnection( tenantId ); } + @Override + public Mutiny.Session getCurrentSession() { + return context.get( contextKeyForSession ); + } + + @Override + public Mutiny.StatelessSession getCurrentStatelessSession() { + return context.get( contextKeyForStatelessSession ); + } + @Override public Uni withSession(Function> work) { Objects.requireNonNull( work, "parameter 'work' is required" ); 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 87cf50bb0..c80be96ed 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 @@ -5,17 +5,6 @@ */ package org.hibernate.reactive.mutiny.impl; -import io.smallrye.mutiny.Uni; -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 org.hibernate.CacheMode; import org.hibernate.Filter; import org.hibernate.FlushMode; @@ -34,10 +23,24 @@ import org.hibernate.reactive.mutiny.Mutiny.Query; import org.hibernate.reactive.mutiny.Mutiny.SelectionQuery; import org.hibernate.reactive.pool.ReactiveConnection; +import org.hibernate.reactive.query.ReactiveQuery; import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.reactive.session.ReactiveQueryProducer; import org.hibernate.reactive.session.ReactiveSession; +import io.smallrye.mutiny.Uni; +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.TypedQueryReference; +import jakarta.persistence.criteria.CriteriaBuilder; +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; @@ -46,7 +49,6 @@ import static org.hibernate.reactive.util.impl.CompletionStages.applyToAll; - /** * Implements the {@link Mutiny.Session} API. This delegating class is * needed to avoid name clashes when implementing both @@ -140,6 +142,12 @@ public MutationQuery createMutationQuery(JpaCriteriaInsert insert) { return new MutinyMutationQueryImpl<>( delegate.createReactiveMutationQuery( insert ), factory ); } + @Override + public Query createQuery(TypedQueryReference typedQueryReference) { + ReactiveQuery reactiveQuery = delegate.createReactiveQuery( typedQueryReference ); + return new MutinyQueryImpl<>( reactiveQuery, factory ); + } + @Override @Deprecated public Query createQuery(String queryString) { return new MutinyQueryImpl<>( delegate.createReactiveQuery( queryString ), factory ); @@ -324,36 +332,22 @@ public Uni lock(Object entity, LockOptions lockOptions) { @Override public FlushMode getFlushMode() { - switch ( delegate.getHibernateFlushMode() ) { - case MANUAL: - return FlushMode.MANUAL; - case COMMIT: - return FlushMode.COMMIT; - case AUTO: - return FlushMode.AUTO; - case ALWAYS: - return FlushMode.ALWAYS; - default: - throw LOG.impossibleFlushModeIllegalState(); - } + return switch (delegate.getHibernateFlushMode()) { + case MANUAL -> FlushMode.MANUAL; + case COMMIT -> FlushMode.COMMIT; + case AUTO -> FlushMode.AUTO; + case ALWAYS -> FlushMode.ALWAYS; + }; } @Override public Mutiny.Session setFlushMode(FlushMode flushMode) { - switch ( flushMode ) { - case COMMIT: - delegate.setHibernateFlushMode( FlushMode.COMMIT ); - break; - case AUTO: - delegate.setHibernateFlushMode( FlushMode.AUTO ); - break; - case MANUAL: - delegate.setHibernateFlushMode( FlushMode.MANUAL ); - break; - case ALWAYS: - delegate.setHibernateFlushMode( FlushMode.ALWAYS ); - break; - } + delegate.setHibernateFlushMode( switch ( flushMode ) { + case COMMIT -> org.hibernate.FlushMode.COMMIT; + case AUTO -> org.hibernate.FlushMode.AUTO; + case MANUAL -> org.hibernate.FlushMode.MANUAL; + case ALWAYS -> org.hibernate.FlushMode.ALWAYS; + } ); return this; } @@ -570,6 +564,11 @@ public Mutiny.SessionFactory getFactory() { return factory; } + @Override + public CriteriaBuilder getCriteriaBuilder() { + return getFactory().getCriteriaBuilder(); + } + @Override public ResultSetMapping getResultSetMapping(Class resultType, String mappingName) { return delegate.getResultSetMapping( resultType, mappingName ); 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 5605a7911..1f3600deb 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 @@ -5,27 +5,31 @@ */ package org.hibernate.reactive.mutiny.impl; -import io.smallrye.mutiny.Uni; -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; +import org.hibernate.reactive.common.AffectedEntities; import org.hibernate.reactive.common.ResultSetMapping; import org.hibernate.reactive.mutiny.Mutiny; import org.hibernate.reactive.mutiny.Mutiny.Query; import org.hibernate.reactive.mutiny.Mutiny.SelectionQuery; import org.hibernate.reactive.pool.ReactiveConnection; +import org.hibernate.reactive.query.ReactiveQuery; import org.hibernate.reactive.session.ReactiveStatelessSession; +import io.smallrye.mutiny.Uni; +import jakarta.persistence.EntityGraph; +import jakarta.persistence.TypedQueryReference; +import jakarta.persistence.criteria.CriteriaBuilder; +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; import java.util.function.Function; import java.util.function.Supplier; - /** * Implements the {@link Mutiny.StatelessSession} API. This delegating * class is needed to avoid name clashes when implementing both @@ -45,7 +49,6 @@ public ReactiveConnection getReactiveConnection() { return delegate.getReactiveConnection(); } - Uni uni(Supplier> stageSupplier) { return factory.uni( stageSupplier ); } @@ -71,6 +74,12 @@ public Uni get(EntityGraph entityGraph, Object id) { return uni( () -> delegate.reactiveGet( entityClass, id, null, entityGraph ) ); } + @Override + public Query createQuery(TypedQueryReference typedQueryReference) { + ReactiveQuery reactiveQuery = delegate.createReactiveQuery( typedQueryReference ); + return new MutinyQueryImpl<>( reactiveQuery, factory ); + } + @Override public Query createQuery(String queryString) { return new MutinyQueryImpl<>( delegate.createReactiveQuery( queryString ), factory ); @@ -83,12 +92,42 @@ public SelectionQuery createQuery(String queryString, Class resultType @Override public SelectionQuery createSelectionQuery(String queryString, Class resultType) { - return new MutinySelectionQueryImpl<>( delegate.createReactiveSelectionQuery( queryString, resultType), factory ); + return new MutinySelectionQueryImpl<>( delegate.createReactiveSelectionQuery( queryString, resultType ), factory ); } @Override public Mutiny.MutationQuery createMutationQuery(String queryString) { - return new MutinyMutationQueryImpl<>( delegate.createReactiveMutationQuery( queryString), factory ); + return new MutinyMutationQueryImpl<>( delegate.createReactiveMutationQuery( queryString ), factory ); + } + + @Override + public Mutiny.MutationQuery createMutationQuery(CriteriaUpdate updateQuery) { + return new MutinyMutationQueryImpl<>( delegate.createReactiveMutationQuery( updateQuery ), factory ); + } + + @Override + public Mutiny.MutationQuery createMutationQuery(CriteriaDelete deleteQuery) { + return new MutinyMutationQueryImpl<>( delegate.createReactiveMutationQuery( deleteQuery ) , factory ); + } + + @Override + public Mutiny.MutationQuery createMutationQuery(JpaCriteriaInsert insert) { + return new MutinyMutationQueryImpl<>( delegate.createReactiveMutationQuery( insert ) , factory ); + } + + @Override + public Query createNativeQuery(String queryString, AffectedEntities affectedEntities) { + return new MutinyQueryImpl<>( delegate.createReactiveNativeQuery( queryString, affectedEntities ), factory ); + } + + @Override + public SelectionQuery createNativeQuery(String queryString, Class resultType, AffectedEntities affectedEntities) { + return new MutinyQueryImpl<>( delegate.createReactiveNativeQuery( queryString, resultType, affectedEntities ), factory ); + } + + @Override + public SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping, AffectedEntities affectedEntities) { + return new MutinyQueryImpl<>( delegate.createReactiveNativeQuery( queryString, resultSetMapping, affectedEntities ), factory ); } @Override @@ -201,11 +240,6 @@ public Uni upsert(Object entity) { return uni( () -> delegate.reactiveUpsert( entity ) ); } - @Override - 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 ) ); @@ -251,26 +285,6 @@ public Object getIdentifier(Object entity) { return delegate.getIdentifier(entity); } -// @Override -// public ResultSetMapping getResultSetMapping(Class resultType, String mappingName) { -// return delegate.getResultSetMapping( resultType, mappingName ); -// } -// -// @Override -// public EntityGraph getEntityGraph(Class entity, String name) { -// return delegate.getEntityGraph( entity, name ); -// } -// -// @Override -// public EntityGraph createEntityGraph(Class entity) { -// return delegate.createEntityGraph( entity ); -// } -// -// @Override -// public EntityGraph createEntityGraph(Class entity, String name) { -// return delegate.createEntityGraph( entity, name ); -// } - @Override public Uni withTransaction(Function> work) { return currentTransaction == null ? new Transaction().execute( work ) : work.apply( currentTransaction ); @@ -286,6 +300,10 @@ public Mutiny.Transaction currentTransaction() { private class Transaction implements Mutiny.Transaction { boolean rollback; + /** + * Execute the given work in a new transaction. Called only + * when no existing transaction was active. + */ Uni execute(Function> work) { currentTransaction = this; return begin() @@ -294,14 +312,15 @@ Uni execute(Function> work) { } /** - * Run the code assuming that a transaction has already started so that we can - * differentiate an error starting a transaction (and therefore doesn't need to rollback) - * and an error thrown by the work. + * Run the code assuming that a transaction has already started + * so that we can differentiate an error starting a transaction + * (which therefore does not need to trigger rollback) from an + * error thrown by the work (which does). */ Uni executeInTransaction(Function> work) { return Uni.createFrom().deferred( () -> work.apply( this ) ) // in the case of an exception or cancellation - // we need to rollback the transaction + // we need to roll back the transaction .onFailure().call( this::rollback ) .onCancellation().call( this::rollback ) // finally, when there was no exception, @@ -351,6 +370,11 @@ public MutinySessionFactoryImpl getFactory() { return factory; } + @Override + public CriteriaBuilder getCriteriaBuilder() { + return getFactory().getCriteriaBuilder(); + } + @Override public ResultSetMapping getResultSetMapping(Class resultType, String mappingName) { return delegate.getResultSetMapping( resultType, mappingName ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java index c1015939c..0ebebe603 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java @@ -55,6 +55,7 @@ import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor; +import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import org.hibernate.reactive.generator.values.internal.ReactiveGeneratedValuesHelper; import org.hibernate.reactive.loader.ast.internal.ReactiveSingleIdArrayLoadPlan; import org.hibernate.reactive.loader.ast.spi.ReactiveSingleIdEntityLoader; @@ -62,7 +63,6 @@ import org.hibernate.reactive.metamodel.mapping.internal.ReactiveCompoundNaturalIdMapping; import org.hibernate.reactive.metamodel.mapping.internal.ReactiveSimpleNaturalIdMapping; import org.hibernate.reactive.pool.ReactiveConnection; -import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.session.impl.ReactiveQueryExecutorLookup; import org.hibernate.sql.SimpleSelect; import org.hibernate.sql.Update; @@ -359,7 +359,8 @@ else if ( result instanceof PersistentCollection ) { final PersistentCollection collection = (PersistentCollection) result; return collection.wasInitialized() ? completedFuture( (T) collection ) - : ( (ReactiveSession) session ).reactiveInitializeCollection( collection, false ) + : ( (ReactiveSharedSessionContractImplementor) session ) + .reactiveInitializeCollection( collection, false ) .thenApply( v -> (T) result ); } else { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java index 9a31d2bc8..c5d068db7 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java @@ -20,11 +20,11 @@ import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.event.spi.EventSource; import org.hibernate.generator.Generator; import org.hibernate.generator.values.GeneratedValues; import org.hibernate.id.IdentityGenerator; import org.hibernate.loader.ast.spi.BatchLoaderFactory; +import org.hibernate.loader.ast.spi.EntityBatchLoader; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; @@ -81,11 +81,11 @@ public class ReactiveAbstractPersisterDelegate { private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - private final Supplier> singleIdEntityLoaderSupplier; - private final Supplier> multiIdEntityLoaderSupplier; + private final Supplier> singleIdEntityLoaderSupplier; + private final Supplier> multiIdEntityLoaderSupplier; - private ReactiveSingleIdEntityLoader singleIdEntityLoader; - private ReactiveMultiIdEntityLoader multiIdEntityLoader; + private ReactiveSingleIdEntityLoader singleIdEntityLoader; + private ReactiveMultiIdEntityLoader multiIdEntityLoader; private final EntityPersister entityDescriptor; @@ -101,12 +101,12 @@ public ReactiveAbstractPersisterDelegate( entityDescriptor = entityPersister; } - public ReactiveSingleIdEntityLoader buildSingleIdEntityLoader() { + public ReactiveSingleIdEntityLoader buildSingleIdEntityLoader() { singleIdEntityLoader = singleIdEntityLoaderSupplier.get(); return singleIdEntityLoader; } - public ReactiveMultiIdEntityLoader buildMultiIdEntityLoader() { + public ReactiveMultiIdEntityLoader buildMultiIdEntityLoader() { multiIdEntityLoader = multiIdEntityLoaderSupplier.get(); return multiIdEntityLoader; } @@ -126,7 +126,7 @@ public DomainResult createDomainResult( /** * @see org.hibernate.persister.entity.AbstractEntityPersister#multiLoad(Object[], EventSource, MultiIdLoadOptions)` */ - public CompletionStage> multiLoad(K[] ids, EventSource session, MultiIdLoadOptions loadOptions) { + public CompletionStage> multiLoad(K[] ids, SharedSessionContractImplementor session, MultiIdLoadOptions loadOptions) { return multiIdEntityLoader.reactiveLoad( ids, loadOptions, session ); } @@ -135,12 +135,12 @@ private static ReactiveMultiIdEntityLoader buildMultiIdEntityLoader( PersistentClass persistentClass, SessionFactoryImplementor factory) { return entityDescriptor.getIdentifierType() instanceof BasicType - && supportsSqlArrayType( factory.getJdbcServices().getDialect() ) + && supportsSqlArrayType( factory.getJdbcServices().getDialect() ) ? new ReactiveMultiIdEntityLoaderArrayParam<>( entityDescriptor, factory ) : new ReactiveMultiIdEntityLoaderStandard<>( entityDescriptor, persistentClass, factory ); } - private static ReactiveSingleIdEntityLoader buildSingleIdEntityLoader( + private static ReactiveSingleIdEntityLoader buildSingleIdEntityLoader( EntityPersister entityDescriptor, PersistentClass bootDescriptor, RuntimeModelCreationContext creationContext, @@ -148,32 +148,45 @@ private static ReactiveSingleIdEntityLoader buildSingleIdEntityLoader( String entityName) { if ( bootDescriptor.getLoaderName() != null ) { // We must resolve the named query on-demand through the boot model because it isn't initialized yet - final NamedQueryMemento namedQueryMemento = factory.getQueryEngine().getNamedObjectRepository() - .resolve( factory, creationContext.getBootModel(), bootDescriptor.getLoaderName() ); - if ( namedQueryMemento == null ) { - throw new IllegalArgumentException( "Could not resolve named load-query [" + entityName + "] : " + bootDescriptor.getLoaderName() ); - } + final NamedQueryMemento namedQueryMemento = + getNamedQueryMemento( bootDescriptor, creationContext, factory, entityName ); return new ReactiveSingleIdEntityLoaderProvidedQueryImpl<>( entityDescriptor, namedQueryMemento ); } - - LoadQueryInfluencers loadQueryInfluencers = new LoadQueryInfluencers( factory ); - if ( loadQueryInfluencers.effectivelyBatchLoadable( entityDescriptor ) ) { - final int batchSize = loadQueryInfluencers.effectiveBatchSize( entityDescriptor ); - if ( batchSize > 1 ) { + else { + final LoadQueryInfluencers loadQueryInfluencers = new LoadQueryInfluencers( factory ); + if ( loadQueryInfluencers.effectivelyBatchLoadable( entityDescriptor ) ) { + final int batchSize = loadQueryInfluencers.effectiveBatchSize( entityDescriptor ); return createBatchingIdEntityLoader( entityDescriptor, batchSize, factory ); } + else { + return new ReactiveSingleIdEntityLoaderStandardImpl<>( entityDescriptor, loadQueryInfluencers ); + } } + } - return new ReactiveSingleIdEntityLoaderStandardImpl<>( entityDescriptor, new LoadQueryInfluencers( factory ) ); + private static NamedQueryMemento getNamedQueryMemento( + PersistentClass bootDescriptor, + RuntimeModelCreationContext creationContext, + SessionFactoryImplementor factory, + String entityName) { + final NamedQueryMemento namedQueryMemento = + factory.getQueryEngine().getNamedObjectRepository() + .resolve(factory, creationContext.getBootModel(), bootDescriptor.getLoaderName() ); + if ( namedQueryMemento == null ) { + throw new IllegalArgumentException( "Could not resolve named query '" + bootDescriptor.getLoaderName() + + "' for loading entity '" + entityName + "'" ); + } + return namedQueryMemento; } - private static ReactiveSingleIdEntityLoader createBatchingIdEntityLoader( + private static ReactiveSingleIdEntityLoader createBatchingIdEntityLoader( EntityMappingType entityDescriptor, int domainBatchSize, SessionFactoryImplementor factory) { - return (ReactiveSingleIdEntityLoader) factory.getServiceRegistry() - .getService( BatchLoaderFactory.class ) - .createEntityBatchLoader( domainBatchSize, entityDescriptor, factory ); + final EntityBatchLoader batchLoader = + factory.getServiceRegistry().requireService( BatchLoaderFactory.class ) + .createEntityBatchLoader( domainBatchSize, entityDescriptor, factory ); + return (ReactiveSingleIdEntityLoader) batchLoader; } public CompletionStage processInsertGeneratedProperties( @@ -236,7 +249,7 @@ protected ReactiveSingleUniqueKeyEntityLoader getReactiveUniqueKeyLoader ); } - public CompletionStage load( + public CompletionStage load( EntityPersister persister, Object id, Object optionalObject, @@ -255,7 +268,7 @@ public Generator reactive(Generator generator) { return generator instanceof IdentityGenerator ? new ReactiveIdentityGenerator() : generator; } - public CompletionStage loadEntityIdByNaturalId( + public CompletionStage loadEntityIdByNaturalId( Object[] orderedNaturalIdValues, LockOptions lockOptions, SharedSessionContractImplementor session) { if ( LOG.isTraceEnabled() ) { LOG.tracef( "Resolving natural-id [%s] to id : %s ", @@ -264,7 +277,7 @@ public CompletionStage loadEntityIdByNaturalId( ); } - return ( (ReactiveNaturalIdLoader) entityDescriptor.getNaturalIdLoader() ) + return ( (ReactiveNaturalIdLoader) entityDescriptor.getNaturalIdLoader() ) .resolveNaturalIdToId( orderedNaturalIdValues, session ); } @@ -323,13 +336,15 @@ public AttributeMapping buildPluralAttributeMapping( } public EntityIdentifierMapping convertEntityIdentifierMapping(EntityIdentifierMapping entityIdentifierMapping) { - if ( entityIdentifierMapping instanceof NonAggregatedIdentifierMappingImpl ) { - return new ReactiveNonAggregatedIdentifierMappingImpl( (NonAggregatedIdentifierMappingImpl) entityIdentifierMapping ); + if ( entityIdentifierMapping instanceof NonAggregatedIdentifierMappingImpl nonAggregatedIdentifierMapping ) { + return new ReactiveNonAggregatedIdentifierMappingImpl(nonAggregatedIdentifierMapping); + } + else if ( entityIdentifierMapping instanceof EmbeddedIdentifierMappingImpl embeddedIdentifierMapping ) { + return new ReactiveEmbeddedIdentifierMappingImpl(embeddedIdentifierMapping); } - if ( entityIdentifierMapping instanceof EmbeddedIdentifierMappingImpl ) { - return new ReactiveEmbeddedIdentifierMappingImpl( (EmbeddedIdentifierMappingImpl) entityIdentifierMapping ); + else { + return entityIdentifierMapping; } - return entityIdentifierMapping; } private static class ReactiveNonAggregatedIdentifierMappingImpl extends NonAggregatedIdentifierMappingImpl { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveEntityPersister.java index 0a36d048d..16d6237f2 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveEntityPersister.java @@ -12,7 +12,6 @@ import org.hibernate.LockOptions; import org.hibernate.bytecode.BytecodeLogging; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.event.spi.EventSource; import org.hibernate.generator.values.GeneratedValues; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.persister.entity.EntityPersister; @@ -92,40 +91,40 @@ CompletionStage reactiveLock( CompletionStage> reactiveMultiLoad( K[] ids, - EventSource session, + SharedSessionContractImplementor session, MultiIdLoadOptions loadOptions); - CompletionStage reactiveLoad( + CompletionStage reactiveLoad( Object id, Object optionalObject, LockMode lockMode, SharedSessionContractImplementor session); - CompletionStage reactiveLoad( + CompletionStage reactiveLoad( Object id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session); - CompletionStage reactiveLoad( + CompletionStage reactiveLoad( Object id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session, Boolean readOnly); - CompletionStage reactiveLoadByUniqueKey( + CompletionStage reactiveLoadByUniqueKey( String propertyName, Object uniqueKey, SharedSessionContractImplementor session); - CompletionStage reactiveLoadByUniqueKey( + CompletionStage reactiveLoadByUniqueKey( String propertyName, Object uniqueKey, Boolean readOnly, SharedSessionContractImplementor session); - CompletionStage reactiveLoadEntityIdByNaturalId( + CompletionStage reactiveLoadEntityIdByNaturalId( Object[] naturalIdValues, LockOptions lockOptions, SharedSessionContractImplementor session); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java index b87ba6910..0182ef403 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java @@ -17,7 +17,6 @@ import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.event.spi.EventSource; import org.hibernate.generator.Generator; import org.hibernate.generator.values.GeneratedValues; import org.hibernate.loader.ast.spi.MultiIdEntityLoader; @@ -83,7 +82,7 @@ protected SingleIdEntityLoader buildSingleIdEntityLoader() { } @Override - protected MultiIdEntityLoader buildMultiIdLoader() { + protected MultiIdEntityLoader buildMultiIdLoader() { return reactiveDelegate.buildMultiIdEntityLoader(); } @@ -239,7 +238,7 @@ public CompletionStage mergeReactive( } @Override - public CompletionStage> reactiveMultiLoad(K[] ids, EventSource session, MultiIdLoadOptions loadOptions) { + public CompletionStage> reactiveMultiLoad(K[] ids, SharedSessionContractImplementor session, MultiIdLoadOptions loadOptions) { return reactiveDelegate.multiLoad( ids, session, loadOptions ); } @@ -313,7 +312,7 @@ protected Object initializeLazyPropertiesFromDatastore(Object entity, Object id, } @Override - public CompletionStage reactiveLoad(Object id, Object optionalObject, LockMode lockMode, SharedSessionContractImplementor session) { + public CompletionStage reactiveLoad(Object id, Object optionalObject, LockMode lockMode, SharedSessionContractImplementor session) { return reactiveLoad( id, optionalObject, new LockOptions().setLockMode( lockMode ), session ); } @@ -323,7 +322,7 @@ public Object load(Object id, Object optionalObject, LockOptions lockOptions, Sh } @Override - public CompletionStage reactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session) { + public CompletionStage reactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session) { return doReactiveLoad( id, optionalObject, lockOptions, null, session ); } @@ -333,11 +332,11 @@ public Object load(Object id, Object optionalObject, LockOptions lockOptions, Sh } @Override - public CompletionStage reactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session, Boolean readOnly) { + public CompletionStage reactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session, Boolean readOnly) { return doReactiveLoad( id, optionalObject, lockOptions, readOnly, session ); } - private CompletionStage doReactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session) { + private CompletionStage doReactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session) { return reactiveDelegate.load( this, id, optionalObject, lockOptions, readOnly, session ); } @@ -371,7 +370,7 @@ public CompletionStage reactiveLoadByUniqueKey(String propertyName, Obje * @see AbstractEntityPersister#loadEntityIdByNaturalId(Object[], LockOptions, SharedSessionContractImplementor) */ @Override - public CompletionStage reactiveLoadEntityIdByNaturalId(Object[] orderedNaturalIdValues, LockOptions lockOptions, SharedSessionContractImplementor session) { + public CompletionStage reactiveLoadEntityIdByNaturalId(Object[] orderedNaturalIdValues, LockOptions lockOptions, SharedSessionContractImplementor session) { verifyHasNaturalId(); return reactiveDelegate.loadEntityIdByNaturalId( orderedNaturalIdValues, lockOptions, session ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java index 381a83b78..3006a0eae 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java @@ -18,7 +18,6 @@ import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.event.spi.EventSource; import org.hibernate.generator.Generator; import org.hibernate.generator.values.GeneratedValues; import org.hibernate.generator.values.GeneratedValuesMutationDelegate; @@ -70,7 +69,7 @@ public class ReactiveSingleTableEntityPersister extends SingleTableEntityPersist private static final Log LOG = make( Log.class, lookup() ); - private ReactiveAbstractPersisterDelegate reactiveDelegate; + private final ReactiveAbstractPersisterDelegate reactiveDelegate; public ReactiveSingleTableEntityPersister( final PersistentClass persistentClass, @@ -102,7 +101,7 @@ protected SingleIdEntityLoader buildSingleIdEntityLoader() { } @Override - protected MultiIdEntityLoader buildMultiIdLoader() { + protected MultiIdEntityLoader buildMultiIdLoader() { return reactiveDelegate.buildMultiIdEntityLoader(); } @@ -302,13 +301,13 @@ public CompletionStage reactiveProcessUpdateGenerated(Object id, Object en * @see AbstractEntityPersister#loadEntityIdByNaturalId(Object[], LockOptions, SharedSessionContractImplementor) */ @Override - public CompletionStage reactiveLoadEntityIdByNaturalId(Object[] orderedNaturalIdValues, LockOptions lockOptions, SharedSessionContractImplementor session) { + public CompletionStage reactiveLoadEntityIdByNaturalId(Object[] orderedNaturalIdValues, LockOptions lockOptions, SharedSessionContractImplementor session) { verifyHasNaturalId(); return reactiveDelegate.loadEntityIdByNaturalId( orderedNaturalIdValues, lockOptions, session ); } @Override - public CompletionStage reactiveLoad(Object id, Object optionalObject, LockMode lockMode, SharedSessionContractImplementor session) { + public CompletionStage reactiveLoad(Object id, Object optionalObject, LockMode lockMode, SharedSessionContractImplementor session) { return reactiveLoad( id, optionalObject, new LockOptions().setLockMode( lockMode ), session ); } @@ -318,7 +317,7 @@ public Object load(Object id, Object optionalObject, LockOptions lockOptions, Sh } @Override - public CompletionStage reactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session) { + public CompletionStage reactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session) { return doReactiveLoad( id, optionalObject, lockOptions, null, session ); } @@ -328,11 +327,11 @@ public Object load(Object id, Object optionalObject, LockOptions lockOptions, Sh } @Override - public CompletionStage reactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session, Boolean readOnly) { + public CompletionStage reactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session, Boolean readOnly) { return doReactiveLoad( id, optionalObject, lockOptions, readOnly, session ); } - private CompletionStage doReactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session) { + private CompletionStage doReactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session) { return reactiveDelegate.load( this, id, optionalObject, lockOptions, readOnly, session ); } @@ -397,7 +396,7 @@ public CompletionStage mergeReactive( } @Override - public CompletionStage> reactiveMultiLoad(K[] ids, EventSource session, MultiIdLoadOptions loadOptions) { + public CompletionStage> reactiveMultiLoad(K[] ids, SharedSessionContractImplementor session, MultiIdLoadOptions loadOptions) { return reactiveDelegate.multiLoad( ids, session, loadOptions ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java index cb7995acb..52871e30f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java @@ -19,7 +19,6 @@ import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.event.spi.EventSource; import org.hibernate.generator.Generator; import org.hibernate.generator.values.GeneratedValues; import org.hibernate.id.IdentityGenerator; @@ -83,7 +82,7 @@ protected SingleIdEntityLoader buildSingleIdEntityLoader() { } @Override - protected MultiIdEntityLoader buildMultiIdLoader() { + protected MultiIdEntityLoader buildMultiIdLoader() { return reactiveDelegate.buildMultiIdEntityLoader(); } @@ -149,14 +148,6 @@ public NaturalIdMapping generateNaturalIdMapping(MappingModelCreationProcess cre return ReactiveAbstractEntityPersister.super.generateNaturalIdMapping(creationProcess, bootEntityDescriptor); } - - @Override - protected void validateGenerator() { - if ( super.getGenerator() instanceof IdentityGenerator ) { - throw new MappingException( "Cannot use identity column key generation with mapping for: " + getEntityName() ); - } - } - @Override protected InsertCoordinator buildInsertCoordinator() { return ReactiveCoordinatorFactory.buildInsertCoordinator( this, getFactory() ); @@ -181,6 +172,13 @@ public DomainResult createDomainResult( return reactiveDelegate.createDomainResult( this, navigablePath, tableGroup, resultVariable, creationState ); } + @Override + protected void validateGenerator() { + if ( super.getGenerator() instanceof IdentityGenerator) { + throw new MappingException( "Cannot use identity column key generation with mapping for: " + getEntityName() ); + } + } + @Override public Generator getGenerator() throws HibernateException { return reactiveDelegate.reactive( super.getGenerator() ); @@ -280,7 +278,7 @@ protected Object initializeLazyPropertiesFromDatastore(Object entity, Object id, } @Override - public CompletionStage reactiveLoad(Object id, Object optionalObject, LockMode lockMode, SharedSessionContractImplementor session) { + public CompletionStage reactiveLoad(Object id, Object optionalObject, LockMode lockMode, SharedSessionContractImplementor session) { return reactiveLoad( id, optionalObject, new LockOptions().setLockMode( lockMode ), session ); } @@ -290,7 +288,7 @@ public Object load(Object id, Object optionalObject, LockOptions lockOptions, Sh } @Override - public CompletionStage reactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session) { + public CompletionStage reactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session) { return doReactiveLoad( id, optionalObject, lockOptions, null, session ); } @@ -300,11 +298,11 @@ public Object load(Object id, Object optionalObject, LockOptions lockOptions, Sh } @Override - public CompletionStage reactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session, Boolean readOnly) { + public CompletionStage reactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session, Boolean readOnly) { return doReactiveLoad( id, optionalObject, lockOptions, readOnly, session ); } - private CompletionStage doReactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session) { + private CompletionStage doReactiveLoad(Object id, Object optionalObject, LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session) { return reactiveDelegate.load( this, id, optionalObject, lockOptions, readOnly, session ); } @@ -365,7 +363,7 @@ public CompletionStage mergeReactive( } @Override - public CompletionStage> reactiveMultiLoad(K[] ids, EventSource session, MultiIdLoadOptions loadOptions) { + public CompletionStage> reactiveMultiLoad(K[] ids, SharedSessionContractImplementor session, MultiIdLoadOptions loadOptions) { return reactiveDelegate.multiLoad( ids, session, loadOptions ); } @@ -373,7 +371,7 @@ public CompletionStage> reactiveMultiLoad(K[] ids, EventSo * @see AbstractEntityPersister#loadEntityIdByNaturalId(Object[], LockOptions, SharedSessionContractImplementor) */ @Override - public CompletionStage reactiveLoadEntityIdByNaturalId(Object[] orderedNaturalIdValues, LockOptions lockOptions, SharedSessionContractImplementor session) { + public CompletionStage reactiveLoadEntityIdByNaturalId(Object[] orderedNaturalIdValues, LockOptions lockOptions, SharedSessionContractImplementor session) { verifyHasNaturalId(); return reactiveDelegate.loadEntityIdByNaturalId( orderedNaturalIdValues, lockOptions, session ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveUpdateCoordinatorStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveUpdateCoordinatorStandard.java index 92c92bb65..9e3a6b86d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveUpdateCoordinatorStandard.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveUpdateCoordinatorStandard.java @@ -30,7 +30,7 @@ import org.hibernate.tuple.entity.EntityMetamodel; import static org.hibernate.engine.jdbc.mutation.internal.ModelMutationHelper.identifiedResultsCheck; -import static org.hibernate.generator.EventType.INSERT; +import static org.hibernate.generator.EventType.UPDATE; import static org.hibernate.internal.util.collections.ArrayHelper.EMPTY_INT_ARRAY; import static org.hibernate.internal.util.collections.ArrayHelper.trim; import static org.hibernate.reactive.persister.entity.mutation.GeneratorValueUtil.generateValue; @@ -193,7 +193,7 @@ private CompletionStage reactivePreUpdateInMemoryValueGeneration( && generator.generatesOnUpdate() ) { final Object currentValue = currentValues[i]; final BeforeExecutionGenerator beforeGenerator = (BeforeExecutionGenerator) generator; - result = result.thenCompose( v -> generateValue( session, entity, currentValue, beforeGenerator, INSERT ) + result = result.thenCompose( v -> generateValue( session, entity, currentValue, beforeGenerator, UPDATE ) .thenAccept( generatedValue -> { currentValues[index] = generatedValue; entityPersister().setValue( entity, index, generatedValue ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/DefaultSqlClientPool.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/DefaultSqlClientPool.java index 67476f71e..5249a79e5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/DefaultSqlClientPool.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/DefaultSqlClientPool.java @@ -13,6 +13,7 @@ import java.util.ServiceConfigurationError; import java.util.ServiceLoader; import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; @@ -31,13 +32,13 @@ import io.vertx.core.Future; import io.vertx.core.Vertx; +import io.vertx.core.net.NetClientOptions; import io.vertx.sqlclient.Pool; import io.vertx.sqlclient.PoolOptions; import io.vertx.sqlclient.SqlConnectOptions; +import io.vertx.sqlclient.impl.Utils; import io.vertx.sqlclient.spi.Driver; -import static java.util.Collections.singletonList; - /** * A pool of reactive connections backed by a Vert.x {@link Pool}. * The {@code Pool} itself is backed by an instance of {@link Vertx} @@ -189,7 +190,7 @@ protected Pool createPool(URI uri) { * * @return the new {@link Pool} */ - protected Pool createPool(URI uri, SqlConnectOptions connectOptions, PoolOptions poolOptions, Vertx vertx) { + protected Pool createPool(URI uri, T connectOptions, PoolOptions poolOptions, Vertx vertx) { try { // First try to load the Pool using the standard ServiceLoader pattern // This only works if exactly 1 Driver is on the classpath. @@ -198,8 +199,9 @@ protected Pool createPool(URI uri, SqlConnectOptions connectOptions, PoolOptions catch (ServiceConfigurationError e) { // Backup option if multiple drivers are on the classpath. // We will be able to remove this once Vertx 3.9.2 is available - final Driver driver = findDriver( uri, e ); - return driver.createPool( vertx, singletonList( connectOptions ), poolOptions ); + final Driver driver = findDriver( uri, e ); + Supplier> database = Utils.singletonSupplier( driver.downcast( connectOptions ) ); + return driver.createPool( vertx, database, poolOptions, new NetClientOptions(), null ); } } @@ -222,15 +224,14 @@ protected URI jdbcUrl(Map configurationValues) { * so we need to disambiguate according to the scheme specified * in the given {@link URI}. * - * @param uri the JDBC URL or database URI + * @param uri the JDBC URL or database URI * @param originalError the error that was thrown - * * @return the disambiguated {@link Driver} */ - private Driver findDriver(URI uri, ServiceConfigurationError originalError) { + private Driver findDriver(URI uri, ServiceConfigurationError originalError) { String scheme = scheme( uri ); - List selected = new ArrayList<>(); - for ( Driver d : ServiceLoader.load( Driver.class ) ) { + List> selected = new ArrayList<>(); + for ( Driver d : ServiceLoader.load( Driver.class ) ) { String driverName = d.getClass().getCanonicalName(); if ( matchesScheme( driverName, scheme ) ) { LOG.selectedDriver( driverName ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveIntegrator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveIntegrator.java index c1fb0deb9..db37d7241 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveIntegrator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveIntegrator.java @@ -27,6 +27,7 @@ import org.hibernate.reactive.event.impl.DefaultReactiveRefreshEventListener; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.logging.impl.Version; import org.hibernate.service.ServiceRegistry; /** @@ -48,7 +49,7 @@ public void integrate(Metadata metadata, BootstrapContext bootstrapContext, Sess private void attachEventContextManagingListenersIfRequired(ServiceRegistry serviceRegistry) { if ( ReactiveModeCheck.isReactiveRegistry( serviceRegistry ) ) { - LOG.startHibernateReactive(); + LOG.startHibernateReactive( Version.getVersionString() ); EventListenerRegistry eventListenerRegistry = serviceRegistry.getService( EventListenerRegistry.class ); eventListenerRegistry.addDuplicationStrategy( ReplacementDuplicationStrategy.INSTANCE ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProvider.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProvider.java index 5c3380f65..a02c3e152 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProvider.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProvider.java @@ -6,10 +6,14 @@ package org.hibernate.reactive.provider.service; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.reactive.logging.impl.Log; import java.sql.Connection; import java.sql.SQLException; +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; + /** * A dummy Hibernate {@link ConnectionProvider} throws an * exception if a JDBC connection is requested. @@ -17,12 +21,13 @@ * @author Gavin King */ public class NoJdbcConnectionProvider implements ConnectionProvider { + private static final Log LOG = make( Log.class, lookup() ); public static final NoJdbcConnectionProvider INSTANCE = new NoJdbcConnectionProvider(); @Override public Connection getConnection() throws SQLException { - throw new SQLException( "Not using JDBC" ); + throw LOG.notUsingJdbc(); } @Override 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 953285dd4..0cf59c413 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 @@ -5,25 +5,29 @@ */ package org.hibernate.reactive.provider.service; -import java.util.Map; -import java.util.concurrent.CompletionStage; - import org.hibernate.boot.registry.StandardServiceInitiator; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.connections.spi.DatabaseConnectionInfo; import org.hibernate.engine.jdbc.dialect.spi.DialectFactory; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentImpl; +import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.reactive.pool.ReactiveConnection; import org.hibernate.reactive.pool.ReactiveConnectionPool; -import org.hibernate.reactive.provider.Settings; import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.service.ServiceRegistry; import org.hibernate.service.spi.ServiceRegistryImplementor; import io.vertx.sqlclient.spi.DatabaseMetadata; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import static java.lang.Integer.parseInt; +import static java.util.Objects.requireNonNullElse; import static java.util.function.Function.identity; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; @@ -32,7 +36,8 @@ * 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 { +public class NoJdbcEnvironmentInitiator extends JdbcEnvironmentInitiator + implements StandardServiceInitiator { public static final NoJdbcEnvironmentInitiator INSTANCE = new NoJdbcEnvironmentInitiator(); @@ -42,14 +47,59 @@ public Class getServiceInitiated() { } @Override - public JdbcEnvironment initiateService(Map configurationValues, ServiceRegistryImplementor registry) { - boolean explicitDialect = configurationValues.containsKey( Settings.DIALECT ); - if ( explicitDialect ) { - DialectFactory dialectFactory = registry.getService( DialectFactory.class ); - return new JdbcEnvironmentImpl( registry, dialectFactory.buildDialect( configurationValues, null ) ); - } + protected void logConnectionInfo(DatabaseConnectionInfo databaseConnectionInfo) { + // Nothing to do we log the connection info somewhere else + } + + @Override + protected JdbcEnvironmentImpl getJdbcEnvironmentWithExplicitConfiguration( + Map configurationValues, + ServiceRegistryImplementor registry, + DialectFactory dialectFactory, + DialectResolutionInfo dialectResolutionInfo) { + return super.getJdbcEnvironmentWithExplicitConfiguration( + configurationValues, + registry, + dialectFactory, + dialectResolutionInfo + ); + } - return new JdbcEnvironmentImpl( registry, new DialectBuilder( configurationValues, registry ).build() ); + @Override + protected JdbcEnvironmentImpl getJdbcEnvironmentWithDefaults( + Map configurationValues, + ServiceRegistryImplementor registry, + DialectFactory dialectFactory) { + return new JdbcEnvironmentImpl( registry, new DialectBuilder( configurationValues, registry ) + .build( dialectFactory ) + ); + } + + @Override + protected JdbcEnvironmentImpl getJdbcEnvironmentUsingJdbcMetadata( + Map configurationValues, + ServiceRegistryImplementor registry, + DialectFactory dialectFactory, + String explicitDatabaseName, + Integer explicitDatabaseMajorVersion, + Integer explicitDatabaseMinorVersion, + String explicitDatabaseVersion) { + try { + final Dialect dialect = new DialectBuilder( configurationValues, registry ) + .build( + dialectFactory, + new ExplicitMetadata( + explicitDatabaseName, + explicitDatabaseMajorVersion, + explicitDatabaseMinorVersion, + explicitDatabaseVersion + ) + ); + return new JdbcEnvironmentImpl( registry, dialect ); + } + catch (RuntimeException e) { + return getJdbcEnvironmentWithDefaults( configurationValues, registry, dialectFactory ); + } } private static class DialectBuilder { @@ -62,24 +112,40 @@ public DialectBuilder(Map configurationValues, ServiceRegistry r this.registry = registry; } - public Dialect build() { - DialectFactory dialectFactory = registry.getService( DialectFactory.class ); + public Dialect build(DialectFactory dialectFactory) { return dialectFactory.buildDialect( configurationValues, this::dialectResolutionInfo ); } + public Dialect build(DialectFactory dialectFactory, ExplicitMetadata explicitMetadata) { + return dialectFactory.buildDialect( configurationValues, () -> dialectResolutionInfo( explicitMetadata ) ); + } + private DialectResolutionInfo dialectResolutionInfo() { - ReactiveConnectionPool connectionPool = registry.getService( ReactiveConnectionPool.class ); - return connectionPool + return dialectResolutionInfo( DialectBuilder::buildResolutionInfo ); + } + + private DialectResolutionInfo dialectResolutionInfo(ExplicitMetadata explicitMetadata) { + return dialectResolutionInfo( reactiveConnection -> DialectBuilder + .buildResolutionInfo( reactiveConnection, explicitMetadata ) + ); + } + + private DialectResolutionInfo dialectResolutionInfo(Function> dialectResolutionFunction) { + return registry + .getService( ReactiveConnectionPool.class ) // The default SqlExceptionHelper in ORM requires the dialect, but we haven't created a dialect yet, // so we need to override it at this stage, or we will have an exception. .getConnection( new SqlExceptionHelper( true ) ) - .thenCompose( DialectBuilder::buildResolutionInfo ) + .thenCompose( dialectResolutionFunction ) .toCompletableFuture().join(); } private static CompletionStage buildResolutionInfo(ReactiveConnection connection) { - final DatabaseMetadata databaseMetadata = connection.getDatabaseMetadata(); - return resolutionInfoStage( connection, databaseMetadata ) + return buildResolutionInfo( connection, null ); + } + + private static CompletionStage buildResolutionInfo(ReactiveConnection connection, ExplicitMetadata explicitMetadata) { + return resolutionInfoStage( connection, explicitMetadata ) .handle( CompletionStages::handle ) .thenCompose( handled -> { if ( handled.hasFailed() ) { @@ -96,19 +162,27 @@ private static CompletionStage buildResolutionInf } ); } - private static CompletionStage resolutionInfoStage(ReactiveConnection connection, DatabaseMetadata databaseMetadata) { - if ( databaseMetadata.productName().equalsIgnoreCase( "PostgreSQL" ) ) { - // We need to check if the database is PostgreSQL or CockroachDB - // Hibernate ORM does it using a query, so we need to check in advance + /** + * @see org.hibernate.dialect.Database#POSTGRESQL for recognizing CockroachDB + */ + private static CompletionStage resolutionInfoStage(ReactiveConnection connection, ExplicitMetadata explicitMetadata) { + final DatabaseMetadata databaseMetadata = explicitMetadata != null + ? new ReactiveDatabaseMetadata( connection.getDatabaseMetadata(), explicitMetadata ) + : connection.getDatabaseMetadata(); + + // If the product name is explicitly set to Postgres, we are not going to override it + if ( ( explicitMetadata == null || explicitMetadata.productName == null ) + && databaseMetadata.productName().equalsIgnoreCase( "PostgreSQL" ) ) { + // CockroachDB returns "PostgreSQL" as product name in the metadata. + // So, we need to check if the database is PostgreSQL or CockroachDB + // We follow the same approach used by ORM: run a new query and check the full version metadata // See org.hibernate.dialect.Database.POSTGRESQL#createDialect return connection.select( "select version()" ) .thenApply( DialectBuilder::readFullVersion ) - .thenApply( fullversion -> { - if ( fullversion.startsWith( "Cockroach" ) ) { - return new CockroachDatabaseMetadata( fullversion ); - } - return databaseMetadata; - } ) + .thenApply( fullVersion -> fullVersion.startsWith( "Cockroach" ) + ? new ReactiveDatabaseMetadata( "Cockroach", databaseMetadata ) + : databaseMetadata + ) .thenApply( ReactiveDialectResolutionInfo::new ); } @@ -122,32 +196,62 @@ private static String readFullVersion(ReactiveConnection.Result result) { } } - private static class CockroachDatabaseMetadata implements DatabaseMetadata { + /** + * Utility class to pass around explicit metadata properties. + * It's different from {@link DatabaseMetadata} because values can be null. + */ + private static class ExplicitMetadata { + private final String productName; + private final String fullVersion; + private final Integer majorVersion; + private final Integer minorVersion; + + public ExplicitMetadata(String explicitDatabaseName, Integer explicitDatabaseMajorVersion, Integer explicitDatabaseMinorVersion, String explicitDatabaseVersion ) { + this.productName = explicitDatabaseName; + this.fullVersion = explicitDatabaseVersion; + this.majorVersion = explicitDatabaseMajorVersion; + this.minorVersion = explicitDatabaseMinorVersion; + } + } - private final String fullversion; + private static class ReactiveDatabaseMetadata implements DatabaseMetadata { + public final String productName; + public final String fullVersion; + public final int majorVersion; + public final int minorVersion; + + public ReactiveDatabaseMetadata(String productName, DatabaseMetadata databaseMetadata) { + this.productName = productName; + this.fullVersion = databaseMetadata.productName(); + this.majorVersion = databaseMetadata.majorVersion(); + this.minorVersion = databaseMetadata.minorVersion(); + } - public CockroachDatabaseMetadata(String fullversion) { - this.fullversion = fullversion; + public ReactiveDatabaseMetadata(DatabaseMetadata metadata, ExplicitMetadata explicitMetadata) { + productName = requireNonNullElse( explicitMetadata.productName, metadata.productName() ); + fullVersion = requireNonNullElse( explicitMetadata.fullVersion, metadata.fullVersion() ); + majorVersion = requireNonNullElse( explicitMetadata.majorVersion, metadata.majorVersion() ); + minorVersion = requireNonNullElse( explicitMetadata.minorVersion, metadata.minorVersion() ); } @Override public String productName() { - return "CockroachDb"; + return productName; } @Override public String fullVersion() { - return fullversion; + return fullVersion; } @Override public int majorVersion() { - return 0; + return majorVersion; } @Override public int minorVersion() { - return 0; + return minorVersion; } } @@ -179,6 +283,27 @@ public int getDatabaseMinorVersion() { return metadata.minorVersion(); } + @Override + public int getDatabaseMicroVersion() { + return databaseMicroVersion( metadata.fullVersion(), metadata.majorVersion(), metadata.minorVersion() ); + } + + // We should move this in ORM and avoid duplicated code + private static int databaseMicroVersion(String version, int major, int minor) { + final String prefix = major + "." + minor + "."; + if ( version.startsWith( prefix ) ) { + try { + final String substring = version.substring( prefix.length() ); + final String micro = new StringTokenizer( substring, " .,-:;/()[]" ).nextToken(); + return parseInt( micro ); + } + catch (NumberFormatException nfe) { + return 0; + } + } + return 0; + } + @Override public String getDriverName() { return getDatabaseName(); @@ -196,6 +321,7 @@ public int getDriverMinorVersion() { @Override public String getSQLKeywords() { + // Vert.x metadata doesn't have this info return null; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveMutationQuery.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveMutationQuery.java index ce3943f25..5833156fc 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveMutationQuery.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveMutationQuery.java @@ -13,12 +13,12 @@ import java.util.concurrent.CompletionStage; import org.hibernate.FlushMode; -import org.hibernate.query.BindableType; import org.hibernate.query.CommonQueryContract; import org.hibernate.query.QueryParameter; import jakarta.persistence.Parameter; import jakarta.persistence.TemporalType; +import jakarta.persistence.metamodel.Type; /** * @see org.hibernate.query.MutationQuery @@ -33,7 +33,7 @@ public interface ReactiveMutationQuery extends CommonQueryContract {

    ReactiveMutationQuery setParameter(String name, P value, Class

    type); @Override -

    ReactiveMutationQuery setParameter(String name, P value, BindableType

    type); +

    ReactiveMutationQuery setParameter(String name, P value, Type

    type); @Override ReactiveMutationQuery setParameter(String name, Instant value, TemporalType temporalType); @@ -51,7 +51,7 @@ public interface ReactiveMutationQuery extends CommonQueryContract {

    ReactiveMutationQuery setParameter(int position, P value, Class

    type); @Override -

    ReactiveMutationQuery setParameter(int position, P value, BindableType

    type); +

    ReactiveMutationQuery setParameter(int position, P value, Type

    type); @Override ReactiveMutationQuery setParameter(int position, Instant value, TemporalType temporalType); @@ -69,7 +69,7 @@ public interface ReactiveMutationQuery extends CommonQueryContract {

    ReactiveMutationQuery setParameter(QueryParameter

    parameter, P value, Class

    type); @Override -

    ReactiveMutationQuery setParameter(QueryParameter

    parameter, P val, BindableType

    type); +

    ReactiveMutationQuery setParameter(QueryParameter

    parameter, P val, Type

    type); @Override ReactiveMutationQuery setParameter(Parameter param, T value); @@ -87,7 +87,7 @@ public interface ReactiveMutationQuery extends CommonQueryContract {

    ReactiveMutationQuery setParameterList(String name, Collection values, Class

    javaType); @Override -

    ReactiveMutationQuery setParameterList(String name, Collection values, BindableType

    type); +

    ReactiveMutationQuery setParameterList(String name, Collection values, Type

    type); @Override ReactiveMutationQuery setParameterList(String name, Object[] values); @@ -96,7 +96,7 @@ public interface ReactiveMutationQuery extends CommonQueryContract {

    ReactiveMutationQuery setParameterList(String name, P[] values, Class

    javaType); @Override -

    ReactiveMutationQuery setParameterList(String name, P[] values, BindableType

    type); +

    ReactiveMutationQuery setParameterList(String name, P[] values, Type

    type); @Override ReactiveMutationQuery setParameterList(int position, Collection values); @@ -105,7 +105,7 @@ public interface ReactiveMutationQuery extends CommonQueryContract {

    ReactiveMutationQuery setParameterList(int position, Collection values, Class

    javaType); @Override -

    ReactiveMutationQuery setParameterList(int position, Collection values, BindableType

    type); +

    ReactiveMutationQuery setParameterList(int position, Collection values, Type

    type); @Override ReactiveMutationQuery setParameterList(int position, Object[] values); @@ -114,7 +114,7 @@ public interface ReactiveMutationQuery extends CommonQueryContract {

    ReactiveMutationQuery setParameterList(int position, P[] values, Class

    javaType); @Override -

    ReactiveMutationQuery setParameterList(int position, P[] values, BindableType

    type); +

    ReactiveMutationQuery setParameterList(int position, P[] values, Type

    type); @Override

    ReactiveMutationQuery setParameterList(QueryParameter

    parameter, Collection values); @@ -123,7 +123,7 @@ public interface ReactiveMutationQuery extends CommonQueryContract {

    ReactiveMutationQuery setParameterList(QueryParameter

    parameter, Collection values, Class

    javaType); @Override -

    ReactiveMutationQuery setParameterList(QueryParameter

    parameter, Collection values, BindableType

    type); +

    ReactiveMutationQuery setParameterList(QueryParameter

    parameter, Collection values, Type

    type); @Override

    ReactiveMutationQuery setParameterList(QueryParameter

    parameter, P[] values); @@ -132,7 +132,7 @@ public interface ReactiveMutationQuery extends CommonQueryContract {

    ReactiveMutationQuery setParameterList(QueryParameter

    parameter, P[] values, Class

    javaType); @Override -

    ReactiveMutationQuery setParameterList(QueryParameter

    parameter, P[] values, BindableType

    type); +

    ReactiveMutationQuery setParameterList(QueryParameter

    parameter, P[] values, Type

    type); @Override ReactiveMutationQuery setProperties(Object bean); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveNativeQuery.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveNativeQuery.java index fb6a1f46d..708b6a486 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveNativeQuery.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveNativeQuery.java @@ -11,12 +11,12 @@ import java.util.Date; import java.util.Map; +import jakarta.persistence.metamodel.Type; import org.hibernate.CacheMode; import org.hibernate.FlushMode; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.metamodel.model.domain.BasicDomainType; -import org.hibernate.query.BindableType; import org.hibernate.query.NativeQuery; import org.hibernate.query.NativeQuery.FetchReturn; import org.hibernate.query.QueryParameter; @@ -225,7 +225,7 @@ public interface ReactiveNativeQuery extends ReactiveQuery {

    ReactiveNativeQuery setParameter(String name, P val, Class

    type); @Override -

    ReactiveNativeQuery setParameter(String name, P val, BindableType

    type); +

    ReactiveNativeQuery setParameter(String name, P val, Type

    type); @Override ReactiveNativeQuery setParameter(String name, Instant value, TemporalType temporalType); @@ -243,7 +243,7 @@ public interface ReactiveNativeQuery extends ReactiveQuery {

    ReactiveNativeQuery setParameter(int position, P val, Class

    type); @Override -

    ReactiveNativeQuery setParameter(int position, P val, BindableType

    type); +

    ReactiveNativeQuery setParameter(int position, P val, Type

    type); @Override ReactiveNativeQuery setParameter(int position, Instant value, TemporalType temporalType); @@ -261,7 +261,7 @@ public interface ReactiveNativeQuery extends ReactiveQuery {

    ReactiveNativeQuery setParameter(QueryParameter

    parameter, P val, Class

    type); @Override -

    ReactiveNativeQuery setParameter(QueryParameter

    parameter, P val, BindableType

    type); +

    ReactiveNativeQuery setParameter(QueryParameter

    parameter, P val, Type

    type); @Override

    ReactiveNativeQuery setParameter(Parameter

    param, P value); @@ -279,7 +279,7 @@ public interface ReactiveNativeQuery extends ReactiveQuery {

    ReactiveNativeQuery setParameterList(String name, Collection values, Class

    type); @Override -

    ReactiveNativeQuery setParameterList(String name, Collection values, BindableType

    type); +

    ReactiveNativeQuery setParameterList(String name, Collection values, Type

    type); @Override ReactiveNativeQuery setParameterList(String name, Object[] values); @@ -288,7 +288,7 @@ public interface ReactiveNativeQuery extends ReactiveQuery {

    ReactiveNativeQuery setParameterList(String name, P[] values, Class

    type); @Override -

    ReactiveNativeQuery setParameterList(String name, P[] values, BindableType

    type); +

    ReactiveNativeQuery setParameterList(String name, P[] values, Type

    type); @Override ReactiveNativeQuery setParameterList(int position, @SuppressWarnings("rawtypes") Collection values); @@ -297,7 +297,7 @@ public interface ReactiveNativeQuery extends ReactiveQuery {

    ReactiveNativeQuery setParameterList(int position, Collection values, Class

    type); @Override -

    ReactiveNativeQuery setParameterList(int position, Collection values, BindableType

    javaType); +

    ReactiveNativeQuery setParameterList(int position, Collection values, Type

    javaType); @Override ReactiveNativeQuery setParameterList(int position, Object[] values); @@ -306,7 +306,7 @@ public interface ReactiveNativeQuery extends ReactiveQuery {

    ReactiveNativeQuery setParameterList(int position, P[] values, Class

    javaType); @Override -

    ReactiveNativeQuery setParameterList(int position, P[] values, BindableType

    javaType); +

    ReactiveNativeQuery setParameterList(int position, P[] values, Type

    javaType); @Override

    ReactiveNativeQuery setParameterList(QueryParameter

    parameter, Collection values); @@ -315,7 +315,7 @@ public interface ReactiveNativeQuery extends ReactiveQuery {

    ReactiveNativeQuery setParameterList(QueryParameter

    parameter, Collection values, Class

    javaType); @Override -

    ReactiveNativeQuery setParameterList(QueryParameter

    parameter, Collection values, BindableType

    type); +

    ReactiveNativeQuery setParameterList(QueryParameter

    parameter, Collection values, Type

    type); @Override

    ReactiveNativeQuery setParameterList(QueryParameter

    parameter, P[] values); @@ -324,7 +324,7 @@ public interface ReactiveNativeQuery extends ReactiveQuery {

    ReactiveNativeQuery setParameterList(QueryParameter

    parameter, P[] values, Class

    javaType); @Override -

    ReactiveNativeQuery setParameterList(QueryParameter

    parameter, P[] values, BindableType

    type); +

    ReactiveNativeQuery setParameterList(QueryParameter

    parameter, P[] values, Type

    type); @Override ReactiveNativeQuery setProperties(Object bean); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveQuery.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveQuery.java index 0dae454e0..9c0fc5de8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveQuery.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveQuery.java @@ -11,11 +11,11 @@ import java.util.Date; import java.util.Map; +import jakarta.persistence.metamodel.Type; import org.hibernate.CacheMode; import org.hibernate.FlushMode; import org.hibernate.LockMode; import org.hibernate.LockOptions; -import org.hibernate.query.BindableType; import org.hibernate.query.ParameterMetadata; import org.hibernate.query.QueryParameter; import org.hibernate.query.ResultListTransformer; @@ -33,10 +33,13 @@ * @see org.hibernate.query.Query */ public interface ReactiveQuery extends ReactiveSelectionQuery, ReactiveMutationQuery { + @Override String getQueryString(); + @Override String getComment(); + @Override ReactiveQuery setComment(String comment); ReactiveQuery addQueryHint(String hint); @@ -55,6 +58,7 @@ public interface ReactiveQuery extends ReactiveSelectionQuery, ReactiveMut QueryOptions getQueryOptions(); + @Override ParameterMetadata getParameterMetadata(); @Override @@ -64,7 +68,7 @@ public interface ReactiveQuery extends ReactiveSelectionQuery, ReactiveMut

    ReactiveQuery setParameter(String parameter, P argument, Class

    type); @Override -

    ReactiveQuery setParameter(String parameter, P argument, BindableType

    type); +

    ReactiveQuery setParameter(String parameter, P argument, Type

    type); /** * Bind an {@link Instant} value to the named query parameter using @@ -85,31 +89,34 @@ public interface ReactiveQuery extends ReactiveSelectionQuery, ReactiveMut * which it occurs, use one of the forms which accepts a "type". * * @see #setParameter(int, Object, Class) - * @see #setParameter(int, Object, BindableType) + * @see #setParameter(int, Object, Type) */ @Override ReactiveQuery setParameter(int parameter, Object argument); /** * Bind the given argument to an ordinal query parameter using the given - * Class reference to attempt to determine the {@link BindableType} - * to use. If unable to determine an appropriate {@link BindableType}, + * Class reference to attempt to determine the {@link Type} + * to use. If unable to determine an appropriate {@link Type}, * {@link #setParameter(int, Object)} is used. * - * @see #setParameter(int, Object, BindableType) + * @see #setParameter(int, Object, Type) */ + @Override

    ReactiveQuery setParameter(int parameter, P argument, Class

    type); /** * Bind the given argument to an ordinal query parameter using the given - * {@link BindableType}. + * {@link Type}. */ -

    ReactiveQuery setParameter(int parameter, P argument, BindableType

    type); + @Override +

    ReactiveQuery setParameter(int parameter, P argument, Type

    type); /** * Bind an {@link Instant} value to the ordinal query parameter using * just the portion indicated by the given {@link TemporalType}. */ + @Override ReactiveQuery setParameter(int parameter, Instant argument, TemporalType temporalType); /** @@ -124,11 +131,14 @@ public interface ReactiveQuery extends ReactiveSelectionQuery, ReactiveMut @Override ReactiveQuery setParameter(int parameter, Calendar argument, TemporalType temporalType); + @Override ReactiveQuery setParameter(QueryParameter parameter, T argument); + @Override

    ReactiveQuery setParameter(QueryParameter

    parameter, P argument, Class

    type); -

    ReactiveQuery setParameter(QueryParameter

    parameter, P argument, BindableType

    type); + @Override +

    ReactiveQuery setParameter(QueryParameter

    parameter, P argument, Type

    type); @Override ReactiveQuery setParameter(Parameter parameter, T argument); @@ -139,20 +149,23 @@ public interface ReactiveQuery extends ReactiveSelectionQuery, ReactiveMut @Override ReactiveQuery setParameter(Parameter parameter, Date argument, TemporalType temporalType); + @Override ReactiveQuery setParameterList(String parameter, @SuppressWarnings("rawtypes") Collection arguments); + @Override

    ReactiveQuery setParameterList(String parameter, Collection arguments, Class

    javaType); /** * Bind multiple arguments to a named query parameter using the given - * {@link BindableType}. + * {@link Type}. * * @apiNote This is used for binding a list of values to an expression * such as {@code entity.field in (:values)}. * * @return {@code this}, for method chaining */ -

    ReactiveQuery setParameterList(String parameter, Collection arguments, BindableType

    type); + @Override +

    ReactiveQuery setParameterList(String parameter, Collection arguments, Type

    type); /** * Bind multiple arguments to a named query parameter. @@ -165,34 +178,37 @@ public interface ReactiveQuery extends ReactiveSelectionQuery, ReactiveMut * * @return {@code this}, for method chaining */ + @Override ReactiveQuery setParameterList(String parameter, Object[] values); /** * Bind multiple arguments to a named query parameter using the given - * Class reference to attempt to determine the {@link BindableType} - * to use. If unable to determine an appropriate {@link BindableType}, + * Class reference to attempt to determine the {@link Type} + * to use. If unable to determine an appropriate {@link Type}, * {@link #setParameterList(String, Collection)} is used. * - * @see #setParameterList(java.lang.String, Object[], BindableType) + * @see #setParameterList(java.lang.String, Object[], Type) * * @apiNote This is used for binding a list of values to an expression * such as {@code entity.field in (:values)}. * * @return {@code this}, for method chaining */ + @Override

    ReactiveQuery setParameterList(String parameter, P[] arguments, Class

    javaType); /** * Bind multiple arguments to a named query parameter using the given - * {@link BindableType}. + * {@link Type}. * * @apiNote This is used for binding a list of values to an expression * such as {@code entity.field in (:values)}. * * @return {@code this}, for method chaining */ -

    ReactiveQuery setParameterList(String parameter, P[] arguments, BindableType

    type); + @Override +

    ReactiveQuery setParameterList(String parameter, P[] arguments, Type

    type); /** * Bind multiple arguments to an ordinal query parameter. @@ -205,33 +221,36 @@ public interface ReactiveQuery extends ReactiveSelectionQuery, ReactiveMut * * @return {@code this}, for method chaining */ + @Override ReactiveQuery setParameterList(int parameter, @SuppressWarnings("rawtypes") Collection arguments); /** * Bind multiple arguments to an ordinal query parameter using the given - * Class reference to attempt to determine the {@link BindableType} - * to use. If unable to determine an appropriate {@link BindableType}, + * Class reference to attempt to determine the {@link Type} + * to use. If unable to determine an appropriate {@link Type}, * {@link #setParameterList(String, Collection)} is used. * - * @see #setParameterList(int, Collection, BindableType) + * @see #setParameterList(int, Collection, Type) * * @apiNote This is used for binding a list of values to an expression * such as {@code entity.field in (:values)}. * * @return {@code this}, for method chaining */ + @Override

    ReactiveQuery setParameterList(int parameter, Collection arguments, Class

    javaType); /** * Bind multiple arguments to an ordinal query parameter using the given - * {@link BindableType}. + * {@link Type}. * * @apiNote This is used for binding a list of values to an expression * such as {@code entity.field in (:values)}. * * @return {@code this}, for method chaining */ -

    ReactiveQuery setParameterList(int parameter, Collection arguments, BindableType

    type); + @Override +

    ReactiveQuery setParameterList(int parameter, Collection arguments, Type

    type); /** * Bind multiple arguments to an ordinal query parameter. @@ -244,33 +263,36 @@ public interface ReactiveQuery extends ReactiveSelectionQuery, ReactiveMut * * @return {@code this}, for method chaining */ + @Override ReactiveQuery setParameterList(int parameter, Object[] arguments); /** * Bind multiple arguments to an ordinal query parameter using the given - * {@link Class} reference to attempt to determine the {@link BindableType} - * to use. If unable to determine an appropriate {@link BindableType}, + * {@link Class} reference to attempt to determine the {@link Type} + * to use. If unable to determine an appropriate {@link Type}, * {@link #setParameterList(String, Collection)} is used. * - * @see #setParameterList(int, Object[], BindableType) + * @see #setParameterList(int, Object[], Type) * * @apiNote This is used for binding a list of values to an expression * such as {@code entity.field in (:values)}. * * @return {@code this}, for method chaining */ + @Override

    ReactiveQuery setParameterList(int parameter, P[] arguments, Class

    javaType); /** * Bind multiple arguments to an ordinal query parameter using the given - * {@link BindableType}. + * {@link Type}. * * @apiNote This is used for binding a list of values to an expression * such as {@code entity.field in (:values)}. * * @return {@code this}, for method chaining */ -

    ReactiveQuery setParameterList(int parameter, P[] arguments, BindableType

    type); + @Override +

    ReactiveQuery setParameterList(int parameter, P[] arguments, Type

    type); /** * Bind multiple arguments to the query parameter represented by the given @@ -284,27 +306,29 @@ public interface ReactiveQuery extends ReactiveSelectionQuery, ReactiveMut * * @return {@code this}, for method chaining */ + @Override

    ReactiveQuery setParameterList(QueryParameter

    parameter, Collection arguments); /** * Bind multiple arguments to the query parameter represented by the given * {@link QueryParameter} using the given Class reference to attempt to - * determine the {@link BindableType} to use. If unable to determine an - * appropriate {@link BindableType}, {@link #setParameterList(String, Collection)} + * determine the {@link Type} to use. If unable to determine an + * appropriate {@link Type}, {@link #setParameterList(String, Collection)} * is used. * - * @see #setParameterList(QueryParameter, java.util.Collection, BindableType) + * @see #setParameterList(QueryParameter, java.util.Collection, Type) * * @apiNote This is used for binding a list of values to an expression such * as {@code entity.field in (:values)}. * * @return {@code this}, for method chaining */ + @Override

    ReactiveQuery setParameterList(QueryParameter

    parameter, Collection arguments, Class

    javaType); /** * Bind multiple arguments to the query parameter represented by the given - * {@link QueryParameter}, inferring the {@link BindableType}. + * {@link QueryParameter}, inferring the {@link Type}. *

    * The "type mapping" for the binding is inferred from the type of the first * collection element. @@ -314,7 +338,8 @@ public interface ReactiveQuery extends ReactiveSelectionQuery, ReactiveMut * * @return {@code this}, for method chaining */ -

    ReactiveQuery setParameterList(QueryParameter

    parameter, Collection arguments, BindableType

    type); + @Override +

    ReactiveQuery setParameterList(QueryParameter

    parameter, Collection arguments, Type

    type); /** * Bind multiple arguments to the query parameter represented by the @@ -329,27 +354,29 @@ public interface ReactiveQuery extends ReactiveSelectionQuery, ReactiveMut * * @return {@code this}, for method chaining */ + @Override

    ReactiveQuery setParameterList(QueryParameter

    parameter, P[] arguments); /** * Bind multiple arguments to the query parameter represented by the * given {@link QueryParameter} using the given Class reference to attempt - * to determine the {@link BindableType} to use. If unable to - * determine an appropriate {@link BindableType}, + * to determine the {@link Type} to use. If unable to + * determine an appropriate {@link Type}, * {@link #setParameterList(String, Collection)} is used * - * @see #setParameterList(QueryParameter, Object[], BindableType) + * @see #setParameterList(QueryParameter, Object[], Type) * * @apiNote This is used for binding a list of values to an expression such * as {@code entity.field in (:values)}. * * @return {@code this}, for method chaining */ + @Override

    ReactiveQuery setParameterList(QueryParameter

    parameter, P[] arguments, Class

    javaType); /** * Bind multiple arguments to the query parameter represented by the - * given {@link QueryParameter}, inferring the {@link BindableType}. + * given {@link QueryParameter}, inferring the {@link Type}. *

    * The "type mapping" for the binding is inferred from the type of * the first collection element @@ -359,7 +386,8 @@ public interface ReactiveQuery extends ReactiveSelectionQuery, ReactiveMut * * @return {@code this}, for method chaining */ -

    ReactiveQuery setParameterList(QueryParameter

    parameter, P[] arguments, BindableType

    type); + @Override +

    ReactiveQuery setParameterList(QueryParameter

    parameter, P[] arguments, Type

    type); /** * Bind the property values of the given bean to named parameters of the query, @@ -370,6 +398,7 @@ public interface ReactiveQuery extends ReactiveSelectionQuery, ReactiveMut * * @return {@code this}, for method chaining */ + @Override ReactiveQuery setProperties(Object bean); /** @@ -381,6 +410,7 @@ public interface ReactiveQuery extends ReactiveSelectionQuery, ReactiveMut * * @return {@code this}, for method chaining */ + @Override ReactiveQuery setProperties(@SuppressWarnings("rawtypes") Map bean); 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 0354a59e5..6d28fbb04 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 @@ -11,7 +11,6 @@ import java.util.Date; import java.util.Map; -import org.hibernate.query.BindableType; import org.hibernate.query.QueryParameter; import org.hibernate.query.ResultListTransformer; import org.hibernate.query.TupleTransformer; @@ -19,6 +18,7 @@ import jakarta.persistence.Parameter; import jakarta.persistence.TemporalType; +import jakarta.persistence.metamodel.Type; public interface ReactiveQueryImplementor extends ReactiveQuery { @@ -37,7 +37,7 @@ public interface ReactiveQueryImplementor extends ReactiveQuery {

    ReactiveQueryImplementor setParameter(String name, P value, Class

    type); @Override -

    ReactiveQueryImplementor setParameter(String name, P value, BindableType

    type); +

    ReactiveQueryImplementor setParameter(String name, P value, Type

    type); @Override ReactiveQueryImplementor setParameter(String name, Instant value, TemporalType temporalType); @@ -55,7 +55,7 @@ public interface ReactiveQueryImplementor extends ReactiveQuery {

    ReactiveQueryImplementor setParameter(int position, P value, Class

    type); @Override -

    ReactiveQueryImplementor setParameter(int position, P value, BindableType

    type); +

    ReactiveQueryImplementor setParameter(int position, P value, Type

    type); @Override ReactiveQueryImplementor setParameter(int position, Instant value, TemporalType temporalType); @@ -73,7 +73,7 @@ public interface ReactiveQueryImplementor extends ReactiveQuery {

    ReactiveQueryImplementor setParameter(QueryParameter

    parameter, P value, Class

    type); @Override -

    ReactiveQueryImplementor setParameter(QueryParameter

    parameter, P val, BindableType

    type); +

    ReactiveQueryImplementor setParameter(QueryParameter

    parameter, P val, Type

    type); @Override ReactiveQueryImplementor setParameter(Parameter param, T value); @@ -91,7 +91,7 @@ public interface ReactiveQueryImplementor extends ReactiveQuery {

    ReactiveQueryImplementor setParameterList(String name, Collection values, Class

    javaType); @Override -

    ReactiveQueryImplementor setParameterList(String name, Collection values, BindableType

    type); +

    ReactiveQueryImplementor setParameterList(String name, Collection values, Type

    type); @Override ReactiveQueryImplementor setParameterList(String name, Object[] values); @@ -100,7 +100,7 @@ public interface ReactiveQueryImplementor extends ReactiveQuery {

    ReactiveQueryImplementor setParameterList(String name, P[] values, Class

    javaType); @Override -

    ReactiveQueryImplementor setParameterList(String name, P[] values, BindableType

    type); +

    ReactiveQueryImplementor setParameterList(String name, P[] values, Type

    type); @Override ReactiveQueryImplementor setParameterList(int position, @SuppressWarnings("rawtypes") Collection values); @@ -112,7 +112,7 @@ public interface ReactiveQueryImplementor extends ReactiveQuery {

    ReactiveQueryImplementor setParameterList( int position, Collection values, - BindableType

    type); + Type

    type); @Override ReactiveQueryImplementor setParameterList(int position, Object[] values); @@ -121,7 +121,7 @@

    ReactiveQueryImplementor setParameterList(

    ReactiveQueryImplementor setParameterList(int position, P[] values, Class

    javaType); @Override -

    ReactiveQueryImplementor setParameterList(int position, P[] values, BindableType

    type); +

    ReactiveQueryImplementor setParameterList(int position, P[] values, Type

    type); @Override

    ReactiveQueryImplementor setParameterList(QueryParameter

    parameter, Collection values); @@ -136,7 +136,7 @@

    ReactiveQueryImplementor setParameterList(

    ReactiveQueryImplementor setParameterList( QueryParameter

    parameter, Collection values, - BindableType

    type); + Type

    type); @Override

    ReactiveQueryImplementor setParameterList(QueryParameter

    parameter, P[] values); @@ -145,7 +145,7 @@

    ReactiveQueryImplementor setParameterList(

    ReactiveQueryImplementor setParameterList(QueryParameter

    parameter, P[] values, Class

    javaType); @Override -

    ReactiveQueryImplementor setParameterList(QueryParameter

    parameter, P[] values, BindableType

    type); +

    ReactiveQueryImplementor setParameterList(QueryParameter

    parameter, P[] values, Type

    type); @Override ReactiveQueryImplementor setProperties(Object bean); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveSelectionQuery.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveSelectionQuery.java index 6c35d9260..32aa72337 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveSelectionQuery.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveSelectionQuery.java @@ -5,24 +5,14 @@ */ package org.hibernate.reactive.query; -import java.time.Instant; -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletionStage; - +import jakarta.persistence.metamodel.Type; import org.hibernate.CacheMode; import org.hibernate.FlushMode; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.spi.RootGraphImplementor; -import org.hibernate.query.BindableType; import org.hibernate.query.CommonQueryContract; -import org.hibernate.query.Order; import org.hibernate.query.QueryParameter; import jakarta.persistence.CacheRetrieveMode; @@ -31,6 +21,14 @@ import jakarta.persistence.LockModeType; import jakarta.persistence.Parameter; import jakarta.persistence.TemporalType; +import java.time.Instant; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletionStage; /** * @see org.hibernate.query.SelectionQuery @@ -55,6 +53,7 @@ default CompletionStage> getReactiveResultList() { CompletionStage> reactiveUniqueResultOptional(); + @Override ReactiveSelectionQuery setHint(String hintName, Object value); // Covariant methods @@ -123,10 +122,6 @@ default CompletionStage> getReactiveResultList() { void applyGraph(RootGraphImplementor graph, GraphSemantic semantic); - ReactiveSelectionQuery setOrder(List> orderList); - - ReactiveSelectionQuery setOrder(Order order); - ReactiveSelectionQuery enableFetchProfile(String profileName); @Override @@ -136,7 +131,7 @@ default CompletionStage> getReactiveResultList() {

    ReactiveSelectionQuery setParameter(String name, P value, Class

    type); @Override -

    ReactiveSelectionQuery setParameter(String name, P value, BindableType

    type); +

    ReactiveSelectionQuery setParameter(String name, P value, Type

    type); @Override ReactiveSelectionQuery setParameter(String name, Instant value, TemporalType temporalType); @@ -154,7 +149,7 @@ default CompletionStage> getReactiveResultList() {

    ReactiveSelectionQuery setParameter(int position, P value, Class

    type); @Override -

    ReactiveSelectionQuery setParameter(int position, P value, BindableType

    type); +

    ReactiveSelectionQuery setParameter(int position, P value, Type

    type); @Override ReactiveSelectionQuery setParameter(int position, Instant value, TemporalType temporalType); @@ -172,7 +167,7 @@ default CompletionStage> getReactiveResultList() {

    ReactiveSelectionQuery setParameter(QueryParameter

    parameter, P value, Class

    type); @Override -

    ReactiveSelectionQuery setParameter(QueryParameter

    parameter, P val, BindableType

    type); +

    ReactiveSelectionQuery setParameter(QueryParameter

    parameter, P val, Type

    type); @Override ReactiveSelectionQuery setParameter(Parameter param, T value); @@ -190,7 +185,7 @@ default CompletionStage> getReactiveResultList() {

    ReactiveSelectionQuery setParameterList(String name, Collection values, Class

    javaType); @Override -

    ReactiveSelectionQuery setParameterList(String name, Collection values, BindableType

    type); +

    ReactiveSelectionQuery setParameterList(String name, Collection values, Type

    type); @Override ReactiveSelectionQuery setParameterList(String name, Object[] values); @@ -199,7 +194,7 @@ default CompletionStage> getReactiveResultList() {

    ReactiveSelectionQuery setParameterList(String name, P[] values, Class

    javaType); @Override -

    ReactiveSelectionQuery setParameterList(String name, P[] values, BindableType

    type); +

    ReactiveSelectionQuery setParameterList(String name, P[] values, Type

    type); @Override ReactiveSelectionQuery setParameterList(int position, @SuppressWarnings("rawtypes") Collection values); @@ -208,7 +203,7 @@ default CompletionStage> getReactiveResultList() {

    ReactiveSelectionQuery setParameterList(int position, Collection values, Class

    javaType); @Override -

    ReactiveSelectionQuery setParameterList(int position, Collection values, BindableType

    type); +

    ReactiveSelectionQuery setParameterList(int position, Collection values, Type

    type); @Override ReactiveSelectionQuery setParameterList(int position, Object[] values); @@ -217,7 +212,7 @@ default CompletionStage> getReactiveResultList() {

    ReactiveSelectionQuery setParameterList(int position, P[] values, Class

    javaType); @Override -

    ReactiveSelectionQuery setParameterList(int position, P[] values, BindableType

    type); +

    ReactiveSelectionQuery setParameterList(int position, P[] values, Type

    type); @Override

    ReactiveSelectionQuery setParameterList(QueryParameter

    parameter, Collection values); @@ -226,7 +221,7 @@ default CompletionStage> getReactiveResultList() {

    ReactiveSelectionQuery setParameterList(QueryParameter

    parameter, Collection values, Class

    javaType); @Override -

    ReactiveSelectionQuery setParameterList(QueryParameter

    parameter, Collection values, BindableType

    type); +

    ReactiveSelectionQuery setParameterList(QueryParameter

    parameter, Collection values, Type

    type); @Override

    ReactiveSelectionQuery setParameterList(QueryParameter

    parameter, P[] values); @@ -235,7 +230,7 @@ default CompletionStage> getReactiveResultList() {

    ReactiveSelectionQuery setParameterList(QueryParameter

    parameter, P[] values, Class

    javaType); @Override -

    ReactiveSelectionQuery setParameterList(QueryParameter

    parameter, P[] values, BindableType

    type); +

    ReactiveSelectionQuery setParameterList(QueryParameter

    parameter, P[] values, Type

    type); @Override ReactiveSelectionQuery setProperties(Object bean); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/internal/ReactiveNamedObjectRepositoryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/internal/ReactiveNamedObjectRepositoryImpl.java index ef2d70afe..9fd78d104 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/internal/ReactiveNamedObjectRepositoryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/internal/ReactiveNamedObjectRepositoryImpl.java @@ -5,6 +5,7 @@ */ package org.hibernate.reactive.query.internal; +import jakarta.persistence.TypedQuery; import java.util.Map; import java.util.function.Consumer; @@ -48,6 +49,11 @@ public void registerNamedQuery(String name, Query query) { delegate.registerNamedQuery( name, query ); } + @Override + public TypedQueryReference registerNamedQuery(String name, TypedQuery query) { + return delegate.registerNamedQuery( name, query ); + } + @Override public void visitSqmQueryMementos(Consumer> action) { delegate.visitSqmQueryMementos( action ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/spi/ReactiveAbstractSelectionQuery.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/spi/ReactiveAbstractSelectionQuery.java index 1137e0a62..9c2647b8f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/spi/ReactiveAbstractSelectionQuery.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/spi/ReactiveAbstractSelectionQuery.java @@ -30,7 +30,7 @@ import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmInterpretationsKey; -import org.hibernate.query.sqm.internal.SqmInterpretationsKey.InterpretationsKeySource; +import org.hibernate.query.sqm.spi.InterpretationsKeySource; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.reactive.engine.impl.ReactiveCallbackImpl; 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 ac8f4e9b4..6ca5b2024 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 @@ -27,8 +27,6 @@ import org.hibernate.graph.RootGraph; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.metamodel.model.domain.BasicDomainType; -import org.hibernate.query.BindableType; -import org.hibernate.query.Order; import org.hibernate.query.QueryParameter; import org.hibernate.query.ResultListTransformer; import org.hibernate.query.TupleTransformer; @@ -55,6 +53,7 @@ import jakarta.persistence.LockModeType; import jakarta.persistence.Parameter; import jakarta.persistence.TemporalType; +import jakarta.persistence.metamodel.Type; import jakarta.persistence.metamodel.SingularAttribute; public class ReactiveNativeQueryImpl extends NativeQueryImpl @@ -528,18 +527,6 @@ public ReactiveNativeQueryImpl setLockMode(String alias, LockMode lockMode) { return this; } - @Override - public ReactiveNativeQueryImpl setOrder(List> orders) { - super.setOrder( orders ); - return this; - } - - @Override - public ReactiveNativeQueryImpl setOrder(Order order) { - super.setOrder( order ); - return this; - } - @Override public ReactiveNativeQueryImpl setComment(String comment) { super.setComment( comment ); @@ -583,7 +570,7 @@ public ReactiveNativeQueryImpl setParameter(String name, Object val) { } @Override - public

    ReactiveNativeQueryImpl setParameter(String name, P val, BindableType

    type) { + public

    ReactiveNativeQueryImpl setParameter(String name, P val, Type

    type) { super.setParameter( name, val, type ); return this; } @@ -625,7 +612,7 @@ public

    ReactiveNativeQueryImpl setParameter(int position, P val, Class

    } @Override - public

    ReactiveNativeQueryImpl setParameter(int position, P val, BindableType

    type) { + public

    ReactiveNativeQueryImpl setParameter(int position, P val, Type

    type) { super.setParameter( position, val, type ); return this; } @@ -661,7 +648,7 @@ public

    ReactiveNativeQueryImpl setParameter(QueryParameter

    parameter, } @Override - public

    ReactiveNativeQueryImpl setParameter(QueryParameter

    parameter, P val, BindableType

    type) { + public

    ReactiveNativeQueryImpl setParameter(QueryParameter

    parameter, P val, Type

    type) { super.setParameter( parameter, val, type ); return this; } @@ -703,7 +690,7 @@ public

    ReactiveNativeQueryImpl setParameterList(String name, Collection ReactiveNativeQueryImpl setParameterList( String name, Collection values, - BindableType

    type) { + Type

    type) { super.setParameterList( name, values, type ); return this; } @@ -721,7 +708,7 @@ public

    ReactiveNativeQueryImpl setParameterList(String name, P[] values, } @Override - public

    ReactiveNativeQueryImpl setParameterList(String name, P[] values, BindableType

    type) { + public

    ReactiveNativeQueryImpl setParameterList(String name, P[] values, Type

    type) { super.setParameterList( name, values, type ); return this; } @@ -745,7 +732,7 @@ public

    ReactiveNativeQueryImpl setParameterList( public

    ReactiveNativeQueryImpl setParameterList( int position, Collection values, - BindableType

    type) { + Type

    type) { super.setParameterList( position, values, type ); return this; } @@ -763,7 +750,7 @@ public

    ReactiveNativeQueryImpl setParameterList(int position, P[] values, } @Override - public

    ReactiveNativeQueryImpl setParameterList(int position, P[] values, BindableType

    type) { + public

    ReactiveNativeQueryImpl setParameterList(int position, P[] values, Type

    type) { super.setParameterList( position, values, type ); return this; } @@ -789,7 +776,7 @@ public

    ReactiveNativeQueryImpl setParameterList( public

    ReactiveNativeQueryImpl setParameterList( QueryParameter

    parameter, Collection values, - BindableType

    type) { + Type

    type) { super.setParameterList( parameter, values, type ); return this; } @@ -810,7 +797,7 @@ public

    ReactiveNativeQueryImpl setParameterList(QueryParameter

    paramet public

    ReactiveNativeQueryImpl setParameterList( QueryParameter

    parameter, P[] values, - BindableType

    type) { + Type

    type) { super.setParameterList( parameter, values, type ); return this; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNativeQueryImplementor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNativeQueryImplementor.java index 3c59afc3e..fd9ca555f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNativeQueryImplementor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNativeQueryImplementor.java @@ -11,12 +11,12 @@ import java.util.Date; import java.util.Map; +import jakarta.persistence.metamodel.Type; import org.hibernate.CacheMode; import org.hibernate.FlushMode; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.metamodel.model.domain.BasicDomainType; -import org.hibernate.query.BindableType; import org.hibernate.query.QueryParameter; import org.hibernate.query.ResultListTransformer; import org.hibernate.query.TupleTransformer; @@ -173,7 +173,7 @@ ReactiveNativeQueryImplementor addJoin( ReactiveNativeQueryImplementor setParameter(String name, Object val); @Override -

    ReactiveNativeQueryImplementor setParameter(String name, P val, BindableType

    type); +

    ReactiveNativeQueryImplementor setParameter(String name, P val, Type

    type); @Override

    ReactiveNativeQueryImplementor setParameter(String name, P val, Class

    type); @@ -194,7 +194,7 @@ ReactiveNativeQueryImplementor addJoin(

    ReactiveNativeQueryImplementor setParameter(int position, P val, Class

    type); @Override -

    ReactiveNativeQueryImplementor setParameter(int position, P val, BindableType

    type); +

    ReactiveNativeQueryImplementor setParameter(int position, P val, Type

    type); @Override ReactiveNativeQueryImplementor setParameter(int position, Instant value, TemporalType temporalType); @@ -212,7 +212,7 @@ ReactiveNativeQueryImplementor addJoin(

    ReactiveNativeQueryImplementor setParameter(QueryParameter

    parameter, P val, Class

    type); @Override -

    ReactiveNativeQueryImplementor setParameter(QueryParameter

    parameter, P val, BindableType

    type); +

    ReactiveNativeQueryImplementor setParameter(QueryParameter

    parameter, P val, Type

    type); @Override

    ReactiveNativeQueryImplementor setParameter(Parameter

    param, P value); @@ -230,7 +230,7 @@ ReactiveNativeQueryImplementor addJoin(

    ReactiveNativeQueryImplementor setParameterList(String name, Collection values, Class

    type); @Override -

    ReactiveNativeQueryImplementor setParameterList(String name, Collection values, BindableType

    type); +

    ReactiveNativeQueryImplementor setParameterList(String name, Collection values, Type

    type); @Override ReactiveNativeQueryImplementor setParameterList(String name, Object[] values); @@ -239,7 +239,7 @@ ReactiveNativeQueryImplementor addJoin(

    ReactiveNativeQueryImplementor setParameterList(String name, P[] values, Class

    type); @Override -

    ReactiveNativeQueryImplementor setParameterList(String name, P[] values, BindableType

    type); +

    ReactiveNativeQueryImplementor setParameterList(String name, P[] values, Type

    type); @Override ReactiveNativeQueryImplementor setParameterList(int position, @SuppressWarnings("rawtypes") Collection values); @@ -248,7 +248,7 @@ ReactiveNativeQueryImplementor addJoin(

    ReactiveNativeQueryImplementor setParameterList(int position, Collection values, Class

    type); @Override -

    ReactiveNativeQueryImplementor setParameterList(int position, Collection values, BindableType

    type); +

    ReactiveNativeQueryImplementor setParameterList(int position, Collection values, Type

    type); @Override ReactiveNativeQueryImplementor setParameterList(int position, Object[] values); @@ -257,7 +257,7 @@ ReactiveNativeQueryImplementor addJoin(

    ReactiveNativeQueryImplementor setParameterList(int position, P[] values, Class

    javaType); @Override -

    ReactiveNativeQueryImplementor setParameterList(int position, P[] values, BindableType

    type); +

    ReactiveNativeQueryImplementor setParameterList(int position, P[] values, Type

    type); @Override @@ -267,7 +267,7 @@ ReactiveNativeQueryImplementor addJoin(

    ReactiveNativeQueryImplementor setParameterList(QueryParameter

    parameter, Collection values, Class

    javaType); @Override -

    ReactiveNativeQueryImplementor setParameterList(QueryParameter

    parameter, Collection values, BindableType

    type); +

    ReactiveNativeQueryImplementor setParameterList(QueryParameter

    parameter, Collection values, Type

    type); @Override

    ReactiveNativeQueryImplementor setParameterList(QueryParameter

    parameter, P[] values); @@ -276,7 +276,7 @@ ReactiveNativeQueryImplementor addJoin(

    ReactiveNativeQueryImplementor setParameterList(QueryParameter

    parameter, P[] values, Class

    javaType); @Override -

    ReactiveNativeQueryImplementor setParameterList(QueryParameter

    parameter, P[] values, BindableType

    type); +

    ReactiveNativeQueryImplementor setParameterList(QueryParameter

    parameter, P[] values, Type

    type); @Override ReactiveNativeQueryImplementor setProperties(Object bean); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/ReactiveSqmSelectionQuery.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/ReactiveSqmSelectionQuery.java index 1d897653f..0794ae435 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/ReactiveSqmSelectionQuery.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/ReactiveSqmSelectionQuery.java @@ -9,19 +9,17 @@ import java.util.Calendar; import java.util.Collection; import java.util.Date; -import java.util.List; import java.util.Map; import org.hibernate.CacheMode; import org.hibernate.FlushMode; -import org.hibernate.query.BindableType; -import org.hibernate.query.Order; import org.hibernate.query.QueryParameter; import org.hibernate.query.spi.SqmQuery; import org.hibernate.reactive.query.ReactiveSelectionQuery; import jakarta.persistence.Parameter; import jakarta.persistence.TemporalType; +import jakarta.persistence.metamodel.Type; /** * @see org.hibernate.query.sqm.SqmSelectionQuery @@ -35,7 +33,7 @@ public interface ReactiveSqmSelectionQuery extends ReactiveSelectionQuery,

    ReactiveSqmSelectionQuery setParameter(String name, P value, Class

    type); @Override -

    ReactiveSqmSelectionQuery setParameter(String name, P value, BindableType

    type); +

    ReactiveSqmSelectionQuery setParameter(String name, P value, Type

    type); @Override ReactiveSqmSelectionQuery setParameter(String name, Instant value, TemporalType temporalType); @@ -53,7 +51,7 @@ public interface ReactiveSqmSelectionQuery extends ReactiveSelectionQuery,

    ReactiveSqmSelectionQuery setParameter(int position, P value, Class

    type); @Override -

    ReactiveSqmSelectionQuery setParameter(int position, P value, BindableType

    type); +

    ReactiveSqmSelectionQuery setParameter(int position, P value, Type

    type); @Override ReactiveSqmSelectionQuery setParameter(int position, Instant value, TemporalType temporalType); @@ -71,7 +69,7 @@ public interface ReactiveSqmSelectionQuery extends ReactiveSelectionQuery,

    ReactiveSqmSelectionQuery setParameter(QueryParameter

    parameter, P value, Class

    type); @Override -

    ReactiveSqmSelectionQuery setParameter(QueryParameter

    parameter, P val, BindableType

    type); +

    ReactiveSqmSelectionQuery setParameter(QueryParameter

    parameter, P val, Type

    type); @Override ReactiveSqmSelectionQuery setParameter(Parameter param, T value); @@ -92,7 +90,7 @@ public interface ReactiveSqmSelectionQuery extends ReactiveSelectionQuery,

    ReactiveSqmSelectionQuery setParameterList( String name, Collection values, - BindableType

    type); + Type

    type); @Override ReactiveSqmSelectionQuery setParameterList(String name, Object[] values); @@ -101,7 +99,7 @@

    ReactiveSqmSelectionQuery setParameterList(

    ReactiveSqmSelectionQuery setParameterList(String name, P[] values, Class

    javaType); @Override -

    ReactiveSqmSelectionQuery setParameterList(String name, P[] values, BindableType

    type); +

    ReactiveSqmSelectionQuery setParameterList(String name, P[] values, Type

    type); @Override ReactiveSqmSelectionQuery setParameterList(int position, Collection values); @@ -113,7 +111,7 @@

    ReactiveSqmSelectionQuery setParameterList(

    ReactiveSqmSelectionQuery setParameterList( int position, Collection values, - BindableType

    type); + Type

    type); @Override ReactiveSqmSelectionQuery setParameterList(int position, Object[] values); @@ -122,7 +120,7 @@

    ReactiveSqmSelectionQuery setParameterList(

    ReactiveSqmSelectionQuery setParameterList(int position, P[] values, Class

    javaType); @Override -

    ReactiveSqmSelectionQuery setParameterList(int position, P[] values, BindableType

    type); +

    ReactiveSqmSelectionQuery setParameterList(int position, P[] values, Type

    type); @Override

    ReactiveSqmSelectionQuery setParameterList(QueryParameter

    parameter, Collection values); @@ -137,7 +135,7 @@

    ReactiveSqmSelectionQuery setParameterList(

    ReactiveSqmSelectionQuery setParameterList( QueryParameter

    parameter, Collection values, - BindableType

    type); + Type

    type); @Override

    ReactiveSqmSelectionQuery setParameterList(QueryParameter

    parameter, P[] values); @@ -146,7 +144,7 @@

    ReactiveSqmSelectionQuery setParameterList(

    ReactiveSqmSelectionQuery setParameterList(QueryParameter

    parameter, P[] values, Class

    javaType); @Override -

    ReactiveSqmSelectionQuery setParameterList(QueryParameter

    parameter, P[] values, BindableType

    type); +

    ReactiveSqmSelectionQuery setParameterList(QueryParameter

    parameter, P[] values, Type

    type); @Override ReactiveSqmSelectionQuery setProperties(Object bean); @@ -169,12 +167,6 @@

    ReactiveSqmSelectionQuery setParameterList( @Override ReactiveSqmSelectionQuery setTimeout(int timeout); - @Override - ReactiveSqmSelectionQuery setOrder(List> orderList); - - @Override - ReactiveSqmSelectionQuery setOrder(Order order); - @Override ReactiveSqmSelectionQuery setFetchSize(int fetchSize); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java index 096196576..5242ab747 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java @@ -32,9 +32,7 @@ import org.hibernate.id.enhanced.Optimizer; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.query.BindableType; import org.hibernate.query.IllegalQueryOperationException; -import org.hibernate.query.Order; import org.hibernate.query.QueryParameter; import org.hibernate.query.ResultListTransformer; import org.hibernate.query.TupleTransformer; @@ -70,6 +68,7 @@ import jakarta.persistence.LockModeType; import jakarta.persistence.Parameter; import jakarta.persistence.TemporalType; +import jakarta.persistence.metamodel.Type; /** * A reactive {@link QuerySqmImpl} @@ -478,18 +477,6 @@ public ReactiveQuerySqmImpl setLockMode(String alias, LockMode lockMode) { return this; } - @Override - public ReactiveQuerySqmImpl setOrder(List> orders) { - super.setOrder( orders ); - return this; - } - - @Override - public ReactiveQuerySqmImpl setOrder(Order order) { - super.setOrder( order ); - return this; - } - @Override public ReactiveQuerySqmImpl setTupleTransformer(TupleTransformer transformer) { throw new UnsupportedOperationException(); @@ -642,7 +629,7 @@ public

    ReactiveQuerySqmImpl setParameter(String name, P value, Class

    j } @Override - public

    ReactiveQuerySqmImpl setParameter(String name, P value, BindableType

    type) { + public

    ReactiveQuerySqmImpl setParameter(String name, P value, Type

    type) { super.setParameter( name, value, type ); return this; } @@ -666,7 +653,7 @@ public

    ReactiveQuerySqmImpl setParameter(int position, P value, Class

    } @Override - public

    ReactiveQuerySqmImpl setParameter(int position, P value, BindableType

    type) { + public

    ReactiveQuerySqmImpl setParameter(int position, P value, Type

    type) { super.setParameter( position, value, type ); return this; } @@ -690,7 +677,7 @@ public

    ReactiveQuerySqmImpl setParameter(QueryParameter

    parameter, P v } @Override - public

    ReactiveQuerySqmImpl setParameter(QueryParameter

    parameter, P value, BindableType

    type) { + public

    ReactiveQuerySqmImpl setParameter(QueryParameter

    parameter, P value, Type

    type) { super.setParameter( parameter, value, type ); return this; } @@ -750,7 +737,7 @@ public

    ReactiveQuerySqmImpl setParameterList(String name, Collection ReactiveQuerySqmImpl setParameterList(String name, Collection values, BindableType

    type) { + public

    ReactiveQuerySqmImpl setParameterList(String name, Collection values, Type

    type) { super.setParameterList( name, values, type ); return this; } @@ -768,7 +755,7 @@ public

    ReactiveQuerySqmImpl setParameterList(String name, P[] values, Cla } @Override - public

    ReactiveQuerySqmImpl setParameterList(String name, P[] values, BindableType

    type) { + public

    ReactiveQuerySqmImpl setParameterList(String name, P[] values, Type

    type) { super.setParameterList( name, values, type ); return this; } @@ -786,7 +773,7 @@ public

    ReactiveQuerySqmImpl setParameterList(int position, Collection ReactiveQuerySqmImpl setParameterList(int position, Collection values, BindableType

    type) { + public

    ReactiveQuerySqmImpl setParameterList(int position, Collection values, Type

    type) { super.setParameterList( position, values, type ); return this; } @@ -804,7 +791,7 @@ public

    ReactiveQuerySqmImpl setParameterList(int position, P[] values, Cl } @Override - public

    ReactiveQuerySqmImpl setParameterList(int position, P[] values, BindableType

    type) { + public

    ReactiveQuerySqmImpl setParameterList(int position, P[] values, Type

    type) { super.setParameterList( position, values, type ); return this; } @@ -822,7 +809,7 @@ public

    ReactiveQuerySqmImpl setParameterList(QueryParameter

    parameter, } @Override - public

    ReactiveQuerySqmImpl setParameterList(QueryParameter

    parameter, Collection values, BindableType

    type) { + public

    ReactiveQuerySqmImpl setParameterList(QueryParameter

    parameter, Collection values, Type

    type) { super.setParameterList( parameter, values, type ); return this; } @@ -840,7 +827,7 @@ public

    ReactiveQuerySqmImpl setParameterList(QueryParameter

    parameter, } @Override - public

    ReactiveQuerySqmImpl setParameterList(QueryParameter

    parameter, P[] values, BindableType

    type) { + public

    ReactiveQuerySqmImpl setParameterList(QueryParameter

    parameter, P[] values, Type

    type) { super.setParameterList( parameter, values, type ); return this; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmSelectionQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmSelectionQueryImpl.java index bca5e2a65..26d6dadfe 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmSelectionQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmSelectionQueryImpl.java @@ -6,17 +6,6 @@ package org.hibernate.reactive.query.sqm.internal; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletionStage; -import java.util.stream.Stream; - import org.hibernate.CacheMode; import org.hibernate.FlushMode; import org.hibernate.HibernateException; @@ -25,8 +14,6 @@ import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.internal.util.collections.IdentitySet; -import org.hibernate.query.BindableType; -import org.hibernate.query.Order; import org.hibernate.query.QueryLogging; import org.hibernate.query.QueryParameter; import org.hibernate.query.internal.DelegatingDomainQueryExecutionContext; @@ -47,6 +34,18 @@ import jakarta.persistence.LockModeType; import jakarta.persistence.Parameter; import jakarta.persistence.TemporalType; +import jakarta.persistence.metamodel.Type; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import java.util.stream.Stream; import static org.hibernate.query.spi.SqlOmittingQueryOptions.omitSqlQueryOptions; @@ -258,18 +257,6 @@ public ReactiveSqmSelectionQueryImpl setFollowOnLocking(boolean enable) { return this; } - @Override - public ReactiveSqmSelectionQueryImpl setOrder(List> orders) { - super.setOrder( orders ); - return this; - } - - @Override - public ReactiveSqmSelectionQueryImpl setOrder(Order order) { - super.setOrder( order ); - return this; - } - @Override public ReactiveSqmSelectionQueryImpl setFetchSize(int fetchSize) { super.setFetchSize( fetchSize ); @@ -356,7 +343,7 @@ public

    ReactiveSqmSelectionQueryImpl setParameter(String name, P value, C } @Override - public

    ReactiveSqmSelectionQueryImpl setParameter(String name, P value, BindableType

    type) { + public

    ReactiveSqmSelectionQueryImpl setParameter(String name, P value, Type

    type) { super.setParameter( name, value, type ); return this; } @@ -380,7 +367,7 @@ public

    ReactiveSqmSelectionQueryImpl setParameter(int position, P value, } @Override - public

    ReactiveSqmSelectionQueryImpl setParameter(int position, P value, BindableType

    type) { + public

    ReactiveSqmSelectionQueryImpl setParameter(int position, P value, Type

    type) { super.setParameter( position, value, type ); return this; } @@ -407,7 +394,7 @@ public

    ReactiveSqmSelectionQueryImpl setParameter(QueryParameter

    param public

    ReactiveSqmSelectionQueryImpl setParameter( QueryParameter

    parameter, P value, - BindableType

    type) { + Type

    type) { super.setParameter( parameter, value, type ); return this; } @@ -476,7 +463,7 @@ public

    ReactiveSqmSelectionQueryImpl setParameterList( public

    ReactiveSqmSelectionQueryImpl setParameterList( String name, Collection values, - BindableType

    type) { + Type

    type) { super.setParameterList( name, values, type ); return this; } @@ -494,7 +481,7 @@ public

    ReactiveSqmSelectionQueryImpl setParameterList(String name, P[] va } @Override - public

    ReactiveSqmSelectionQueryImpl setParameterList(String name, P[] values, BindableType

    type) { + public

    ReactiveSqmSelectionQueryImpl setParameterList(String name, P[] values, Type

    type) { super.setParameterList( name, values, type ); return this; } @@ -518,7 +505,7 @@ public

    ReactiveSqmSelectionQueryImpl setParameterList( public

    ReactiveSqmSelectionQueryImpl setParameterList( int position, Collection values, - BindableType

    type) { + Type

    type) { super.setParameterList( position, values, type ); return this; } @@ -536,7 +523,7 @@ public

    ReactiveSqmSelectionQueryImpl setParameterList(int position, P[] v } @Override - public

    ReactiveSqmSelectionQueryImpl setParameterList(int position, P[] values, BindableType

    type) { + public

    ReactiveSqmSelectionQueryImpl setParameterList(int position, P[] values, Type

    type) { super.setParameterList( position, values, type ); return this; } @@ -562,7 +549,7 @@ public

    ReactiveSqmSelectionQueryImpl setParameterList( public

    ReactiveSqmSelectionQueryImpl setParameterList( QueryParameter

    parameter, Collection values, - BindableType

    type) { + Type

    type) { super.setParameterList( parameter, values, type ); return this; } @@ -586,7 +573,7 @@ public

    ReactiveSqmSelectionQueryImpl setParameterList( public

    ReactiveSqmSelectionQueryImpl setParameterList( QueryParameter

    parameter, P[] values, - BindableType

    type) { + Type

    type) { super.setParameterList( parameter, values, type ); return this; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java index 20d0e31c5..806b75718 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java @@ -30,8 +30,8 @@ import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveAbstractMutationHandler; -import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.sql.ast.SqlAstTranslator; @@ -176,7 +176,7 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter StandardReactiveSelectExecutor.INSTANCE.list( select, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java index 1e063f287..603563e0f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java @@ -41,10 +41,10 @@ import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement; import org.hibernate.query.sqm.tree.insert.SqmValues; +import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; -import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.spi.NavigablePath; @@ -554,7 +554,7 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter StandardReactiveSelectExecutor.INSTANCE.list( select, 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 a3b667fea..4169d479d 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 @@ -5,8 +5,6 @@ */ package org.hibernate.reactive.session; -import java.util.concurrent.CompletionStage; - import org.hibernate.Incubating; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -21,9 +19,11 @@ import org.hibernate.reactive.query.ReactiveSelectionQuery; import jakarta.persistence.EntityGraph; +import jakarta.persistence.TypedQueryReference; import jakarta.persistence.criteria.CriteriaDelete; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.CriteriaUpdate; +import java.util.concurrent.CompletionStage; /** @@ -53,6 +53,8 @@ public interface ReactiveQueryProducer extends ReactiveConnectionSupplier { ReactiveQuery createReactiveQuery(String queryString); + ReactiveQuery createReactiveQuery(TypedQueryReference typedQueryReference); + ReactiveQuery createReactiveQuery(CriteriaQuery criteriaQuery); ReactiveQuery createReactiveQuery(String queryString, Class resultType); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveSession.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveSession.java index f53ed4fca..ab2089c0d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveSession.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveSession.java @@ -16,10 +16,10 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.UnknownProfileException; -import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.event.spi.DeleteContext; +import org.hibernate.event.spi.LoadEventListener; import org.hibernate.event.spi.MergeContext; import org.hibernate.event.spi.PersistContext; import org.hibernate.event.spi.RefreshContext; @@ -44,6 +44,7 @@ public interface ReactiveSession extends ReactiveQueryProducer, ReactiveSharedSe ReactiveActionQueue getReactiveActionQueue(); + @Override SessionImplementor getSharedContract(); CompletionStage reactiveFetch(E entity, Attribute field); @@ -58,14 +59,14 @@ public interface ReactiveSession extends ReactiveQueryProducer, ReactiveSharedSe CompletionStage reactiveRemove(Object entity); - CompletionStage reactiveRemove(String entityName, boolean isCascadeDeleteEnabled, DeleteContext transientObjects); - CompletionStage reactiveRemove(String entityName, Object child, boolean isCascadeDeleteEnabled, DeleteContext transientEntities); CompletionStage reactiveMerge(T object); CompletionStage reactiveMerge(Object object, MergeContext copiedAlready); + CompletionStage reactiveLoad(LoadEventListener.LoadType loadType, Object id, String entityName, LockOptions lockOptions, Boolean readOnly); + CompletionStage reactiveFlush(); CompletionStage reactiveAutoflush(); @@ -88,10 +89,6 @@ public interface ReactiveSession extends ReactiveQueryProducer, ReactiveSharedSe CompletionStage reactiveFind(Class entityClass, Map naturalIds); - CompletionStage reactiveImmediateLoad(String entityName, Object id); - - CompletionStage reactiveInitializeCollection(PersistentCollection collection, boolean writing); - CompletionStage reactiveRemoveOrphanBeforeUpdates(String entityName, Object child); void setHibernateFlushMode(FlushMode flushMode); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveSqmQueryImplementor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveSqmQueryImplementor.java index 090ab4748..dd19efc21 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveSqmQueryImplementor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveSqmQueryImplementor.java @@ -16,7 +16,6 @@ import org.hibernate.Incubating; import org.hibernate.LockMode; import org.hibernate.LockOptions; -import org.hibernate.query.BindableType; import org.hibernate.query.QueryParameter; import org.hibernate.query.ResultListTransformer; import org.hibernate.query.TupleTransformer; @@ -28,6 +27,7 @@ import jakarta.persistence.LockModeType; import jakarta.persistence.Parameter; import jakarta.persistence.TemporalType; +import jakarta.persistence.metamodel.Type; /** @@ -101,7 +101,7 @@ public interface ReactiveSqmQueryImplementor extends ReactiveQueryImplementor

    ReactiveSqmQueryImplementor setParameter(String name, P value, Class

    type); @Override -

    ReactiveSqmQueryImplementor setParameter(String name, P value, BindableType

    type); +

    ReactiveSqmQueryImplementor setParameter(String name, P value, Type

    type); @Override ReactiveSqmQueryImplementor setParameter(String name, Instant value, TemporalType temporalType); @@ -119,7 +119,7 @@ public interface ReactiveSqmQueryImplementor extends ReactiveQueryImplementor

    ReactiveSqmQueryImplementor setParameter(int position, P value, Class

    type); @Override -

    ReactiveSqmQueryImplementor setParameter(int position, P value, BindableType

    type); +

    ReactiveSqmQueryImplementor setParameter(int position, P value, Type

    type); @Override ReactiveSqmQueryImplementor setParameter(int position, Instant value, TemporalType temporalType); @@ -137,7 +137,7 @@ public interface ReactiveSqmQueryImplementor extends ReactiveQueryImplementor

    ReactiveSqmQueryImplementor setParameter(QueryParameter

    parameter, P value, Class

    type); @Override -

    ReactiveSqmQueryImplementor setParameter(QueryParameter

    parameter, P val, BindableType

    type); +

    ReactiveSqmQueryImplementor setParameter(QueryParameter

    parameter, P val, Type

    type); @Override ReactiveSqmQueryImplementor setParameter(Parameter param, T value); @@ -155,7 +155,7 @@ public interface ReactiveSqmQueryImplementor extends ReactiveQueryImplementor

    ReactiveSqmQueryImplementor setParameterList(String name, Collection values, Class

    javaType); @Override -

    ReactiveSqmQueryImplementor setParameterList(String name, Collection values, BindableType

    type); +

    ReactiveSqmQueryImplementor setParameterList(String name, Collection values, Type

    type); @Override ReactiveSqmQueryImplementor setParameterList(String name, Object[] values); @@ -164,7 +164,7 @@ public interface ReactiveSqmQueryImplementor extends ReactiveQueryImplementor

    ReactiveSqmQueryImplementor setParameterList(String name, P[] values, Class

    javaType); @Override -

    ReactiveSqmQueryImplementor setParameterList(String name, P[] values, BindableType

    type); +

    ReactiveSqmQueryImplementor setParameterList(String name, P[] values, Type

    type); @Override ReactiveSqmQueryImplementor setParameterList(int position, @SuppressWarnings("rawtypes") Collection values); @@ -173,7 +173,7 @@ public interface ReactiveSqmQueryImplementor extends ReactiveQueryImplementor

    ReactiveSqmQueryImplementor setParameterList(int position, Collection values, Class

    javaType); @Override -

    ReactiveSqmQueryImplementor setParameterList(int position, Collection values, BindableType

    type); +

    ReactiveSqmQueryImplementor setParameterList(int position, Collection values, Type

    type); @Override ReactiveSqmQueryImplementor setParameterList(int position, Object[] values); @@ -182,7 +182,7 @@ public interface ReactiveSqmQueryImplementor extends ReactiveQueryImplementor

    ReactiveSqmQueryImplementor setParameterList(int position, P[] values, Class

    javaType); @Override -

    ReactiveSqmQueryImplementor setParameterList(int position, P[] values, BindableType

    type); +

    ReactiveSqmQueryImplementor setParameterList(int position, P[] values, Type

    type); @Override

    ReactiveSqmQueryImplementor setParameterList(QueryParameter

    parameter, Collection values); @@ -191,7 +191,7 @@ public interface ReactiveSqmQueryImplementor extends ReactiveQueryImplementor

    ReactiveSqmQueryImplementor setParameterList(QueryParameter

    parameter, Collection values, Class

    javaType); @Override -

    ReactiveSqmQueryImplementor setParameterList(QueryParameter

    parameter, Collection values, BindableType

    type); +

    ReactiveSqmQueryImplementor setParameterList(QueryParameter

    parameter, Collection values, Type

    type); @Override

    ReactiveSqmQueryImplementor setParameterList(QueryParameter

    parameter, P[] values); @@ -200,7 +200,7 @@ public interface ReactiveSqmQueryImplementor extends ReactiveQueryImplementor

    ReactiveSqmQueryImplementor setParameterList(QueryParameter

    parameter, P[] values, Class

    javaType); @Override -

    ReactiveSqmQueryImplementor setParameterList(QueryParameter

    parameter, P[] values, BindableType

    type); +

    ReactiveSqmQueryImplementor setParameterList(QueryParameter

    parameter, P[] values, Type

    type); @Override ReactiveSqmQueryImplementor setProperties(Object bean); 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 e6d2aa1ee..006fbb63f 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 @@ -46,18 +46,12 @@ public interface ReactiveStatelessSession extends ReactiveQueryProducer, Reactiv CompletionStage reactiveUpsert(Object entity); - CompletionStage reactiveUpsert(String entityName, Object entity); - CompletionStage reactiveUpsertAll(int batchSize, Object... entities); CompletionStage reactiveRefresh(Object entity); - CompletionStage reactiveRefresh(String entityName, Object entity); - CompletionStage reactiveRefresh(Object entity, LockMode lockMode); - CompletionStage reactiveRefresh(String entityName, Object entity, LockMode lockMode); - CompletionStage reactiveInsertAll(Object... entities); CompletionStage reactiveInsertAll(int batchSize, Object... entities); 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 72157e46e..795b565ff 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 @@ -5,14 +5,6 @@ */ package org.hibernate.reactive.session.impl; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletionException; -import java.util.concurrent.CompletionStage; -import java.util.function.Supplier; - import org.hibernate.CacheMode; import org.hibernate.FlushMode; import org.hibernate.HibernateException; @@ -38,14 +30,12 @@ import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; -import org.hibernate.event.service.spi.EventListenerGroup; import org.hibernate.event.spi.AutoFlushEvent; import org.hibernate.event.spi.DeleteContext; import org.hibernate.event.spi.DeleteEvent; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.FlushEvent; import org.hibernate.event.spi.InitializeCollectionEvent; -import org.hibernate.event.spi.InitializeCollectionEventListener; import org.hibernate.event.spi.LoadEvent; import org.hibernate.event.spi.LoadEventListener; import org.hibernate.event.spi.LockEvent; @@ -64,6 +54,7 @@ import org.hibernate.jpa.spi.NativeQueryTupleTransformer; import org.hibernate.loader.LoaderLogging; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; +import org.hibernate.loader.internal.IdentifierLoadAccessImpl; import org.hibernate.loader.internal.LoadAccessContext; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.NaturalIdMapping; @@ -76,6 +67,8 @@ import org.hibernate.query.criteria.JpaCriteriaInsert; import org.hibernate.query.hql.spi.SqmQueryImplementor; import org.hibernate.query.named.NamedResultSetMappingMemento; +import org.hibernate.query.specification.internal.MutationSpecificationImpl; +import org.hibernate.query.specification.internal.SelectionSpecificationImpl; import org.hibernate.query.spi.HqlInterpretation; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.query.sql.spi.NamedNativeQueryMemento; @@ -87,6 +80,7 @@ import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.query.sqm.tree.select.SqmQueryGroup; import org.hibernate.query.sqm.tree.select.SqmQuerySpec; +import org.hibernate.query.sqm.tree.select.SqmSelectClause; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.common.AffectedEntities; @@ -123,10 +117,19 @@ import jakarta.persistence.EntityGraph; import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.Tuple; +import jakarta.persistence.TypedQueryReference; +import jakarta.persistence.criteria.CommonAbstractCriteria; 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.Map; +import java.util.Set; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; import static java.lang.Boolean.TRUE; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; @@ -139,10 +142,12 @@ import static org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister.forceInitialize; import static org.hibernate.reactive.session.impl.SessionUtil.checkEntityFound; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture; import static org.hibernate.reactive.util.impl.CompletionStages.rethrow; import static org.hibernate.reactive.util.impl.CompletionStages.returnNullorRethrow; import static org.hibernate.reactive.util.impl.CompletionStages.returnOrRethrow; +import static org.hibernate.reactive.util.impl.CompletionStages.supplyStage; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** @@ -213,8 +218,7 @@ public Object immediateLoad(String entityName, Object id) throws HibernateExcept public CompletionStage reactiveImmediateLoad(String entityName, Object id) throws HibernateException { if ( LOG.isDebugEnabled() ) { - final EntityPersister persister = getFactory().getMappingMetamodel() - .getEntityDescriptor( entityName ); + final EntityPersister persister = requireEntityPersister( entityName ); LOG.debugf( "Initializing proxy: %s", MessageHelper.infoString( persister, id, getFactory() ) ); } threadCheck(); @@ -247,10 +251,7 @@ public CompletionStage reactiveInternalLoad(String entityName, Object id } threadCheck(); - final LoadEvent event = new LoadEvent( - id, entityName, true, this, - getReadOnlyFromLoadQueryInfluencers() - ); + final LoadEvent event = makeLoadEvent( entityName, id, getReadOnlyFromLoadQueryInfluencers(), true ); return fireLoadNoChecks( event, type ) .thenApply( v -> { final Object result = event.getResult(); @@ -266,6 +267,43 @@ public CompletionStage reactiveInternalLoad(String entityName, Object id } ); } + @Override + public Object load(LoadEventListener.LoadType loadType, Object id, String entityName, LockOptions lockOptions, Boolean readOnly) { + // When the user needs a reference to the entity, we are not supposed to touche the database, and we don't return + // a CompletionStage. So it's fine to delegate to ORM. + // Everywhere else, reactiveLoad should be used. + return super.load( loadType, id, entityName, lockOptions, readOnly ); + } + + /** + * @see SessionImpl#load(LoadEventListener.LoadType, Object, String, LockOptions, Boolean) + */ + public CompletionStage reactiveLoad(LoadEventListener.LoadType loadType, Object id, String entityName, LockOptions lockOptions, Boolean readOnly) { + if ( lockOptions != null ) { + // (from ORM) TODO: I doubt that this branch is necessary, and it's probably even wrong + final LoadEvent event = makeLoadEvent( entityName, id, readOnly, lockOptions ); + return fireLoad( event, loadType ) + .thenApply( v -> { + final Object result = event.getResult(); + releaseLoadEvent( event ); + return result; + } ); + } + else { + final LoadEvent event = makeLoadEvent( entityName, id, readOnly, false ); + return supplyStage( () -> fireLoad( event, loadType ) + .thenApply( v -> { + final Object result = event.getResult(); + releaseLoadEvent( event ); + if ( !loadType.isAllowNulls() && result == null ) { + getSession().getFactory().getEntityNotFoundDelegate().handleEntityNotFound( entityName, id ); + } + return result; + } ) + ).whenComplete( (o, throwable) -> afterOperation( throwable != null ) ); + } + } + @Override //Note: when making changes to this method, please also consider // the similar code in Mutiny.fetch() and Stage.fetch() @@ -274,9 +312,8 @@ public CompletionStage reactiveFetch(T association, boolean unproxy) { if ( association == null ) { return nullFuture(); } - - if ( association instanceof HibernateProxy proxy ) { - LazyInitializer initializer = proxy.getHibernateLazyInitializer(); + else if ( association instanceof HibernateProxy proxy ) { + final LazyInitializer initializer = proxy.getHibernateLazyInitializer(); if ( !initializer.isUninitialized() ) { return completedFuture( unproxy ? (T) initializer.getImplementation() : association ); } @@ -334,9 +371,10 @@ public ReactiveQuery createReactiveQuery(CriteriaQuery criteriaQuery) final SqmSelectStatement selectStatement = (SqmSelectStatement) criteriaQuery; if ( ! ( selectStatement.getQueryPart() instanceof SqmQueryGroup ) ) { final SqmQuerySpec querySpec = selectStatement.getQuerySpec(); - if ( querySpec.getSelectClause().getSelections().isEmpty() ) { + final SqmSelectClause selectClause = querySpec.getSelectClause(); + if ( selectClause.getSelections().isEmpty() ) { if ( querySpec.getFromClause().getRoots().size() == 1 ) { - querySpec.getSelectClause().setSelection( querySpec.getFromClause().getRoots().get(0) ); + selectClause.setSelection( querySpec.getFromClause().getRoots().get(0) ); } } } @@ -344,7 +382,7 @@ public ReactiveQuery createReactiveQuery(CriteriaQuery criteriaQuery) return createReactiveCriteriaQuery( selectStatement, criteriaQuery.getResultType() ); } catch (RuntimeException e) { - if ( getSessionFactory().getJpaMetamodel().getJpaCompliance().isJpaTransactionComplianceEnabled() ) { + if ( getSessionFactory().getSessionFactoryOptions().getJpaCompliance().isJpaTransactionComplianceEnabled() ) { markForRollbackOnly(); } throw getExceptionConverter().convert( e ); @@ -357,6 +395,31 @@ protected ReactiveQueryImplementor createReactiveCriteriaQuery(SqmStateme return query; } + @Override + public ReactiveQuery createReactiveQuery(TypedQueryReference typedQueryReference) { + checksBeforeQueryCreation(); + if ( typedQueryReference instanceof SelectionSpecificationImpl specification ) { + final CriteriaQuery query = specification.buildCriteria( getCriteriaBuilder() ); + return new ReactiveQuerySqmImpl<>( (SqmStatement) query, specification.getResultType(), this ); + } + else if ( typedQueryReference instanceof MutationSpecificationImpl specification ) { + final CommonAbstractCriteria query = specification.buildCriteria( getCriteriaBuilder() ); + return new ReactiveQuerySqmImpl<>( (SqmStatement) query, (Class) specification.getResultType(), this ); + } + else { + @SuppressWarnings("unchecked") + // this cast is fine because of all our impls of TypedQueryReference return Class + final Class resultType = (Class) typedQueryReference.getResultType(); + final ReactiveQueryImplementor query = (ReactiveQueryImplementor) buildNamedQuery( + typedQueryReference.getName(), + memento -> createSqmQueryImplementor(resultType, memento), + memento -> createNativeQueryImplementor(resultType, memento) + ); + typedQueryReference.getHints().forEach(query::setHint); + return query; + } + } + @Override public ReactiveQuery createReactiveQuery(String queryString) { return createReactiveQuery( queryString, null ); @@ -760,8 +823,7 @@ public CompletionStage reactiveInitializeCollection(PersistentCollection eventListenerGroupInitCollection = getFactory().getEventListenerGroups().eventListenerGroup_INIT_COLLECTION; - return eventListenerGroupInitCollection + return getFactory().getEventListenerGroups().eventListenerGroup_INIT_COLLECTION .fireEventOnEachListener( event, (DefaultReactiveInitializeCollectionEventListener l) -> l::onReactiveInitializeCollection @@ -854,16 +916,6 @@ public CompletionStage reactiveRemove(Object entity) { return fireRemove( new DeleteEvent( entity, this ) ); } - @Override - public CompletionStage reactiveRemove( - String entityName, - boolean isCascadeDeleteEnabled, - DeleteContext transientEntities) - throws HibernateException { - // I'm not quite sure if we need this method - return reactiveRemove( entityName, null, isCascadeDeleteEnabled, transientEntities ); - } - @Override public CompletionStage reactiveRemove( String entityName, @@ -1035,7 +1087,7 @@ public CompletionStage reactiveForceFlush(EntityEntry entry) { } if ( getPersistenceContextInternal().getCascadeLevel() > 0 ) { - return CompletionStages.failedFuture( new ObjectDeletedException( + return failedFuture( new ObjectDeletedException( "deleted object would be re-saved by cascade (remove deleted object from associations)", entry.getId(), entry.getPersister().getEntityName() @@ -1163,10 +1215,16 @@ private CompletionStage fireLock(LockEvent event) { } @Override - public CompletionStage reactiveGet( - Class entityClass, - Object id) { - return new ReactiveIdentifierLoadAccessImpl<>( entityClass ).load( id ); + public CompletionStage reactiveGet(Class entityClass, Object id) { + return reactiveById( entityClass ).load( id ); + } + + private ReactiveIdentifierLoadAccessImpl reactiveById(Class entityClass) { + return new ReactiveIdentifierLoadAccessImpl<>( this, requireEntityPersister( entityClass ) ); + } + + private ReactiveIdentifierLoadAccessImpl reactiveById(String entityName) { + return new ReactiveIdentifierLoadAccessImpl<>( this, requireEntityPersister( entityName ) ); } @Override @@ -1176,60 +1234,77 @@ public CompletionStage reactiveFind( LockOptions lockOptions, EntityGraph fetchGraph) { checkOpen(); + return supplyStage( () -> { + if ( fetchGraph != null ) { + getLoadQueryInfluencers() + .getEffectiveEntityGraph() + .applyGraph( (RootGraphImplementor) fetchGraph, GraphSemantic.FETCH ); + } + getLoadQueryInfluencers().setReadOnly( readOnlyHint( null ) ); + + return reactiveById( entityClass ) + .with( determineAppropriateLocalCacheMode( null ) ) + .with( lockOptions ) + .load( id ); + } ).handle( CompletionStages::handle ) + .thenCompose( handler -> handleReactiveFindException( entityClass, id, lockOptions, handler ) ) + .whenComplete( (v, e) -> { + getLoadQueryInfluencers().getEffectiveEntityGraph().clear(); + getLoadQueryInfluencers().setReadOnly( null ); + } ); + } - if ( fetchGraph != null ) { - getLoadQueryInfluencers() - .getEffectiveEntityGraph() - .applyGraph( (RootGraphImplementor) fetchGraph, GraphSemantic.FETCH ); - } - -// Boolean readOnly = properties == null ? null : (Boolean) properties.get( QueryHints.HINT_READONLY ); -// getLoadQueryInfluencers().setReadOnly( readOnly ); - - final ReactiveIdentifierLoadAccessImpl loadAccess = - new ReactiveIdentifierLoadAccessImpl<>( entityClass ) - .with( determineAppropriateLocalCacheMode( null ) ) - .with( lockOptions ); - - return loadAccess.load( id ) - .handle( (result, e) -> { - if ( e instanceof EntityNotFoundException ) { - // DefaultLoadEventListener.returnNarrowedProxy may throw ENFE (see HHH-7861 for details), - // which find() should not throw. Find() should return null if the entity was not found. - // if ( log.isDebugEnabled() ) { - // String entityName = entityClass != null ? entityClass.getName(): null; - // String identifierValue = id != null ? id.toString() : null ; - // log.ignoringEntityNotFound( entityName, identifierValue ); - // } - throw new UnsupportedOperationException(); - } - if ( e instanceof ObjectDeletedException ) { - //the spec is silent about people doing remove() find() on the same PC - throw new UnsupportedOperationException(); - } - if ( e instanceof ObjectNotFoundException ) { - //should not happen on the entity itself with get - throw new IllegalArgumentException( e.getMessage(), e ); - } - if ( e instanceof MappingException - || e instanceof TypeMismatchException - || e instanceof ClassCastException ) { - throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage(), e ) ); - } - if ( e instanceof JDBCException ) { -// if ( accessTransaction().getRollbackOnly() ) { -// // assume this is the similar to the WildFly / IronJacamar "feature" described under HHH-12472 -// throw new UnsupportedOperationException(); -// } - throw getExceptionConverter().convert( (JDBCException) e, lockOptions ); - } - if ( e instanceof RuntimeException ) { - throw getExceptionConverter().convert( (RuntimeException) e, lockOptions ); - } + private CompletionStage handleReactiveFindException( + Class entityClass, + Object primaryKey, + LockOptions lockOptions, + CompletionStages.CompletionStageHandler handler) { + if ( !handler.hasFailed() ) { + return handler.getResultAsCompletionStage(); + } + final Throwable e = handler.getThrowable(); + if ( e instanceof EntityNotFoundException ) { + // We swallow other sorts of EntityNotFoundException and return null + // For example, DefaultLoadEventListener.proxyImplementation() throws + // EntityNotFoundException if there's an existing proxy in the session, + // but the underlying database row has been deleted (see HHH-7861) + logIgnoringEntityNotFound( entityClass, primaryKey ); + return nullFuture(); + } + if ( e instanceof ObjectDeletedException ) { + // the spec is silent about people doing remove() find() on the same PC + return null; + } + if ( e instanceof ObjectNotFoundException ) { + // should not happen on the entity itself with get + // TODO: in fact this will occur instead of EntityNotFoundException + // when using StandardEntityNotFoundDelegate, so probably we + // should return null here, as we do above + return failedFuture( new IllegalArgumentException( e.getMessage(), e ) ); + } + if ( e instanceof MappingException || e instanceof TypeMismatchException || e instanceof ClassCastException ) { + return failedFuture( getExceptionConverter().convert( new IllegalArgumentException( + e.getMessage(), + e + ) ) ); + } + if ( e instanceof JDBCException ) { + // I don't think this is ever going to happen in Hibernate Reactive + if ( accessTransaction().isActive() && accessTransaction().getRollbackOnly() ) { + // Assume situation HHH-12472 running on WildFly + // Just log the exception and return null + LOG.jdbcExceptionThrownWithTransactionRolledBack( (JDBCException) e ); + return nullFuture(); + } + else { + return failedFuture( getExceptionConverter().convert( (JDBCException) e, lockOptions ) ); + } + } + if ( e instanceof RuntimeException ) { + return failedFuture( getExceptionConverter().convert( (RuntimeException) e, lockOptions ) ); + } - return result; - } ) - .whenComplete( (v, e) -> getLoadQueryInfluencers().getEffectiveEntityGraph().clear() ); + return handler.getResultAsCompletionStage(); } @Override @@ -1250,17 +1325,18 @@ private ReactiveEntityPersister entityPersister(Class entityClass) { return (ReactiveEntityPersister) getFactory().getMappingMetamodel().getEntityDescriptor( entityClass ); } - private EntityPersister requireEntityPersister(Class entityClass) { - return getFactory().getMappingMetamodel().getEntityDescriptor( entityClass ); - } - - private CompletionStage fireReactiveLoad(LoadEvent event, LoadEventListener.LoadType loadType) { + private CompletionStage fireLoad(LoadEvent event, LoadEventListener.LoadType loadType) { checkOpenOrWaitingForAutoClose(); - return fireLoadNoChecks( event, loadType ) - .whenComplete( (v, e) -> delayedAfterCompletion() ); + .thenAccept( v -> delayedAfterCompletion() ); } + /** + * This version of {@link #load} is for use by internal methods only. + * It skips the session open check, transaction sync checks, and so on, + * which have been shown to be expensive (apparently they prevent these + * hot methods from being inlined). + */ private CompletionStage fireLoadNoChecks(LoadEvent event, LoadEventListener.LoadType loadType) { pulseTransactionCoordinator(); @@ -1283,80 +1359,38 @@ public void checkTransactionNeededForUpdateOperation(String exceptionMessage) { //no-op because we don't support transactions } - private Boolean getReadOnlyFromLoadQueryInfluencers() { - return getLoadQueryInfluencers().getReadOnly(); - } - - private class ReactiveIdentifierLoadAccessImpl { - - private final EntityPersister entityPersister; - - private LockOptions lockOptions; - private CacheMode cacheMode; - - //Note that entity graphs aren't supported at all - //because we're not using the EntityLoader from - //the plan package, so this stuff is useless - private RootGraphImplementor rootGraph; - private GraphSemantic graphSemantic; - - public ReactiveIdentifierLoadAccessImpl(EntityPersister entityPersister) { - this.entityPersister = entityPersister; - } - - public ReactiveIdentifierLoadAccessImpl(String entityName) { - this( getFactory().getMappingMetamodel().getEntityDescriptor( entityName ) ); - } - - public ReactiveIdentifierLoadAccessImpl(Class entityClass) { - this( getFactory().getMappingMetamodel().getEntityDescriptor( entityClass ) ); - } - - public final ReactiveIdentifierLoadAccessImpl with(LockOptions lockOptions) { - this.lockOptions = lockOptions; - return this; - } - - public ReactiveIdentifierLoadAccessImpl with(CacheMode cacheMode) { - this.cacheMode = cacheMode; - return this; - } - - public ReactiveIdentifierLoadAccessImpl with(RootGraph graph, GraphSemantic semantic) { - rootGraph = (RootGraphImplementor) graph; - graphSemantic = semantic; - return this; - } + private class ReactiveIdentifierLoadAccessImpl extends IdentifierLoadAccessImpl> { - public final CompletionStage getReference(Object id) { - return perform( () -> doGetReference( id ) ); + public ReactiveIdentifierLoadAccessImpl(LoadAccessContext context, EntityPersister entityPersister) { + super(context, entityPersister); } + @Override protected CompletionStage perform(Supplier> executor) { - if ( graphSemantic != null ) { - if ( rootGraph == null ) { + if ( getGraphSemantic() != null ) { + if ( getRootGraph() == null ) { throw new IllegalArgumentException( "Graph semantic specified, but no RootGraph was supplied" ); } } CacheMode sessionCacheMode = getCacheMode(); boolean cacheModeChanged = false; - if ( cacheMode != null ) { + if ( getCacheMode() != null ) { // naive check for now... // todo : account for "conceptually equal" - if ( cacheMode != sessionCacheMode ) { - setCacheMode( cacheMode ); + if ( getCacheMode() != sessionCacheMode ) { + setCacheMode( getCacheMode() ); cacheModeChanged = true; } } - if ( graphSemantic != null ) { - getLoadQueryInfluencers().getEffectiveEntityGraph().applyGraph( rootGraph, graphSemantic ); + if ( getGraphSemantic() != null ) { + getLoadQueryInfluencers().getEffectiveEntityGraph().applyGraph( getRootGraph(), getGraphSemantic() ); } boolean finalCacheModeChanged = cacheModeChanged; return executor.get() .whenComplete( (v, x) -> { - if ( graphSemantic != null ) { + if ( getGraphSemantic() != null ) { getLoadQueryInfluencers().getEffectiveEntityGraph().clear(); } if ( finalCacheModeChanged ) { @@ -1366,76 +1400,44 @@ protected CompletionStage perform(Supplier> executor) { } ); } - @SuppressWarnings("unchecked") + @Override protected CompletionStage doGetReference(Object id) { - if ( lockOptions != null ) { - LoadEvent event = new LoadEvent( - id, - entityPersister.getEntityName(), - lockOptions, - ReactiveSessionImpl.this, - getReadOnlyFromLoadQueryInfluencers() - ); - return fireReactiveLoad( event, LoadEventListener.LOAD ).thenApply( v -> (T) event.getResult() ); - } - - LoadEvent event = new LoadEvent( - id, - entityPersister.getEntityName(), - false, - ReactiveSessionImpl.this, - getReadOnlyFromLoadQueryInfluencers() - ); - return fireReactiveLoad( event, LoadEventListener.LOAD ) - .thenApply( v -> { - if ( event.getResult() == null ) { - getFactory().getEntityNotFoundDelegate().handleEntityNotFound( - entityPersister.getEntityName(), - id - ); - } - return (T) event.getResult(); - } ).whenComplete( (v, x) -> afterOperation( x != null ) ); - } - - public final CompletionStage load(Object id) { - return perform( () -> doLoad( id, LoadEventListener.GET ) ); + // getReference si supposed to return T, not CompletionStage + // I can't think of a way to change the super class so that it is mapped correctly. + // So, for now, I will throw an exception and make sure that it doesn't really get called + // (the one in SessionImpl should be used) + throw new UnsupportedOperationException(); } - // public final CompletionStage fetch(Object id) { -// return perform( () -> doLoad( id, LoadEventListener.IMMEDIATE_LOAD) ); -// } -// + @Override @SuppressWarnings("unchecked") - protected final CompletionStage doLoad(Object id, LoadEventListener.LoadType loadType) { + protected CompletionStage doLoad(Object id) { if ( id == null ) { + // This is needed to make the tests with NaturalIds pass. + // It's was already part of Hibernate Reactive. + // I'm not sure why though, it doesn't seem like Hibernate ORM does it. return nullFuture(); } - if ( lockOptions != null ) { - LoadEvent event = new LoadEvent( - id, - entityPersister.getEntityName(), - lockOptions, - ReactiveSessionImpl.this, - getReadOnlyFromLoadQueryInfluencers() - ); - return fireReactiveLoad( event, loadType ).thenApply( v -> (T) event.getResult() ); - } - LoadEvent event = new LoadEvent( - id, - entityPersister.getEntityName(), - false, - ReactiveSessionImpl.this, - getReadOnlyFromLoadQueryInfluencers() - ); - return fireReactiveLoad( event, loadType ) - .whenComplete( (v, t) -> afterOperation( t != null ) ) - .thenApply( v -> (T) event.getResult() ); + final ReactiveSession session = (ReactiveSession) getContext().getSession(); + return supplyStage( () -> session + .reactiveLoad( LoadEventListener.GET, coerceId( id, session.getFactory() ), getEntityPersister().getEntityName(), getLockOptions(), isReadOnly( getContext().getSession() ) ) + ) + .handle( CompletionStages::handle ) + .thenCompose( handler -> handler.getThrowable() instanceof ObjectNotFoundException + ? nullFuture() + : handler.getResultAsCompletionStage() + ) + .thenApply( result -> { + // ORM calls + // initializeIfNecessary( result ); + // But, Hibernate Reactive doesn't support lazy initializations + return (T) result; + } ); } } private class ReactiveMultiIdentifierLoadAccessImpl implements MultiIdLoadOptions { - private final EntityPersister entityPersister; + private final ReactiveEntityPersister entityPersister; private LockOptions lockOptions; private CacheMode cacheMode; @@ -1450,7 +1452,7 @@ private class ReactiveMultiIdentifierLoadAccessImpl implements MultiIdLoadOpt private boolean readOnly; public ReactiveMultiIdentifierLoadAccessImpl(EntityPersister entityPersister) { - this.entityPersister = entityPersister; + this.entityPersister = (ReactiveEntityPersister) entityPersister; } @Override @@ -1489,12 +1491,7 @@ public Integer getBatchSize() { } public ReactiveMultiIdentifierLoadAccessImpl withBatchSize(int batchSize) { - if ( batchSize < 1 ) { - this.batchSize = null; - } - else { - this.batchSize = batchSize; - } + this.batchSize = batchSize < 1 ? null : batchSize; return this; } @@ -1537,8 +1534,11 @@ public CompletionStage> multiLoad(Object... ids) { Object[] sids = new Object[ids.length]; System.arraycopy( ids, 0, sids, 0, ids.length ); - return perform( () -> (CompletionStage) ( (ReactiveEntityPersister) entityPersister ) - .reactiveMultiLoad( sids, ReactiveSessionImpl.this, this ) ); + return perform( () -> { + final CompletionStage> stage = + entityPersister.reactiveMultiLoad( sids, ReactiveSessionImpl.this, this ); + return (CompletionStage>) stage; + }); } public CompletionStage> perform(Supplier>> executor) { @@ -1572,12 +1572,6 @@ public CompletionStage> perform(Supplier>> execu } } ); } - - @SuppressWarnings("unchecked") - public CompletionStage> multiLoad(List ids) { - return perform( () -> (CompletionStage>) - entityPersister.multiLoad( ids.toArray( new Object[0] ), ReactiveSessionImpl.this, this ) ); - } } private class NaturalIdLoadAccessImpl { @@ -1602,10 +1596,6 @@ public NaturalIdLoadAccessImpl with(LockOptions lockOptions) { return this; } - protected void synchronizationEnabled(boolean synchronizationEnabled) { - this.synchronizationEnabled = synchronizationEnabled; - } - /** * @see org.hibernate.loader.internal.BaseNaturalIdLoadAccessImpl#doGetReference(Object) */ @@ -1685,14 +1675,6 @@ protected void performAnyNeededCrossReferenceSynchronizations() { } } - protected final ReactiveIdentifierLoadAccessImpl getIdentifierLoadAccess() { - final ReactiveIdentifierLoadAccessImpl identifierLoadAccess = new ReactiveIdentifierLoadAccessImpl<>( entityPersister ); - if ( this.lockOptions != null ) { - identifierLoadAccess.with( lockOptions ); - } - return identifierLoadAccess; - } - protected ReactiveEntityPersister entityPersister() { return entityPersister; } 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 2d3efe4f5..d67cc2018 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 @@ -5,34 +5,22 @@ */ package org.hibernate.reactive.session.impl; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.function.Supplier; - import org.hibernate.HibernateException; import org.hibernate.LockMode; -import org.hibernate.LockOptions; -import org.hibernate.TransientObjectException; +import org.hibernate.SessionException; import org.hibernate.UnknownEntityTypeException; import org.hibernate.UnresolvableObjectException; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; -import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; 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.CollectionEntry; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.PersistenceContext; -import org.hibernate.engine.spi.PersistentAttributeInterceptable; -import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.event.spi.PostInsertEvent; -import org.hibernate.event.spi.PostInsertEventListener; -import org.hibernate.event.spi.PreInsertEvent; -import org.hibernate.event.spi.PreInsertEventListener; +import org.hibernate.event.monitor.spi.DiagnosticEvent; +import org.hibernate.event.monitor.spi.EventMonitor; import org.hibernate.generator.BeforeExecutionGenerator; import org.hibernate.generator.Generator; import org.hibernate.graph.GraphSemantic; @@ -50,10 +38,10 @@ 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.specification.internal.MutationSpecificationImpl; +import org.hibernate.query.specification.internal.SelectionSpecificationImpl; import org.hibernate.query.spi.HqlInterpretation; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.query.sql.spi.NativeQueryImplementor; @@ -84,26 +72,38 @@ import org.hibernate.reactive.query.sqm.internal.ReactiveSqmSelectionQueryImpl; import org.hibernate.reactive.session.ReactiveSqmQueryImplementor; import org.hibernate.reactive.session.ReactiveStatelessSession; -import org.hibernate.reactive.util.impl.CompletionStages; +import org.hibernate.reactive.util.impl.CompletionStages.Completable; import org.hibernate.stat.spi.StatisticsImplementor; import jakarta.persistence.EntityGraph; import jakarta.persistence.Tuple; +import jakarta.persistence.TypedQueryReference; +import jakarta.persistence.criteria.CommonAbstractCriteria; 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; +import java.util.function.BiConsumer; +import java.util.function.Supplier; import static java.lang.Boolean.TRUE; import static java.lang.invoke.MethodHandles.lookup; +import static java.util.function.Function.identity; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; import static org.hibernate.engine.internal.Versioning.incrementVersion; import static org.hibernate.engine.internal.Versioning.seedVersion; import static org.hibernate.engine.internal.Versioning.setVersion; +import static org.hibernate.event.internal.DefaultInitializeCollectionEventListener.handlePotentiallyEmptyCollection; import static org.hibernate.generator.EventType.INSERT; +import static org.hibernate.internal.util.NullnessUtil.castNonNull; import static org.hibernate.internal.util.StringHelper.isEmpty; import static org.hibernate.internal.util.StringHelper.isNotEmpty; import static org.hibernate.loader.ast.spi.CascadingFetchProfile.REFRESH; +import static org.hibernate.loader.internal.CacheLoadHelper.initializeCollectionFromCache; +import static org.hibernate.pretty.MessageHelper.collectionInfoString; import static org.hibernate.pretty.MessageHelper.infoString; import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; import static org.hibernate.reactive.id.impl.IdentifierGeneration.castToIdentifierType; @@ -114,6 +114,7 @@ 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.supplyStage; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** @@ -127,11 +128,8 @@ public class ReactiveStatelessSessionImpl extends StatelessSessionImpl implement private static final Log LOG = make( Log.class, lookup() ); private final LoadQueryInfluencers influencers; - private final ReactiveConnection reactiveConnection; - private final ReactiveStatelessSessionImpl batchingHelperSession; - private final PersistenceContext persistenceContext; public ReactiveStatelessSessionImpl(SessionFactoryImpl factory, SessionCreationOptions options, ReactiveConnection connection) { @@ -219,28 +217,21 @@ public CompletionStage> reactiveGet(Class entityClass, Object... checkOpen(); for (Object id : ids) { if ( id == null ) { - throw new IllegalArgumentException("Null id"); + return failedFuture( new IllegalArgumentException( "Null id" ) ); } } - final EntityPersister persister = getEntityPersister( entityClass.getName() ); + Object[] sids = new Object[ids.length]; + System.arraycopy( ids, 0, sids, 0, ids.length ); - final JpaCriteriaQuery query = getCriteriaBuilder().createQuery(entityClass); - final JpaRoot from = query.from(entityClass); - query.where( from.get( persister.getIdentifierPropertyName() ).in(ids) ); - return createReactiveQuery(query).getReactiveResultList() - .thenApply( resultList -> { - final List idList = new ArrayList<>( resultList.size() ); - for (T entity : resultList) { - idList.add( persister.getIdentifier(entity, this) ); - } - final List list = new ArrayList<>( ids.length ); - for (Object id : ids) { - final int pos = idList.indexOf(id); - list.add( pos < 0 ? null : resultList.get(pos) ); + return getEntityPersister( entityClass.getName() ) + .reactiveMultiLoad( sids, this, StatelessSessionImpl.MULTI_ID_LOAD_OPTIONS ) + .whenComplete( (list, e) -> { + if ( getPersistenceContext().isLoadFinished() ) { + getPersistenceContext().clear(); } - return list; - }); + } ) + .thenApply( list -> (List) list ); } @Override @@ -269,7 +260,15 @@ public CompletionStage reactiveGet(String entityName, Object id, LockMode .applyGraph( (RootGraphImplementor) fetchGraph, GraphSemantic.FETCH ); } - return getEntityPersister( entityName ) + ReactiveEntityPersister persister = getEntityPersister( entityName ); + if ( persister.canReadFromCache() ) { + final Object cachedEntity = loadFromSecondLevelCache( persister, generateEntityKey( id, persister ), null, lockMode ); + if ( cachedEntity != null ) { + getPersistenceContext().clear(); + return completedFuture( (T) cachedEntity ); + } + } + return persister .reactiveLoad( id, null, getNullSafeLockMode( lockMode ), this ) .whenComplete( (v, e) -> { if ( getPersistenceContext().isLoadFinished() ) { @@ -294,8 +293,11 @@ public ReactiveEntityPersister getEntityPersister(String entityName, Object obje public CompletionStage reactiveInsert(Object entity) { checkOpen(); final ReactiveEntityPersister persister = getEntityPersister( null, entity ); - return reactiveInsert( entity, persister ) - .thenAccept( v -> { + final Object[] state = persister.getValues( entity ); + return reactiveInsert( entity, state, persister ) + .thenCompose( id -> recreateCollections( entity, id, persister ) ) + .thenAccept( id -> { + firePostInsert( entity, id, state, persister ); final StatisticsImplementor statistics = getFactory().getStatistics(); if ( statistics.isStatisticsEnabled() ) { statistics.insertEntity( persister.getEntityName() ); @@ -303,94 +305,145 @@ public CompletionStage reactiveInsert(Object entity) { } ); } - private CompletionStage reactiveInsert(Object entity, ReactiveEntityPersister persister) { - final Object[] state = persister.getValues( entity ); - final Generator generator = persister.getGenerator(); - if ( !generator.generatedOnExecution() ) { - return generateId( persister, entity, generator ) - .thenCompose( generatedId -> { - final Object id = castToIdentifierType( generatedId, persister ); - if ( persister.isVersioned() ) { - if ( seedVersion( entity, state, persister, this ) ) { - persister.setValues( entity, state ); - } - } - if ( firePreInsert( entity, id, state, persister ) ) { - return voidFuture(); - } - getInterceptor() - .onInsert( - entity, - id, - state, - persister.getPropertyNames(), - persister.getPropertyTypes() - ); - return persister - .insertReactive( id, state, entity, this ) - .thenAccept( ignore -> { - persister.setIdentifier( entity, id, this ); - firePostInsert( entity, id, state, persister ); - } ); - } ); + private static class Loop { + private CompletionStage loop = voidFuture(); + + public void then(Supplier> step) { + loop = loop.thenCompose( v -> step.get() ); } - else { - if ( firePreInsert( entity, null, state, persister ) ) { - return voidFuture(); + + public void whenComplete(BiConsumer consumer) { + loop = loop.whenComplete( consumer ); + } + } + + private CompletionStage recreateCollections(Object entity, Object id, EntityPersister persister) { + final Completable stage = new Completable<>(); + final Loop loop = new Loop(); + forEachOwnedCollection( + entity, id, persister, (descriptor, collection) -> { + firePreRecreate( collection, descriptor ); + final EventMonitor eventMonitor = getEventMonitor(); + final DiagnosticEvent event = eventMonitor.beginCollectionRecreateEvent(); + loop.then( () -> supplyStage( () -> ( (ReactiveCollectionPersister) descriptor ) + .reactiveRecreate( collection, id, this ) ) + .whenComplete( (t, throwable) -> eventMonitor + .completeCollectionRecreateEvent( event, id, descriptor.getRole(), throwable != null, this ) + ) + .thenAccept( unused -> { + final StatisticsImplementor statistics = getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.recreateCollection( descriptor.getRole() ); + } + firePostRecreate( collection, descriptor ); + } ) + ); + } + ); + loop.whenComplete( stage::complete ); + return stage.getStage(); + } + + private CompletionStage reactiveInsert(Object entity, Object[] state, ReactiveEntityPersister persister) { + if ( persister.isVersioned() ) { + if ( seedVersion( entity, state, persister, this ) ) { + persister.setValues( entity, state ); } - getInterceptor() - .onInsert( entity, null, state, persister.getPropertyNames(), persister.getPropertyTypes() ); - return persister - .insertReactive( state, entity, this ) - .thenAccept( id -> { - persister.setIdentifier( entity, id, this ); - firePostInsert( entity, id, state, persister ); - } ); } + final Generator generator = persister.getGenerator(); + if ( generator.generatedBeforeExecution( entity, this ) ) { + return generatedIdBeforeInsert( entity, persister, generator, state ); + } + if ( generator.generatedOnExecution( entity, this ) ) { + return generateIdOnInsert( entity, persister, generator, state ); + } + return applyAssignedIdentifierInsert( entity, persister, state ); } - private boolean firePreInsert(Object entity, Object id, Object[] state, EntityPersister persister) { - if ( getFactory().getEventListenerGroups().eventListenerGroup_PRE_INSERT.isEmpty() ) { - return false; + private CompletionStage applyAssignedIdentifierInsert(Object entity, ReactiveEntityPersister persister, Object[] state) { + Object id = persister.getIdentifier( entity, this ); + if ( id == null ) { + return failedFuture( new IdentifierGenerationException( "Identifier of entity '" + persister.getEntityName() + "' must be manually assigned before calling 'insert()'" ) ); } - else { - boolean veto = false; - final PreInsertEvent event = new PreInsertEvent( entity, id, state, persister, null ); - for ( PreInsertEventListener listener : getFactory().getEventListenerGroups().eventListenerGroup_PRE_INSERT.listeners() ) { - veto |= listener.onPreInsert( event ); - } - return veto; + if ( firePreInsert( entity, id, state, persister ) ) { + return completedFuture( id ); } + getInterceptor().onInsert( entity, id, state, persister.getPropertyNames(), persister.getPropertyTypes() ); + final EventMonitor eventMonitor = getEventMonitor(); + final DiagnosticEvent event = eventMonitor.beginEntityInsertEvent(); + // try-block + return supplyStage( () -> persister.insertReactive( id, state, entity, this ) ) + // finally: catches error in case insertReactive fails before returning a CompletionStage + .whenComplete( (generatedValues, throwable) -> eventMonitor + .completeEntityInsertEvent( event, id, persister.getEntityName(), throwable != null, this ) + ); } - private void firePostInsert(Object entity, Object id, Object[] state, EntityPersister persister) { - if ( !getFactory().getEventListenerGroups().eventListenerGroup_POST_INSERT.isEmpty() ) { - final PostInsertEvent event = new PostInsertEvent( entity, id, state, persister, null ); - for ( PostInsertEventListener listener : getFactory().getEventListenerGroups().eventListenerGroup_POST_INSERT.listeners() ) { - listener.onPostInsert( event ); - } + private CompletionStage generateIdOnInsert( + Object entity, + ReactiveEntityPersister persister, + Generator generator, + Object[] state) { + if ( !generator.generatesOnInsert() ) { + throw new IdentifierGenerationException( "Identifier generator must generate on insert" ); + } + if ( firePreInsert( entity, null, state, persister ) ) { + return nullFuture(); } + getInterceptor().onInsert( entity, null, state, persister.getPropertyNames(), persister.getPropertyTypes() ); + final EventMonitor eventMonitor = getEventMonitor(); + final DiagnosticEvent event = eventMonitor.beginEntityInsertEvent(); + // try-block + return supplyStage( () -> persister + .insertReactive( state, entity, this ) + .thenApply( generatedValues -> castNonNull( generatedValues ) + .getGeneratedValue( persister.getIdentifierMapping() ) + ) ) + // finally-block: catch the exceptions from insertReactive and getGeneratedValues + .whenComplete( (id, throwable) -> eventMonitor + .completeEntityInsertEvent( event, id, persister.getEntityName(), throwable != null, this ) + ) + .thenApply( id -> { + persister.setIdentifier( entity, id, this ); + return id; + } ); } - private CompletionStage generateId(EntityPersister persister, Object entity, Generator generator) { - if ( generator.generatesOnInsert() ) { - if ( generator instanceof ReactiveIdentifierGenerator reactiveGenerator ) { - return reactiveGenerator.generate(this, this); - } - else if ( generator instanceof BeforeExecutionGenerator beforeExecutionGenerator ) { - return completedFuture( beforeExecutionGenerator.generate(this, entity, null, INSERT) ); - } - else { - throw new IllegalArgumentException( "Unsupported generator type: " + generator.getClass().getName() ); - } - } - else { - final Object id = persister.getIdentifier( entity, this ); - if ( id == null ) { - throw new IdentifierGenerationException( "Identifier of entity '" + persister.getEntityName() + "' must be manually assigned before calling 'insert()'" ); - } - return completedFuture( id ); + private CompletionStage generatedIdBeforeInsert( + Object entity, + ReactiveEntityPersister persister, + Generator generator, + Object[] state) { + if ( !generator.generatesOnInsert() ) { + return failedFuture( new IdentifierGenerationException( "Identifier generator must generate on insert" ) ); } + return generateIdForInsert( entity, generator, persister ) + .thenCompose( id -> { + persister.setIdentifier( entity, id, this ); + if ( firePreInsert( entity, id, state, persister ) ) { + return completedFuture( id ); + } + getInterceptor().onInsert( entity, id, state, persister.getPropertyNames(), persister.getPropertyTypes() ); + final EventMonitor eventMonitor = getEventMonitor(); + final DiagnosticEvent event = eventMonitor.beginEntityInsertEvent(); + // try-block + return supplyStage( () -> persister.insertReactive( id, state, entity, this ) ) + // finally: catches error in case insertReactive fails before returning a CompletionStage + .whenComplete( (generatedValues, throwable) -> eventMonitor + .completeEntityInsertEvent( event, id, persister.getEntityName(), throwable != null, this ) + ) + .thenApply( identity() ); + } ); + } + + private CompletionStage generateIdForInsert(Object entity, Generator generator, ReactiveEntityPersister persister) { + if ( generator instanceof ReactiveIdentifierGenerator reactiveGenerator ) { + return reactiveGenerator.generate( this, this ) + .thenApply( id -> castToIdentifierType( id, persister ) ); + } + + final Object currentValue = generator.allowAssignedIdentifiers() ? persister.getIdentifier( entity ) : null; + return completedFuture( ( (BeforeExecutionGenerator) generator ).generate( this, entity, currentValue, INSERT ) ); } @Override @@ -399,7 +452,57 @@ public CompletionStage reactiveDelete(Object entity) { final ReactiveEntityPersister persister = getEntityPersister( null, entity ); final Object id = persister.getIdentifier( entity, this ); final Object version = persister.getVersion( entity ); - return persister.deleteReactive( id, version, entity, this ); + if ( firePreDelete( entity, id, persister ) ) { + return voidFuture(); + } + + getInterceptor().onDelete( entity, id, persister.getPropertyNames(), persister.getPropertyTypes() ); + return removeCollections( entity, id, persister ) + .thenCompose( v -> { + final Object ck = lockCacheItem( id, version, persister ); + final EventMonitor eventMonitor = getEventMonitor(); + final DiagnosticEvent event = eventMonitor.beginEntityDeleteEvent(); + // try-block + return supplyStage( () -> persister.deleteReactive( id, version, entity, this ) ) + // finally-block + .whenComplete( (unused, throwable) -> eventMonitor + .completeEntityDeleteEvent( event, id, persister.getEntityName(), throwable != null, this ) + ) + .thenAccept( unused -> removeCacheItem( ck, persister ) ); + } ) + .thenAccept( v -> { + firePostDelete( entity, id, persister ); + final StatisticsImplementor statistics = getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.deleteEntity( persister.getEntityName() ); + } + } ); + } + + private CompletionStage removeCollections(Object entity, Object id, EntityPersister persister) { + final Completable stage = new Completable<>(); + final Loop loop = new Loop(); + forEachOwnedCollection( entity, id, persister, + (descriptor, collection) -> { + firePreRemove( collection, entity, descriptor ); + final EventMonitor eventMonitor = getEventMonitor(); + final DiagnosticEvent event = eventMonitor.beginCollectionRemoveEvent(); + loop.then( () -> supplyStage( () -> ( (ReactiveCollectionPersister) descriptor ) + .reactiveRemove( id, this ) ) + .whenComplete( (unused, throwable) -> eventMonitor + .completeCollectionRemoveEvent( event, id, descriptor.getRole(), throwable != null, this ) + ) + .thenAccept( v -> { + firePostRemove( collection, entity, descriptor ); + final StatisticsImplementor statistics = getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.removeCollection( descriptor.getRole() ); + } + } ) + ); + } ); + loop.whenComplete( stage::complete ); + return stage.getStage(); } @Override @@ -424,29 +527,70 @@ private CompletionStage executeReactiveUpdate(Object entity) { final ReactiveEntityPersister persister = getEntityPersister( null, entity ); final Object id = persister.getIdentifier( entity, this ); final Object[] state = persister.getValues( entity ); - final Object oldVersion; + final Object oldVersion = persister.isVersioned() ? persister.getVersion( entity ) : null; if ( persister.isVersioned() ) { - oldVersion = persister.getVersion( entity ); final Object newVersion = incrementVersion( entity, oldVersion, persister, this ); setVersion( state, newVersion, persister ); persister.setValues( entity, state ); } - else { - oldVersion = null; + + if ( firePreUpdate(entity, id, state, persister) ) { + return voidFuture(); } - return persister - .updateReactive( id, state, null, false, null, oldVersion, entity, null, this ) - .thenCompose( CompletionStages::voidFuture ); + + getInterceptor().onUpdate( entity, id, state, persister.getPropertyNames(), persister.getPropertyTypes() ); + final Object ck = lockCacheItem( id, oldVersion, persister ); + final EventMonitor eventMonitor = getEventMonitor(); + final DiagnosticEvent event = eventMonitor.beginEntityUpdateEvent(); + // try-block + return supplyStage( () -> persister + .updateReactive( id, state, null, false, null, oldVersion, entity, null, this ) ) + // finally-block + .whenComplete( (generatedValues, throwable) -> eventMonitor + .completeEntityUpdateEvent( event, id, persister.getEntityName(), throwable != null, this ) + ) + .thenAccept( generatedValues -> removeCacheItem( ck, persister ) ) + .thenCompose( v -> removeAndRecreateCollections( entity, id, persister ) ) + .thenAccept( v -> { + firePostUpdate( entity, id, state, persister ); + final StatisticsImplementor statistics = getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.updateEntity( persister.getEntityName() ); + } + } ); } - @Override - public CompletionStage reactiveRefresh(Object entity) { - return reactiveRefresh( bestGuessEntityName( entity ), entity, LockMode.NONE ); + private CompletionStage removeAndRecreateCollections(Object entity, Object id, EntityPersister persister) { + final Completable stage = new Completable<>(); + final Loop loop = new Loop(); + forEachOwnedCollection( entity, id, persister, + (descriptor, collection) -> { + firePreUpdate( collection, descriptor ); + final EventMonitor eventMonitor = getEventMonitor(); + final DiagnosticEvent event = eventMonitor.beginCollectionRemoveEvent(); + ReactiveCollectionPersister reactivePersister = (ReactiveCollectionPersister) persister; + loop.then( () -> supplyStage( () -> reactivePersister + .reactiveRemove( id, this ) + .thenCompose( v -> reactivePersister.reactiveRecreate( collection, id, this ) ) ) + .whenComplete( (unused, throwable) -> eventMonitor + .completeCollectionRemoveEvent( event, id, descriptor.getRole(), throwable != null, this ) + ) + .thenAccept( v -> { + firePostUpdate( collection, descriptor ); + final StatisticsImplementor statistics = getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.updateCollection( descriptor.getRole() ); + } + } ) + ); + } ); + loop.whenComplete( stage::complete ); + return stage.getStage(); } @Override - public CompletionStage reactiveRefresh(String entityName, Object entity) { - return reactiveRefresh( entityName, entity, LockMode.NONE ); + public CompletionStage reactiveRefresh(Object entity) { + return reactiveRefresh( bestGuessEntityName( entity ), entity, LockMode.NONE ); } @Override @@ -454,8 +598,8 @@ public CompletionStage reactiveRefresh(Object entity, LockMode lockMode) { return reactiveRefresh( bestGuessEntityName( entity ), entity, lockMode ); } - @Override - public CompletionStage reactiveRefresh(String entityName, Object entity, LockMode lockMode) { + private CompletionStage reactiveRefresh(String entityName, Object entity, LockMode lockMode) { + checkOpen(); final ReactiveEntityPersister persister = getEntityPersister( entityName, entity ); final Object id = persister.getIdentifier( entity, this ); @@ -466,31 +610,23 @@ public CompletionStage reactiveRefresh(String entityName, Object entity, L if ( persister.canWriteToCache() ) { final EntityDataAccess cacheAccess = persister.getCacheAccessStrategy(); if ( cacheAccess != null ) { - final Object ck = cacheAccess.generateCacheKey( - id, - persister, - getFactory(), - getTenantIdentifier() - ); + final Object ck = cacheAccess.generateCacheKey( id, persister, getFactory(), getTenantIdentifier() ); cacheAccess.evict( ck ); } } return fromInternalFetchProfile( REFRESH, () -> persister.reactiveLoad( id, entity, getNullSafeLockMode( lockMode ), this ) ) .thenAccept( result -> { + UnresolvableObjectException.throwIfNull( result, id, persister.getEntityName() ); if ( getPersistenceContext().isLoadFinished() ) { getPersistenceContext().clear(); } - UnresolvableObjectException.throwIfNull( result, id, persister.getEntityName() ); } ); } - private CompletionStage fromInternalFetchProfile( - CascadingFetchProfile cascadingFetchProfile, - Supplier> supplier) { + private CompletionStage fromInternalFetchProfile(CascadingFetchProfile cascadingFetchProfile, Supplier> supplier) { CascadingFetchProfile previous = getLoadQueryInfluencers().getEnabledCascadingFetchProfile(); - return voidFuture() - .thenCompose( v -> { + return supplyStage( () -> { getLoadQueryInfluencers().setEnabledCascadingFetchProfile( cascadingFetchProfile ); return supplier.get(); } ) @@ -503,48 +639,31 @@ private CompletionStage fromInternalFetchProfile( @Override public CompletionStage reactiveUpsert(Object entity) { checkOpen(); - return reactiveUpsert( null, entity ); - } - - /** - * @see StatelessSessionImpl#upsert(String, Object) - */ - @Override - public CompletionStage reactiveUpsert(String entityName, Object entity) { - checkOpen(); - final ReactiveEntityPersister persister = getEntityPersister( entityName, entity ); - Object id = persister.getIdentifier( entity, this ); - Boolean knownTransient = persister.isTransient( entity, this ); - if ( knownTransient != null && knownTransient ) { - throw new TransientObjectException( - "Object passed to upsert() has a null identifier: " - + persister.getEntityName() ); -// final Generator generator = persister.getGenerator(); -// if ( !generator.generatedOnExecution() ) { -// id = ( (BeforeExecutionGenerator) generator).generate( this, entity, null, INSERT ); -// } - } + final ReactiveEntityPersister persister = getEntityPersister( null, entity ); + final Object id = idToUpsert( entity, persister ); final Object[] state = persister.getValues( entity ); - final Object oldVersion; - if ( persister.isVersioned() ) { - oldVersion = persister.getVersion( entity ); - if ( oldVersion == null ) { - if ( seedVersion( entity, state, persister, this ) ) { - persister.setValues( entity, state ); - } - } - else { - final Object newVersion = incrementVersion( entity, oldVersion, persister, this ); - setVersion( state, newVersion, persister ); - persister.setValues( entity, state ); - } - } - else { - oldVersion = null; + if ( firePreUpsert( entity, id, state, persister ) ) { + return voidFuture(); } - - return persister - .mergeReactive( id, state, null, false, null, oldVersion, entity, null, this ); + getInterceptor().onUpsert( entity, id, state, persister.getPropertyNames(), persister.getPropertyTypes() ); + final Object oldVersion = versionToUpsert( entity, persister, state ); + final Object ck = lockCacheItem( id, oldVersion, persister ); + final EventMonitor eventMonitor = getEventMonitor(); + final DiagnosticEvent event = eventMonitor.beginEntityUpsertEvent(); + return supplyStage( () -> persister + .mergeReactive( id, state, null, false, null, oldVersion, entity, null, this ) ) + .whenComplete( (v, throwable) -> eventMonitor + .completeEntityUpsertEvent( event, id, persister.getEntityName(), throwable != null, this ) + ) + .thenAccept( v -> { + removeCacheItem( ck, persister ); + final StatisticsImplementor statistics = getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.upsertEntity( persister.getEntityName() ); + } + } ) + .thenCompose( v -> removeAndRecreateCollections( entity, id, persister ) ) + .thenAccept( v -> firePostUpsert( entity, id, state, persister ) ); } @Override @@ -604,7 +723,6 @@ public CompletionStage reactiveDeleteAll(int batchSize, Object... entities .whenComplete( (v, throwable) -> batchingHelperSession.setJdbcBatchSize( jdbcBatchSize ) ); } - @Override public CompletionStage reactiveRefreshAll(Object... entities) { return loop( entities, batchingHelperSession::reactiveRefresh ) @@ -626,96 +744,73 @@ private ReactiveConnection batchingConnection(int batchSize) { .withBatchSize( batchSize ); } - private Object createProxy(EntityKey entityKey) { - final Object proxy = entityKey.getPersister().createProxy( entityKey.getIdentifier(), this ); - getPersistenceContext().addProxy( entityKey, proxy ); - return proxy; + @Override + public CompletionStage reactiveInternalLoad(String entityName, Object id, boolean eager, boolean nullable) { + Object object = super.internalLoad( entityName, id, eager, nullable ); + return object instanceof CompletionStage + ? (CompletionStage) object + : completedFuture( object ); } @Override - public CompletionStage reactiveInternalLoad( - String entityName, - Object id, - boolean eager, - boolean nullable) { - checkOpen(); - - final EntityPersister persister = getEntityPersister( entityName ); - final EntityKey entityKey = generateEntityKey( id, persister ); - - // first, try to load it from the temp PC associated to this SS - final PersistenceContext persistenceContext = getPersistenceContext(); - final Object loaded = persistenceContext.getEntity( entityKey ); - if ( loaded != null ) { - // we found it in the temp PC. Should indicate we are in the midst of processing a result set - // containing eager fetches via join fetch - return completedFuture( loaded ); - } - - if ( !eager ) { - // caller did not request forceful eager loading, see if we can create - // some form of proxy - - // first, check to see if we can use "bytecode proxies" - - final BytecodeEnhancementMetadata enhancementMetadata = persister.getBytecodeEnhancementMetadata(); - if ( enhancementMetadata.isEnhancedForLazyLoading() ) { - - // if the entity defines a HibernateProxy factory, see if there is an - // existing proxy associated with the PC - and if so, use it - if ( persister.getRepresentationStrategy().getProxyFactory() != null ) { - final Object proxy = persistenceContext.getProxy( entityKey ); - - if ( proxy != null ) { - if ( LOG.isTraceEnabled() ) { - LOG.trace( "Entity proxy found in session cache" ); - } - if ( LOG.isDebugEnabled() && ( (HibernateProxy) proxy ).getHibernateLazyInitializer().isUnwrap() ) { - LOG.debug( "Ignoring NO_PROXY to honor laziness" ); - } - - return completedFuture( persistenceContext.narrowProxy( proxy, persister, entityKey, null ) ); - } - - // specialized handling for entities with subclasses with a HibernateProxy factory - if ( persister.hasSubclasses() ) { - // entities with subclasses that define a ProxyFactory can create - // a HibernateProxy. -// LOG.debugf( "Creating a HibernateProxy for to-one association with subclasses to honor laziness" ); - return completedFuture( createProxy( entityKey ) ); - } - return completedFuture( enhancementMetadata.createEnhancedProxy( entityKey, false, this ) ); - } - else if ( !persister.hasSubclasses() ) { - return completedFuture( enhancementMetadata.createEnhancedProxy( entityKey, false, this ) ); - } - // If we get here, then the entity class has subclasses and there is no HibernateProxy factory. - // The entity will get loaded below. - } - else { - if ( persister.hasProxy() ) { - final Object existingProxy = persistenceContext.getProxy( entityKey ); - if ( existingProxy != null ) { - return completedFuture( persistenceContext.narrowProxy( existingProxy, persister, entityKey, null ) ); - } - else { - return completedFuture( createProxy( entityKey ) ); - } - } - } + public CompletionStage reactiveImmediateLoad(String entityName, Object id) { + if ( persistenceContext.isLoadFinished() ) { + throw new SessionException( "proxies cannot be fetched by a stateless session" ); } + // unless we are still in the process of handling a top-level load + return reactiveGet( entityName, id ); + } + @Override + protected Object internalLoadGet(String entityName, Object id, PersistenceContext persistenceContext) { // otherwise immediately materialize it // IMPLEMENTATION NOTE: increment/decrement the load count before/after getting the value // to ensure that #get does not clear the PersistenceContext. persistenceContext.beforeLoad(); - return this.reactiveGet( persister.getEntityName(), id ) + return this.reactiveGet( entityName, id ) .whenComplete( (r, e) -> persistenceContext.afterLoad() ); } @Override - @SuppressWarnings("unchecked") + public CompletionStage reactiveInitializeCollection(PersistentCollection collection, boolean writing) { + checkOpen(); + final CollectionEntry ce = persistenceContext.getCollectionEntry( collection ); + if ( ce == null ) { + throw new HibernateException( "no entry for collection" ); + } + if ( collection.wasInitialized() ) { + return voidFuture(); + } + else { + final ReactiveCollectionPersister loadedPersister = + (ReactiveCollectionPersister) ce.getLoadedPersister(); + final Object loadedKey = ce.getLoadedKey(); + if ( LOG.isTraceEnabled() ) { + LOG.trace( "Initializing collection " + + collectionInfoString( loadedPersister, collection, loadedKey, this ) ); + } + final boolean foundInCache = + initializeCollectionFromCache( loadedKey, loadedPersister, collection, this ); + if ( foundInCache ) { + LOG.trace( "Collection initialized from cache" ); + return voidFuture(); + } + else { + return loadedPersister.reactiveInitialize( loadedKey, this ) + .thenAccept( v -> { + handlePotentiallyEmptyCollection( collection, persistenceContext, loadedKey, loadedPersister ); + LOG.trace( "Collection initialized" ); + final StatisticsImplementor statistics = getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.fetchCollection( loadedPersister.getRole() ); + } + } ); + } + } + } + + @Override public CompletionStage reactiveFetch(T association, boolean unproxy) { checkOpen(); if ( association == null ) { @@ -725,84 +820,92 @@ public CompletionStage reactiveFetch(T association, boolean unproxy) { final PersistenceContext persistenceContext = getPersistenceContext(); final LazyInitializer initializer = extractLazyInitializer( association ); if ( initializer != null ) { - if ( initializer.isUninitialized() ) { - final String entityName = initializer.getEntityName(); - final Object id = initializer.getIdentifier(); - initializer.setSession( this ); - persistenceContext.beforeLoad(); - - final ReactiveEntityPersister persister = getEntityPersister( entityName ); - - // This is hard to test because it happens on slower machines like the ones we use on CI. - // See AbstractLazyInitializer#initialize, it happens when the object is not initialized and we need to - // call session.immediateLoad - final CompletionStage stage = initializer.getImplementation() instanceof CompletionStage - ? (CompletionStage) initializer.getImplementation() - : completedFuture( initializer.getImplementation() ); - - return stage.thenCompose( implementation -> persister.reactiveLoad( id, implementation, LockOptions.NONE, this ) ) - .thenApply( entity -> { - checkEntityFound( this, entityName, id, entity ); - initializer.setImplementation( entity ); - return unproxy ? (T) entity : association; - } ) - .whenComplete( (v, e) -> { - initializer.unsetSession(); - persistenceContext.afterLoad(); - if ( persistenceContext.isLoadFinished() ) { - persistenceContext.clear(); - } - } ); - } - else { - // Initialized - return completedFuture( unproxy ? (T) initializer.getImplementation() : association ); - } - } - else if ( association instanceof PersistentCollection collection ) { - if ( collection.wasInitialized() ) { - return completedFuture( association ); - } - else { - final ReactiveCollectionPersister collectionDescriptor = - (ReactiveCollectionPersister) getFactory().getMappingMetamodel() - .getCollectionDescriptor( collection.getRole() ); - - final Object key = collection.getKey(); - persistenceContext.addUninitializedCollection( collectionDescriptor, collection, key ); - collection.setCurrentSession( this ); - return collectionDescriptor.reactiveInitialize( key, this ) - .whenComplete( (v, e) -> { - collection.unsetSession( this ); - if ( persistenceContext.isLoadFinished() ) { - persistenceContext.clear(); - } - } ) - .thenApply( v -> association ); - } + return initializer.isUninitialized() + ? fetchUninitialized( association, unproxy, initializer, persistenceContext ) + : completedFuture( unproxy ? (T) initializer.getImplementation() : association ); } else if ( isPersistentAttributeInterceptable( association ) ) { - final PersistentAttributeInterceptable interceptable = asPersistentAttributeInterceptable( association ); - final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); - if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor lazinessInterceptor ) { - lazinessInterceptor.setSession( this ); - return forceInitialize( association, null, lazinessInterceptor.getIdentifier(), lazinessInterceptor.getEntityName(), this ) + if ( asPersistentAttributeInterceptable( association ).$$_hibernate_getInterceptor() + instanceof EnhancementAsProxyLazinessInterceptor proxyInterceptor ) { + proxyInterceptor.setSession( this ); + return forceInitialize( association, null, proxyInterceptor.getIdentifier(), proxyInterceptor.getEntityName(), this ) .whenComplete( (i,e) -> { - lazinessInterceptor.unsetSession(); + proxyInterceptor.unsetSession(); if ( persistenceContext.isLoadFinished() ) { persistenceContext.clear(); } } ) .thenApply( i -> association ); - - } - else { - return completedFuture( association ); } } - else { - return completedFuture( association ); + else if ( association instanceof PersistentCollection collection && !collection.wasInitialized() ) { + final ReactiveCollectionPersister collectionDescriptor = (ReactiveCollectionPersister) getFactory() + .getMappingMetamodel().getCollectionDescriptor( collection.getRole() ); + + final Object key = collection.getKey(); + persistenceContext.addUninitializedCollection( collectionDescriptor, collection, key ); + collection.setCurrentSession( this ); + return supplyStage( () -> { + if ( initializeCollectionFromCache( key, collectionDescriptor, collection, this ) ) { + LOG.trace( "Collection fetched from cache" ); + return completedFuture( association ); + } + else { + return collectionDescriptor + .reactiveInitialize( key, this ) + .thenApply( v -> { + handlePotentiallyEmptyCollection( collection, getPersistenceContextInternal(), key, collectionDescriptor ); + LOG.trace( "Collection fetched" ); + final StatisticsImplementor statistics = getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.fetchCollection( collectionDescriptor.getRole() ); + } + return association; + } ); + } + } ).whenComplete( (t, throwable) -> { + collection.$$_hibernate_setInstanceId( 0 ); + collection.unsetSession( this ); + if ( persistenceContext.isLoadFinished() ) { + persistenceContext.clear(); + } + } ); } + + return completedFuture( association ); + } + + private CompletionStage fetchUninitialized( + T association, + boolean unproxy, + LazyInitializer initializer, + PersistenceContext persistenceContext) { + final String entityName = initializer.getEntityName(); + final Object id = initializer.getIdentifier(); + initializer.setSession( this ); + persistenceContext.beforeLoad(); + return reactiveImplementation( initializer ) + .thenApply( entity -> { + checkEntityFound( this, entityName, id, entity ); + initializer.setImplementation( entity ); + return unproxy ? (T) entity : association; + } ) + .whenComplete( (v, e) -> { + initializer.unsetSession(); + persistenceContext.afterLoad(); + if ( persistenceContext.isLoadFinished() ) { + persistenceContext.clear(); + } + } ); + } + + private static CompletionStage reactiveImplementation(LazyInitializer initializer) { + // This is hard to test because it happens on slower machines like the ones we use on CI. + // See AbstractLazyInitializer#initialize, it happens when the object is not initialized, and we need to + // call session.immediateLoad + return initializer.getImplementation() instanceof CompletionStage + ? (CompletionStage) initializer.getImplementation() + : completedFuture( initializer.getImplementation() ); } @Override @@ -830,6 +933,29 @@ public RootGraphImplementor getEntityGraph(Class entity, String name) return (RootGraphImplementor) entityGraph; } + @Override + public ReactiveQuery createReactiveQuery(TypedQueryReference typedQueryReference) { + checksBeforeQueryCreation(); + if ( typedQueryReference instanceof SelectionSpecificationImpl specification ) { + final CriteriaQuery query = specification.buildCriteria( getCriteriaBuilder() ); + return new ReactiveQuerySqmImpl<>( (SqmStatement) query, specification.getResultType(), this ); + } + if ( typedQueryReference instanceof MutationSpecificationImpl specification ) { + final CommonAbstractCriteria query = specification.buildCriteria( getCriteriaBuilder() ); + return new ReactiveQuerySqmImpl<>( (SqmStatement) query, (Class) specification.getResultType(), this ); + } + @SuppressWarnings("unchecked") + // this cast is fine because of all our impls of TypedQueryReference return Class + final Class resultType = (Class) typedQueryReference.getResultType(); + ReactiveQueryImplementor query = (ReactiveQueryImplementor) buildNamedQuery( + typedQueryReference.getName(), + memento -> createSqmQueryImplementor( resultType, memento ), + memento -> createNativeQueryImplementor( resultType, memento ) + ); + typedQueryReference.getHints().forEach( query::setHint ); + return query; + } + @Override public ReactiveSqmQueryImplementor createReactiveQuery(String queryString) { return createReactiveQuery( queryString, null ); @@ -853,7 +979,7 @@ public ReactiveQuery createReactiveQuery(CriteriaQuery criteriaQuery) return createReactiveCriteriaQuery( selectStatement, criteriaQuery.getResultType() ); } catch (RuntimeException e) { - if ( getSessionFactory().getJpaMetamodel().getJpaCompliance().isJpaTransactionComplianceEnabled() ) { + if ( getSessionFactory().getSessionFactoryOptions().getJpaCompliance().isJpaTransactionComplianceEnabled() ) { markForRollbackOnly(); } throw getExceptionConverter().convert( e ); 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 64c5b3af3..5993d1893 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 @@ -412,14 +412,17 @@ public CompletionStage resolveJdbcValuesSource( } } + /** + * Copied from Hibernate ORM + */ 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 - ); + return coalesceSuppliedValues( + () -> queryOptions == null ? null : queryOptions.getCacheMode(), + session::getCacheMode, + () -> CacheMode.NORMAL + ); } /** diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableAssembler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableAssembler.java new file mode 100644 index 000000000..db8222838 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableAssembler.java @@ -0,0 +1,42 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.graph.embeddable.internal; + +import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; +import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableAssembler; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; + +import java.util.concurrent.CompletionStage; + +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; + +/** + * @see org.hibernate.sql.results.graph.embeddable.internal.EmbeddableAssembler + */ +public class ReactiveEmbeddableAssembler extends EmbeddableAssembler implements ReactiveDomainResultsAssembler { + + public ReactiveEmbeddableAssembler(EmbeddableInitializer initializer) { + super( initializer ); + } + + @Override + public CompletionStage reactiveAssemble(ReactiveRowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) { + final ReactiveInitializer reactiveInitializer = (ReactiveInitializer) getInitializer(); + final InitializerData data = reactiveInitializer.getData( rowProcessingState ); + final Initializer.State state = data.getState(); + if ( state == Initializer.State.KEY_RESOLVED ) { + return reactiveInitializer + .reactiveResolveInstance( data ) + .thenApply( v -> reactiveInitializer.getResolvedInstance( data ) ); + } + return completedFuture( reactiveInitializer.getResolvedInstance( data ) ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java index 50034533b..46a15c1e9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java @@ -6,14 +6,20 @@ package org.hibernate.reactive.sql.results.graph.embeddable.internal; import org.hibernate.engine.FetchTiming; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchSelectImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable; import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl; +import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl; public class ReactiveEmbeddableFetchImpl extends EmbeddableFetchImpl { @@ -37,4 +43,23 @@ public EmbeddableInitializer createInitializer( AssemblerCreationState creationState) { return new ReactiveEmbeddableInitializerImpl( this, getDiscriminatorFetch(), parent, creationState, true ); } + + @Override + public DomainResultAssembler createAssembler(InitializerParent parent, AssemblerCreationState creationState) { + Initializer initializer = creationState.resolveInitializer( this, parent, this ); + EmbeddableInitializer embeddableInitializer = initializer.asEmbeddableInitializer(); + return new ReactiveEmbeddableAssembler( embeddableInitializer ); + } + + @Override + public Fetch findFetch(Fetchable fetchable) { + Fetch fetch = super.findFetch( fetchable ); + if ( fetch instanceof EntityFetchSelectImpl entityFetchSelect ) { + return new ReactiveEntityFetchSelectImpl( entityFetchSelect ); + } + else if ( fetch instanceof EmbeddableFetchImpl embeddableFetch ) { + return new ReactiveEmbeddableFetchImpl( embeddableFetch ); + } + return fetch; + } } 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 a1d4a890e..3df56721f 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 @@ -5,12 +5,18 @@ */ package org.hibernate.reactive.sql.results.graph.embeddable.internal; + import java.util.concurrent.CompletionStage; import java.util.function.BiFunction; import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.mapping.VirtualModelPart; +import org.hibernate.metamodel.spi.EmbeddableInstantiator; +import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.InitializerData; import org.hibernate.sql.results.graph.InitializerParent; @@ -19,8 +25,13 @@ import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableInitializerImpl; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; 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.reactive.util.impl.CompletionStages.whileLoop; +import static org.hibernate.sql.results.graph.embeddable.EmbeddableLoadingLogger.EMBEDDED_LOAD_LOGGER; +import static org.hibernate.sql.results.graph.entity.internal.BatchEntityInsideEmbeddableSelectFetchInitializer.BATCH_PROPERTY; public class ReactiveEmbeddableInitializerImpl extends EmbeddableInitializerImpl implements ReactiveInitializer { @@ -33,6 +44,10 @@ public ReactiveEmbeddableInitializerData( super( initializer, rowProcessingState ); } + public Object[] getRowState(){ + return rowState; + } + @Override public void setState(State state) { super.setState( state ); @@ -64,10 +79,128 @@ protected InitializerData createInitializerData(RowProcessingState rowProcessing @Override public CompletionStage reactiveResolveInstance(EmbeddableInitializerData data) { - super.resolveInstance( data ); + if ( data.getState() != State.KEY_RESOLVED ) { + return voidFuture(); + } + + data.setState( State.RESOLVED ); + return extractRowState( (ReactiveEmbeddableInitializerData) data ) + .thenAccept( unused -> prepareCompositeInstance( (ReactiveEmbeddableInitializerData) data ) ); + } + + private CompletionStage extractRowState(ReactiveEmbeddableInitializerData data) { + final DomainResultAssembler[] subAssemblers = assemblers[data.getSubclassId()]; + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final Object[] rowState = data.getRowState(); + final boolean[] stateAllNull = {true}; + final int[] index = {0}; + final boolean[] forceExit = { false }; + return whileLoop( + () -> index[0] < subAssemblers.length && !forceExit[0], + () -> { + final int i = index[0]++; + final DomainResultAssembler assembler = subAssemblers[i]; + if ( assembler instanceof ReactiveDomainResultsAssembler reactiveAssembler ) { + return reactiveAssembler.reactiveAssemble( (ReactiveRowProcessingState) rowProcessingState ) + .thenAccept( contributorValue -> setContributorValue( + contributorValue, + i, + rowState, + stateAllNull, + forceExit + ) ); + } + else { + setContributorValue( + assembler == null ? null : assembler.assemble( rowProcessingState ), + i, + rowState, + stateAllNull, + forceExit + ); + return voidFuture(); + } + }) + .whenComplete( + (unused, throwable) -> { + if ( stateAllNull[0] ) { + data.setState( State.MISSING ); + } + } + ); + } + + private void setContributorValue( + Object contributorValue, + int index, + Object[] rowState, + boolean[] stateAllNull, + boolean[] forceExit) { + if ( contributorValue == BATCH_PROPERTY ) { + rowState[index] = null; + } + else { + rowState[index] = contributorValue; + } + if ( contributorValue != null ) { + stateAllNull[0] = false; + } + else if ( isPartOfKey() ) { + // If this is a foreign key and there is a null part, the whole thing has to be turned into null + stateAllNull[0] = true; + forceExit[0] = true; + } + } + + private CompletionStage prepareCompositeInstance(ReactiveEmbeddableInitializerData data) { + // Virtual model parts use the owning entity as container which the fetch parent access provides. + // For an identifier or foreign key this is called during the resolveKey phase of the fetch parent, + // so we can't use the fetch parent access in that case. + final ReactiveInitializer parent = (ReactiveInitializer) getParent(); + if ( parent != null && getInitializedPart() instanceof VirtualModelPart && !isPartOfKey() && data.getState() != State.MISSING ) { + final ReactiveEmbeddableInitializerData subData = parent.getData( data.getRowProcessingState() ); + return parent + .reactiveResolveInstance( subData ) + .thenCompose( + unused -> { + data.setInstance( parent.getResolvedInstance( subData ) ); + if ( data.getState() == State.INITIALIZED ) { + return voidFuture(); + } + return doCreateCompositeInstance( data ) + .thenAccept( v -> EMBEDDED_LOAD_LOGGER.debugf( + "Created composite instance [%s]", + getNavigablePath() + ) ); + } ); + } + + return doCreateCompositeInstance( data ) + .thenAccept( v -> EMBEDDED_LOAD_LOGGER.debugf( "Created composite instance [%s]", getNavigablePath() ) ); + + } + + private CompletionStage doCreateCompositeInstance(ReactiveEmbeddableInitializerData data) { + if ( data.getInstance() == null ) { + return createCompositeInstance( data ) + .thenAccept( data::setInstance ); + } return voidFuture(); } + private CompletionStage createCompositeInstance(ReactiveEmbeddableInitializerData data) { + if ( data.getState() == State.MISSING ) { + return nullFuture(); + } + + final EmbeddableInstantiator instantiator = data.getConcreteEmbeddableType() == null + ? getInitializedPart().getEmbeddableTypeDescriptor().getRepresentationStrategy().getInstantiator() + : data.getConcreteEmbeddableType().getInstantiator(); + final Object instance = instantiator.instantiate( data ); + data.setState( State.RESOLVED ); + return completedFuture( instance ); + } + @Override public CompletionStage reactiveInitializeInstance(EmbeddableInitializerData data) { super.initializeInstance( data ); @@ -81,16 +214,21 @@ public CompletionStage forEachReactiveSubInitializer( final ReactiveEmbeddableInitializerData embeddableInitializerData = (ReactiveEmbeddableInitializerData) data; final RowProcessingState rowProcessingState = embeddableInitializerData.getRowProcessingState(); if ( embeddableInitializerData.getConcreteEmbeddableType() == null ) { - return loop( subInitializers, subInitializer -> loop( subInitializer, initializer -> consumer - .apply( (ReactiveInitializer) initializer, rowProcessingState ) - ) ); + return loop( subInitializers, subInitializer -> + loop( subInitializer, initializer -> + initializer != null + ? consumer.apply( (ReactiveInitializer) initializer, rowProcessingState ) + : voidFuture() + ) + ); } else { Initializer[] initializers = subInitializers[embeddableInitializerData.getSubclassId()]; - return loop( 0, initializers.length, i -> { - ReactiveInitializer reactiveInitializer = (ReactiveInitializer) initializers[i]; - return consumer.apply( reactiveInitializer, rowProcessingState ); - } ); + return loop(0, initializers.length, i -> + initializers[i] != null + ? consumer.apply( (ReactiveInitializer) initializers[i], rowProcessingState ) + : voidFuture() + ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java index 8601d434f..c6ee36363 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java @@ -24,8 +24,9 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.LazyInitializer; import org.hibernate.proxy.map.MapProxy; -import org.hibernate.reactive.session.ReactiveSession; +import org.hibernate.reactive.session.ReactiveQueryProducer; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResult; @@ -254,16 +255,21 @@ public CompletionStage reactiveResolveInstance(EntityInitializerData origi final RowProcessingState rowProcessingState = data.getRowProcessingState(); data.setState( State.RESOLVED ); if ( data.getEntityKey() == null ) { - assert getIdentifierAssembler() != null; - final Object id = getIdentifierAssembler().assemble( rowProcessingState ); - if ( id == null ) { - setMissing( data ); - return voidFuture(); - } - resolveEntityKey( data, id ); + return assembleId( rowProcessingState ) + .thenCompose( id -> { + if ( id == null ) { + setMissing( data ); + return voidFuture(); + } + resolveEntityKey( data, id ); + return postAssembleId( rowProcessingState, data ); + } ); } - final PersistenceContext persistenceContext = rowProcessingState.getSession() - .getPersistenceContextInternal(); + return postAssembleId( rowProcessingState, data ); + } + + private CompletionStage postAssembleId(RowProcessingState rowProcessingState, ReactiveEntityInitializerData data) { + final PersistenceContext persistenceContext = rowProcessingState.getSession().getPersistenceContextInternal(); data.setEntityHolder( persistenceContext.claimEntityHolderIfPossible( data.getEntityKey(), null, @@ -274,29 +280,37 @@ public CompletionStage reactiveResolveInstance(EntityInitializerData origi if ( useEmbeddedIdentifierInstanceAsEntity( data ) ) { data.setEntityInstanceForNotify( rowProcessingState.getEntityId() ); data.setInstance( data.getEntityInstanceForNotify() ); + postResolveInstance( data ); + return voidFuture(); } - else { - return reactiveResolveEntityInstance1( data ) - .thenAccept( v -> { - if ( data.getUniqueKeyAttributePath() != null ) { - final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final EntityPersister concreteDescriptor = getConcreteDescriptor( data ); - final EntityUniqueKey euk = new EntityUniqueKey( - concreteDescriptor.getEntityName(), - data.getUniqueKeyAttributePath(), - rowProcessingState.getEntityUniqueKey(), - data.getUniqueKeyPropertyTypes()[concreteDescriptor.getSubclassId()], - session.getFactory() - ); - session.getPersistenceContextInternal().addEntity( euk, data.getInstance() ); - } - postResolveInstance( data ); - } ); - } - postResolveInstance( data ); - return voidFuture(); + + return reactiveResolveEntityInstance1( data ) + .thenAccept( v -> { + if ( data.getUniqueKeyAttributePath() != null ) { + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + final EntityPersister concreteDescriptor = getConcreteDescriptor( data ); + final EntityUniqueKey euk = new EntityUniqueKey( + concreteDescriptor.getEntityName(), + data.getUniqueKeyAttributePath(), + rowProcessingState.getEntityUniqueKey(), + data.getUniqueKeyPropertyTypes()[concreteDescriptor.getSubclassId()], + session.getFactory() + ); + session.getPersistenceContextInternal().addEntity( euk, data.getInstance() ); + } + postResolveInstance( data ); + } ); + } + + private CompletionStage assembleId(RowProcessingState rowProcessingState) { + final DomainResultAssembler identifierAssembler = getIdentifierAssembler(); + assert identifierAssembler != null; + return identifierAssembler instanceof ReactiveDomainResultsAssembler reactiveAssembler + ? reactiveAssembler.reactiveAssemble( (ReactiveRowProcessingState) rowProcessingState ) + : completedFuture( identifierAssembler.assemble( rowProcessingState ) ); } + // We could move this method in ORM private void postResolveInstance(ReactiveEntityInitializerData data) { if ( data.getInstance() != null ) { upgradeLockMode( data ); @@ -532,7 +546,7 @@ protected CompletionStage reactiveResolveEntityInstance(ReactiveEntityIn // If this initializer owns the entity, we have to remove the entity holder, // because the subsequent loading process will claim the entity session.getPersistenceContextInternal().removeEntityHolder( data.getEntityKey() ); - return ( (ReactiveSession) session ).reactiveInternalLoad( + return ( (ReactiveQueryProducer) session ).reactiveInternalLoad( data.getConcreteDescriptor().getEntityName(), data.getEntityKey().getIdentifier(), true, 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 8f848d01b..6d5ea5418 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 @@ -23,7 +23,7 @@ import org.hibernate.proxy.LazyInitializer; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.session.ReactiveSession; +import org.hibernate.reactive.session.ReactiveQueryProducer; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; @@ -154,7 +154,7 @@ else if ( data.getInstance() == null ) { data.setState( State.INITIALIZED ); final String entityName = concreteDescriptor.getEntityName(); - return ( (ReactiveSession) session ).reactiveInternalLoad( + return ( (ReactiveQueryProducer) session ).reactiveInternalLoad( entityName, data.getEntityIdentifier(), true, 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 b490f2fb3..dccb3a072 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 @@ -5,6 +5,7 @@ */ package org.hibernate.reactive.stage; +import jakarta.persistence.TypedQueryReference; import java.lang.invoke.MethodHandles; import java.util.List; import java.util.concurrent.CompletionStage; @@ -24,7 +25,6 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.jpa.internal.util.FlushModeTypeHelper; import org.hibernate.proxy.HibernateProxy; -import org.hibernate.query.Order; import org.hibernate.query.Page; import org.hibernate.query.criteria.HibernateCriteriaBuilder; import org.hibernate.query.criteria.JpaCriteriaInsert; @@ -360,37 +360,6 @@ default SelectionQuery setLockMode(String alias, LockModeType lockModeType) { return setLockMode( alias, convertToLockMode(lockModeType) ); } -// /** -// * Set the {@link LockOptions} to use for the whole query. -// * -// * @see org.hibernate.query.Query#setLockOptions(LockOptions) -// */ -// Query setLockOptions(LockOptions lockOptions); - - /** - * If the result type of this query is an entity class, add one or more - * {@linkplain Order rules} for ordering the query results. - * - * @param orderList one or more instances of {@link Order} - * - * @see Order - * - * @see org.hibernate.query.Query#setOrder(List) - */ - SelectionQuery setOrder(List> orderList); - - /** - * If the result type of this query is an entity class, add a - * {@linkplain Order rule} for ordering the query results. - * - * @param order an instance of {@link Order} - * - * @see Order - * - * @see org.hibernate.query.Query#setOrder(Order) - */ - SelectionQuery setOrder(Order order); - /** * Set the {@link EntityGraph} that will be used as a fetch plan for * the root entity returned by this query. @@ -515,732 +484,750 @@ default Query setLockMode(String alias, LockModeType lockModeType) { } /** - * A non-blocking counterpart to the Hibernate {@link org.hibernate.Session} - * interface, allowing a reactive style of interaction with the database. - *

    - * The semantics of operations on this interface are identical to the - * semantics of the similarly-named operations of {@code Session}, except - * that the operations are performed asynchronously, returning a - * {@link CompletionStage} without blocking the calling thread. - *

    - * Entities associated with an {@code Session} do not support transparent - * lazy association fetching. Instead, {@link #fetch(Object)} should be used - * to explicitly request asynchronous fetching of an association, or the - * association should be fetched eagerly when the entity is first retrieved, - * for example, by: - * - *

      - *
    • {@link #enableFetchProfile(String) enabling a fetch profile}, - *
    • using an {@link EntityGraph}, or - *
    • writing a {@code join fetch} clause in a HQL query. - *
    + * Operations common to objects which act as factories for instances of + * {@link Query}. This is a common supertype of {@link Session} and + * {@link StatelessSession}. * - * @see org.hibernate.Session + * @since 3.0 */ - interface Session extends Closeable { - + interface QueryProducer { /** - * Asynchronously return the persistent instance of the given entity - * class with the given identifier, or {@code null} if there is no such - * persistent instance. If the instance is already associated with - * the session, return the associated instance. This method never - * returns an uninitialized instance. - * - *
    -		 * {@code session.find(Book.class, id).thenAccept(book -> print(book.getTitle()));}
    -		 * 
    + * Create an instance of {@link SelectionQuery} for the given HQL/JPQL + * query string. * - * @param entityClass The entity type - * @param id an identifier + * @param queryString The HQL/JPQL query * - * @return a persistent instance or null via a {@code CompletionStage} + * @return The {@link SelectionQuery} instance for manipulation and execution * - * @see jakarta.persistence.EntityManager#find(Class, Object) + * @see jakarta.persistence.EntityManager#createQuery(String, Class) */ - CompletionStage find(Class entityClass, Object id); + SelectionQuery createSelectionQuery(String queryString, Class resultType); /** - * Asynchronously return the persistent instance of the given entity - * class with the given identifier, requesting the given {@link LockMode}. + * Create an instance of {@link MutationQuery} for the given HQL/JPQL + * update or delete statement. * - * @param entityClass The entity type - * @param id an identifier - * @param lockMode the requested {@link LockMode} + * @param queryString The HQL/JPQL query, update or delete statement * - * @return a persistent instance or null via a {@code CompletionStage} + * @return The {@link MutationQuery} instance for manipulation and execution * - * @see #find(Class,Object) - * @see #lock(Object, LockMode) this discussion of lock modes + * @see jakarta.persistence.EntityManager#createQuery(String) */ - CompletionStage find(Class entityClass, Object id, LockMode lockMode); + MutationQuery createMutationQuery(String queryString); /** - * Asynchronously return the persistent instance of the given entity - * class with the given identifier, requesting the given {@link LockModeType}. - * - * @param entityClass The entity type - * @param id an identifier - * @param lockModeType the requested {@link LockModeType} - * - * @return a persistent instance or null via a {@code CompletionStage} + * Create an instance of {@link MutationQuery} for the given update tree. * - * @see #find(Class,Object) - * @see #lock(Object, LockMode) this discussion of lock modes - */ - default CompletionStage find(Class entityClass, Object id, LockModeType lockModeType) { - return find( entityClass, id, convertToLockMode(lockModeType) ); - } - -// /** -// * Asynchronously return the persistent instance of the given entity -// * class with the given identifier, requesting the given {@link LockOptions}. -// * -// * @param entityClass The entity type -// * @param id an identifier -// * @param lockOptions the requested {@link LockOptions} -// * -// * @return a persistent instance or null via a {@code CompletionStage} -// * -// * @see #find(Class,Object) -// * @see #lock(Object, LockMode) this discussion of lock modes -// */ -// CompletionStage find(Class entityClass, Object id, LockOptions lockOptions); - - /** - * Asynchronously return the persistent instance with the given - * identifier of an entity class, using the given {@link EntityGraph} - * as a fetch plan. + * @param updateQuery the update criteria query * - * @param entityGraph an {@link EntityGraph} specifying the entity - * and associations to be fetched - * @param id an identifier + * @return The {@link MutationQuery} instance for manipulation and execution * - * @see #find(Class,Object) + * @see org.hibernate.query.QueryProducer#createMutationQuery(CriteriaUpdate) */ - CompletionStage find(EntityGraph entityGraph, Object id); + MutationQuery createMutationQuery(CriteriaUpdate updateQuery); /** - * Asynchronously return the persistent instances of the given entity - * class with the given identifiers, or null if there is no such - * persistent instance. + * Create an instance of {@link MutationQuery} for the given delete tree. * - * @param entityClass The entity type - * @param ids the identifiers + * @param deleteQuery the delete criteria query * - * @return a list of persistent instances and nulls via a {@code CompletionStage} + * @return The {@link MutationQuery} instance for manipulation and execution + * + * @see org.hibernate.query.QueryProducer#createMutationQuery(CriteriaDelete) */ - CompletionStage> find(Class entityClass, Object... ids); + MutationQuery createMutationQuery(CriteriaDelete deleteQuery); /** - * Asynchronously return the persistent instance of the given entity - * class with the given natural identifiers, or null if there is no - * such persistent instance. + * Create a {@link MutationQuery} from the given insert select criteria tree * - * @param entityClass The entity type - * @param naturalId the natural identifier + * @param insert the insert select criteria query * - * @return a persistent instance or null via a {@code CompletionStage} + * @return The {@link MutationQuery} instance for manipulation and execution + * + * @see org.hibernate.query.QueryProducer#createMutationQuery(JpaCriteriaInsert) */ - @Incubating - CompletionStage find(Class entityClass, Identifier naturalId); + MutationQuery createMutationQuery(JpaCriteriaInsert insert); /** - * Return the persistent instance of the given entity class with the - * given identifier, assuming that the instance exists. This method - * never results in access to the underlying data store, and thus - * might return a proxy that must be initialized explicitly using - * {@link #fetch(Object)}. - *

    - * You should not use this method to determine if an instance exists - * (use {@link #find} instead). Use this only to retrieve an instance - * which you safely assume exists, where non-existence would be an - * actual error. + * Create a typed {@link Query} instance for the given typed query reference. * - * @param entityClass a persistent class - * @param id a valid identifier of an existing persistent instance of the class + * @param typedQueryReference the type query reference * - * @return the persistent instance or proxy + * @return The {@link Query} instance for execution * - * @see jakarta.persistence.EntityManager#getReference(Class, Object) + * @throws IllegalArgumentException if a query has not been + * defined with the name of the typed query reference or if + * the query result is found to not be assignable to + * result class of the typed query reference + * + * @see org.hibernate.query.QueryProducer#createQuery(TypedQueryReference) */ - T getReference(Class entityClass, Object id); + Query createQuery(TypedQueryReference typedQueryReference); /** - * Return the persistent instance with the same identity as the given - * instance, which might be detached, assuming that the instance is - * still persistent in the database. This method never results in - * access to the underlying data store, and thus might return a proxy - * that must be initialized explicitly using {@link #fetch(Object)}. + * Create an instance of {@link Query} for the given HQL/JPQL query + * string or HQL/JPQL update or delete statement. In the case of an + * update or delete, the returned {@link Query} must be executed using + * {@link Query#executeUpdate()} which returns an affected row count. * - * @param entity a detached persistent instance + * @param queryString The HQL/JPQL query, update or delete statement * - * @return the persistent instance or proxy + * @return The {@link Query} instance for manipulation and execution + * + * @deprecated See explanation in + * {@link org.hibernate.query.QueryProducer#createSelectionQuery(String)} + * + * @see jakarta.persistence.EntityManager#createQuery(String) */ - T getReference(T entity); + @Deprecated + Query createQuery(String queryString); /** - * Asynchronously persist the given transient instance, first assigning - * a generated identifier. (Or using the current value of the identifier - * property if the entity has assigned identifiers.) - *

    - * This operation cascades to associated instances if the association is - * mapped with {@link jakarta.persistence.CascadeType#PERSIST}. + * Create an instance of {@link SelectionQuery} for the given HQL/JPQL + * query string and query result type. * - *

    -		 * {@code session.persist(newBook).thenAccept(v -> session.flush());}
    -		 * 
    + * @param queryString The HQL/JPQL query + * @param resultType the Java type returned in each row of query results * - * @param entity a transient instance of a persistent class + * @return The {@link Query} instance for manipulation and execution * - * @see jakarta.persistence.EntityManager#persist(Object) + * @see jakarta.persistence.EntityManager#createQuery(String, Class) */ - CompletionStage persist(Object entity); + SelectionQuery createQuery(String queryString, Class resultType); /** - * Make a transient instance persistent and mark it for later insertion in the - * database. This operation cascades to associated instances if the association - * is mapped with {@link jakarta.persistence.CascadeType#PERSIST}. - *

    - * For entities with a {@link jakarta.persistence.GeneratedValue generated id}, - * {@code persist()} ultimately results in generation of an identifier for the - * given instance. But this may happen asynchronously, when the session is - * {@linkplain #flush() flushed}, depending on the identifier generation strategy. + * Create an instance of {@link MutationQuery} for the given criteria + * update. * - * @param entityName the entity name - * @param object a transient instance to be made persistent - * @see #persist(Object) - */ - CompletionStage persist(String entityName, Object object); - - /** - * Persist multiple transient entity instances at once. + * @param criteriaUpdate The {@link CriteriaUpdate} * - * @see #persist(Object) + * @return The {@link MutationQuery} instance for manipulation and execution */ - CompletionStage persist(Object... entities); + MutationQuery createQuery(CriteriaUpdate criteriaUpdate); /** - * Asynchronously remove a persistent instance from the datastore. The - * argument may be an instance associated with the receiving session or - * a transient instance with an identifier associated with existing - * persistent state. - *

    - * This operation cascades to associated instances if the association is - * mapped with {@link jakarta.persistence.CascadeType#REMOVE}. - * - *

    -		 * {@code session.delete(book).thenAccept(v -> session.flush());}
    -		 * 
    - * - * @param entity the managed persistent instance to be removed + * Create an instance of {@link MutationQuery} for the given criteria + * delete. * - * @throws IllegalArgumentException if the given instance is not managed + * @param criteriaDelete The {@link CriteriaDelete} * - * @see jakarta.persistence.EntityManager#remove(Object) + * @return The {@link MutationQuery} instance for manipulation and execution */ - CompletionStage remove(Object entity); + MutationQuery createQuery(CriteriaDelete criteriaDelete); /** - * Remove multiple entity instances at once. + * Create an instance of {@link Query} for the named query. * - * @see #remove(Object) + * @param queryName The name of the query + * + * @return The {@link Query} instance for manipulation and execution + * + * @see jakarta.persistence.EntityManager#createQuery(String) */ - CompletionStage remove(Object... entities); + Query createNamedQuery(String queryName); /** - * Copy the state of the given object onto the persistent instance with - * the same identifier. If there is no such persistent instance currently - * associated with the session, it will be loaded. Return the persistent - * instance. Or, if the given instance is transient, save a copy of it - * and return the copy as a newly persistent instance. The given instance - * does not become associated with the session. - *

    - * This operation cascades to associated instances if the association is - * mapped with {@link jakarta.persistence.CascadeType#MERGE}. + * Create an instance of {@link SelectionQuery} for the named query. * - * @param entity a detached instance with state to be copied + * @param queryName The name of the query + * @param resultType the Java type returned in each row of query results * - * @return an updated persistent instance + * @return The {@link SelectionQuery} instance for manipulation and execution * - * @see jakarta.persistence.EntityManager#merge(Object) + * @see jakarta.persistence.EntityManager#createQuery(String, Class) */ - CompletionStage merge(T entity); + SelectionQuery createNamedQuery(String queryName, Class resultType); /** - * Merge multiple entity instances at once. + * Create an instance of {@link Query} for the given SQL query string, + * using the given {@code resultType} to interpret the results. * - * @see #merge(Object) + *

      + *
    • If the given result type is {@link Object}, or a built-in type + * such as {@link String} or {@link Integer}, the result set must + * have a single column, which will be returned as a scalar.
    • + *
    • If the given result type is {@code Object[]}, then the result set + * must have multiple columns, which will be returned as elements of + * arrays of type {@code Object[]}.
    • + *
    • Otherwise, the given result type must be an entity class, in which + * case the result set column aliases must map to the fields of the + * entity, and the query will return instances of the entity.
    • + *
    + * + * @param queryString The SQL query + * @param resultType the Java type returned in each row of query results + * + * @return The {@link SelectionQuery} instance for manipulation and execution + * + * @see jakarta.persistence.EntityManager#createNativeQuery(String, Class) */ - CompletionStage merge(Object... entities); + SelectionQuery createNativeQuery(String queryString, Class resultType); /** - * Re-read the state of the given instance from the underlying database. - * It is inadvisable to use this to implement long-running sessions that - * span many business tasks. This method is, however, useful in certain - * special circumstances, for example: + * Create an instance of {@link SelectionQuery} for the given SQL query + * string, using the given {@code resultType} to interpret the results. * *
      - *
    • where a database trigger alters the object state after insert or - * update, or - *
    • after executing direct native SQL in the same session. + *
    • If the given result type is {@link Object}, or a built-in type + * such as {@link String} or {@link Integer}, the result set must + * have a single column, which will be returned as a scalar.
    • + *
    • If the given result type is {@code Object[]}, then the result set + * must have multiple columns, which will be returned as elements of + * arrays of type {@code Object[]}.
    • + *
    • Otherwise, the given result type must be an entity class, in which + * case the result set column aliases must map to the fields of the + * entity, and the query will return instances of the entity.
    • *
    * - * @param entity a managed persistent instance + * Any {@link AffectedEntities affected entities} are synchronized with + * the database before execution of the query. * - * @throws IllegalArgumentException if the given instance is not managed + * @param queryString The SQL query + * @param resultType the Java type returned in each row of query results + * @param affectedEntities The entities which are affected by the query * - * @see jakarta.persistence.EntityManager#refresh(Object) + * @return The {@link Query} instance for manipulation and execution + * + * @see jakarta.persistence.EntityManager#createNativeQuery(String, Class) */ - CompletionStage refresh(Object entity); + SelectionQuery createNativeQuery(String queryString, Class resultType, AffectedEntities affectedEntities); /** - * Re-read the state of the given instance from the underlying database, - * requesting the given {@link LockMode}. + * Create an instance of {@link SelectionQuery} for the given SQL query + * string, using the given {@link ResultSetMapping} to interpret the + * result set. * - * @param entity a managed persistent entity instance - * @param lockMode the requested lock mode + * @param queryString The SQL query + * @param resultSetMapping the result set mapping * - * @see #refresh(Object) + * @return The {@link Query} instance for manipulation and execution + * + * @see #getResultSetMapping(Class, String) + * @see jakarta.persistence.EntityManager#createNativeQuery(String, String) */ - CompletionStage refresh(Object entity, LockMode lockMode); + SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping); /** - * Re-read the state of the given instance from the underlying database, - * requesting the given {@link LockModeType}. + * Create an instance of {@link SelectionQuery} for the given SQL query + * string, using the given {@link ResultSetMapping} to interpret the + * result set. + *

    + * Any {@link AffectedEntities affected entities} are synchronized with + * the database before execution of the query. * - * @param entity a managed persistent entity instance - * @param lockModeType the requested lock mode + * @param queryString The SQL query + * @param resultSetMapping the result set mapping + * @param affectedEntities The entities which are affected by the query * - * @see #refresh(Object) + * @return The {@link Query} instance for manipulation and execution + * + * @see #getResultSetMapping(Class, String) + * @see jakarta.persistence.EntityManager#createNativeQuery(String, String) */ - default CompletionStage refresh(Object entity, LockModeType lockModeType) { - return refresh( entity, convertToLockMode(lockModeType) ); - } - -// /** -// * Re-read the state of the given instance from the underlying database, -// * requesting the given {@link LockOptions}. -// * -// * @param entity a managed persistent entity instance -// * @param lockOptions the requested {@link LockOptions} -// * -// * @see #refresh(Object) -// */ -// CompletionStage refresh(Object entity, LockOptions lockOptions); + SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping, AffectedEntities affectedEntities); /** - * Refresh multiple entity instances at once. + * Create an instance of {@link Query} for the given SQL query string, + * or SQL update, insert, or delete statement. In the case of an update, + * insert, or delete, the returned {@link Query} must be executed using + * {@link Query#executeUpdate()} which returns an affected row count. + * In the case of a query: * - * @see #refresh(Object) + *

      + *
    • If the result set has a single column, the results will be returned + * as scalars.
    • + *
    • Otherwise, if the result set has multiple columns, the results will + * be returned as elements of arrays of type {@code Object[]}.
    • + *
    + * + * @param queryString The SQL select, update, insert, or delete statement */ - CompletionStage refresh(Object... entities); + Query createNativeQuery(String queryString); /** - * Obtain the specified lock level upon the given object. For example, - * this operation may be used to: + * Create an instance of {@link Query} for the given SQL query string, + * or SQL update, insert, or delete statement. In the case of an update, + * insert, or delete, the returned {@link Query} must be executed using + * {@link Query#executeUpdate()} which returns an affected row count. + * In the case of a query: * *
      - *
    • perform a version check with {@link LockMode#PESSIMISTIC_READ}, - *
    • upgrade to a pessimistic lock with {@link LockMode#PESSIMISTIC_WRITE}, - *
    • force a version increment with {@link LockMode#PESSIMISTIC_FORCE_INCREMENT}, - *
    • schedule a version check just before the end of the transaction with - * {@link LockMode#OPTIMISTIC}, or - *
    • schedule a version increment just before the end of the transaction - * with {@link LockMode#OPTIMISTIC_FORCE_INCREMENT}. + *
    • If the result set has a single column, the results will be returned + * as scalars.
    • + *
    • Otherwise, if the result set has multiple columns, the results will + * be returned as elements of arrays of type {@code Object[]}.
    • *
    * - * This operation cascades to associated instances if the association is - * mapped with {@link org.hibernate.annotations.CascadeType#LOCK}. - * - * @param entity a managed persistent instance - * @param lockMode the lock level + * Any {@link AffectedEntities affected entities} are synchronized with + * the database before execution of the statement. * - * @throws IllegalArgumentException if the given instance is not managed + * @param queryString The SQL select, update, insert, or delete statement + * @param affectedEntities The entities which are affected by the statement */ - CompletionStage lock(Object entity, LockMode lockMode); + Query createNativeQuery(String queryString, AffectedEntities affectedEntities); /** - * Obtain the specified lock level upon the given object. For example, - * this operation may be used to: - * - *
      - *
    • perform a version check with {@link LockModeType#PESSIMISTIC_READ}, - *
    • upgrade to a pessimistic lock with {@link LockModeType#PESSIMISTIC_WRITE}, - *
    • force a version increment with {@link LockModeType#PESSIMISTIC_FORCE_INCREMENT}, - *
    • schedule a version check just before the end of the transaction with - * {@link LockModeType#OPTIMISTIC}, or - *
    • schedule a version increment just before the end of the transaction - * with {@link LockModeType#OPTIMISTIC_FORCE_INCREMENT}. - *
    + * Create an instance of {@link SelectionQuery} for the given criteria + * query. * - * This operation cascades to associated instances if the association is - * mapped with {@link org.hibernate.annotations.CascadeType#LOCK}. + * @param criteriaQuery The {@link CriteriaQuery} * - * @param entity a managed persistent instance - * @param lockModeType the lock level + * @return The {@link SelectionQuery} instance for manipulation and execution * - * @throws IllegalArgumentException if the given instance is not managed + * @see jakarta.persistence.EntityManager#createQuery(String) */ - default CompletionStage lock(Object entity, LockModeType lockModeType) { - return lock( entity, convertToLockMode(lockModeType) ); - } + SelectionQuery createQuery(CriteriaQuery criteriaQuery); -// /** -// * Obtain the specified lock level upon the given object, with the given -// * {@link LockOptions}. -// * -// * @param entity a managed persistent instance -// * @param lockOptions the requested {@link LockOptions} -// * -// * @throws IllegalArgumentException if the given instance is not managed -// */ -// CompletionStage lock(Object entity, LockOptions lockOptions); + /** + * Obtain a native SQL result set mapping defined via the annotation + * {@link jakarta.persistence.SqlResultSetMapping}. + */ + ResultSetMapping getResultSetMapping(Class resultType, String mappingName); /** - * Force this session to flush asynchronously. Must be called at the - * end of a unit of work, before committing the transaction and closing - * the session. Flushing is the process of synchronizing the - * underlying persistent store with state held in memory. - * - *
    -		 * {@code session.flush().thenAccept(v -> print("done saving changes"));}
    -		 * 
    + * Obtain a named {@link EntityGraph} + */ + EntityGraph getEntityGraph(Class rootType, String graphName); + + /** + * Create a new mutable {@link EntityGraph} + */ + EntityGraph createEntityGraph(Class rootType); + + /** + * Create a new mutable copy of a named {@link EntityGraph} + */ + EntityGraph createEntityGraph(Class rootType, String graphName); + + /** + * Convenience method to obtain the {@link CriteriaBuilder}. * - * @see jakarta.persistence.EntityManager#flush() + * @since 3 */ - CompletionStage flush(); + CriteriaBuilder getCriteriaBuilder(); + } + + /** + * A non-blocking counterpart to the Hibernate {@link org.hibernate.Session} + * interface, allowing a reactive style of interaction with the database. + *

    + * The semantics of operations on this interface are identical to the + * semantics of the similarly-named operations of {@code Session}, except + * that the operations are performed asynchronously, returning a + * {@link CompletionStage} without blocking the calling thread. + *

    + * Entities associated with an {@code Session} do not support transparent + * lazy association fetching. Instead, {@link #fetch(Object)} should be used + * to explicitly request asynchronous fetching of an association, or the + * association should be fetched eagerly when the entity is first retrieved, + * for example, by: + * + *

      + *
    • {@link #enableFetchProfile(String) enabling a fetch profile}, + *
    • using an {@link EntityGraph}, or + *
    • writing a {@code join fetch} clause in a HQL query. + *
    + * + * @see org.hibernate.Session + */ + interface Session extends QueryProducer, Closeable { /** - * Asynchronously fetch an association that's configured for lazy loading. - *

    - *

    -		 * {@code session.fetch(author.getBook()).thenAccept(book -> print(book.getTitle()))}
    -		 * 
    - *

    - *

    - * It can also initialize proxys. For example: + * Asynchronously return the persistent instance of the given entity + * class with the given identifier, or {@code null} if there is no such + * persistent instance. If the instance is already associated with + * the session, return the associated instance. This method never + * returns an uninitialized instance. + * *

    -		 * {@code session.fetch(session.getReference(Author.class, authorId))}
    +		 * {@code session.find(Book.class, id).thenAccept(book -> print(book.getTitle()));}
     		 * 
    - *

    * - * @param association a lazy-loaded association, or a proxy + * @param entityClass The entity type + * @param id an identifier * - * @return the fetched association, via a {@code CompletionStage} + * @return a persistent instance or null via a {@code CompletionStage} * - * @see Stage#fetch(Object) - * @see #getReference(Class, Object) - * @see org.hibernate.Hibernate#initialize(Object) + * @see jakarta.persistence.EntityManager#find(Class, Object) */ - CompletionStage fetch(T association); + CompletionStage find(Class entityClass, Object id); /** - * Fetch a lazy property of the given entity, identified by a JPA - * {@link Attribute attribute metamodel}. Note that this feature is - * only supported in conjunction with the Hibernate bytecode enhancer. + * Asynchronously return the persistent instance of the given entity + * class with the given identifier, requesting the given {@link LockMode}. * - *
    -		 * {@code session.fetch(book, Book_.isbn).thenAccept(isbn -> print(isbn))}
    -		 * 
    + * @param entityClass The entity type + * @param id an identifier + * @param lockMode the requested {@link LockMode} + * + * @return a persistent instance or null via a {@code CompletionStage} + * + * @see #find(Class,Object) + * @see #lock(Object, LockMode) this discussion of lock modes */ - CompletionStage fetch(E entity, Attribute field); + CompletionStage find(Class entityClass, Object id, LockMode lockMode); /** - * Asynchronously fetch an association that's configured for lazy loading, - * and unwrap the underlying entity implementation from any proxy. + * Asynchronously return the persistent instance of the given entity + * class with the given identifier, requesting the given {@link LockModeType}. * - *
    -		 * {@code session.unproxy(author.getBook()).thenAccept(book -> print(book.getTitle()));}
    -		 * 
    + * @param entityClass The entity type + * @param id an identifier + * @param lockModeType the requested {@link LockModeType} * - * @param association a lazy-loaded association + * @return a persistent instance or null via a {@code CompletionStage} * - * @return the fetched association, via a {@code CompletionStage} + * @see #find(Class,Object) + * @see #lock(Object, LockMode) this discussion of lock modes + */ + default CompletionStage find(Class entityClass, Object id, LockModeType lockModeType) { + return find( entityClass, id, convertToLockMode(lockModeType) ); + } + + /** + * Asynchronously return the persistent instance with the given + * identifier of an entity class, using the given {@link EntityGraph} + * as a fetch plan. * - * @see org.hibernate.Hibernate#unproxy(Object) + * @param entityGraph an {@link EntityGraph} specifying the entity + * and associations to be fetched + * @param id an identifier + * + * @see #find(Class,Object) */ - CompletionStage unproxy(T association); + CompletionStage find(EntityGraph entityGraph, Object id); /** - * Determine the current lock mode of the given entity. + * Asynchronously return the persistent instances of the given entity + * class with the given identifiers, or null if there is no such + * persistent instance. + * + * @param entityClass The entity type + * @param ids the identifiers + * + * @return a list of persistent instances and nulls via a {@code CompletionStage} */ - LockMode getLockMode(Object entity); + CompletionStage> find(Class entityClass, Object... ids); /** - * Determine if the given instance belongs to this persistence context. + * Asynchronously return the persistent instance of the given entity + * class with the given natural identifiers, or null if there is no + * such persistent instance. + * + * @param entityClass The entity type + * @param naturalId the natural identifier + * + * @return a persistent instance or null via a {@code CompletionStage} */ - boolean contains(Object entity); + @Incubating + CompletionStage find(Class entityClass, Identifier naturalId); /** - * Create an instance of {@link SelectionQuery} for the given HQL/JPQL - * query string. + * Return the persistent instance of the given entity class with the + * given identifier, assuming that the instance exists. This method + * never results in access to the underlying data store, and thus + * might return a proxy that must be initialized explicitly using + * {@link #fetch(Object)}. + *

    + * You should not use this method to determine if an instance exists + * (use {@link #find} instead). Use this only to retrieve an instance + * which you safely assume exists, where non-existence would be an + * actual error. * - * @param queryString The HQL/JPQL query + * @param entityClass a persistent class + * @param id a valid identifier of an existing persistent instance of the class * - * @return The {@link SelectionQuery} instance for manipulation and execution + * @return the persistent instance or proxy * - * @see jakarta.persistence.EntityManager#createQuery(String, Class) + * @see jakarta.persistence.EntityManager#getReference(Class, Object) */ - SelectionQuery createSelectionQuery(String queryString, Class resultType); + T getReference(Class entityClass, Object id); /** - * Create an instance of {@link MutationQuery} for the given HQL/JPQL - * update or delete statement. - * - * @param queryString The HQL/JPQL query, update or delete statement + * Return the persistent instance with the same identity as the given + * instance, which might be detached, assuming that the instance is + * still persistent in the database. This method never results in + * access to the underlying data store, and thus might return a proxy + * that must be initialized explicitly using {@link #fetch(Object)}. * - * @return The {@link MutationQuery} instance for manipulation and execution + * @param entity a detached persistent instance * - * @see jakarta.persistence.EntityManager#createQuery(String) + * @return the persistent instance or proxy */ - MutationQuery createMutationQuery(String queryString); + T getReference(T entity); /** - * Create an instance of {@link MutationQuery} for the given update tree. + * Asynchronously persist the given transient instance, first assigning + * a generated identifier. (Or using the current value of the identifier + * property if the entity has assigned identifiers.) + *

    + * This operation cascades to associated instances if the association is + * mapped with {@link jakarta.persistence.CascadeType#PERSIST}. * - * @param updateQuery the update criteria query + *

    +		 * {@code session.persist(newBook).thenAccept(v -> session.flush());}
    +		 * 
    * - * @return The {@link MutationQuery} instance for manipulation and execution + * @param entity a transient instance of a persistent class * - * @see org.hibernate.query.QueryProducer#createMutationQuery(CriteriaUpdate) + * @see jakarta.persistence.EntityManager#persist(Object) */ - MutationQuery createMutationQuery(CriteriaUpdate updateQuery); + CompletionStage persist(Object entity); /** - * Create an instance of {@link MutationQuery} for the given delete tree. + * Make a transient instance persistent and mark it for later insertion in the + * database. This operation cascades to associated instances if the association + * is mapped with {@link jakarta.persistence.CascadeType#PERSIST}. + *

    + * For entities with a {@link jakarta.persistence.GeneratedValue generated id}, + * {@code persist()} ultimately results in generation of an identifier for the + * given instance. But this may happen asynchronously, when the session is + * {@linkplain #flush() flushed}, depending on the identifier generation strategy. * - * @param deleteQuery the delete criteria query + * @param entityName the entity name + * @param object a transient instance to be made persistent + * @see #persist(Object) + */ + CompletionStage persist(String entityName, Object object); + + /** + * Persist multiple transient entity instances at once. * - * @return The {@link MutationQuery} instance for manipulation and execution + * @see #persist(Object) + */ + CompletionStage persist(Object... entities); + + /** + * Asynchronously remove a persistent instance from the datastore. The + * argument may be an instance associated with the receiving session or + * a transient instance with an identifier associated with existing + * persistent state. + *

    + * This operation cascades to associated instances if the association is + * mapped with {@link jakarta.persistence.CascadeType#REMOVE}. * - * @see org.hibernate.query.QueryProducer#createMutationQuery(CriteriaDelete) + *

    +		 * {@code session.delete(book).thenAccept(v -> session.flush());}
    +		 * 
    + * + * @param entity the managed persistent instance to be removed + * + * @throws IllegalArgumentException if the given instance is not managed + * + * @see jakarta.persistence.EntityManager#remove(Object) */ - MutationQuery createMutationQuery(CriteriaDelete deleteQuery); + CompletionStage remove(Object entity); /** - * Create a {@link MutationQuery} from the given insert select criteria tree + * Remove multiple entity instances at once. + * + * @see #remove(Object) + */ + CompletionStage remove(Object... entities); + + /** + * Copy the state of the given object onto the persistent instance with + * the same identifier. If there is no such persistent instance currently + * associated with the session, it will be loaded. Return the persistent + * instance. Or, if the given instance is transient, save a copy of it + * and return the copy as a newly persistent instance. The given instance + * does not become associated with the session. + *

    + * This operation cascades to associated instances if the association is + * mapped with {@link jakarta.persistence.CascadeType#MERGE}. * - * @param insert the insert select criteria query + * @param entity a detached instance with state to be copied * - * @return The {@link MutationQuery} instance for manipulation and execution + * @return an updated persistent instance * - * @see org.hibernate.query.QueryProducer#createMutationQuery(JpaCriteriaInsert) + * @see jakarta.persistence.EntityManager#merge(Object) */ - MutationQuery createMutationQuery(JpaCriteriaInsert insert); + CompletionStage merge(T entity); /** - * Create an instance of {@link Query} for the given HQL/JPQL query - * string or HQL/JPQL update or delete statement. In the case of an - * update or delete, the returned {@link Query} must be executed using - * {@link Query#executeUpdate()} which returns an affected row count. - * - * @param queryString The HQL/JPQL query, update or delete statement - * - * @return The {@link Query} instance for manipulation and execution - * - * @deprecated See explanation in - * {@link org.hibernate.query.QueryProducer#createSelectionQuery(String)} + * Merge multiple entity instances at once. * - * @see jakarta.persistence.EntityManager#createQuery(String) + * @see #merge(Object) */ - @Deprecated - Query createQuery(String queryString); + CompletionStage merge(Object... entities); /** - * Create an instance of {@link SelectionQuery} for the given HQL/JPQL - * query string and query result type. - * - * @param queryString The HQL/JPQL query - * @param resultType the Java type returned in each row of query results + * Re-read the state of the given instance from the underlying database. + * It is inadvisable to use this to implement long-running sessions that + * span many business tasks. This method is, however, useful in certain + * special circumstances, for example: * - * @return The {@link Query} instance for manipulation and execution + *

      + *
    • where a database trigger alters the object state after insert or + * update, or + *
    • after executing direct native SQL in the same session. + *
    * - * @see jakarta.persistence.EntityManager#createQuery(String, Class) - */ - SelectionQuery createQuery(String queryString, Class resultType); - - /** - * Create an instance of {@link MutationQuery} for the given criteria - * update. + * @param entity a managed persistent instance * - * @param criteriaUpdate The {@link CriteriaUpdate} + * @throws IllegalArgumentException if the given instance is not managed * - * @return The {@link MutationQuery} instance for manipulation and execution + * @see jakarta.persistence.EntityManager#refresh(Object) */ - MutationQuery createQuery(CriteriaUpdate criteriaUpdate); + CompletionStage refresh(Object entity); /** - * Create an instance of {@link MutationQuery} for the given criteria - * delete. + * Re-read the state of the given instance from the underlying database, + * requesting the given {@link LockMode}. * - * @param criteriaDelete The {@link CriteriaDelete} + * @param entity a managed persistent entity instance + * @param lockMode the requested lock mode * - * @return The {@link MutationQuery} instance for manipulation and execution + * @see #refresh(Object) */ - MutationQuery createQuery(CriteriaDelete criteriaDelete); + CompletionStage refresh(Object entity, LockMode lockMode); /** - * Create an instance of {@link Query} for the named query. - * - * @param queryName The name of the query + * Re-read the state of the given instance from the underlying database, + * requesting the given {@link LockModeType}. * - * @return The {@link Query} instance for manipulation and execution + * @param entity a managed persistent entity instance + * @param lockModeType the requested lock mode * - * @see jakarta.persistence.EntityManager#createQuery(String) + * @see #refresh(Object) */ - Query createNamedQuery(String queryName); + default CompletionStage refresh(Object entity, LockModeType lockModeType) { + return refresh( entity, convertToLockMode(lockModeType) ); + } /** - * Create an instance of {@link SelectionQuery} for the named query. - * - * @param queryName The name of the query - * @param resultType the Java type returned in each row of query results - * - * @return The {@link SelectionQuery} instance for manipulation and execution + * Refresh multiple entity instances at once. * - * @see jakarta.persistence.EntityManager#createQuery(String, Class) + * @see #refresh(Object) */ - SelectionQuery createNamedQuery(String queryName, Class resultType); + CompletionStage refresh(Object... entities); /** - * Create an instance of {@link Query} for the given SQL query string, - * using the given {@code resultType} to interpret the results. + * Obtain the specified lock level upon the given object. For example, + * this operation may be used to: * *
      - *
    • If the given result type is {@link Object}, or a built-in type - * such as {@link String} or {@link Integer}, the result set must - * have a single column, which will be returned as a scalar.
    • - *
    • If the given result type is {@code Object[]}, then the result set - * must have multiple columns, which will be returned as elements of - * arrays of type {@code Object[]}.
    • - *
    • Otherwise, the given result type must be an entity class, in which - * case the result set column aliases must map to the fields of the - * entity, and the query will return instances of the entity.
    • + *
    • perform a version check with {@link LockMode#PESSIMISTIC_READ}, + *
    • upgrade to a pessimistic lock with {@link LockMode#PESSIMISTIC_WRITE}, + *
    • force a version increment with {@link LockMode#PESSIMISTIC_FORCE_INCREMENT}, + *
    • schedule a version check just before the end of the transaction with + * {@link LockMode#OPTIMISTIC}, or + *
    • schedule a version increment just before the end of the transaction + * with {@link LockMode#OPTIMISTIC_FORCE_INCREMENT}. *
    * - * @param queryString The SQL query - * @param resultType the Java type returned in each row of query results + * This operation cascades to associated instances if the association is + * mapped with {@link org.hibernate.annotations.CascadeType#LOCK}. * - * @return The {@link SelectionQuery} instance for manipulation and execution + * @param entity a managed persistent instance + * @param lockMode the lock level * - * @see jakarta.persistence.EntityManager#createNativeQuery(String, Class) + * @throws IllegalArgumentException if the given instance is not managed */ - SelectionQuery createNativeQuery(String queryString, Class resultType); + CompletionStage lock(Object entity, LockMode lockMode); /** - * Create an instance of {@link SelectionQuery} for the given SQL query - * string, using the given {@code resultType} to interpret the results. + * Obtain the specified lock level upon the given object. For example, + * this operation may be used to: * *
      - *
    • If the given result type is {@link Object}, or a built-in type - * such as {@link String} or {@link Integer}, the result set must - * have a single column, which will be returned as a scalar.
    • - *
    • If the given result type is {@code Object[]}, then the result set - * must have multiple columns, which will be returned as elements of - * arrays of type {@code Object[]}.
    • - *
    • Otherwise, the given result type must be an entity class, in which - * case the result set column aliases must map to the fields of the - * entity, and the query will return instances of the entity.
    • + *
    • perform a version check with {@link LockModeType#PESSIMISTIC_READ}, + *
    • upgrade to a pessimistic lock with {@link LockModeType#PESSIMISTIC_WRITE}, + *
    • force a version increment with {@link LockModeType#PESSIMISTIC_FORCE_INCREMENT}, + *
    • schedule a version check just before the end of the transaction with + * {@link LockModeType#OPTIMISTIC}, or + *
    • schedule a version increment just before the end of the transaction + * with {@link LockModeType#OPTIMISTIC_FORCE_INCREMENT}. *
    * - * Any {@link AffectedEntities affected entities} are synchronized with - * the database before execution of the query. - * - * @param queryString The SQL query - * @param resultType the Java type returned in each row of query results - * @param affectedEntities The entities which are affected by the query + * This operation cascades to associated instances if the association is + * mapped with {@link org.hibernate.annotations.CascadeType#LOCK}. * - * @return The {@link Query} instance for manipulation and execution + * @param entity a managed persistent instance + * @param lockModeType the lock level * - * @see jakarta.persistence.EntityManager#createNativeQuery(String, Class) + * @throws IllegalArgumentException if the given instance is not managed */ - SelectionQuery createNativeQuery(String queryString, Class resultType, AffectedEntities affectedEntities); + default CompletionStage lock(Object entity, LockModeType lockModeType) { + return lock( entity, convertToLockMode(lockModeType) ); + } /** - * Create an instance of {@link SelectionQuery} for the given SQL query - * string, using the given {@link ResultSetMapping} to interpret the - * result set. - * - * @param queryString The SQL query - * @param resultSetMapping the result set mapping + * Force this session to flush asynchronously. Must be called at the + * end of a unit of work, before committing the transaction and closing + * the session. Flushing is the process of synchronizing the + * underlying persistent store with state held in memory. * - * @return The {@link Query} instance for manipulation and execution + *
    +		 * {@code session.flush().thenAccept(v -> print("done saving changes"));}
    +		 * 
    * - * @see #getResultSetMapping(Class, String) - * @see jakarta.persistence.EntityManager#createNativeQuery(String, String) + * @see jakarta.persistence.EntityManager#flush() */ - SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping); + CompletionStage flush(); /** - * Create an instance of {@link SelectionQuery} for the given SQL query - * string, using the given {@link ResultSetMapping} to interpret the - * result set. + * Asynchronously fetch an association configured for lazy loading. *

    - * Any {@link AffectedEntities affected entities} are synchronized with - * the database before execution of the query. + *

    +		 * {@code session.fetch(author.getBook()).thenAccept(book -> print(book.getTitle()))}
    +		 * 
    + *

    + *

    + * This operation may be even be used to initialize a reference returned by + * {@link #getReference(Class, Object)}. + *

    + *

    +		 * {@code session.fetch(session.getReference(Author.class, authorId))}
    +		 * 
    + *

    * - * @param queryString The SQL query - * @param resultSetMapping the result set mapping - * @param affectedEntities The entities which are affected by the query + * @param association a lazy-loaded association, or a proxy * - * @return The {@link Query} instance for manipulation and execution + * @return the fetched association, via a {@code CompletionStage} * - * @see #getResultSetMapping(Class, String) - * @see jakarta.persistence.EntityManager#createNativeQuery(String, String) + * @see Stage#fetch(Object) + * @see #getReference(Class, Object) + * @see org.hibernate.Hibernate#initialize(Object) */ - SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping, AffectedEntities affectedEntities); + CompletionStage fetch(T association); /** - * Create an instance of {@link Query} for the given SQL query string, - * or SQL update, insert, or delete statement. In the case of an update, - * insert, or delete, the returned {@link Query} must be executed using - * {@link Query#executeUpdate()} which returns an affected row count. - * In the case of a query: - * - *
      - *
    • If the result set has a single column, the results will be returned - * as scalars.
    • - *
    • Otherwise, if the result set has multiple columns, the results will - * be returned as elements of arrays of type {@code Object[]}.
    • - *
    + * Fetch a lazy property of the given entity, identified by a JPA + * {@link Attribute attribute metamodel}. Note that this feature is + * only supported in conjunction with the Hibernate bytecode enhancer. * - * @param queryString The SQL select, update, insert, or delete statement + *
    +		 * {@code session.fetch(book, Book_.isbn).thenAccept(isbn -> print(isbn))}
    +		 * 
    */ - Query createNativeQuery(String queryString); + CompletionStage fetch(E entity, Attribute field); /** - * Create an instance of {@link Query} for the given SQL query string, - * or SQL update, insert, or delete statement. In the case of an update, - * insert, or delete, the returned {@link Query} must be executed using - * {@link Query#executeUpdate()} which returns an affected row count. - * In the case of a query: + * Asynchronously fetch an association that's configured for lazy loading, + * and unwrap the underlying entity implementation from any proxy. * - *
      - *
    • If the result set has a single column, the results will be returned - * as scalars.
    • - *
    • Otherwise, if the result set has multiple columns, the results will - * be returned as elements of arrays of type {@code Object[]}.
    • - *
    + *
    +		 * {@code session.unproxy(author.getBook()).thenAccept(book -> print(book.getTitle()));}
    +		 * 
    * - * Any {@link AffectedEntities affected entities} are synchronized with - * the database before execution of the statement. + * @param association a lazy-loaded association * - * @param queryString The SQL select, update, insert, or delete statement - * @param affectedEntities The entities which are affected by the statement + * @return the fetched association, via a {@code CompletionStage} + * + * @see org.hibernate.Hibernate#unproxy(Object) */ - Query createNativeQuery(String queryString, AffectedEntities affectedEntities); + CompletionStage unproxy(T association); + + /** + * Determine the current lock mode of the given entity. + */ + LockMode getLockMode(Object entity); /** - * Create an instance of {@link SelectionQuery} for the given criteria - * query. - * - * @param criteriaQuery The {@link CriteriaQuery} - * - * @return The {@link SelectionQuery} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String) + * Determine if the given instance belongs to this persistence context. */ - SelectionQuery createQuery(CriteriaQuery criteriaQuery); + boolean contains(Object entity); /** * Set the {@link FlushMode flush mode} for this session. @@ -1312,27 +1299,6 @@ default Session setFlushMode(FlushModeType flushModeType) { */ Session enableFetchProfile(String name); - /** - * Obtain a native SQL result set mapping defined via the annotation - * {@link jakarta.persistence.SqlResultSetMapping}. - */ - ResultSetMapping getResultSetMapping(Class resultType, String mappingName); - - /** - * Obtain a named {@link EntityGraph} - */ - EntityGraph getEntityGraph(Class rootType, String graphName); - - /** - * Create a new mutable {@link EntityGraph} - */ - EntityGraph createEntityGraph(Class rootType); - - /** - * Create a new mutable copy of a named {@link EntityGraph} - */ - EntityGraph createEntityGraph(Class rootType, String graphName); - /** * Disable a particular fetch profile on this session, or do nothing if * the requested fetch profile is not enabled. @@ -1589,7 +1555,7 @@ default Session setCacheRetrieveMode(CacheRetrieveMode cacheRetrieveMode) { * * @see org.hibernate.StatelessSession */ - interface StatelessSession extends Closeable { + interface StatelessSession extends QueryProducer, Closeable { /** * Retrieve a row. @@ -1654,178 +1620,6 @@ default CompletionStage get(Class entityClass, Object id, LockModeType */ CompletionStage get(EntityGraph entityGraph, Object id); - /** - * Create an instance of {@link Query} for the given HQL/JPQL query - * string or HQL/JPQL update or delete statement. In the case of an - * update or delete, the returned {@link Query} must be executed using - * {@link Query#executeUpdate()} which returns an affected row count. - * - * @param queryString The HQL/JPQL query, update or delete statement - * - * @return The {@link Query} instance for manipulation and execution - * - * @deprecated See explanation in - * {@link org.hibernate.query.QueryProducer#createSelectionQuery(String)} - * - * @see org.hibernate.Session#createQuery(String) - */ - @Deprecated - Query createQuery(String queryString); - - /** - * Create an instance of {@link SelectionQuery} for the given HQL/JPQL - * query string and query result type. - * - * @param queryString The HQL/JPQL query - * @param resultType the Java type returned in each row of query results - * - * @return The {@link Query} instance for manipulation and execution - * - * @see org.hibernate.Session#createQuery(String, Class) - */ - SelectionQuery createQuery(String queryString, Class resultType); - - /** - * Create an instance of {@link SelectionQuery} for the given HQL/JPQL - * query string. - * - * @param queryString The HQL/JPQL query - * - * @return The {@link SelectionQuery} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String, Class) - */ - SelectionQuery createSelectionQuery(String queryString, Class resultType); - - /** - * Create an instance of {@link MutationQuery} for the given HQL/JPQL - * update or delete statement. - * - * @param queryString The HQL/JPQL query, update or delete statement - * - * @return The {@link MutationQuery} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String) - */ - MutationQuery createMutationQuery(String queryString); - - /** - * Create an instance of {@link MutationQuery} for the given update tree. - * - * @param updateQuery the update criteria query - * - * @return The {@link MutationQuery} instance for manipulation and execution - * - * @see org.hibernate.query.QueryProducer#createMutationQuery(CriteriaUpdate) - */ - MutationQuery createMutationQuery(CriteriaUpdate updateQuery); - - /** - * Create an instance of {@link MutationQuery} for the given delete tree. - * - * @param deleteQuery the delete criteria query - * - * @return The {@link MutationQuery} instance for manipulation and execution - * - * @see org.hibernate.query.QueryProducer#createMutationQuery(CriteriaDelete) - */ - MutationQuery createMutationQuery(CriteriaDelete deleteQuery); - - /** - * Create a {@link MutationQuery} from the given insert select criteria tree - * - * @param insert the insert select criteria query - * - * @return The {@link MutationQuery} instance for manipulation and execution - * - * @see org.hibernate.query.QueryProducer#createMutationQuery(JpaCriteriaInsert) - */ - MutationQuery createMutationQuery(JpaCriteriaInsert insert); - - /** - * Create an instance of {@link Query} for the given SQL query string, - * or SQL update, insert, or delete statement. In the case of an update, - * insert, or delete, the returned {@link Query} must be executed using - * {@link Query#executeUpdate()} which returns an affected row count. - * - * @param queryString The SQL select, update, insert, or delete statement - * - * @see org.hibernate.Session#createNativeQuery(String) - */ - Query createNativeQuery(String queryString); - - /** - * Create an instance of {@link Query} for the named query. - * - * @param queryName The name of the query - * - * @return The {@link Query} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String) - */ - Query createNamedQuery(String queryName); - - /** - * Create an instance of {@link Query} for the named query. - * - * @param queryName The name of the query - * @param resultType the Java type returned in each row of query results - * - * @return The {@link Query} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String, Class) - */ - SelectionQuery createNamedQuery(String queryName, Class resultType); - - /** - * Create an instance of {@link Query} for the given SQL query string, - * using the given {@code resultType} to interpret the results. - * - * @param queryString The SQL query - * @param resultType the Java type returned in each row of query results - * - * @return The {@link Query} instance for manipulation and execution - * - * @see org.hibernate.Session#createNativeQuery(String, Class) - */ - SelectionQuery createNativeQuery(String queryString, Class resultType); - - /** - * Create an instance of {@link SelectionQuery} for the given criteria - * query. - * - * @param criteriaQuery The {@link CriteriaQuery} - * - * @return The {@link SelectionQuery} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String) - */ - SelectionQuery createQuery(CriteriaQuery criteriaQuery); - - /** - * Create an instance of {@link MutationQuery} for the given criteria - * update. - * - * @param criteriaUpdate The {@link CriteriaUpdate} - * - * @return The {@link MutationQuery} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String) - */ - MutationQuery createQuery(CriteriaUpdate criteriaUpdate); - - /** - * Create an instance of {@link MutationQuery} for the given criteria - * delete. - * - * @param criteriaDelete The {@link CriteriaDelete} - * - * @return The {@link MutationQuery} instance for manipulation and execution - * - * @see jakarta.persistence.EntityManager#createQuery(String) - */ - MutationQuery createQuery(CriteriaDelete criteriaDelete); - /** * Insert a row. * @@ -2012,15 +1806,6 @@ default CompletionStage refresh(Object entity, LockModeType lockModeType) */ CompletionStage upsert(Object entity); - /** - * - * @param entityName The entityName for the entity to be merged - * @param entity a detached entity instance - * - * @see org.hibernate.StatelessSession#upsert(String, Object) - */ - 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 @@ -2086,27 +1871,6 @@ default CompletionStage refresh(Object entity, LockModeType lockModeType) */ Object getIdentifier(Object entity); - /** - * Obtain a native SQL result set mapping defined via the annotation - * {@link jakarta.persistence.SqlResultSetMapping}. - */ - ResultSetMapping getResultSetMapping(Class resultType, String mappingName); - - /** - * Obtain a named {@link EntityGraph} - */ - EntityGraph getEntityGraph(Class rootType, String graphName); - - /** - * Create a new mutable {@link EntityGraph} - */ - EntityGraph createEntityGraph(Class rootType); - - /** - * Create a new mutable copy of a named {@link EntityGraph} - */ - EntityGraph createEntityGraph(Class rootType, String graphName); - /** * Performs the given work within the scope of a database transaction, * automatically flushing the session. The transaction will be rolled @@ -2155,6 +1919,15 @@ default CompletionStage refresh(Object entity, LockModeType lockModeType) * The {@link SessionFactory} which created this session. */ SessionFactory getFactory(); + + /** + * Convenience method to obtain the {@link CriteriaBuilder}. + * + * @since 3 + */ + default CriteriaBuilder getCriteriaBuilder() { + return getFactory().getCriteriaBuilder(); + } } /** @@ -2207,7 +1980,7 @@ interface Transaction { interface SessionFactory extends AutoCloseable { /** - * Obtain a new {@link Session reactive session} {@link CompletionStage}, the main + * Obtain a new {@linkplain Session reactive session} {@link CompletionStage}, the main * interaction point between the user's program and Hibernate * Reactive. *

    @@ -2221,7 +1994,7 @@ interface SessionFactory extends AutoCloseable { CompletionStage openSession(); /** - * Obtain a new {@link Session reactive session} {@link CompletionStage} for a + * Obtain a new {@linkplain Session reactive session} {@link CompletionStage} for a * specified tenant. *

    * When the {@link CompletionStage} completes successfully it returns a newly created session. @@ -2260,7 +2033,7 @@ interface SessionFactory extends AutoCloseable { CompletionStage openStatelessSession(String tenantId); /** - * Perform work using a {@link Session reactive session}. + * Perform work using a {@linkplain Session reactive session}. *

    * *

  • If there is already a session associated with the current @@ -2279,7 +2052,7 @@ interface SessionFactory extends AutoCloseable { CompletionStage withSession(Function> work); /** - * Perform work using a {@link Session reactive session} for a + * Perform work using a {@linkplain Session reactive session} for a * specified tenant. *

    * @@ -2299,7 +2072,7 @@ interface SessionFactory extends AutoCloseable { CompletionStage withSession(String tenantId, Function> work); /** - * Perform work using a {@link Session reactive session} within an + * Perform work using a {@linkplain Session reactive session} within an * associated {@link Transaction transaction}. *

    * @@ -2323,7 +2096,7 @@ interface SessionFactory extends AutoCloseable { CompletionStage withTransaction(BiFunction> work); /** - * Perform work using a {@link Session reactive session} within an + * Perform work using a {@linkplain Session reactive session} within an * associated transaction. *

    * @@ -2348,15 +2121,17 @@ default CompletionStage withTransaction(Function * - *

  • If there is already a session associated with the - * current reactive stream and the given tenant, then the work will be executed using that - * session. - *
  • Otherwise, if there is no stateless session associated with the - * current stream and the given tenant, a new stateless session will be created. + *
  • If there is already a session associated with the current + * reactive stream and given tenant id, then the work will be + * executed using that session. + *
  • Otherwise, if there is no stateless session associated with + * the current stream and given tenant id, a new stateless session + * will be created. * *

    * The session will be {@link Session#flush() flushed} and closed @@ -2372,8 +2147,8 @@ default CompletionStage withTransaction(Function CompletionStage withTransaction(String tenantId, BiFunction> work); /** - * Perform work using a {@link StatelessSession reactive session} within an - * associated {@link Transaction transaction}. + * Perform work using a {@linkplain StatelessSession reactive session} + * within an associated {@link Transaction transaction}. *

    * *

  • If there is already a stateless session associated with the @@ -2383,7 +2158,8 @@ default CompletionStage withTransaction(Function *

    - * The session will be closed automatically, and the transaction committed automatically. + * The session will be closed automatically, and the transaction committed + * automatically. * * @param work a function which accepts the stateless session and returns * the result of the work as a {@link CompletionStage}. @@ -2396,8 +2172,8 @@ default CompletionStage withStatelessTransaction(Function * *

  • If there is already a stateless session associated with the @@ -2407,7 +2183,8 @@ default CompletionStage withStatelessTransaction(Function *

    - * The session will be closed automatically, and the transaction committed automatically. + * The session will be closed automatically, and the transaction committed + * automatically. * * @param work a function which accepts the stateless session and returns * the result of the work as a {@link CompletionStage}. @@ -2418,18 +2195,21 @@ default CompletionStage withStatelessTransaction(Function CompletionStage withStatelessTransaction(BiFunction> work); /** - * Perform work using a {@link StatelessSession reactive session} within an - * associated {@link Transaction transaction}. + * Perform work using a {@linkplain StatelessSession reactive session} + * for the tenant with the specified tenant id within an associated + * {@link Transaction transaction}. *

    * *

  • If there is already a stateless session associated with the - * current reactive stream and the given tenant, then the work will be executed using that - * session. + * current reactive stream and given tenant id, then the work will be + * executed using that session. *
  • Otherwise, if there is no stateless session associated with the - * current stream, a new stateless session will be created. + * current stream and given tenant id, a new stateless session will be + * created. * *

    - * The session will be closed automatically, and the transaction committed automatically. + * The session will be closed automatically and the transaction committed + * automatically. * * @param tenantId the id of the tenant * @param work a function which accepts the stateless session and returns @@ -2441,7 +2221,7 @@ default CompletionStage withStatelessTransaction(Function CompletionStage withStatelessTransaction(String tenantId, BiFunction> work); /** - * Perform work using a {@link StatelessSession stateless session}. + * Perform work using a {@linkplain StatelessSession stateless session}. *

    * *

  • If there is already a stateless session associated with the @@ -2459,7 +2239,7 @@ default CompletionStage withStatelessTransaction(Function CompletionStage withStatelessSession(Function> work); /** - * Perform work using a {@link StatelessSession stateless session}. + * Perform work using a {@linkplain StatelessSession stateless session}. *

    * *

  • If there is already a stateless session associated with the @@ -2500,6 +2280,31 @@ default CompletionStage withStatelessTransaction(Function setLockMode(LockMode lockMode) { return this; } - @Override - public Stage.SelectionQuery setOrder(List> orders) { - delegate.setOrder( orders ); - return this; - } - - @Override - public Stage.SelectionQuery setOrder(Order order) { - delegate.setOrder( (List>) order ); - return this; - } - @Override public Query setPlan(EntityGraph entityGraph) { delegate.applyGraph( (RootGraphImplementor) entityGraph, GraphSemantic.FETCH ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java index eaf1c9a53..71beb5df3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java @@ -13,7 +13,6 @@ import org.hibernate.LockMode; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.spi.RootGraphImplementor; -import org.hibernate.query.Order; import org.hibernate.query.Page; import org.hibernate.reactive.query.ReactiveSelectionQuery; import org.hibernate.reactive.stage.Stage.SelectionQuery; @@ -194,18 +193,6 @@ public SelectionQuery setLockMode(String alias, LockMode lockMode) { return this; } - @Override - public SelectionQuery setOrder(List> orders) { - delegate.setOrder( orders ); - return this; - } - - @Override - public SelectionQuery setOrder(Order order) { - delegate.setOrder( order ); - return this; - } - @Override public SelectionQuery setParameter(String name, Object value) { delegate.setParameter( name, value ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java index 78d52e687..56c574ce4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java @@ -138,6 +138,16 @@ private CompletionStage connection(String tenantId) { : connectionPool.getConnection( tenantId ); } + @Override + public Stage.Session getCurrentSession() { + return context.get( contextKeyForSession ); + } + + @Override + public Stage.StatelessSession getCurrentStatelessSession() { + return context.get( contextKeyForStatelessSession ); + } + @Override public CompletionStage withSession(Function> work) { Objects.requireNonNull( work, "parameter 'work' is required" ); 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 150c2ec96..da09a284b 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,10 +5,6 @@ */ package org.hibernate.reactive.stage.impl; -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; @@ -23,9 +19,8 @@ import org.hibernate.reactive.common.ResultSetMapping; import org.hibernate.reactive.engine.ReactiveActionQueue; import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.pool.ReactiveConnection; +import org.hibernate.reactive.query.ReactiveQuery; import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.reactive.session.ReactiveQueryProducer; import org.hibernate.reactive.session.ReactiveSession; @@ -40,10 +35,15 @@ import jakarta.persistence.FlushModeType; import jakarta.persistence.LockModeType; import jakarta.persistence.PersistenceException; +import jakarta.persistence.TypedQueryReference; +import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaDelete; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.CriteriaUpdate; import jakarta.persistence.metamodel.Attribute; +import java.util.List; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; import static org.hibernate.reactive.util.impl.CompletionStages.applyToAll; import static org.hibernate.reactive.util.impl.CompletionStages.returnOrRethrow; @@ -56,8 +56,6 @@ */ public class StageSessionImpl implements Stage.Session { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - private final ReactiveSession delegate; public StageSessionImpl(ReactiveSession session) { @@ -249,38 +247,22 @@ public CompletionStage lock(Object entity, LockOptions lockOptions) { @Override public FlushMode getFlushMode() { - switch ( delegate.getHibernateFlushMode() ) { - case MANUAL: - return FlushMode.MANUAL; - case COMMIT: - return FlushMode.COMMIT; - case AUTO: - return FlushMode.AUTO; - case ALWAYS: - return FlushMode.ALWAYS; - default: - throw LOG.impossibleFlushModeIllegalState(); - } + return switch ( delegate.getHibernateFlushMode() ) { + case MANUAL -> FlushMode.MANUAL; + case COMMIT -> FlushMode.COMMIT; + case AUTO -> FlushMode.AUTO; + case ALWAYS -> FlushMode.ALWAYS; + }; } @Override public Stage.Session setFlushMode(FlushMode flushMode) { - switch ( flushMode ) { - case COMMIT: - delegate.setHibernateFlushMode( org.hibernate.FlushMode.COMMIT ); - break; - case AUTO: - delegate.setHibernateFlushMode( org.hibernate.FlushMode.AUTO ); - break; - case MANUAL: - delegate.setHibernateFlushMode( org.hibernate.FlushMode.MANUAL ); - break; - case ALWAYS: - delegate.setHibernateFlushMode( org.hibernate.FlushMode.ALWAYS ); - break; - default: - throw new IllegalArgumentException( "Unsupported flushMode: " + flushMode ); - } + delegate.setHibernateFlushMode( switch ( flushMode ) { + case COMMIT -> org.hibernate.FlushMode.COMMIT; + case AUTO -> org.hibernate.FlushMode.AUTO; + case MANUAL -> org.hibernate.FlushMode.MANUAL; + case ALWAYS -> org.hibernate.FlushMode.ALWAYS; + } ); return this; } @@ -372,17 +354,17 @@ public boolean isFetchProfileEnabled(String name) { @Override public Filter enableFilter(String filterName) { - return delegate.enableFilter(filterName); + return delegate.enableFilter( filterName ); } @Override public void disableFilter(String filterName) { - delegate.disableFilter(filterName); + delegate.disableFilter( filterName ); } @Override public Filter getEnabledFilter(String filterName) { - return delegate.getEnabledFilter(filterName); + return delegate.getEnabledFilter( filterName ); } @Override @@ -510,6 +492,11 @@ public Stage.SessionFactory getFactory() { return delegate.getFactory().unwrap( Stage.SessionFactory.class ); } + @Override + public CriteriaBuilder getCriteriaBuilder() { + return getFactory().getCriteriaBuilder(); + } + @Override public ResultSetMapping getResultSetMapping(Class resultType, String mappingName) { return delegate.getResultSetMapping( resultType, mappingName ); @@ -530,6 +517,12 @@ public EntityGraph createEntityGraph(Class rootType, String graphName) return delegate.createEntityGraph( rootType, graphName ); } + @Override + public Query createQuery(TypedQueryReference typedQueryReference) { + ReactiveQuery reactiveQuery = delegate.createReactiveQuery( typedQueryReference ); + return new StageQueryImpl<>( reactiveQuery ); + } + @Override @Deprecated public Query createQuery(String queryString) { return new StageQueryImpl<>( delegate.createReactiveQuery( queryString ) ); 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 c48a7eab8..07f7ef35a 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 @@ -8,8 +8,10 @@ import org.hibernate.LockMode; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.query.criteria.JpaCriteriaInsert; +import org.hibernate.reactive.common.AffectedEntities; import org.hibernate.reactive.common.ResultSetMapping; import org.hibernate.reactive.pool.ReactiveConnection; +import org.hibernate.reactive.query.ReactiveQuery; import org.hibernate.reactive.session.ReactiveStatelessSession; import org.hibernate.reactive.stage.Stage; import org.hibernate.reactive.stage.Stage.MutationQuery; @@ -17,6 +19,8 @@ import org.hibernate.reactive.stage.Stage.SelectionQuery; import jakarta.persistence.EntityGraph; +import jakarta.persistence.TypedQueryReference; +import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaDelete; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.CriteriaUpdate; @@ -150,11 +154,6 @@ public CompletionStage upsert(Object entity) { return delegate.reactiveUpsert( entity ); } - @Override - public CompletionStage upsert(String entityName, Object entity) { - return delegate.reactiveUpsert( entityName, entity ); - } - @Override public CompletionStage upsertAll(Object... entities) { return delegate.reactiveUpsertAll( entities.length, entities ); @@ -204,6 +203,11 @@ public Stage.SessionFactory getFactory() { return delegate.getFactory().unwrap( Stage.SessionFactory.class ); } + @Override + public CriteriaBuilder getCriteriaBuilder() { + return getFactory().getCriteriaBuilder(); + } + private Transaction currentTransaction; @Override @@ -289,6 +293,12 @@ public Query createQuery(String queryString) { return new StageQueryImpl<>( delegate.createReactiveQuery( queryString ) ); } + @Override + public Query createQuery(TypedQueryReference typedQueryReference) { + ReactiveQuery reactiveQuery = delegate.createReactiveQuery( typedQueryReference ); + return new StageQueryImpl<>( reactiveQuery ); + } + @Override public SelectionQuery createSelectionQuery(String queryString, Class resultType) { return new StageSelectionQueryImpl<>( delegate.createReactiveSelectionQuery( queryString, resultType ) ); @@ -324,6 +334,11 @@ public Query createNativeQuery(String queryString) { return new StageQueryImpl<>( delegate.createReactiveNativeQuery( queryString ) ); } + @Override + public Query createNativeQuery(String queryString, AffectedEntities affectedEntities) { + return new StageQueryImpl<>( delegate.createReactiveNativeQuery( queryString, affectedEntities ) ); + } + @Override public Query createNamedQuery(String queryName) { return new StageQueryImpl<>( delegate.createReactiveNamedQuery( queryName ) ); @@ -339,6 +354,21 @@ public SelectionQuery createNativeQuery(String queryString, Class resu return new StageSelectionQueryImpl<>( delegate.createReactiveNativeQuery( queryString, resultType ) ); } + @Override + public SelectionQuery createNativeQuery(String queryString, Class resultType, AffectedEntities affectedEntities) { + return new StageSelectionQueryImpl<>( delegate.createReactiveNativeQuery( queryString, resultType, affectedEntities ) ); + } + + @Override + public SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping) { + return new StageSelectionQueryImpl<>( delegate.createReactiveNativeQuery( queryString, resultSetMapping ) ); + } + + @Override + public SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping, AffectedEntities affectedEntities) { + return new StageSelectionQueryImpl<>( delegate.createReactiveNativeQuery( queryString, resultSetMapping, affectedEntities) ); + } + @Override public SelectionQuery createQuery(CriteriaQuery criteriaQuery) { return new StageSelectionQueryImpl<>( delegate.createReactiveQuery( criteriaQuery ) ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcType.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcType.java index adc00ff79..81af936ba 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcType.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcType.java @@ -5,12 +5,6 @@ */ package org.hibernate.reactive.type.descriptor.jdbc; -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -import org.hibernate.dialect.JsonHelper; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.ValueBinder; @@ -23,9 +17,14 @@ import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JsonHelper; import org.hibernate.type.descriptor.jdbc.JsonJdbcType; import io.vertx.core.json.JsonArray; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; /** * @see org.hibernate.type.descriptor.jdbc.JsonArrayJdbcType diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveOracleArrayJdbcType.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveOracleArrayJdbcType.java index 52a9d9206..09000a9e0 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveOracleArrayJdbcType.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveOracleArrayJdbcType.java @@ -13,7 +13,8 @@ import org.hibernate.HibernateException; import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.OracleArrayJdbcType; + +import org.hibernate.dialect.type.OracleArrayJdbcType; import org.hibernate.reactive.adaptor.impl.ArrayAdaptor; import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.ValueBinder; @@ -30,7 +31,7 @@ import static java.sql.Types.ARRAY; /** - * @see org.hibernate.dialect.OracleArrayJdbcType + * @see org.hibernate.dialect.type.OracleArrayJdbcType */ public class ReactiveOracleArrayJdbcType extends OracleArrayJdbcType { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlArrayJdbcType.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlArrayJdbcType.java index 306e619b8..7f30d8d29 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlArrayJdbcType.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlArrayJdbcType.java @@ -5,12 +5,6 @@ */ package org.hibernate.reactive.type.descriptor.jdbc; -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.sql.SQLXML; - -import org.hibernate.dialect.XmlHelper; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.WrapperOptions; @@ -18,6 +12,12 @@ import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.XmlArrayJdbcType; +import org.hibernate.type.descriptor.jdbc.XmlHelper; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.SQLXML; import static java.lang.invoke.MethodHandles.lookup; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlJdbcType.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlJdbcType.java index 3c3667aa3..a81840263 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlJdbcType.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlJdbcType.java @@ -5,20 +5,20 @@ */ package org.hibernate.reactive.type.descriptor.jdbc; -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.sql.SQLXML; - -import org.hibernate.dialect.XmlHelper; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.jdbc.BasicBinder; +import org.hibernate.type.descriptor.jdbc.XmlHelper; import org.hibernate.type.descriptor.jdbc.XmlJdbcType; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.SQLXML; + import static java.lang.invoke.MethodHandles.lookup; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/impl/CompletionStages.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/impl/CompletionStages.java index eb311aba6..397ca0e9e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/impl/CompletionStages.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/impl/CompletionStages.java @@ -90,6 +90,26 @@ public static CompletionStage completedFuture(T value) { return CompletableFuture.completedFuture( value ); } + /** + * Useful for implementing try-finally blocks. + * For example: + *
    {@code
    +	 * return supplyStage( () -> {
    +	 *     // Any error here will get caught
    +	 *     ...
    +	 *     })
    +	 *     .whenComplete( (r,t) -> {
    +	 *         // finally block
    +	 *         ...
    +	 *     })
    +	 * }
    + */ + public static CompletionStage supplyStage(Supplier> supplier) { + // Using the voidFuture() is the simplest way I found to make sure that everything run in the correct executor + return voidFuture() + .thenCompose( v -> supplier.get() ); + } + public static CompletionStage failedFuture(Throwable t) { CompletableFuture ret = new CompletableFuture<>(); ret.completeExceptionally( t ); 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 deleted file mode 100644 index 80ffcb31c..000000000 --- a/hibernate-reactive-core/src/main/resources/META-INF/services/io.vertx.core.spi.VertxServiceProvider +++ /dev/null @@ -1 +0,0 @@ -org.hibernate.reactive.context.impl.ContextualDataStorage \ No newline at end of file diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BaseReactiveTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BaseReactiveTest.java index 1a5f51e3b..508625bcb 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BaseReactiveTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BaseReactiveTest.java @@ -7,7 +7,6 @@ import java.util.Collection; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Supplier; @@ -29,6 +28,7 @@ import org.hibernate.reactive.provider.service.ReactiveGenerationTarget; import org.hibernate.reactive.stage.Stage; import org.hibernate.reactive.testing.SessionFactoryManager; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.tool.schema.spi.SchemaManagementTool; import org.junit.jupiter.api.AfterAll; @@ -41,7 +41,6 @@ import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.smallrye.mutiny.Uni; -import io.vertx.core.Promise; import io.vertx.core.VertxOptions; import io.vertx.junit5.RunTestOnContext; import io.vertx.junit5.Timeout; @@ -75,7 +74,7 @@ public abstract class BaseReactiveTest { * Configure Vertx JUnit5 test context */ @RegisterExtension - static RunTestOnContext testOnContext = new RunTestOnContext( vertxOptions() ); + static RunTestOnContext testOnContext = new RunTestOnContext( vertxOptions(), false ); private static VertxOptions vertxOptions() { Metrics.addRegistry( new SimpleMeterRegistry() ); @@ -92,6 +91,18 @@ protected void setProperties(Configuration configuration) { setDefaultProperties( configuration ); } + /** + * Set the properties related to the SQL logs. + * Some tests won't extend this class, but we still want to apply the same rules for these kinds of properties. + */ + public static void setSqlLoggingProperties(Configuration configuration) { + // Use JAVA_TOOL_OPTIONS='-Dhibernate.show_sql=true' + // Keep the default to false, otherwise the log on CI becomes too big + configuration.setProperty( Settings.SHOW_SQL, System.getProperty( Settings.SHOW_SQL, "false" ) ); + configuration.setProperty( Settings.FORMAT_SQL, System.getProperty( Settings.FORMAT_SQL, "false" ) ); + configuration.setProperty( Settings.HIGHLIGHT_SQL, System.getProperty( Settings.HIGHLIGHT_SQL, "true" ) ); + } + /** * Configure default properties common to most tests. */ @@ -102,13 +113,9 @@ public static void setDefaultProperties(Configuration configuration) { configuration.setProperty( Settings.HBM2DDL_IMPORT_FILES, "/db2.sql" ); doneTablespace = true; } - // Use JAVA_TOOL_OPTIONS='-Dhibernate.show_sql=true' - // Keep the default to false, otherwise the log on CI becomes too big - configuration.setProperty( Settings.SHOW_SQL, System.getProperty( Settings.SHOW_SQL, "false" ) ); - configuration.setProperty( Settings.FORMAT_SQL, System.getProperty( Settings.FORMAT_SQL, "false" ) ); - configuration.setProperty( Settings.HIGHLIGHT_SQL, System.getProperty( Settings.HIGHLIGHT_SQL, "true" ) ); configuration.setProperty( PersistentTableStrategy.DROP_ID_TABLES, "true" ); configuration.setProperty( GlobalTemporaryTableStrategy.DROP_ID_TABLES, "true" ); + setSqlLoggingProperties( configuration ); } public static final SessionFactoryManager factoryManager = new SessionFactoryManager(); @@ -209,33 +216,19 @@ protected CompletionStage setupSessionFactory(Configuration configuration) * @return a {@link CompletionStage} void that succeeds when the factory is ready. */ protected CompletionStage setupSessionFactory(Supplier confSupplier) { - CompletableFuture future = new CompletableFuture<>(); - testOnContext.vertx() + return testOnContext.vertx() .executeBlocking( // schema generation is a blocking operation and so it causes an // exception when run on the Vert.x event loop. So call it using // Vertx.executeBlocking() - promise -> startFactoryManager( promise, confSupplier ), - event -> { - if ( event.succeeded() ) { - future.complete( null ); - } - else { - future.completeExceptionally( event.cause() ); - } - } - ); - return future; - } - - private void startFactoryManager(Promise p, Supplier confSupplier) { - try { - factoryManager.start( () -> createHibernateSessionFactory( confSupplier.get() ) ); - p.complete(); - } - catch (Throwable e) { - p.fail( e ); - } + () -> startFactoryManager( confSupplier ), + false + ).toCompletionStage().thenCompose( CompletionStages::voidFuture ); + } + + private Object startFactoryManager(Supplier confSupplier) { + factoryManager.start( () -> createHibernateSessionFactory( confSupplier.get() ) ); + return null; } private SessionFactory createHibernateSessionFactory(Configuration configuration) { diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithOneToOneTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithOneToOneTest.java new file mode 100644 index 000000000..f5cbf5fd9 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithOneToOneTest.java @@ -0,0 +1,131 @@ +/* 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.VertxTestContext; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EmbeddedIdWithOneToOneTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( FooEntity.class, BarEntity.class ); + } + + @Test + public void test(VertxTestContext context) { + BarEntity barEntity = new BarEntity( "1" ); + FooId fooId = new FooId( barEntity ); + FooEntity entity = new FooEntity( fooId ); + + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.persistAll( barEntity, entity ) ) + .chain( () -> getMutinySessionFactory() + .withTransaction( s -> s.find( FooEntity.class, fooId ) ) + ) + .invoke( result -> { + assertThat( result.getId() ).isEqualTo( fooId ); + assertThat( result.getId().getIdEntity() ).isEqualTo( fooId.getIdEntity() ); + assertThat( result.getId().getIdEntity().getId() ).isEqualTo( fooId.getIdEntity().getId() ); + } ) + ); + } + + @Entity(name = "bar") + public static class BarEntity { + + @Id + private String id; + + public BarEntity() { + } + + public BarEntity(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + } + + @Entity(name = "foo") + public static class FooEntity { + + @EmbeddedId + private FooId id; + + public FooEntity() { + } + + public FooEntity(FooId id) { + this.id = id; + } + + public FooId getId() { + return id; + } + + public void setId(FooId id) { + this.id = id; + } + } + + @Embeddable + public static class FooId { + + @OneToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "id", nullable = false) + private BarEntity barEntity; + + public FooId() { + } + + public FooId(BarEntity barEntity) { + this.barEntity = barEntity; + } + + public BarEntity getIdEntity() { + return barEntity; + } + + public void setIdEntity(BarEntity barEntity) { + this.barEntity = barEntity; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + FooId fooId = (FooId) o; + return Objects.equals( barEntity, fooId.barEntity ); + } + + @Override + public int hashCode() { + return Objects.hashCode( barEntity ); + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/HQLQueryTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/HQLQueryTest.java index e48d3ec69..77331fe2b 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/HQLQueryTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/HQLQueryTest.java @@ -5,6 +5,7 @@ */ package org.hibernate.reactive; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -17,32 +18,41 @@ import io.vertx.junit5.Timeout; import io.vertx.junit5.VertxTestContext; import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; +import static jakarta.persistence.CascadeType.PERSIST; +import static jakarta.persistence.FetchType.LAZY; import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; @Timeout(value = 10, timeUnit = MINUTES) - public class HQLQueryTest extends BaseReactiveTest { Flour spelt = new Flour( 1, "Spelt", "An ancient grain, is a hexaploid species of wheat.", "Wheat flour" ); Flour rye = new Flour( 2, "Rye", "Used to bake the traditional sourdough breads of Germany.", "Wheat flour" ); Flour almond = new Flour( 3, "Almond", "made from ground almonds.", "Gluten free" ); + Author miller = new Author( "Madeline Miller"); + Author camilleri = new Author( "Andrea Camilleri"); + Book circe = new Book( "9780316556347", "Circe", miller ); + Book shapeOfWater = new Book( "0-330-49286-1 ", "The Shape of Water", camilleri ); + Book spider = new Book( "978-0-14-311203-7", "The Patience of the Spider", camilleri ); + @Override protected Collection> annotatedEntities() { - return List.of( Flour.class ); + return List.of( Flour.class, Book.class, Author.class ); } @BeforeEach public void populateDb(VertxTestContext context) { - test( context, getMutinySessionFactory() - .withTransaction( (session, transaction) -> session.persistAll( spelt, rye, almond ) ) ); + test( context, getMutinySessionFactory().withTransaction( session -> session + .persistAll( spelt, rye, almond, miller, camilleri, circe, shapeOfWater, spider ) ) + ); } @Test @@ -69,7 +79,7 @@ public void testAutoFlushOnResultList(VertxTestContext context) { public void testSelectScalarString(VertxTestContext context) { test( context, getSessionFactory().withSession( s -> { Stage.SelectionQuery qr = s.createSelectionQuery( "SELECT 'Prova' FROM Flour WHERE id = " + rye.getId(), String.class ); - assertNotNull( qr ); + assertThat( qr ).isNotNull(); return qr.getSingleResult(); } ).thenAccept( found -> assertEquals( "Prova", found ) ) ); } @@ -78,7 +88,7 @@ public void testSelectScalarString(VertxTestContext context) { public void testSelectScalarCount(VertxTestContext context) { test( context, getSessionFactory().withSession( s -> { Stage.SelectionQuery qr = s.createSelectionQuery( "SELECT count(*) FROM Flour", Long.class ); - assertNotNull( qr ); + assertThat( qr ).isNotNull(); return qr.getSingleResult(); } ).thenAccept( found -> assertEquals( 3L, found ) ) ); } @@ -86,14 +96,17 @@ public void testSelectScalarCount(VertxTestContext context) { @Test public void testSelectWithMultipleScalarValues(VertxTestContext context) { test( context, getSessionFactory().withSession( s -> { - Stage.SelectionQuery qr = s.createSelectionQuery( "SELECT 'Prova', f.id FROM Flour f WHERE f.id = " + rye.getId(), Object[].class ); - assertNotNull( qr ); - return qr.getSingleResult(); - } ).thenAccept( found -> { - assertTrue( found instanceof Object[] ); - assertEquals( "Prova", ( (Object[]) found )[0] ); - assertEquals( rye.getId(), ( (Object[]) found )[1] ); - } ) + Stage.SelectionQuery qr = s.createSelectionQuery( + "SELECT 'Prova', f.id FROM Flour f WHERE f.id = " + rye.getId(), + Object[].class + ); + assertThat( qr ).isNotNull(); + return qr.getSingleResult(); + } ).thenAccept( found -> { + assertThat( found ).isInstanceOf( Object[].class ); + assertEquals( "Prova", ( (Object[]) found )[0] ); + assertEquals( rye.getId(), ( (Object[]) found )[1] ); + } ) ); } @@ -101,7 +114,7 @@ public void testSelectWithMultipleScalarValues(VertxTestContext context) { public void testSingleResultQueryOnId(VertxTestContext context) { test( context, getSessionFactory().withSession( s -> { Stage.SelectionQuery qr = s.createSelectionQuery( "FROM Flour WHERE id = 1", Flour.class ); - assertNotNull( qr ); + assertThat( qr ).isNotNull(); return qr.getSingleResult(); } ).thenAccept( flour -> assertEquals( spelt, flour ) ) ); @@ -111,7 +124,7 @@ public void testSingleResultQueryOnId(VertxTestContext context) { public void testSingleResultQueryOnName(VertxTestContext context) { test( context, getSessionFactory().withSession( s -> { Stage.SelectionQuery qr = s.createSelectionQuery( "FROM Flour WHERE name = 'Almond'", Flour.class ); - assertNotNull( qr ); + assertThat( qr ).isNotNull(); return qr.getSingleResult(); } ).thenAccept( flour -> assertEquals( almond, flour ) ) ); @@ -122,13 +135,28 @@ public void testFromQuery(VertxTestContext context) { test( context, getSessionFactory() .withSession( s -> { Stage.SelectionQuery qr = s.createSelectionQuery( "FROM Flour ORDER BY name", Flour.class ); - assertNotNull( qr ); + assertThat( qr ).isNotNull(); return qr.getResultList(); } ) .thenAccept( results -> assertThat( results ).containsExactly( almond, rye, spelt ) ) ); } + @Test + public void testSelectNewConstructor(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withTransaction( session -> session + .createQuery( "SELECT NEW Book(b.title, b.author) FROM Book b ORDER BY b.title DESC", Book.class ) + .getResultList() + ) + .invoke( books -> assertThat( books ).containsExactly( + new Book( shapeOfWater.title, camilleri ), + new Book( spider.title, camilleri ), + new Book( circe.title, miller ) + ) ) + ); + } + @Entity(name = "Flour") @Table(name = "Flour") public static class Flour { @@ -204,4 +232,122 @@ public int hashCode() { return Objects.hash( name, description, type ); } } + + @Entity(name = "Book") + @Table(name = "Book_HQL") + public static class Book { + @Id + @GeneratedValue + private Integer id; + + private String isbn; + + private String title; + + @ManyToOne(fetch = LAZY) + private Author author; + + public Book() { + } + + public Book(String title, Author author) { + this.title = title; + this.author = author; + } + + public Book(String isbn, String title, Author author) { + this.isbn = isbn; + this.title = title; + this.author = author; + author.books.add( this ); + } + + public Integer getId() { + return id; + } + + public String getIsbn() { + return isbn; + } + + public String getTitle() { + return title; + } + + public Author getAuthor() { + return author; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Book book = (Book) o; + return Objects.equals( isbn, book.isbn ) && Objects.equals( + title, + book.title + ) && Objects.equals( author, book.author ); + } + + @Override + public int hashCode() { + return Objects.hash( isbn, title, author ); + } + + @Override + public String toString() { + return id + ":" + isbn + ":" + title + ":" + author; + } + } + + @Entity(name = "Author") + @Table(name = "Author_HQL") + public static class Author { + @Id @GeneratedValue + private Integer id; + + private String name; + + @OneToMany(mappedBy = "author", cascade = PERSIST) + private List books = new ArrayList<>(); + + public Author() { + } + + public Author(String name) { + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public List getBooks() { + return books; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Author author = (Author) o; + return Objects.equals( name, author.name ); + } + + @Override + public int hashCode() { + return Objects.hashCode( name ); + } + + @Override + public String toString() { + return id + ":" + name; + } + } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java index 2b0e3f9c6..ab64e1925 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java @@ -5,11 +5,6 @@ */ package org.hibernate.reactive; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Objects; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -26,12 +21,13 @@ import jakarta.persistence.Table; import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Objects; import static java.util.concurrent.TimeUnit.MINUTES; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; @Timeout(value = 10, timeUnit = MINUTES) @@ -56,9 +52,8 @@ public void testRootClassViaAssociation(VertxTestContext context) { .thenCompose( v -> openSession() ) .thenCompose( s2 -> s2.find( Author.class, author.getId() ) ) .thenAccept( auth -> { - assertNotNull( auth ); - assertEquals( author, auth ); - assertEquals( book.getTitle(), auth.getBook().getTitle() ); + assertThat( auth ).isEqualTo( author ); + assertThat( auth.getBook().getTitle() ).isEqualTo( book.getTitle() ); } ) ) ); @@ -77,9 +72,8 @@ public void testSubclassViaAssociation(VertxTestContext context) { .thenCompose( v -> s.flush() ) .thenCompose( v -> s.find( Author.class, author.getId() ) ) .thenAccept( auth -> { - assertNotNull( auth ); - assertEquals( author, auth ); - assertEquals( book.getTitle(), auth.getBook().getTitle() ); + assertThat( auth ).isEqualTo( author ); + assertThat( auth.getBook().getTitle() ).isEqualTo( book.getTitle() ); } ) ) ); @@ -87,7 +81,6 @@ public void testSubclassViaAssociation(VertxTestContext context) { @Test public void testRootClassViaFind(VertxTestContext context) { - final Book novel = new Book( 6, "The Boy, The Mole, The Fox and The Horse", new Date()); final Author author = new Author( "Charlie Mackesy", novel ); @@ -99,9 +92,8 @@ public void testRootClassViaFind(VertxTestContext context) { .thenCompose( v -> openSession() .thenCompose( s -> s.find(Book.class, 6) ) ) .thenAccept(book -> { - assertNotNull(book); - assertFalse(book instanceof SpellBook); - assertEquals(book.getTitle(), "The Boy, The Mole, The Fox and The Horse"); + assertThat( book ).isNotInstanceOf( SpellBook.class ); + assertThat( book.getTitle() ).isEqualTo( "The Boy, The Mole, The Fox and The Horse" ); })); } @@ -117,35 +109,41 @@ public void testSubclassViaFind(VertxTestContext context) { .thenCompose( v -> s.flush() ) ) .thenCompose( v -> openSession().thenCompose( s -> s.find(Book.class, 6) ) ) .thenAccept(book -> { - assertNotNull(book); - assertTrue(book instanceof SpellBook); - assertEquals(book.getTitle(), "Necronomicon"); + assertThat( book ).isInstanceOf( SpellBook.class ); + assertThat( book.getTitle() ).isEqualTo( "Necronomicon" ); })); } @Test public void testQueryUpdate(VertxTestContext context) { final SpellBook spells = new SpellBook( 6, "Necronomicon", true, new Date() ); -// final Author author = new Author( "Abdul Alhazred", spells ); - test( context, + test( + context, openSession() - .thenCompose( s -> s.persist(spells).thenCompose( v -> s.flush() ) ) + .thenCompose( s -> s.persist( spells ).thenCompose( v -> s.flush() ) ) .thenCompose( vv -> openSession() - .thenCompose( s -> s.withTransaction( t -> s.createMutationQuery("update SpellBook set title='x' where forbidden=false").executeUpdate() ) - .thenCompose( v -> s.withTransaction( t -> s.createMutationQuery("update SpellBook set forbidden=false where title='Necronomicon'").executeUpdate() ) ) - .thenCompose( v -> s.withTransaction( t -> s.createMutationQuery("update Book set title=title||' II' where title='Necronomicon'").executeUpdate() ) ) - .thenCompose( v -> s.find(Book.class, 6) ) + .thenCompose( s -> s.withTransaction( t -> s + .createMutationQuery( "update SpellBook set title='x' where forbidden=false" ) + .executeUpdate() ) + .thenCompose( v -> s.withTransaction( t -> s + .createMutationQuery( "update SpellBook set forbidden=false where title='Necronomicon'" ) + .executeUpdate() ) ) + .thenCompose( v -> s.withTransaction( t -> s + .createMutationQuery( "update Book set title=title||' II' where title='Necronomicon'" ) + .executeUpdate() ) ) + .thenCompose( v -> s.find( Book.class, 6 ) ) .thenAccept( book -> { - assertNotNull(book); - assertTrue(book instanceof SpellBook); - assertEquals(book.getTitle(), "Necronomicon II"); + assertThat( book ).isInstanceOf( SpellBook.class ); + assertThat( book.getTitle() ).isEqualTo( "Necronomicon II" ); } ) - ) ) + ) ) .thenCompose( vv -> openSession() - .thenCompose( s -> s.withTransaction( t -> s.createMutationQuery("delete Book where title='Necronomicon II'").executeUpdate() ) ) + .thenCompose( s -> s.withTransaction( t -> s + .createMutationQuery( "delete Book where title='Necronomicon II'" ) + .executeUpdate() ) ) .thenCompose( v -> openSession() ) - .thenCompose( s -> s.find(Book.class, 6) ) + .thenCompose( s -> s.find( Book.class, 6 ) ) .thenAccept( Assertions::assertNull ) ) ); @@ -154,7 +152,6 @@ public void testQueryUpdate(VertxTestContext context) { @Test public void testQueryUpdateWithParameters(VertxTestContext context) { final SpellBook spells = new SpellBook( 6, "Necronomicon", true, new Date() ); -// final Author author = new Author( "Abdul Alhazred", spells ); test( context, openSession() @@ -174,11 +171,9 @@ public void testQueryUpdateWithParameters(VertxTestContext context) { .thenCompose( v -> openSession() .thenCompose( s -> s.find( Book.class, 6) ) .thenAccept( book -> { - assertNotNull(book); - assertTrue(book instanceof SpellBook); - assertEquals(book.getTitle(), "Necronomicon II"); - } - ) + assertThat( book ).isInstanceOf( SpellBook.class ); + assertThat( book.getTitle() ).isEqualTo( "Necronomicon II" ); + } ) ) .thenCompose( v -> openSession() .thenCompose( s -> s.withTransaction( t -> s.createMutationQuery("delete Book where title=:tit") @@ -207,6 +202,11 @@ public SpellBook(Integer id, String title, boolean forbidden, Date published) { public boolean getForbidden() { return forbidden; } + + @Override + public String toString() { + return "SpellBook{" + super.toString() + ",forbidden=" + forbidden + '}'; + } } @Entity(name="Book") @@ -268,6 +268,11 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash( title ); } + + @Override + public String toString() { + return id + ", " + title + ", " + published; + } } @Entity(name = "Author") diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToOneMapsIdAndEmbeddedIdTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToOneMapsIdAndEmbeddedIdTest.java new file mode 100644 index 000000000..314c69b48 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToOneMapsIdAndEmbeddedIdTest.java @@ -0,0 +1,189 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.hibernate.annotations.EmbeddedColumnNaming; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MapsId; +import jakarta.persistence.OneToMany; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ManyToOneMapsIdAndEmbeddedIdTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( Link.class, GPSPoint.class, NetPoint.class ); + } + + @BeforeEach + public void populateDb(VertxTestContext context) { + NetPointKey startKey = new NetPointKey( 1, NetPointType.STOP_POINT ); + NetPointKey endKey = new NetPointKey( 2, NetPointType.STOP_POINT ); + LinkKey linkKey = new LinkKey( startKey, endKey, "123" ); + + final NetPoint start = new NetPoint(); + fillWithBogusValues( start ); + start.key = startKey; + + final NetPoint end = new NetPoint(); + fillWithBogusValues( end ); + end.key = endKey; + + final Link link = new Link(); + link.key = linkKey; + link.addPoint( new GPSPoint( new GPSPointKey( linkKey, 0 ), link, 1, 1, 1 ) ); + link.addPoint( new GPSPoint( new GPSPointKey( linkKey, 1 ), link, 1, 1, 1 ) ); + link.addPoint( new GPSPoint( new GPSPointKey( linkKey, 2 ), link, 1, 1, 1 ) ); + + test( + context, getMutinySessionFactory().withTransaction( session -> session + .persistAll( start, end, link ) ) + ); + } + + @Test + public void test(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withTransaction( session -> session + .createQuery( "from Link", Link.class ).getResultList() ) + .invoke( links -> assertThat( links ).hasSize( 1 ) ) + ); + } + + void fillWithBogusValues(NetPoint start) { + start.gpsLatitude = 1; + start.gpsLongitude = 1; + start.operatingDepartmentId = "123"; + start.operatingDepartmentShortName = "123 - 123"; + } + + @Entity(name = "GPSPoint") + public static class GPSPoint { + + @EmbeddedId + public GPSPointKey key; + + @ManyToOne + @MapsId("link") + public Link link; + + @Column(nullable = false) + public Integer latitude; + + @Column(nullable = false) + public Integer longitude; + + @Column(nullable = false) + public Integer distance; + + public GPSPoint() { + } + + public GPSPoint(GPSPointKey key, Link link, Integer latitude, Integer longitude, Integer distance) { + this.key = key; + this.link = link; + this.latitude = latitude; + this.longitude = longitude; + this.distance = distance; + } + } + + @Embeddable + public record GPSPointKey( + @Embedded + LinkKey link, + + Integer position + ) { + + } + + @Entity(name = "Link") + public static class Link { + + @EmbeddedId + public LinkKey key; + + @OneToMany(mappedBy = "link", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + public List gpsPoints = new ArrayList<>(); + + public void addPoint(GPSPoint point) { + gpsPoints.add( point ); + point.link = this; + } + } + + @Embeddable + public record LinkKey( + @Embedded + @EmbeddedColumnNaming("start_%s") + NetPointKey start, + + @Embedded + @EmbeddedColumnNaming("end_%s") + NetPointKey end, + + String operatingDepartmentId + ) { + + } + + @Embeddable + public record NetPointKey( + Integer id, + + @Enumerated(EnumType.ORDINAL) + NetPointType type + ) { + + } + + @Entity(name = "NetPoint") + public static class NetPoint { + + @EmbeddedId + public NetPointKey key; + + @Column(nullable = false) + public String operatingDepartmentId; + + @Column(nullable = false) + public String operatingDepartmentShortName; + + @Column(nullable = false) + public Integer gpsLatitude; + + @Column(nullable = false) + public Integer gpsLongitude; + } + + public enum NetPointType { + @Deprecated UNSPECIFIED, + STOP_POINT, + DEPOT_POINT, + BEACON, + SECTION_POINT + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MetadataAccessTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MetadataAccessTest.java new file mode 100644 index 000000000..da1578045 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MetadataAccessTest.java @@ -0,0 +1,260 @@ +/* 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.hibernate.HibernateException; +import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.DatabaseVersion; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl; +import org.hibernate.engine.jdbc.dialect.spi.DialectFactory; +import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfoSource; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.reactive.containers.DatabaseConfiguration; +import org.hibernate.reactive.provider.ReactiveServiceRegistryBuilder; +import org.hibernate.reactive.provider.Settings; +import org.hibernate.reactive.testing.SqlStatementTracker; +import org.hibernate.service.spi.ServiceRegistryImplementor; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Map; +import java.util.Properties; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.cfg.JdbcSettings.ALLOW_METADATA_ON_BOOT; +import static org.hibernate.cfg.JdbcSettings.DIALECT; +import static org.hibernate.cfg.JdbcSettings.JAKARTA_HBM2DDL_DB_NAME; +import static org.hibernate.cfg.JdbcSettings.JAKARTA_JDBC_URL; +import static org.hibernate.reactive.BaseReactiveTest.setSqlLoggingProperties; +import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * Hibernate ORM allows starting up without access to the DB ("offline") + * when {@link Settings#ALLOW_METADATA_ON_BOOT} is set to false. + *

    + * Inspired by the test + * {@code org.hibernate.orm.test.boot.database.metadata.MetadataAccessTests} + * in Hibernate ORM. + *

    + */ +public class MetadataAccessTest { + + private static SqlStatementTracker sqlTracker; + + private static final int EXPECTED_MAJOR = 123; + private static final int EXPECTED_MINOR = 456; + private static final DatabaseVersion EXPECTED_VERSION = DatabaseVersion.make( EXPECTED_MAJOR, EXPECTED_MINOR ); + + private static Properties dialectMajorMinorProperties() { + Properties dbProperties = new Properties(); + // Major and Minor should override the full version, so we keep them different + dbProperties.setProperty( Settings.DIALECT_DB_MAJOR_VERSION, String.valueOf( EXPECTED_MAJOR ) ); + dbProperties.setProperty( Settings.DIALECT_DB_MINOR_VERSION, String.valueOf( EXPECTED_MINOR ) ); + return dbProperties; + } + + private static Properties jakartaMajorMinorProperties() { + Properties dbProperties = new Properties(); + dbProperties.setProperty( Settings.JAKARTA_HBM2DDL_DB_MAJOR_VERSION, String.valueOf( EXPECTED_MAJOR ) ); + dbProperties.setProperty( Settings.JAKARTA_HBM2DDL_DB_MINOR_VERSION, String.valueOf( EXPECTED_MINOR ) ); + return dbProperties; + } + + private static Properties jakartaFullDbVersion() { + Properties dbProperties = new Properties(); + dbProperties.setProperty( Settings.JAKARTA_HBM2DDL_DB_VERSION, EXPECTED_MAJOR + "." + EXPECTED_MINOR ); + return dbProperties; + } + + private static Properties dialectFullDbVersion() { + Properties dbProperties = new Properties(); + dbProperties.setProperty( Settings.DIALECT_DB_VERSION, EXPECTED_MAJOR + "." + EXPECTED_MINOR ); + return dbProperties; + } + + static Stream explicitVersionProperties() { + return Stream.of( + arguments( "Jakarta properties", jakartaMajorMinorProperties() ), + arguments( "Deprecated dialect properties", dialectMajorMinorProperties() ), + arguments( "Jakarta db version property", jakartaFullDbVersion() ), + arguments( "Deprecated dialect db version property", dialectFullDbVersion() ) + ); + } + + @ParameterizedTest(name = "Test {0} with " + DIALECT) + @MethodSource("explicitVersionProperties") + public void testExplicitVersionWithDialect(String display, Properties dbProperties) { + dbProperties.setProperty( ALLOW_METADATA_ON_BOOT, "false" ); + dbProperties.setProperty( DIALECT, dbType().getDialectClass().getName() ); + + try (StandardServiceRegistry serviceRegistry = createServiceRegistry( dbProperties )) { + final Dialect dialect = dialect( serviceRegistry ); + assertThat( dialect ).isInstanceOf( dbType().getDialectClass() ); + assertThat( dialect.getVersion() ).isEqualTo( EXPECTED_VERSION ); + } + + assertThat( sqlTracker.getLoggedQueries() ) + .as( "No query should be executed at start up" ) + .isEmpty(); + } + + @ParameterizedTest(name = "Test {0} with " + JAKARTA_HBM2DDL_DB_NAME) + @MethodSource("explicitVersionProperties") + public void testExplicitVersionWithJakartaDbName(String display, Properties dbProperties) { + dbProperties.setProperty( ALLOW_METADATA_ON_BOOT, "false" ); + dbProperties.setProperty( JAKARTA_HBM2DDL_DB_NAME, dbType().getProductName() ); + + try (StandardServiceRegistry serviceRegistry = createServiceRegistry( dbProperties )) { + final Dialect dialect = dialect( serviceRegistry ); + assertThat( dialect ).isInstanceOf( dbType().getDialectClass() ); + assertThat( dialect.getVersion() ).isEqualTo( EXPECTED_VERSION ); + } + + assertThat( sqlTracker.getLoggedQueries() ) + .as( "No query should be executed at start up" ) + .isEmpty(); + } + + @Test + public void testMinimumDatabaseVersionWithDialect() { + final Properties dbProperties = new Properties(); + dbProperties.setProperty( ALLOW_METADATA_ON_BOOT, "false" ); + dbProperties.setProperty( DIALECT, dbType().getDialectClass().getName() ); + + try (StandardServiceRegistry serviceRegistry = createServiceRegistry( dbProperties )) { + final Dialect dialect = dialect( serviceRegistry ); + assertThat( dialect ).isInstanceOf( dbType().getDialectClass() ); + assertThat( dialect.getVersion() ).isEqualTo( dbType().getMinimumVersion() ); + } + + assertThat( sqlTracker.getLoggedQueries() ) + .as( "No query should be executed at start up" ) + .isEmpty(); + } + + @Test + public void testMinimumDatabaseVersionWithJakartaDbName() { + final Properties dbProperties = new Properties(); + dbProperties.setProperty( ALLOW_METADATA_ON_BOOT, "false" ); + dbProperties.setProperty( JAKARTA_HBM2DDL_DB_NAME, dbType().getProductName() ); + + try (StandardServiceRegistry serviceRegistry = createServiceRegistry( dbProperties )) { + final Dialect dialect = dialect( serviceRegistry ); + assertThat( dialect ).isInstanceOf( dbType().getDialectClass() ); + assertThat( dialect.getVersion() ).isEqualTo( dbType().getMinimumVersion() ); + } + + assertThat( sqlTracker.getLoggedQueries() ) + .as( "No query should be executed at start up" ) + .isEmpty(); + } + + @Test + public void testDeterminedVersion() { + final Properties disabledProperties = new Properties(); + disabledProperties.setProperty( ALLOW_METADATA_ON_BOOT, "false" ); + disabledProperties.setProperty( DIALECT, dbType().getDialectClass().getName() ); + // The dialect when ALLOW_METADATA_ON_BOOT si set to false + final Dialect metadataDisabledDialect; + try (StandardServiceRegistry serviceRegistry = createServiceRegistry( disabledProperties )) { + metadataDisabledDialect = dialect( serviceRegistry ); + // We didn't set the version anywhere else, so we expect it to be the minimum version + assertThat( metadataDisabledDialect.getVersion() ).isEqualTo( dbType().getMinimumVersion() ); + } + + assertThat( sqlTracker.getLoggedQueries() ) + .as( "No query should be executed at start up" ) + .isEmpty(); + + final Properties enabledProperties = new Properties(); + enabledProperties.setProperty( ALLOW_METADATA_ON_BOOT, "true" ); + enabledProperties.setProperty( JAKARTA_JDBC_URL, DatabaseConfiguration.getJdbcUrl() ); + try (StandardServiceRegistry serviceRegistry = createServiceRegistry( enabledProperties )) { + final Dialect metadataEnabledDialect = dialect( serviceRegistry ); + + // We expect determineDatabaseVersion(), when called on metadataAccessDisabledDialect, + // to return the version that would have been returned, + // had we booted up with auto-detection of version (metadata access allowed). + DatabaseVersion determinedDatabaseVersion = metadataDisabledDialect + .determineDatabaseVersion( dialectResolutionInfo( serviceRegistry ) ); + + // Whatever the version, we don't expect the minimum one + assertThat( determinedDatabaseVersion ).isNotEqualTo( dbType().getMinimumVersion() ); + + assertThat( determinedDatabaseVersion ).isEqualTo( metadataEnabledDialect.getVersion() ); + assertThat( determinedDatabaseVersion.getMajor() ).isEqualTo( metadataEnabledDialect.getVersion().getMajor() ); + assertThat( determinedDatabaseVersion.getMinor() ).isEqualTo( metadataEnabledDialect.getVersion().getMinor() ); + assertThat( determinedDatabaseVersion.getMicro() ).isEqualTo( metadataEnabledDialect.getVersion().getMicro() ); + } + } + + private Configuration constructConfiguration(Properties properties) { + Configuration configuration = new Configuration(); + setSqlLoggingProperties( configuration ); + configuration.addProperties( properties ); + + // 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( s -> true, configuration.getProperties() ); + return configuration; + } + + private StandardServiceRegistry createServiceRegistry(Properties properties) { + Configuration configuration = constructConfiguration( properties ); + StandardServiceRegistryBuilder builder = new ReactiveServiceRegistryBuilder(); + // We will set these properties when needed + assertThat( builder.getSettings() ).doesNotContainKeys( DIALECT, JAKARTA_HBM2DDL_DB_NAME ); + builder.applySettings( configuration.getProperties() ); + + builder.addInitiator( new CapturingDialectFactory.Initiator() ); + sqlTracker.registerService( builder ); + return builder.enableAutoClose().build(); + } + + private static Dialect dialect(StandardServiceRegistry registry) { + return registry.getService( JdbcEnvironment.class ).getDialect(); + } + + private static DialectResolutionInfo dialectResolutionInfo(StandardServiceRegistry registry) { + return ( (CapturingDialectFactory) registry.getService( DialectFactory.class ) ) + .capturedDialectResolutionInfoSource.getDialectResolutionInfo(); + } + + // A hack to easily retrieve DialectResolutionInfo exactly as it would be constructed by Hibernate ORM + private static class CapturingDialectFactory extends DialectFactoryImpl { + + static class Initiator implements StandardServiceInitiator { + @Override + public Class getServiceInitiated() { + return DialectFactory.class; + } + + @Override + public DialectFactory initiateService(Map configurationValues, ServiceRegistryImplementor registry) { + return new CapturingDialectFactory(); + } + } + + DialectResolutionInfoSource capturedDialectResolutionInfoSource; + + @Override + public Dialect buildDialect(Map configValues, DialectResolutionInfoSource resolutionInfoSource) + throws HibernateException { + this.capturedDialectResolutionInfoSource = resolutionInfoSource; + return super.buildDialect( configValues, resolutionInfoSource ); + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java index fb8c7e1f1..bf46f835a 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java @@ -27,12 +27,7 @@ import jakarta.persistence.metamodel.EntityType; import static java.util.concurrent.TimeUnit.MINUTES; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; @Timeout(value = 10, timeUnit = MINUTES) @@ -668,6 +663,38 @@ public void testForceFlushWithDelete(VertxTestContext context) { ); } + @Test + public void testCurrentSession(VertxTestContext context) { + test( context, + getMutinySessionFactory().withSession(session -> + getMutinySessionFactory().withSession(s -> { + assertEquals(session, s); + Mutiny.Session currentSession = getMutinySessionFactory().getCurrentSession(); + assertNotNull(currentSession); + assertTrue(currentSession.isOpen()); + assertEquals(session, currentSession); + return Uni.createFrom().voidItem(); + }).invoke(() -> assertNotNull(getMutinySessionFactory().getCurrentSession())) + ).invoke(() -> assertNull(getMutinySessionFactory().getCurrentSession())) + ); + } + + @Test + public void testCurrentStatelessSession(VertxTestContext context) { + test( context, + getMutinySessionFactory().withStatelessSession(session -> + getMutinySessionFactory().withStatelessSession(s -> { + assertEquals(session, s); + Mutiny.StatelessSession currentSession = getMutinySessionFactory().getCurrentStatelessSession(); + assertNotNull(currentSession); + assertTrue(currentSession.isOpen()); + assertEquals(session, currentSession); + return Uni.createFrom().voidItem(); + }).invoke(() -> assertNotNull(getMutinySessionFactory().getCurrentStatelessSession())) + ).invoke(() -> assertNull(getMutinySessionFactory().getCurrentStatelessSession())) + ); + } + private void assertThatPigsAreEqual(GuineaPig expected, GuineaPig actual) { assertNotNull( actual ); assertEquals( expected.getId(), actual.getId() ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinyStatelessSessionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinyStatelessSessionTest.java index cde496e57..8314179c9 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinyStatelessSessionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinyStatelessSessionTest.java @@ -141,22 +141,27 @@ public void testStatelessSessionGetMultiple(VertxTestContext context) { @Test public void testStatelessSessionCriteria(VertxTestContext context) { GuineaPig pig = new GuineaPig( "Aloi" ); + GuineaPig mate = new GuineaPig("Aloina"); + pig.mate = mate; CriteriaBuilder cb = getSessionFactory().getCriteriaBuilder(); CriteriaQuery query = cb.createQuery( GuineaPig.class ); Root gp = query.from( GuineaPig.class ); query.where( cb.equal( gp.get( "name" ), cb.parameter( String.class, "n" ) ) ); + query.orderBy( cb.asc( gp.get( "name" ) ) ); CriteriaUpdate update = cb.createCriteriaUpdate( GuineaPig.class ); - update.from( GuineaPig.class ); + Root updatedPig = update.from(GuineaPig.class); update.set( "name", "Bob" ); + update.where( updatedPig.get( "mate" ).isNotNull() ); CriteriaDelete delete = cb.createCriteriaDelete( GuineaPig.class ); - delete.from( GuineaPig.class ); + Root deletedPig = delete.from( GuineaPig.class ); + delete.where( deletedPig.get( "mate" ).isNotNull() ); test( context, getMutinySessionFactory().openStatelessSession() - .chain( ss -> ss.insert( pig ) + .chain( ss -> ss.insertMultiple( List.of(mate, pig) ) .chain( v -> ss.createQuery( query ) .setParameter( "n", pig.name ) .getResultList() ) @@ -223,6 +228,9 @@ public static class GuineaPig { @Version private int version; + @ManyToOne + private GuineaPig mate; + public GuineaPig() { } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java deleted file mode 100644 index 48b73829f..000000000 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java +++ /dev/null @@ -1,345 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive; - -import java.util.Collection; -import java.util.List; -import java.util.Objects; - -import org.hibernate.metamodel.model.domain.EntityDomainType; -import org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl; - -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 jakarta.persistence.metamodel.SingularAttribute; - -import static java.util.concurrent.TimeUnit.MINUTES; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.hibernate.query.Order.asc; -import static org.hibernate.query.Order.desc; - -@Timeout(value = 10, timeUnit = MINUTES) -public class OrderTest extends BaseReactiveTest { - final Book book1 = new Book( "9781932394153", "Hibernate in Action" ); - final Book book2 = new Book( "9781617290459", "Java Persistence with Hibernate" ); - - SingularAttribute isbn; - SingularAttribute title; - - @Override - protected Collection> annotatedEntities() { - return List.of( Book.class ); - } - - @BeforeEach - public void populateDB(VertxTestContext context) { - isbn = attribute( "isbn" ); - title = attribute( "title" ); - test( context, getSessionFactory().withTransaction( session -> session.persist( book1, book2 ) ) ); - } - - @Test - public void descPositionalColumnWithStage(VertxTestContext context) { - test( context, getSessionFactory().withSession( session -> session - // Not sure if it's a bug, but setOrder doesn't work if we use String.class - .createSelectionQuery( "select title from Book", Object[].class ) - .setOrder( desc( 1 ) ) - .getResultList() - .thenAccept( results -> assertThat( results ) - // Keep the title - .map( row -> row[0] ) - .containsExactly( book2.title, book1.title ) ) - ) ); - } - - @Test - public void descPositionalColumnWithMutiny(VertxTestContext context) { - test( context, getMutinySessionFactory().withSession( session -> session - // Not sure if it's a bug, but setOrder doesn't work if we use String.class or Object.class - .createSelectionQuery( "select title from Book", Object[].class ) - .setOrder( desc( 1 ) ) - .getResultList() - .invoke( results -> assertThat( results ) - // Keep the title - .map( row -> row[0] ) - .containsExactly( book2.title, book1.title ) - ) - ) ); - } - - @Test - public void ascAttributeWithStage(VertxTestContext context) { - test( context, getSessionFactory().withSession( session -> session - .createSelectionQuery( "from Book", Book.class ) - .setOrder( asc( title ) ) - .getResultList() - .thenAccept( books -> assertThat( books ).containsExactly( book1, book2 ) ) - ) ); - } - - @Test - public void ascAttributeWithMutiny(VertxTestContext context) { - test( context, getMutinySessionFactory().withSession( session -> session - .createSelectionQuery( "from Book", Book.class ) - .setOrder( asc( title ) ) - .getResultList() - .invoke( books -> assertThat( books ).containsExactly( book1, book2 ) ) - ) ); - } - - @Test - public void descAttributeWithStage(VertxTestContext context) { - test( context, getSessionFactory().withSession( session -> session - .createSelectionQuery( "from Book", Book.class ) - .setOrder( desc( title ) ) - .getResultList() - .thenAccept( books -> assertThat( books ).containsExactly( book2, book1 ) ) - ) ); - } - - @Test - public void descAttributeWithMutiny(VertxTestContext context) { - test( context, getMutinySessionFactory().withSession( session -> session - .createSelectionQuery( "from Book", Book.class ) - .setOrder( desc( title ) ) - .getResultList() - .invoke( books -> assertThat( books ).containsExactly( book2, book1 ) ) - ) ); - } - - @Test - public void ascIdWithStage(VertxTestContext context) { - test( context, getSessionFactory().withSession( session -> session - .createSelectionQuery( "from Book", Book.class ) - .setOrder( asc( isbn ) ) - .getResultList() - .thenAccept( books -> assertThat( books ).containsExactly( book2, book1 ) ) - ) ); - } - - @Test - public void ascIdWithMutiny(VertxTestContext context) { - test( context, getMutinySessionFactory().withSession( session -> session - .createSelectionQuery( "from Book", Book.class ) - .setOrder( asc( isbn ) ) - .getResultList() - .invoke( books -> assertThat( books ).containsExactly( book2, book1 ) ) - ) ); - } - - @Test - public void descIdWithStage(VertxTestContext context) { - test( context, getSessionFactory().withSession( session -> session - .createSelectionQuery( "from Book", Book.class ) - .setOrder( desc( isbn ) ) - .getResultList() - .thenAccept( books -> assertThat( books ).containsExactly( book1, book2 ) ) - ) ); - } - - @Test - public void descIdWithMutiny(VertxTestContext context) { - test( context, getMutinySessionFactory().withSession( session -> session - .createSelectionQuery( "from Book", Book.class ) - .setOrder( desc( isbn ) ) - .getResultList() - .invoke( books -> assertThat( books ).containsExactly( book1, book2 ) ) - ) ); - } - - @Test - public void testAscDescBySelectElement(VertxTestContext context) { - test( context, getSessionFactory().withSession( session -> session - .createSelectionQuery( "select isbn, title from Book", Object[].class ) - .setOrder( asc( 2 ) ) - .getResultList() - .thenAccept( list -> assertOrderByBookArray( list, book1, book2 ) ) - .thenCompose( v -> session - .createSelectionQuery( "select isbn, title from Book", Object[].class ) - .setOrder( desc( 2 ) ) - .getResultList() - .thenAccept( list -> assertOrderByBookArray( list, book2, book1 ) ) - ) - ) ); - } - - @Test - public void testAscDescBySelectElementMutiny(VertxTestContext context) { - test( context, getMutinySessionFactory().withSession( session -> session - .createSelectionQuery( "select isbn, title from Book", Object[].class ) - .setOrder( asc( 2 ) ) - .getResultList() - .invoke( list -> assertOrderByBookArray( list, book1, book2 ) ) - .chain( v -> session - .createSelectionQuery( "select isbn, title from Book", Object[].class ) - .setOrder( desc( 2 ) ) - .getResultList() - .invoke( list -> assertOrderByBookArray( list, book2, book1 ) ) - ) - ) ); - } - - @Test - public void testOrderWithList(VertxTestContext context) { - test( context, getSessionFactory().withSession( session -> session - .createSelectionQuery( "from Book", Book.class ) - .setOrder( List.of( asc( isbn ), desc( title ) ) ).getResultList() - .thenAccept( isbnAsc -> assertThat( isbnAsc ).containsExactly( book2, book1 ) ) - .thenCompose( v -> session - .createSelectionQuery( "from Book", Book.class ) - .setOrder( List.of( desc( isbn ), desc( title ) ) ).getResultList() - .thenAccept( isbnDesc -> assertThat( isbnDesc ).containsExactly( book1, book2 ) ) - ) - ) ); - } - - @Test - public void testOrderWithListMutiny(VertxTestContext context) { - test( context, getMutinySessionFactory().withSession( session -> session - .createSelectionQuery( "from Book", Book.class ) - .setOrder( List.of( asc( isbn ), desc( title ) ) ).getResultList() - .invoke( isbnAsc -> assertThat( isbnAsc ).containsExactly( book2, book1 ) ) - .chain( v -> session - .createSelectionQuery( "from Book", Book.class ) - .setOrder( List.of( desc( isbn ), desc( title ) ) ) - .getResultList() - .invoke( isbnDesc -> assertThat( isbnDesc ).containsExactly( book1, book2 ) ) - ) - ) ); - } - - @Test - public void testAscDescWithNamedParam(VertxTestContext context) { - test( context, getSessionFactory().withSession( session -> session - .createSelectionQuery( "from Book where title like :title", Book.class ) - .setParameter( "title", "%Hibernate%" ) - .setOrder( asc( title ) ) - .getResultList() - .thenAccept( list -> assertThat( list ).containsExactly( book1, book2 ) ) - .thenCompose( v -> session - .createSelectionQuery( "from Book where title like :title", Book.class ) - .setParameter( "title", "%Hibernate%" ) - .setOrder( desc( title ) ).getResultList() - .thenAccept( isbnDesc -> assertThat( isbnDesc ).containsExactly( book2, book1 ) ) - ) - ) ); - } - - @Test - public void testAscDescWithNamedParamMutiny(VertxTestContext context) { - test( context, getMutinySessionFactory().withSession( session -> session - .createSelectionQuery( "from Book where title like :title", Book.class ) - .setParameter( "title", "%Hibernate%" ) - .setOrder( asc( title ) ) - .getResultList() - .invoke( list -> assertThat( list ).containsExactly( book1, book2 ) ) - .chain( v -> session - .createSelectionQuery( "from Book where title like :title", Book.class ) - .setParameter( "title", "%Hibernate%" ) - .setOrder( desc( title ) ) - .getResultList() - .invoke( isbnDesc -> assertThat( isbnDesc ).containsExactly( book2, book1 ) ) - ) - ) ); - } - - @Test - public void testAscDescWithPositionalParam(VertxTestContext context) { - test( context, getSessionFactory().withSession( session -> session - .createSelectionQuery( "from Book where title like :title", Book.class ) - .setParameter( "title", "%Hibernate%" ) - .setOrder( asc( title ) ) - .getResultList() - .thenAccept( list -> assertThat( list ).containsExactly( book1, book2 ) ) - .thenCompose( v -> session - .createSelectionQuery( "from Book where title like :title", Book.class ) - .setParameter( "title", "%Hibernate%" ) - .setOrder( desc( title ) ) - .getResultList() - .thenAccept( isbnDesc -> assertThat( isbnDesc ).containsExactly( book2, book1 ) ) - ) - ) ); - } - - @Test - public void testAscDescWithPositionalParamMutiny(VertxTestContext context) { - test( context, getMutinySessionFactory().withSession( session -> session - .createSelectionQuery( "from Book where title like :title", Book.class ) - .setParameter( "title", "%Hibernate%" ) - .setOrder( asc( title ) ) - .getResultList() - .invoke( list -> assertThat( list ).containsExactly( book1, book2 ) ) - .chain( v -> session - .createSelectionQuery( "from Book where title like :title", Book.class ) - .setParameter( "title", "%Hibernate%" ) - .setOrder( desc( title ) ) - .getResultList() - .invoke( isbnDesc -> assertThat( isbnDesc ).containsExactly( book2, book1 ) ) - ) - ) ); - } - - private void assertOrderByBookArray(List resultList, Book... expectedBooks) { - List books = resultList.stream() - .map( objects -> new Book( (String) objects[0], (String) objects[1] ) ) - .collect( toList() ); - assertThat( books ).containsExactly( expectedBooks ); - } - - private SingularAttribute attribute(String name) { - MappingMetamodelImpl metamodel = (MappingMetamodelImpl) getSessionFactory().getMetamodel(); - EntityDomainType bookType = metamodel.getJpaMetamodel().findEntityType( Book.class ); - return bookType.findSingularAttribute( name ); - } - - @Entity(name = "Book") - @Table(name = "OrderTest_Book" ) - static class Book { - @Id - String isbn; - String title; - - Book(String isbn, String title) { - this.isbn = isbn; - this.title = title; - } - - Book() { - } - - @Override - public boolean equals(Object o) { - if ( this == o ) { - return true; - } - if ( o == null || getClass() != o.getClass() ) { - return false; - } - Book book = (Book) o; - return Objects.equals( isbn, book.isbn ) && Objects.equals( - title, - book.title - ); - } - - @Override - public int hashCode() { - return Objects.hash( isbn, title ); - } - - @Override - public String toString() { - return isbn + ":" + title; - } - } -} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QuerySpecificationTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QuerySpecificationTest.java new file mode 100644 index 000000000..01f85f35e --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QuerySpecificationTest.java @@ -0,0 +1,423 @@ +/* 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.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl; +import org.hibernate.query.Order; +import org.hibernate.query.restriction.Restriction; +import org.hibernate.query.specification.MutationSpecification; +import org.hibernate.query.specification.SelectionSpecification; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.TypedQueryReference; +import jakarta.persistence.metamodel.SingularAttribute; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.query.Order.asc; +import static org.hibernate.query.Order.desc; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * Test for queries created using {@link SelectionSpecification}. + */ +@Timeout(value = 10, timeUnit = MINUTES) +public class QuerySpecificationTest extends BaseReactiveTest { + static final Book hibBook = new Book( 1L, "Hibernate in Action" ); + static final Book jpBook = new Book( 3L, "Java Persistence with Hibernate" ); + static final Book animalFarmBook = new Book( 2L, "Animal Farm" ); + + // This is only added when testing order with multiple columns + static final Book animalFarmBook2 = new Book( 4L, "Animal Farm" ); + + @Override + protected Collection> annotatedEntities() { + return List.of( Book.class ); + } + + @BeforeEach + public void populateDB(VertxTestContext context) { + test( context, getSessionFactory().withTransaction( session -> session + .persist( hibBook, jpBook, animalFarmBook ) ) + ); + } + + static Stream singleColumnOrderExpectation() { + return Stream.of( + arguments( asc( Book.class, "title" ), List.of( animalFarmBook, hibBook, jpBook ) ), + arguments( desc( Book.class, "title" ), List.of( jpBook, hibBook, animalFarmBook ) ), + arguments( asc( Book.class, "isbn" ), List.of( hibBook, animalFarmBook, jpBook ) ), + arguments( desc( Book.class, "isbn" ), List.of( jpBook, animalFarmBook, hibBook ) ) + ); + } + + @ParameterizedTest + @MethodSource("singleColumnOrderExpectation") + public void singleColumnOrderWithStage(Order order, List expectedList, VertxTestContext context) { + var bookReference = SelectionSpecification + .create( Book.class, "from Book" ) + .sort( order ) + .reference(); + + test( context, getSessionFactory() + .withSession( session -> session + .createQuery( bookReference ) + .getResultList() ) + .thenAccept( books -> assertThat( books ).isEqualTo( expectedList ) ) + ); + } + + @ParameterizedTest + @MethodSource("singleColumnOrderExpectation") + public void singleColumnOrderWithStageStateless(Order order, List expectedList, VertxTestContext context) { + var bookReference = SelectionSpecification + .create( Book.class, "from Book" ) + .sort( order ) + .reference(); + + test( context, getSessionFactory() + .withStatelessSession( session -> session + .createQuery( bookReference ) + .getResultList() ) + .thenAccept( books -> assertThat( books ).isEqualTo( expectedList ) ) + ); + } + + @ParameterizedTest + @MethodSource("singleColumnOrderExpectation") + public void singleColumnOrderWithMutiny(Order order, List expectedList, VertxTestContext context) { + var bookReference = SelectionSpecification + .create( Book.class, "from Book" ) + .sort( order ) + .reference(); + + test( context, getMutinySessionFactory() + .withSession( session -> session + .createQuery( bookReference ) + .getResultList() ) + .invoke( books -> assertThat( books ).isEqualTo( expectedList ) ) + ); + } + + @ParameterizedTest + @MethodSource("singleColumnOrderExpectation") + public void singleColumnOrderWithMutinyStateless(Order order, List expectedList, VertxTestContext context) { + var bookReference = SelectionSpecification + .create( Book.class, "from Book" ) + .sort( order ) + .reference(); + + test( context, getMutinySessionFactory() + .withStatelessSession( session -> session + .createQuery( bookReference ) + .getResultList() ) + .invoke( books -> assertThat( books ).isEqualTo( expectedList ) ) + ); + } + + @ParameterizedTest + @MethodSource("singleColumnOrderExpectation") + public void singleAttributeOrderWithStage(Order order, List expectedList, VertxTestContext context) { + final SingularAttribute attribute = attribute( order.attributeName() ); + var bookReference = SelectionSpecification + .create( Book.class, "from Book" ) + .sort( Order.by( attribute, order.direction() ) ) + .reference(); + + test( context, getSessionFactory() + .withSession( session -> session + .createQuery( bookReference ) + .getResultList() ) + .thenAccept( books -> assertThat( books ).isEqualTo( expectedList ) ) + ); + } + + @ParameterizedTest + @MethodSource("singleColumnOrderExpectation") + public void singleAttributeOrderWithStageStateless(Order order, List expectedList, VertxTestContext context) { + final SingularAttribute attribute = attribute( order.attributeName() ); + var bookReference = SelectionSpecification + .create( Book.class, "from Book" ) + .sort( Order.by( attribute, order.direction() ) ) + .reference(); + + test( context, getSessionFactory() + .withStatelessSession( session -> session + .createQuery( bookReference ) + .getResultList() ) + .thenAccept( books -> assertThat( books ).isEqualTo( expectedList ) ) + ); + } + + @ParameterizedTest + @MethodSource("singleColumnOrderExpectation") + public void singleAttributeOrderWithMutiny(Order order, List expectedList, VertxTestContext context) { + final SingularAttribute attribute = attribute( order.attributeName() ); + var bookReference = SelectionSpecification + .create( Book.class, "from Book" ) + .sort( Order.by( attribute, order.direction() ) ) + .reference(); + + test( context, getMutinySessionFactory() + .withSession( session -> session + .createQuery( bookReference ) + .getResultList() ) + .invoke( books -> assertThat( books ).isEqualTo( expectedList ) ) + ); + } + + @ParameterizedTest + @MethodSource("singleColumnOrderExpectation") + public void singleAttributeOrderWithMutinyStateless(Order order, List expectedList, VertxTestContext context) { + final SingularAttribute attribute = attribute( order.attributeName() ); + var bookReference = SelectionSpecification + .create( Book.class, "from Book" ) + .sort( Order.by( attribute, order.direction() ) ) + .reference(); + + test( context, getMutinySessionFactory() + .withStatelessSession( session -> session + .createQuery( bookReference ) + .getResultList() ) + .invoke( books -> assertThat( books ).isEqualTo( expectedList ) ) + ); + } + + @ParameterizedTest + @MethodSource("multipleColumnOrderExpectation") + public void multipleColumnsOrderWithStage(List> orders, List expectedList, VertxTestContext context) { + var columnsSpec = SelectionSpecification + .create( Book.class, "from Book" ); + for ( Order order : orders ) { + columnsSpec.sort( order ); + } + + test( context, getSessionFactory() + .withTransaction( session -> session.persist( animalFarmBook2 ) ) + .thenCompose( v -> getSessionFactory().withSession( session -> session + .createQuery( columnsSpec.reference() ) + .getResultList() ) ) + .thenAccept( list -> assertThat( list ).isEqualTo( expectedList ) ) + ); + } + + static Stream multipleColumnOrderExpectation() { + return Stream.of( + arguments( + List.of( asc( Book.class, "title" ), asc( Book.class, "isbn" ) ), + List.of( animalFarmBook, animalFarmBook2, hibBook, jpBook ) + ), + arguments( + List.of( asc( Book.class, "title" ), desc( Book.class, "isbn" ) ), + List.of( animalFarmBook2, animalFarmBook, hibBook, jpBook ) + ), + arguments( + List.of( desc( Book.class, "isbn" ), asc( Book.class, "title" ) ), + List.of( animalFarmBook2, jpBook, animalFarmBook, hibBook ) + ), + arguments( + List.of( desc( Book.class, "isbn" ), desc( Book.class, "title" ) ), + List.of( animalFarmBook2, jpBook, animalFarmBook, hibBook ) + ) + ); + } + + @ParameterizedTest + @MethodSource("multipleColumnOrderExpectation") + public void multipleColumnsOrderWithStageStateless(List> orders, List expectedList, VertxTestContext context) { + var columnsSpec = SelectionSpecification + .create( Book.class, "from Book" ); + for ( Order order : orders ) { + columnsSpec.sort( order ); + } + + test( context, getSessionFactory() + .withTransaction( session -> session.persist( animalFarmBook2 ) ) + .thenCompose( v -> getSessionFactory().withStatelessSession( session -> session + .createQuery( columnsSpec.reference() ) + .getResultList() ) ) + .thenAccept( list -> assertThat( list ).isEqualTo( expectedList ) ) + ); + } + + @ParameterizedTest + @MethodSource("multipleColumnOrderExpectation") + public void multipleColumnsOrderWithMutiny(List> orders, List expectedList, VertxTestContext context) { + var columnsSpec = SelectionSpecification + .create( Book.class, "from Book" ); + for ( Order order : orders ) { + columnsSpec.sort( order ); + } + + test( context, getMutinySessionFactory() + .withTransaction( session -> session.persist( animalFarmBook2 ) ) + .chain( v -> getMutinySessionFactory().withSession( session -> session + .createQuery( columnsSpec.reference() ) + .getResultList() ) ) + .invoke( list -> assertThat( list ).isEqualTo( expectedList ) ) + ); + } + + @ParameterizedTest + @MethodSource("multipleColumnOrderExpectation") + public void multipleColumnsOrderWithMutinyStateless(List> orders, List expectedList, VertxTestContext context) { + var columnsSpec = SelectionSpecification + .create( Book.class, "from Book" ); + for ( Order order : orders ) { + columnsSpec.sort( order ); + } + + test( context, getMutinySessionFactory() + .withTransaction( session -> session.persist( animalFarmBook2 ) ) + .chain( v -> getMutinySessionFactory().withStatelessSession( session -> session + .createQuery( columnsSpec.reference() ) + .getResultList() ) ) + .invoke( list -> assertThat( list ).isEqualTo( expectedList ) ) + ); + } + + @Test + public void mutationSpecificationWithStage(VertxTestContext context) { + SingularAttribute title = (SingularAttribute) attribute( "title" ); + TypedQueryReference deleteAnimalFarm = MutationSpecification + .create( Book.class, "delete Book" ) + .restrict( Restriction.equalIgnoringCase( title, animalFarmBook.title ) ) + .reference(); + + test( context, getSessionFactory() + .withTransaction( session -> session.persist( animalFarmBook2 ) ) + .thenCompose( v -> getSessionFactory().withTransaction( session -> session + .createQuery( deleteAnimalFarm ) + .executeUpdate() ) ) + .thenAccept( deleted -> assertThat( deleted ).isEqualTo( 2 ) ) + .thenCompose( v -> getSessionFactory().withSession( session -> session + .createQuery( "from Book", Book.class ).getResultList() ) ) + .thenAccept( list -> assertThat( list ).containsExactlyInAnyOrder( hibBook, jpBook ) ) + ); + } + + @Test + public void mutationSpecificationWithStageStateless(VertxTestContext context) { + SingularAttribute title = (SingularAttribute) attribute( "title" ); + TypedQueryReference deleteAnimalFarm = MutationSpecification + .create( Book.class, "delete Book" ) + .restrict( Restriction.equalIgnoringCase( title, animalFarmBook.title ) ) + .reference(); + + test( context, getSessionFactory() + .withTransaction( session -> session.persist( animalFarmBook2 ) ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction( session -> session + .createQuery( deleteAnimalFarm ) + .executeUpdate() ) ) + .thenAccept( deleted -> assertThat( deleted ).isEqualTo( 2 ) ) + .thenCompose( v -> getSessionFactory().withSession( session -> session + .createQuery( "from Book", Book.class ).getResultList() ) ) + .thenAccept( list -> assertThat( list ).containsExactlyInAnyOrder( hibBook, jpBook ) ) + ); + } + + @Test + public void mutationSpecificationWithMutiny(VertxTestContext context) { + SingularAttribute title = (SingularAttribute) attribute( "title" ); + TypedQueryReference deleteAnimalFarm = MutationSpecification + .create( Book.class, "delete Book" ) + .restrict( Restriction.equalIgnoringCase( title, animalFarmBook.title ) ) + .reference(); + + test( context, getMutinySessionFactory() + .withTransaction( session -> session.persist( animalFarmBook2 ) ) + .chain( v -> getMutinySessionFactory().withTransaction( session -> session + .createQuery( deleteAnimalFarm ) + .executeUpdate() ) ) + .invoke( deleted -> assertThat( deleted ).isEqualTo( 2 ) ) + .chain( () -> getMutinySessionFactory().withSession( session -> session + .createQuery( "from Book", Book.class ).getResultList() ) ) + .invoke( list -> assertThat( list ).containsExactlyInAnyOrder( hibBook, jpBook ) ) + ); + } + + @Test + public void mutationSpecificationWithMutinyStateless(VertxTestContext context) { + SingularAttribute title = (SingularAttribute) attribute( "title" ); + TypedQueryReference deleteAnimalFarm = MutationSpecification + .create( Book.class, "delete Book" ) + .restrict( Restriction.equalIgnoringCase( title, animalFarmBook.title ) ) + .reference(); + + test( context, getMutinySessionFactory() + .withStatelessTransaction( session -> session.insert( animalFarmBook2 ) ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction( session -> session + .createQuery( deleteAnimalFarm ) + .executeUpdate() ) ) + .invoke( deleted -> assertThat( deleted ).isEqualTo( 2 ) ) + .chain( () -> getMutinySessionFactory().withStatelessSession( session -> session + .createQuery( "from Book", Book.class ).getResultList() ) ) + .invoke( list -> assertThat( list ).containsExactlyInAnyOrder( hibBook, jpBook ) ) + ); + } + + private SingularAttribute attribute(String name) { + MappingMetamodelImpl metamodel = (MappingMetamodelImpl) getSessionFactory().getMetamodel(); + EntityDomainType bookType = metamodel.getJpaMetamodel().findEntityType( Book.class ); + return bookType.findSingularAttribute( name ); + } + + @Entity(name = "Book") + @Table(name = "OrderTest_Book" ) + public static class Book { + @Id + Long isbn; + String title; + + Book(Long isbn, String title) { + this.isbn = isbn; + this.title = title; + } + + Book() { + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Book book = (Book) o; + return Objects.equals( isbn, book.isbn ) && Objects.equals( + title, + book.title + ); + } + + @Override + public int hashCode() { + return Objects.hash( isbn, title ); + } + + @Override + public String toString() { + return isbn + ":" + title; + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java index 78494bd1c..ed0c724f1 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java @@ -32,12 +32,7 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; @Timeout(value = 10, timeUnit = MINUTES) @@ -969,6 +964,42 @@ context, openSession() ); } + @Test + public void testCurrentSession(VertxTestContext context) { + test( context, + getSessionFactory().withSession(session -> + getSessionFactory().withSession(s -> { + assertEquals(session, s); + Stage.Session currentSession = getSessionFactory().getCurrentSession(); + assertNotNull(currentSession); + assertTrue(currentSession.isOpen()); + assertEquals(session, currentSession); + return CompletionStages.voidFuture(); + }) + .thenAccept(v -> assertNotNull(getSessionFactory().getCurrentSession())) + ) + .thenAccept(v -> assertNull(getSessionFactory().getCurrentSession())) + ); + } + + @Test + public void testCurrentStatelessSession(VertxTestContext context) { + test( context, + getSessionFactory().withStatelessSession(session -> + getSessionFactory().withStatelessSession(s -> { + assertEquals(session, s); + Stage.StatelessSession currentSession = getSessionFactory().getCurrentStatelessSession(); + assertNotNull(currentSession); + assertTrue(currentSession.isOpen()); + assertEquals(session, currentSession); + return CompletionStages.voidFuture(); + }) + .thenAccept(v -> assertNotNull(getSessionFactory().getCurrentStatelessSession())) + ) + .thenAccept(v -> assertNull(getSessionFactory().getCurrentStatelessSession())) + ); + } + private void assertThatPigsAreEqual(GuineaPig expected, GuineaPig actual) { assertNotNull( actual ); assertEquals( expected.getId(), actual.getId() ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessWithBatchTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessWithBatchTest.java index a30cc1364..38e386bf2 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessWithBatchTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessWithBatchTest.java @@ -364,7 +364,7 @@ public void testStageBatchingUpdate(VertxTestContext context) { .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s .createQuery( "from GuineaPig p order by p.id", GuineaPig.class ) .getResultList() - .thenApply( pigs -> { + .thenCompose( pigs -> { pigs.get( 0 ).setName( "One updated" ); pigs.get( 1 ).setName( "Two updated" ); return s.update( 10, pigs.toArray() ); @@ -383,7 +383,7 @@ public void testStageBatchingUpdateMultiple(VertxTestContext context) { .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s .createQuery( "from GuineaPig p order by p.id", GuineaPig.class ) .getResultList() - .thenApply( pigs -> { + .thenCompose( pigs -> { pigs.get( 0 ).setName( "One updated" ); pigs.get( 1 ).setName( "Two updated" ); return s.updateMultiple( pigs ); @@ -402,7 +402,7 @@ public void testStageBatchingUpdateNoBatchSizeParameter(VertxTestContext context .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s .createQuery( "from GuineaPig p order by p.id", GuineaPig.class ) .getResultList() - .thenApply( pigs -> { + .thenCompose( pigs -> { pigs.get( 0 ).setName( "One updated" ); pigs.get( 1 ).setName( "Two updated" ); return s.update( pigs.get( 0 ), pigs.get( 1 ) ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/StandAloneReactiveTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/StandAloneReactiveTest.java deleted file mode 100644 index 29ef9de85..000000000 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/StandAloneReactiveTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* 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.hibernate.boot.MetadataSources; -import org.hibernate.boot.registry.StandardServiceRegistry; -import org.hibernate.dialect.PostgreSQLDialect; -import org.hibernate.reactive.provider.Settings; -import org.hibernate.reactive.provider.ReactiveServiceRegistryBuilder; -import org.hibernate.reactive.stage.Stage; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class StandAloneReactiveTest { - - @Test - public void createReactiveSessionFactory() { - StandardServiceRegistry registry = new ReactiveServiceRegistryBuilder() - .applySetting( Settings.TRANSACTION_COORDINATOR_STRATEGY, "jta" ) - .applySetting( Settings.DIALECT, PostgreSQLDialect.class.getName() ) - .applySetting( Settings.URL, "jdbc:postgresql://localhost/hreact?user=none" ) - .build(); - - Stage.SessionFactory factory = new MetadataSources( registry ) - .buildMetadata() - .getSessionFactoryBuilder() - .build() - .unwrap( Stage.SessionFactory.class ); - - assertThat( factory ).isNotNull(); - } -} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/TenantDependentPool.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/TenantDependentPool.java index 70b3a1ba6..4dd18b30e 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/TenantDependentPool.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/TenantDependentPool.java @@ -7,16 +7,12 @@ import java.net.URI; import java.util.Map; -import java.util.function.Function; import java.util.stream.Collectors; import org.hibernate.reactive.MyCurrentTenantIdentifierResolver.Tenant; import org.hibernate.reactive.pool.impl.DefaultSqlClientPool; -import io.vertx.core.AsyncResult; -import io.vertx.core.Context; import io.vertx.core.Future; -import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.sqlclient.Pool; import io.vertx.sqlclient.PoolOptions; @@ -58,8 +54,18 @@ protected Pool createPool(URI uri, SqlConnectOptions connectOptions, PoolOptions return pools; } - private Pool createPool(URI uri, SqlConnectOptions connectOptions, PoolOptions poolOptions, Vertx vertx, Tenant tenant) { - return super.createPool( changeDbName( uri, tenant ), changeDbName( connectOptions, tenant ), poolOptions, vertx ); + private Pool createPool( + URI uri, + SqlConnectOptions connectOptions, + PoolOptions poolOptions, + Vertx vertx, + Tenant tenant) { + return super.createPool( + changeDbName( uri, tenant ), + changeDbName( connectOptions, tenant ), + poolOptions, + vertx + ); } /** @@ -100,11 +106,6 @@ public Pool getTenantPool(Tenant tenantId) { return poolMap.get( tenantId ); } - @Override - public void getConnection(Handler> handler) { - poolMap.get( defaultTenantId ).getConnection( handler ); - } - @Override public Future getConnection() { return poolMap.get( defaultTenantId ).getConnection(); @@ -125,21 +126,6 @@ public PreparedQuery> preparedQuery(String sql, PrepareOptions optio return poolMap.get( defaultTenantId ).preparedQuery( sql, options ); } - @Override - public void close(Handler> handler) { - poolMap.forEach( (tenant, pool) -> pool.close( handler ) ); - } - - @Override - public Pool connectHandler(Handler handler) { - return poolMap.get( defaultTenantId ).connectHandler( handler ); - } - - @Override - public Pool connectionProvider(Function> provider) { - return poolMap.get( defaultTenantId ).connectionProvider( provider ); - } - @Override public int size() { return poolMap.get( defaultTenantId ).size(); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/TimestampTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/TimestampTest.java index 8a6f631fc..2d6665982 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/TimestampTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/TimestampTest.java @@ -6,6 +6,7 @@ package org.hibernate.reactive; import java.time.Instant; +import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.Collection; import java.util.List; @@ -18,6 +19,9 @@ import io.vertx.junit5.Timeout; import io.vertx.junit5.VertxTestContext; import jakarta.persistence.Basic; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; @@ -28,12 +32,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; @Timeout(value = 10, timeUnit = MINUTES) - public class TimestampTest extends BaseReactiveTest { @Override protected Collection> annotatedEntities() { - return List.of( Record.class ); + return List.of( Record.class, Event.class ); } @Test @@ -56,6 +59,30 @@ public void test(VertxTestContext context) { ); } + @Test + public void testEmbedded(VertxTestContext context) { + Event event = new Event(); + History history = new History(); + event.name = "Concert"; + test( context, getMutinySessionFactory() + .withSession( session -> session.persist( event ) + .chain( session::flush ) + .invoke( () -> { + history.created = event.history.created; + history.updated = event.history.updated; + assertEquals( + event.history.created.truncatedTo( ChronoUnit.HOURS ), + event.history.updated.truncatedTo( ChronoUnit.HOURS ) + ); }) + .invoke( () -> event.name = "Conference" ) + .chain( session::flush ) + .invoke( () -> assertInstants( event, history ) ) ) + .chain( () -> getMutinySessionFactory().withSession( session -> session + .find( Record.class, event.id ) ) ) + .invoke( r -> assertInstants( event, history ) ) + ); + } + private static void assertInstants(Record r) { assertNotNull( r.created ); assertNotNull( r.updated ); @@ -66,6 +93,18 @@ private static void assertInstants(Record r) { ); } + private static void assertInstants(Event e, History h) { + assertNotNull( e.history.created ); + assertNotNull( e.history.updated ); + // Sometimes, when the test suite is fast enough, they might be the same: + assertTrue( + !e.history.updated.isBefore( e.history.created ), + "Updated instant is before created. Updated[" + e.history.updated + "], Created[" + e.history.created + "]" + ); + assertEquals( h.created, e.history.created ); + + } + @Entity(name = "Record") static class Record { @GeneratedValue @@ -78,4 +117,30 @@ static class Record { @UpdateTimestamp Instant updated; } + + @Entity(name = "Event") + static class Event { + + @Id + @GeneratedValue + public Long id; + + public String name; + + @Embedded + public History history; + + } + + @Embeddable + static class History { + @Column + @CreationTimestamp + public LocalDateTime created; + + @Column + @UpdateTimestamp + public LocalDateTime updated; + + } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java index 52dc52a72..8dcc07a6a 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java @@ -104,34 +104,6 @@ public void testMutinyUpsert(VertxTestContext context) { ); } - @Test - public void testMutinyUpsertWithEntityName(VertxTestContext context) { - test( context, getMutinySessionFactory().withStatelessTransaction( ss -> ss - .upsert( Record.class.getName(), new Record( 123L, "hello earth" ) ) - .call( () -> ss.upsert( Record.class.getName(), new Record( 456L, "hello mars" ) ) ) - .invoke( this::assertQueries ) - ) - .call( v -> getMutinySessionFactory().withStatelessTransaction( ss -> ss - .createSelectionQuery( "from Record order by id", Record.class ).getResultList() ) - .invoke( results -> assertThat( results ).containsExactly( - new Record( 123L, "hello earth" ), - new Record( 456L, "hello mars" ) - ) ) - ) - .call( () -> getMutinySessionFactory().withStatelessTransaction( ss -> ss - .upsert( Record.class.getName(), new Record( 123L, "goodbye earth" ) ) - ) ) - .invoke( this::assertQueries ) - .call( v -> getMutinySessionFactory().withStatelessTransaction( ss -> ss - .createSelectionQuery( "from Record order by id", Record.class ).getResultList() ) - .invoke( results -> assertThat( results ).containsExactly( - new Record( 123L, "goodbye earth" ), - new Record( 456L, "hello mars" ) - ) ) - ) - ); - } - @Test public void testStageUpsert(VertxTestContext context) { test( context, getSessionFactory().withStatelessTransaction( ss -> ss @@ -160,34 +132,6 @@ public void testStageUpsert(VertxTestContext context) { ); } - @Test - public void testStageUpsertWithEntityName(VertxTestContext context) { - test( context, getSessionFactory().withStatelessTransaction( ss -> ss - .upsert( Record.class.getName(), new Record( 123L, "hello earth" ) ) - .thenCompose( v -> ss.upsert( Record.class.getName(), new Record( 456L, "hello mars" ) ) ) - ) - .thenAccept( v -> this.assertQueries() ) - .thenCompose( v -> getSessionFactory().withStatelessTransaction( ss -> ss - .createSelectionQuery( "from Record order by id", Record.class ).getResultList() ) - .thenAccept( results -> assertThat( results ).containsExactly( - new Record( 123L, "hello earth" ), - new Record( 456L, "hello mars" ) - ) ) - ) - .thenCompose( v -> getSessionFactory().withStatelessTransaction( ss -> ss - .upsert( Record.class.getName(), new Record( 123L, "goodbye earth" ) ) - ) ) - .thenAccept( v -> this.assertQueries() ) - .thenCompose( v -> getSessionFactory().withStatelessTransaction( ss -> ss - .createSelectionQuery( "from Record order by id", Record.class ).getResultList() ) - .thenAccept( results -> assertThat( results ).containsExactly( - new Record( 123L, "goodbye earth" ), - new Record( 456L, "hello mars" ) - ) ) - ) - ); - } - private void assertQueries() { if ( hasMergeOperator() ) { assertThat( sqlTracker.getLoggedQueries() ).have( IS_USING_MERGE ); 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 3ec042a38..e1a0928d1 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.15" ) ) + public static final CockroachContainer cockroachDb = new CockroachContainer( imageName( "cockroachdb/cockroach", "v24.3.13" ) ) // 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/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DatabaseConfiguration.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DatabaseConfiguration.java index f0d497ee2..c0a081126 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DatabaseConfiguration.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DatabaseConfiguration.java @@ -5,6 +5,7 @@ */ package org.hibernate.reactive.containers; +import java.lang.reflect.Field; import java.util.Arrays; import java.util.Map; import java.util.Objects; @@ -12,6 +13,7 @@ import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.DatabaseVersion; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; @@ -28,15 +30,16 @@ public class DatabaseConfiguration { public static final boolean USE_DOCKER = Boolean.getBoolean("docker"); public enum DBType { - DB2( DB2Database.INSTANCE, 50000, "com.ibm.db2.jcc.DB2Driver", DB2Dialect.class ), - MYSQL( MySQLDatabase.INSTANCE, 3306, "com.mysql.cj.jdbc.Driver", MySQLDialect.class ), - MARIA( MariaDatabase.INSTANCE, 3306, "org.mariadb.jdbc.Driver", MariaDBDialect.class, "mariadb" ), - POSTGRESQL( PostgreSQLDatabase.INSTANCE, 5432, "org.postgresql.Driver", PostgreSQLDialect.class, "POSTGRES", "PG" ), - COCKROACHDB( CockroachDBDatabase.INSTANCE, 26257, "org.postgresql.Driver", CockroachDialect.class, "COCKROACH" ), - SQLSERVER( MSSQLServerDatabase.INSTANCE, 1433, "com.microsoft.sqlserver.jdbc.SQLServerDriver", SQLServerDialect.class, "MSSQL", "MSSQLSERVER" ), - ORACLE( OracleDatabase.INSTANCE, 1521, "oracle.jdbc.OracleDriver", OracleDialect.class ); + DB2( DB2Database.INSTANCE, "DB2", 50000, "com.ibm.db2.jcc.DB2Driver", DB2Dialect.class ), + MYSQL( MySQLDatabase.INSTANCE, "MySQL",3306, "com.mysql.cj.jdbc.Driver", MySQLDialect.class ), + MARIA( MariaDatabase.INSTANCE, "MariaDB",3306, "org.mariadb.jdbc.Driver", MariaDBDialect.class, "mariadb" ), + POSTGRESQL( PostgreSQLDatabase.INSTANCE, "PostgreSQL", 5432, "org.postgresql.Driver", PostgreSQLDialect.class, "POSTGRES", "PG" ), + COCKROACHDB( CockroachDBDatabase.INSTANCE, "CockroachDb", 26257, "org.postgresql.Driver", CockroachDialect.class, "COCKROACH" ), + SQLSERVER( MSSQLServerDatabase.INSTANCE, "Microsoft SQL Server", 1433, "com.microsoft.sqlserver.jdbc.SQLServerDriver", SQLServerDialect.class, "MSSQL", "MSSQLSERVER" ), + ORACLE( OracleDatabase.INSTANCE, "Oracle", 1521, "oracle.jdbc.OracleDriver", OracleDialect.class ); private final TestableDatabase configuration; + private final String productName; private final int defaultPort; // A list of alternative names that can be used to select the db @@ -47,8 +50,9 @@ public enum DBType { private final Class dialect; - DBType(TestableDatabase configuration, int defaultPort, String jdbcDriver, Class dialect, String... aliases) { + DBType(TestableDatabase configuration, String productName, int defaultPort, String jdbcDriver, Class dialect, String... aliases) { this.configuration = configuration; + this.productName = productName; this.defaultPort = defaultPort; this.aliases = aliases; this.dialect = dialect; @@ -81,6 +85,10 @@ public static DBType fromString(String dbName) { .toString( DBType.values() ) ); } + public String getProductName() { + return productName; + } + public int getDefaultPort() { return defaultPort; } @@ -92,6 +100,24 @@ public String getJdbcDriver() { public Class getDialectClass() { return dialect; } + + /** + * The minimum version of the database supported by the dialect. + *

    + * We use reflection because it's not accessible from the tests. + * Copied from MetadataAccessTests in Hibernate ORM. + *

    + */ + public DatabaseVersion getMinimumVersion() { + try { + Field field = dialect.getDeclaredField( "MINIMUM_VERSION" ); + field.setAccessible( true ); + return (DatabaseVersion) field.get( null ); + } + catch (IllegalAccessException | NoSuchFieldException e) { + throw new RuntimeException( "Error extracting 'MINIMUM_VERSION' from '" + dialect + "'", e ); + } + } } public static final String USERNAME = "hreact"; @@ -101,7 +127,7 @@ public Class getDialectClass() { private static DBType dbType; public static DBType dbType() { - if (dbType == null) { + if ( dbType == null ) { String dbTypeString = System.getProperty( "db", DBType.POSTGRESQL.name() ); dbType = DBType.fromString( dbTypeString ); System.out.println( "Using database type: " + dbType.name() ); @@ -131,5 +157,4 @@ public static String expectedDatatype(Class dataType) { private DatabaseConfiguration() { } - } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java index a636bc5a9..d57f9103a 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java @@ -57,10 +57,10 @@ class OracleDatabase implements TestableDatabase { expectedDBTypeForClass.put( Integer.class, "NUMBER" ); expectedDBTypeForClass.put( long.class, "NUMBER" ); expectedDBTypeForClass.put( Long.class, "NUMBER" ); - expectedDBTypeForClass.put( float.class, "FLOAT" ); - expectedDBTypeForClass.put( Float.class, "FLOAT" ); - expectedDBTypeForClass.put( double.class, "FLOAT" ); - expectedDBTypeForClass.put( Double.class, "FLOAT" ); + expectedDBTypeForClass.put( float.class, "BINARY_FLOAT" ); + expectedDBTypeForClass.put( Float.class, "BINARY_FLOAT" ); + expectedDBTypeForClass.put( double.class, "BINARY_DOUBLE" ); + expectedDBTypeForClass.put( Double.class, "BINARY_DOUBLE" ); expectedDBTypeForClass.put( byte.class, "NUMBER" ); expectedDBTypeForClass.put( Byte.class, "NUMBER" ); expectedDBTypeForClass.put( URL.class, "VARCHAR2" ); 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 5b427f4e7..56ef4f878 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", "17.4" ) ) + public static final PostgreSQLContainer postgresql = new PostgreSQLContainer<>( imageName( "postgres", "17.5" ) ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteSessionDelegator.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteSessionDelegator.java new file mode 100644 index 000000000..2c93d9226 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteSessionDelegator.java @@ -0,0 +1,17 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.delegation; + +import org.hibernate.reactive.mutiny.Mutiny; +import org.hibernate.reactive.mutiny.delegation.MutinySessionDelegator; + +@SuppressWarnings("unused") +class ConcreteSessionDelegator extends MutinySessionDelegator { + @Override + public Mutiny.Session delegate() { + throw new UnsupportedOperationException(); + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteStatelessSessionDelegator.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteStatelessSessionDelegator.java new file mode 100644 index 000000000..e275d0180 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteStatelessSessionDelegator.java @@ -0,0 +1,17 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.delegation; + +import org.hibernate.reactive.mutiny.Mutiny; +import org.hibernate.reactive.mutiny.delegation.MutinyStatelessSessionDelegator; + +@SuppressWarnings("unused") +class ConcreteStatelessSessionDelegator extends MutinyStatelessSessionDelegator { + @Override + public Mutiny.StatelessSession delegate() { + throw new UnsupportedOperationException(); + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java index 3132b8498..56def4ef9 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java @@ -34,6 +34,7 @@ import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MARIA; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MYSQL; +import static org.hibernate.reactive.testing.ReactiveAssertions.assertThrown; import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.GROUPED; import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.INDIVIDUALLY; import static org.junit.jupiter.params.provider.Arguments.arguments; @@ -126,13 +127,8 @@ context, setupFactory( strategy, type ) validateConf.addAnnotatedClass( BasicTypesTestEntity.class ); // The table mapping this entity shouldn't be in the db validateConf.addAnnotatedClass( Extra.class ); - return setupSessionFactory( validateConf ) - .handle( (unused, throwable) -> { - assertThat( throwable ) - .isInstanceOf( SchemaManagementException.class ) - .hasMessage( errorMessage ); - return null; - } ); + return assertThrown( SchemaManagementException.class, setupSessionFactory( validateConf ) ) + .thenAccept( throwable -> assertThat( throwable ).hasMessage( errorMessage ) ); } ) ); } diff --git a/integration-tests/bytecode-enhancements-it/build.gradle b/integration-tests/bytecode-enhancements-it/build.gradle index 177deae9c..071745993 100644 --- a/integration-tests/bytecode-enhancements-it/build.gradle +++ b/integration-tests/bytecode-enhancements-it/build.gradle @@ -17,7 +17,7 @@ description = 'Bytecode enhancements integration tests' ext { log4jVersion = '2.20.0' - assertjVersion = '3.26.3' + assertjVersion = '3.27.3' } dependencies { @@ -30,7 +30,7 @@ dependencies { runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" // Allow authentication to PostgreSQL using SCRAM: - runtimeOnly 'com.ongres.scram:client:2.1' + runtimeOnly 'com.ongres.scram:scram-client:3.1' // logging runtimeOnly "org.apache.logging.log4j:log4j-core:${log4jVersion}" 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 ff9f5e601..70251e20e 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 @@ -7,7 +7,6 @@ import java.util.Collection; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -20,6 +19,7 @@ import org.hibernate.reactive.provider.ReactiveServiceRegistryBuilder; import org.hibernate.reactive.provider.Settings; import org.hibernate.reactive.stage.Stage; +import org.hibernate.reactive.util.impl.CompletionStages; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; @@ -29,7 +29,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.smallrye.mutiny.Uni; -import io.vertx.core.Promise; import io.vertx.core.VertxOptions; import io.vertx.junit5.RunTestOnContext; import io.vertx.junit5.VertxExtension; @@ -53,7 +52,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:17.4" ) + .parse( "docker.io/postgres:17.5" ) .asCompatibleSubstituteFor( "postgres" ); public static final String USERNAME = "hreact"; @@ -73,7 +72,7 @@ public abstract class BaseReactiveIT { * Configure Vertx JUnit5 test context */ @RegisterExtension - static RunTestOnContext testOnContext = new RunTestOnContext( vertxOptions() ); + static RunTestOnContext testOnContext = new RunTestOnContext( vertxOptions(), false ); private static VertxOptions vertxOptions() { return new VertxOptions() @@ -202,33 +201,19 @@ protected CompletionStage setupSessionFactory(Configuration configuration) * @return a {@link CompletionStage} void that succeeds when the factory is ready. */ protected CompletionStage setupSessionFactory(Supplier confSupplier) { - CompletableFuture future = new CompletableFuture<>(); - testOnContext.vertx() + return testOnContext.vertx() .executeBlocking( // schema generation is a blocking operation and so it causes an // exception when run on the Vert.x event loop. So call it using // Vertx.executeBlocking() - promise -> startFactoryManager( promise, confSupplier ), - event -> { - if ( event.succeeded() ) { - future.complete( null ); - } - else { - future.completeExceptionally( event.cause() ); - } - } - ); - return future; + () -> startFactoryManager( confSupplier ), + true + ).toCompletionStage().thenCompose( CompletionStages::voidFuture ); } - private void startFactoryManager(Promise p, Supplier confSupplier) { - try { - ormSessionFactory = createHibernateSessionFactory( confSupplier.get() ); - p.complete(); - } - catch (Throwable e) { - p.fail( e ); - } + private Object startFactoryManager(Supplier confSupplier) { + ormSessionFactory = createHibernateSessionFactory( confSupplier.get() ); + return null; } private SessionFactory createHibernateSessionFactory(Configuration configuration) { diff --git a/integration-tests/hibernate-validator-postgres-it/build.gradle b/integration-tests/hibernate-validator-postgres-it/build.gradle index 8236f6509..abd6e2bc0 100644 --- a/integration-tests/hibernate-validator-postgres-it/build.gradle +++ b/integration-tests/hibernate-validator-postgres-it/build.gradle @@ -17,7 +17,7 @@ description = 'Quarkus QE integration tests' ext { log4jVersion = '2.20.0' - assertjVersion = '3.26.3' + assertjVersion = '3.27.3' } dependencies { @@ -32,7 +32,7 @@ dependencies { runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" // Allow authentication to PostgreSQL using SCRAM: - runtimeOnly 'com.ongres.scram:client:2.1' + runtimeOnly 'com.ongres.scram:scram-client:3.1' // logging runtimeOnly "org.apache.logging.log4j:log4j-core:${log4jVersion}" 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 afff092e9..f65358ce3 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 @@ -7,7 +7,6 @@ import java.util.Collection; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -20,6 +19,7 @@ import org.hibernate.reactive.provider.ReactiveServiceRegistryBuilder; import org.hibernate.reactive.provider.Settings; import org.hibernate.reactive.stage.Stage; +import org.hibernate.reactive.util.impl.CompletionStages; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; @@ -29,7 +29,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.smallrye.mutiny.Uni; -import io.vertx.core.Promise; import io.vertx.core.VertxOptions; import io.vertx.junit5.RunTestOnContext; import io.vertx.junit5.VertxExtension; @@ -53,7 +52,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:17.4" ) + .parse( "docker.io/postgres:17.5" ) .asCompatibleSubstituteFor( "postgres" ); public static final String USERNAME = "hreact"; @@ -73,7 +72,7 @@ public abstract class BaseReactiveIT { * Configure Vertx JUnit5 test context */ @RegisterExtension - static RunTestOnContext testOnContext = new RunTestOnContext( vertxOptions() ); + static RunTestOnContext testOnContext = new RunTestOnContext( vertxOptions(), false ); private static VertxOptions vertxOptions() { return new VertxOptions() @@ -202,33 +201,19 @@ protected CompletionStage setupSessionFactory(Configuration configuration) * @return a {@link CompletionStage} void that succeeds when the factory is ready. */ protected CompletionStage setupSessionFactory(Supplier confSupplier) { - CompletableFuture future = new CompletableFuture<>(); - testOnContext.vertx() + return testOnContext.vertx() .executeBlocking( // schema generation is a blocking operation and so it causes an // exception when run on the Vert.x event loop. So call it using // Vertx.executeBlocking() - promise -> startFactoryManager( promise, confSupplier ), - event -> { - if ( event.succeeded() ) { - future.complete( null ); - } - else { - future.completeExceptionally( event.cause() ); - } - } - ); - return future; + () -> startFactoryManager( confSupplier ), + true + ) + .toCompletionStage().thenCompose( CompletionStages::voidFuture ); } - private void startFactoryManager(Promise p, Supplier confSupplier) { - try { - ormSessionFactory = createHibernateSessionFactory( confSupplier.get() ); - p.complete(); - } - catch (Throwable e) { - p.fail( e ); - } + private Object startFactoryManager(Supplier confSupplier) { + return ormSessionFactory = createHibernateSessionFactory( confSupplier.get() ); } private SessionFactory createHibernateSessionFactory(Configuration configuration) { diff --git a/integration-tests/techempower-postgres-it/build.gradle b/integration-tests/techempower-postgres-it/build.gradle index 925aedfd8..603a60db5 100644 --- a/integration-tests/techempower-postgres-it/build.gradle +++ b/integration-tests/techempower-postgres-it/build.gradle @@ -14,7 +14,7 @@ description = 'TechEmpower integration tests' ext { jacksonDatabindVersion = '2.15.2' jbossLoggingVersion = '3.5.0.Final' - assertjVersion = '3.26.3' + assertjVersion = '3.27.3' vertxWebVersion = project.hasProperty( 'vertxWebVersion' ) ? project.property( 'vertxWebVersion' ) : vertxSqlClientVersion @@ -30,7 +30,7 @@ dependencies { runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" // The Pg client requires this dependency - runtimeOnly "com.ongres.scram:client:2.1" + runtimeOnly "com.ongres.scram:scram-client:3.1" runtimeOnly "com.fasterxml.jackson.core:jackson-databind:${jacksonDatabindVersion}" // logging diff --git a/integration-tests/techempower-postgres-it/src/main/java/org/hibernate/reactive/it/techempower/WorldVerticle.java b/integration-tests/techempower-postgres-it/src/main/java/org/hibernate/reactive/it/techempower/WorldVerticle.java index 4a12c216f..8b89e2d9d 100644 --- a/integration-tests/techempower-postgres-it/src/main/java/org/hibernate/reactive/it/techempower/WorldVerticle.java +++ b/integration-tests/techempower-postgres-it/src/main/java/org/hibernate/reactive/it/techempower/WorldVerticle.java @@ -45,19 +45,14 @@ public WorldVerticle(Supplier emfSupplier) { this.emfSupplier = emfSupplier; } - private void startHibernate(Promise p) { - try { - this.emf = emfSupplier.get(); - p.complete(); - } - catch (Throwable t) { - p.fail( t ); - } + private Object startHibernate() { + this.emf = emfSupplier.get(); + return null; } @Override public void start(Promise startPromise) { - final Future startHibernate = vertx.executeBlocking( this::startHibernate ) + final Future startHibernate = vertx.executeBlocking( this::startHibernate, true ) .onSuccess( s -> LOG.infof( "✅ Hibernate Reactive is ready" ) ); Router router = Router.router( vertx ); diff --git a/integration-tests/techempower-postgres-it/src/test/java/org/hibernate/reactive/techempower/TechEmpowerTest.java b/integration-tests/techempower-postgres-it/src/test/java/org/hibernate/reactive/techempower/TechEmpowerTest.java index 1f28b2bb2..ac628337d 100644 --- a/integration-tests/techempower-postgres-it/src/test/java/org/hibernate/reactive/techempower/TechEmpowerTest.java +++ b/integration-tests/techempower-postgres-it/src/test/java/org/hibernate/reactive/techempower/TechEmpowerTest.java @@ -73,7 +73,7 @@ public void testWorldRepository(VertxTestContext context) { .compose( this::updates ) .onSuccess( res -> context.completeNow() ) .onFailure( context::failNow ) - .eventually( unused -> vertx.close() ); + .eventually( vertx::close ); } /** diff --git a/integration-tests/verticle-postgres-it/build.gradle b/integration-tests/verticle-postgres-it/build.gradle index 4b48e87aa..cf98e8f56 100644 --- a/integration-tests/verticle-postgres-it/build.gradle +++ b/integration-tests/verticle-postgres-it/build.gradle @@ -14,7 +14,7 @@ description = 'Bytecode enhancements integration tests' ext { jacksonDatabindVersion = '2.15.2' jbossLoggingVersion = '3.5.0.Final' - assertjVersion = '3.26.3' + assertjVersion = '3.27.3' vertxWebVersion = project.hasProperty( 'vertxWebVersion' ) ? project.property( 'vertxWebVersion' ) : vertxSqlClientVersion @@ -30,7 +30,7 @@ dependencies { runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" // The Pg client requires this dependency - runtimeOnly "com.ongres.scram:client:2.1" + runtimeOnly "com.ongres.scram:scram-client:3.1" runtimeOnly "com.fasterxml.jackson.core:jackson-databind:${jacksonDatabindVersion}" // logging diff --git a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/ProductVerticle.java b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/ProductVerticle.java index 882231ad1..91d886110 100644 --- a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/ProductVerticle.java +++ b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/ProductVerticle.java @@ -40,19 +40,14 @@ public ProductVerticle(Supplier emfSupplier) { this.emfSupplier = emfSupplier; } - private void startHibernate(Promise p) { - try { - this.emf = emfSupplier.get(); - p.complete(); - } - catch (Throwable t) { - p.fail( t ); - } + private Object startHibernate() { + this.emf = emfSupplier.get(); + return null; } @Override public void start(Promise startPromise) { - final Future startHibernate = vertx.executeBlocking( this::startHibernate ) + final Future startHibernate = vertx.executeBlocking( this::startHibernate, true ) .onSuccess( s -> LOG.infof( "✅ Hibernate Reactive is ready" ) ); Router router = Router.router( vertx ); 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 238e5278b..990fef754 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:17.4"; + public static final String IMAGE_NAME = "postgres:17.5"; public static final String USERNAME = "hreact"; public static final String PASSWORD = "hreact"; public static final String DB_NAME = "hreact"; diff --git a/integration-tests/verticle-postgres-it/src/test/java/org/hibernate/reactive/it/LocalContextTest.java b/integration-tests/verticle-postgres-it/src/test/java/org/hibernate/reactive/it/LocalContextTest.java index 0c819a393..c2300977e 100644 --- a/integration-tests/verticle-postgres-it/src/test/java/org/hibernate/reactive/it/LocalContextTest.java +++ b/integration-tests/verticle-postgres-it/src/test/java/org/hibernate/reactive/it/LocalContextTest.java @@ -31,7 +31,6 @@ import io.vertx.junit5.VertxExtension; import io.vertx.junit5.VertxTestContext; -import static io.vertx.core.CompositeFuture.all; import static io.vertx.core.Future.all; import static io.vertx.core.Future.failedFuture; import static io.vertx.core.Future.succeededFuture; @@ -88,7 +87,7 @@ public void testProductsGeneration(VertxTestContext context) { .compose( this::findProducts ) .onSuccess( res -> context.completeNow() ) .onFailure( context::failNow ) - .eventually( unused -> vertx.close() ); + .eventually( vertx::close ); } /** @@ -97,7 +96,7 @@ public void testProductsGeneration(VertxTestContext context) { * @see #REQUEST_NUMBER */ private Future createProducts(WebClient webClient) { - List postRequests = new ArrayList<>(); + List> postRequests = new ArrayList<>(); for ( int i = 0; i < REQUEST_NUMBER; i++ ) { final Product product = new Product( i + 1 ); diff --git a/podman.md b/podman.md index 5d7d9476b..bc4d96ba7 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:17.4 +podman run --rm --name HibernateTestingPGSQL postgres:17.5 ``` becomes for Docker: ``` -sudo docker run --rm --name HibernateTestingPGSQL postgres:17.4 +sudo docker run --rm --name HibernateTestingPGSQL postgres:17.5 ``` --- @@ -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:17.4 + -p 5432:5432 docker.io/postgres:17.5 ``` When the database has started, you can run the tests on PostgreSQL with: @@ -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.15 start-single-node --insecure + docker.io/cockroachdb/cockroach:v24.3.13 start-single-node --insecure ``` Some of tests needs temporary tables and because this is an experimental feature in diff --git a/publish.gradle b/publish.gradle index 977092d9a..f77b26959 100644 --- a/publish.gradle +++ b/publish.gradle @@ -1,6 +1,5 @@ apply plugin: 'java' apply plugin: 'maven-publish' -apply plugin: 'signing' // Java / publishing @@ -75,97 +74,17 @@ publishing { } } } -} - - -// Signing - -var signingExtension = project.getExtensions().getByType(SigningExtension) as SigningExtension - -var publishingExtension = project.getExtensions().getByType(PublishingExtension) as PublishingExtension -signingExtension.sign publishingExtension.publications.publishedArtifacts - -var signingKey = resolveSigningKey() -var signingPassphrase = resolveSigningPassphrase() -signingExtension.useInMemoryPgpKeys(signingKey, signingPassphrase) - -gradle.taskGraph.whenReady { TaskExecutionGraph graph -> - boolean wasPublishingRequested = false - - graph.allTasks.each {task -> - if ( task instanceof PublishToMavenRepository ) { - logger.lifecycle( "Found PublishToMavenRepository task : {}", task.path ) - wasPublishingRequested = true + repositories { + maven { + name = "staging" + url = rootProject.layout.buildDirectory.dir("staging-deploy${File.separator}maven") } - } - - if ( wasPublishingRequested ) { - def publishUser = resolvePublishUser() - def publishPass = resolvePublishPass() - if ( publishUser == null || publishPass == null ) { - throw new RuntimeException( "Cannot perform publishing to OSSRH without credentials." ) + maven { + name = 'snapshots' + url = "https://oss.sonatype.org/content/repositories/snapshots/" + // So that Gradle uses the `ORG_GRADLE_PROJECT_snapshotsPassword` / `ORG_GRADLE_PROJECT_snapshotsUsername` + // env variables to read the username/password for the `snapshots` repository publishing: + credentials(PasswordCredentials) } - - logger.lifecycle "Publishing {} : {} : {}", project.group, project.name, project.version - - // require signing if publishing to OSSRH - signingExtension.required = true - } - else if ( signingKey == null || signingPassphrase == null ) { - tasks.withType( Sign ).each { t-> t.enabled = false } - } -} - - -static String resolveSigningKey() { - var key = System.getenv().get( "SIGNING_GPG_PRIVATE_KEY" ) - if ( key != null ) { - return key - } - - var keyFile = System.getenv().get( "SIGNING_GPG_PRIVATE_KEY_PATH" ) - if ( keyFile != null ) { - return new File( keyFile ).text - } - - return null -} - -static String resolveSigningPassphrase() { - return System.getenv().get( "SIGNING_GPG_PASSPHRASE" ) -} - -String resolvePublishUser() { - var envVar = System.getenv().get( "ORG_GRADLE_PROJECT_sonatypeUsername" ) - if ( envVar != null ) { - return envVar - } - - def projectProp = projectPropOrNull( "sonatypeUsername" ) - if ( projectProp != null ) { - return projectProp } - - return null } - -String resolvePublishPass() { - var envVar = System.getenv().get( "ORG_GRADLE_PROJECT_sonatypePassword" ) - if ( envVar != null ) { - return envVar - } - - def projectProp = projectPropOrNull( "sonatypePassword" ) - if ( projectProp != null ) { - return projectProp - } - - return null -} - -String projectPropOrNull(String name) { - if ( project.hasProperty( name ) ) { - return project.findProperty( name ) - } - return null; -} \ No newline at end of file diff --git a/release/build.gradle b/release/build.gradle index 2b507a9a2..690534750 100644 --- a/release/build.gradle +++ b/release/build.gradle @@ -236,7 +236,6 @@ void updateVersionFile(var version) { def publishReleaseArtifactsTask = tasks.register( 'publishReleaseArtifacts' ) { dependsOn uploadDocumentationTask - dependsOn ":hibernate-reactive-core:publishToSonatype" mustRunAfter gitPreparationForReleaseTask } diff --git a/tooling/jbang/CockroachDBReactiveTest.java.qute b/tooling/jbang/CockroachDBReactiveTest.java.qute index 81b79ee5d..d0ebda569 100755 --- a/tooling/jbang/CockroachDBReactiveTest.java.qute +++ b/tooling/jbang/CockroachDBReactiveTest.java.qute @@ -5,17 +5,17 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//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 io.vertx:vertx-pg-client:$\{vertx.version:5.0.0} +//DEPS io.vertx:vertx-unit:$\{vertx.version:5.0.0} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:4.0.0.Final} +//DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:cockroachdb:1.20.6 +//DEPS org.testcontainers:cockroachdb:1.21.0 //DEPS org.slf4j:slf4j-simple:2.0.7 //// Testcontainer needs the JDBC drivers to start the container //// Hibernate Reactive doesn't need it -//DEPS org.postgresql:postgresql:42.7.4 +//DEPS org.postgresql:postgresql:42.7.5 import java.io.IOException; import jakarta.persistence.Entity; @@ -70,7 +70,7 @@ public class {baseName} { } @ClassRule - public final static CockroachContainer database = new CockroachContainer( imageName( "cockroachdb", "cockroach", "v24.1.15" ) ); + public final static CockroachContainer database = new CockroachContainer( imageName( "cockroachdb", "cockroach", "v24.3.13" ) ); private Mutiny.SessionFactory sessionFactory; diff --git a/tooling/jbang/Db2ReactiveTest.java.qute b/tooling/jbang/Db2ReactiveTest.java.qute index 6aeeae23a..d9396c13f 100755 --- a/tooling/jbang/Db2ReactiveTest.java.qute +++ b/tooling/jbang/Db2ReactiveTest.java.qute @@ -5,12 +5,12 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//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 io.vertx:vertx-db2-client:$\{vertx.version:5.0.0} +//DEPS io.vertx:vertx-unit:$\{vertx.version:5.0.0} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:4.0.0.Final} +//DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:db2:1.20.6 +//DEPS org.testcontainers:db2:1.21.0 //DEPS org.slf4j:slf4j-simple:2.0.7 import jakarta.persistence.Entity; diff --git a/tooling/jbang/Example.java b/tooling/jbang/Example.java index 893b5c80f..3dde6e2b1 100644 --- a/tooling/jbang/Example.java +++ b/tooling/jbang/Example.java @@ -5,11 +5,11 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS com.ongres.scram:client:2.1 -//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 com.ongres.scram:scram-client:3.1 +//DEPS io.vertx:vertx-pg-client:${vertx.version:5.0.0} +//DEPS io.vertx:vertx-mysql-client:${vertx.version:5.0.0} +//DEPS io.vertx:vertx-db2-client:${vertx.version:5.0.0} +//DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:4.0.0.Final} //DEPS org.slf4j:slf4j-simple:2.0.7 //DESCRIPTION Allow authentication to PostgreSQL using SCRAM: @@ -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:17.4
    + *                      -p 5432:5432 postgres:17.5
      *              
    * *
    3. Run the example with JBang
    diff --git a/tooling/jbang/MariaDBReactiveTest.java.qute b/tooling/jbang/MariaDBReactiveTest.java.qute index 682238370..d3f304147 100755 --- a/tooling/jbang/MariaDBReactiveTest.java.qute +++ b/tooling/jbang/MariaDBReactiveTest.java.qute @@ -5,17 +5,17 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//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 io.vertx:vertx-mysql-client:$\{vertx.version:5.0.0} +//DEPS io.vertx:vertx-unit:$\{vertx.version:5.0.0} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:4.0.0.Final} +//DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:mariadb:1.20.6 +//DEPS org.testcontainers:mariadb:1.21.0 //DEPS org.slf4j:slf4j-simple:2.0.7 //// Testcontainer needs the JDBC drivers to start the container //// Hibernate Reactive doesn't need it -//DEPS org.mariadb.jdbc:mariadb-java-client:3.5.1 +//DEPS org.mariadb.jdbc:mariadb-java-client:3.5.3 import jakarta.persistence.Entity; import jakarta.persistence.Id; diff --git a/tooling/jbang/MySQLReactiveTest.java.qute b/tooling/jbang/MySQLReactiveTest.java.qute index b252459d1..67925f99a 100755 --- a/tooling/jbang/MySQLReactiveTest.java.qute +++ b/tooling/jbang/MySQLReactiveTest.java.qute @@ -5,17 +5,17 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//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 io.vertx:vertx-mysql-client:$\{vertx.version:5.0.0} +//DEPS io.vertx:vertx-unit:$\{vertx.version:5.0.0} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:4.0.0.Final} +//DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:mysql:1.20.6 +//DEPS org.testcontainers:mysql:1.21.0 //DEPS org.slf4j:slf4j-simple:2.0.7 //// Testcontainer needs the JDBC drivers to start the container //// Hibernate Reactive doesn't need it -//DEPS com.mysql:mysql-connector-j:9.1.0 +//DEPS com.mysql:mysql-connector-j:9.3.0 import jakarta.persistence.Entity; import jakarta.persistence.Id; diff --git a/tooling/jbang/PostgreSQLReactiveTest.java.qute b/tooling/jbang/PostgreSQLReactiveTest.java.qute index 04a0272e8..0993b9227 100755 --- a/tooling/jbang/PostgreSQLReactiveTest.java.qute +++ b/tooling/jbang/PostgreSQLReactiveTest.java.qute @@ -5,12 +5,12 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//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 io.vertx:vertx-pg-client:$\{vertx.version:5.0.0} +//DEPS io.vertx:vertx-unit:$\{vertx.version:5.0.0} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:4.0.0.Final} +//DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:postgresql:1.20.6 +//DEPS org.testcontainers:postgresql:1.21.0 //DEPS org.slf4j:slf4j-simple:2.0.7 //DESCRIPTION Allow authentication to PostgreSQL using SCRAM: //DEPS com.ongres.scram:client:2.1 @@ -67,7 +67,7 @@ public class {baseName} { } @ClassRule - public final static PostgreSQLContainer database = new PostgreSQLContainer( imageName( "docker.io", "postgres", "17.4" ) ); + public final static PostgreSQLContainer database = new PostgreSQLContainer( imageName( "docker.io", "postgres", "17.5" ) ); private Mutiny.SessionFactory sessionFactory; diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java index c6b116321..28656a504 100755 --- a/tooling/jbang/ReactiveTest.java +++ b/tooling/jbang/ReactiveTest.java @@ -5,25 +5,25 @@ */ ///usr/bin/env jbang "$0" "$@" ; exit $? -//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.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 io.vertx:vertx-pg-client:${vertx.version:5.0.0} +//DEPS com.ongres.scram:scram-client:3.1 +//DEPS io.vertx:vertx-db2-client:${vertx.version:5.0.0} +//DEPS io.vertx:vertx-mysql-client:${vertx.version:5.0.0} +//DEPS io.vertx:vertx-unit:${vertx.version:5.0.0} +//DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:4.0.0.Final} +//DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//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 +//DEPS org.testcontainers:postgresql:1.21.0 +//DEPS org.testcontainers:mysql:1.21.0 +//DEPS org.testcontainers:db2:1.21.0 +//DEPS org.testcontainers:mariadb:1.21.0 +//DEPS org.testcontainers:cockroachdb:1.21.0 // //// Testcontainer needs the JDBC drivers to start the containers //// Hibernate Reactive doesn't use them -//DEPS org.postgresql:postgresql:42.7.4 -//DEPS com.mysql:mysql-connector-j:9.1.0 -//DEPS org.mariadb.jdbc:mariadb-java-client:3.5.1 +//DEPS org.postgresql:postgresql:42.7.5 +//DEPS com.mysql:mysql-connector-j:9.3.0 +//DEPS org.mariadb.jdbc:mariadb-java-client:3.5.3 // import java.util.function.Supplier; @@ -228,11 +228,11 @@ public String toString() { * It's a wrapper around the testcontainers classes. */ enum Database { - POSTGRESQL( () -> new PostgreSQLContainer( "postgres:17.4" ) ), + POSTGRESQL( () -> new PostgreSQLContainer( "postgres:17.5" ) ), 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.15" ) ); + COCKROACHDB( () -> new CockroachContainer( "cockroachdb/cockroach:v24.3.13" ) ); private final Supplier> containerSupplier;