diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..4d1c7e327 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + groups: + workflow-actions: + patterns: + - "*" + allow: + - dependency-name: "actions/*" + - dependency-name: "redhat-actions/*" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6c90f0b9c..f818b94e8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,12 +5,20 @@ on: branches: - 'main' - 'wip/**' + - '2.*' + - '3.*' + - '4.*' tags: - '2.*' + - '3.*' + - '4.*' pull_request: branches: - 'main' - 'wip/**' + - '2.*' + - '3.*' + - '4.*' # For building snapshots workflow_call: inputs: @@ -45,7 +53,7 @@ jobs: # Label used to access the service container mysql: # Docker Hub image - image: mysql:8.4.0 + image: mysql:9.2.0 env: MYSQL_ROOT_PASSWORD: hreact MYSQL_DATABASE: hreact @@ -61,7 +69,7 @@ jobs: - 3306:3306 postgres: # Docker Hub image - image: postgres:16.3 + image: postgres:17.4 env: POSTGRES_DB: hreact POSTGRES_USER: hreact @@ -76,7 +84,7 @@ jobs: - 5432:5432 steps: - name: Checkout ${{ inputs.branch }} - uses: actions/checkout@v2 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ inputs.branch }} - name: Get year/month for cache key @@ -85,7 +93,7 @@ jobs: echo "::set-output name=yearmonth::$(/bin/date -u "+%Y-%m")" shell: bash - name: Cache Gradle downloads - uses: actions/cache@v2 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 id: cache-gradle with: path: | @@ -95,16 +103,23 @@ jobs: # refresh cache every month to avoid unlimited growth key: gradle-examples-${{ matrix.db }}-${{ steps.get-date.outputs.yearmonth }} - name: Set up JDK 11 - uses: actions/setup-java@v2.2.0 + if: ${{ startsWith( inputs.branch, 'wip/2' ) }} + 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@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + with: + distribution: 'temurin' + java-version: 17 - name: Print the effective ORM version used run: ./gradlew :${{ matrix.example }}:dependencyInsight --dependency org.hibernate.orm:hibernate-core - name: Run examples in '${{ matrix.example }}' on ${{ matrix.db }} run: ./gradlew :${{ matrix.example }}:runAllExamplesOn${{ matrix.db }} - name: Upload reports (if build failed) - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: failure() with: name: reports-examples-${{ matrix.db }} @@ -118,7 +133,7 @@ jobs: db: [ 'MariaDB', 'MySQL', 'PostgreSQL', 'MSSQLServer', 'CockroachDB', 'Db2', 'Oracle' ] steps: - name: Checkout ${{ inputs.branch }} - uses: actions/checkout@v2 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ inputs.branch }} - name: Get year/month for cache key @@ -127,7 +142,7 @@ jobs: echo "::set-output name=yearmonth::$(/bin/date -u "+%Y-%m")" shell: bash - name: Cache Gradle downloads - uses: actions/cache@v2 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 id: cache-gradle with: path: | @@ -137,16 +152,24 @@ jobs: # refresh cache every month to avoid unlimited growth key: gradle-db-${{ matrix.db }}-${{ steps.get-date.outputs.yearmonth }} - name: Set up JDK 11 - uses: actions/setup-java@v2.2.0 + if: ${{ startsWith( inputs.branch, 'wip/2' ) }} + 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@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + + with: + distribution: 'temurin' + java-version: 17 - name: Print the effective ORM version used run: ./gradlew :hibernate-reactive-core:dependencyInsight --dependency org.hibernate.orm:hibernate-core - name: Build and Test with ${{ matrix.db }} run: ./gradlew build -PshowStandardOutput -Pdocker -Pdb=${{ matrix.db }} - name: Upload reports (if build failed) - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: failure() with: name: reports-db-${{ matrix.db }} @@ -165,19 +188,17 @@ jobs: # To see the available versions and download links on jdk.java.net: # https://github.com/oracle-actions/setup-java/blob/main/jdk.java.net-uri.properties java: - - { name: "11", java_version_numeric: 11 } - { name: "17", java_version_numeric: 17 } # We want to enable preview features when testing newer builds of OpenJDK: # even if we don't use these features, just enabling them can cause side effects # and it's useful to test that. - { name: "20", java_version_numeric: 20, jvm_args: '--enable-preview' } - { name: "21", java_version_numeric: 21, jvm_args: '--enable-preview' } - - { name: "23", java_version_numeric: 23, from: 'jdk.java.net', jvm_args: '--enable-preview' } - - { name: "24-ea", java_version_numeric: 24, from: 'jdk.java.net', jvm_args: '--enable-preview' } + - { name: "24", java_version_numeric: 24, from: 'jdk.java.net', jvm_args: '--enable-preview' } - { name: "25-ea", java_version_numeric: 25, from: 'jdk.java.net', jvm_args: '--enable-preview' } steps: - name: Checkout ${{ inputs.branch }} - uses: actions/checkout@v2 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ inputs.branch }} - name: Get year/month for cache key @@ -198,7 +219,7 @@ jobs: echo "buildtool-cache-key=${ROOT_CACHE_KEY}-${CURRENT_MONTH}-${CURRENT_BRANCH}-${CURRENT_DAY}" >> $GITHUB_OUTPUT - name: Cache Maven/Gradle Dependency/Dist Caches id: cache-maven - uses: actions/cache@v4 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 # if it's not a pull request, we restore and save the cache if: github.event_name != 'pull_request' with: @@ -215,7 +236,7 @@ jobs: ${{ steps.cache-key.outputs.buildtool-monthly-branch-cache-key }}- ${{ steps.cache-key.outputs.buildtool-monthly-cache-key }}- - name: Restore Maven/Gradle Dependency/Dist Caches - uses: actions/cache/restore@v4 + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 # if it's a pull request, we restore the cache, but we don't save it if: github.event_name == 'pull_request' with: @@ -231,13 +252,13 @@ jobs: - name: Set up latest JDK ${{ matrix.java.name }} from jdk.java.net if: matrix.java.from == 'jdk.java.net' - uses: oracle-actions/setup-java@v1 + uses: oracle-actions/setup-java@2e744f723b003fdd759727d0ff654c8717024845 # v1.4.0 with: website: jdk.java.net 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@v2.2.0 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: distribution: 'temurin' java-version: ${{ matrix.java.java_version_numeric }} @@ -245,13 +266,20 @@ jobs: - name: Export path to JDK ${{ matrix.java.name }} id: testjdk-exportpath run: echo "::set-output name=path::${JAVA_HOME}" - # Always use JDK 11 to build the main code: that's what we use for releases. - name: Set up JDK 11 - uses: actions/setup-java@v2.2.0 + if: ${{ startsWith( inputs.branch, 'wip/2' ) }} + 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@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + with: + distribution: 'temurin' + java-version: 17 + check-latest: true - name: Export path to JDK 11 id: mainjdk-exportpath run: echo "::set-output name=path::${JAVA_HOME}" @@ -266,7 +294,7 @@ jobs: -Porg.gradle.java.installations.paths=${{ steps.mainjdk-exportpath.outputs.path }},${{ steps.testjdk-exportpath.outputs.path }} \ ${{ matrix.java.jvm_args && '-Ptest.jdk.launcher.args=' }}${{ matrix.java.jvm_args }} - name: Upload reports (if build failed) - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: failure() with: name: reports-java${{ matrix.java.name }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 584de137b..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: @@ -24,24 +24,24 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: distribution: temurin - java-version: 11 + java-version: 17 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@7e3036b9cd87fc26dd06747b7aa4b96c27aaef3a # v3.28.4 with: languages: ${{ matrix.language }} queries: +security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@7e3036b9cd87fc26dd06747b7aa4b96c27aaef3a # v3.28.4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@7e3036b9cd87fc26dd06747b7aa4b96c27aaef3a # v3.28.4 with: category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/scheduler.yml b/.github/workflows/scheduler.yml index 01f827e22..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' ] + 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/.idea/icon.svg b/.idea/icon.svg new file mode 100644 index 000000000..2b19df67f --- /dev/null +++ b/.idea/icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file 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 new file mode 100644 index 000000000..bb825420f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,113 @@ +# Contributing + +Contributions from the community are essential in keeping Hibernate (and any Open Source +project really) strong and successful. + +# Legal + +All original contributions to Hibernate are licensed under the +[Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0). + +The Apache 2.0 license text is included verbatim in the [LICENSE](LICENSE) file in the root directory +of the Hibernate Reactive repository. + +All contributions are subject to the [Developer Certificate of Origin (DCO)](https://developercertificate.org/). + +The DCO text is available verbatim in the [dco.txt](dco.txt) file in the root directory +of the Hibernate Reactive repository. + +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 +we ask that you mind. + +For code contributions, these guidelines include: +* Respect the project code style - find templates for [IntelliJ IDEA](https://hibernate.org/community/contribute/intellij-idea/) or [Eclipse](https://hibernate.org/community/contribute/eclipse-ide/) +* Have a corresponding GitHub [issue](https://github.com/hibernate/hibernate-reactive/issues) and be sure to include + the key for this issue in your commit messages. +* Have a set of appropriate tests. + For your convenience, a [set of test templates](https://github.com/hibernate/hibernate-test-case-templates/tree/main/reactive) + have been made available. + + When submitting bug reports, the tests should reproduce the initially reported bug and illustrate that your solution addresses the issue. + For features/enhancements, the tests should demonstrate that the feature works as intended. + In both cases, be sure to incorporate your tests into the project to protect against possible regressions. +* If applicable, documentation should be updated to reflect the introduced changes +* The code compiles and the tests pass (`./gradlew clean build`) + +For documentation contributions, mainly to respect the project code style, especially in regard +to the use of tabs - as mentioned above, code style templates are available for both IntelliJ IDEA and Eclipse +IDEs. Ideally, these contributions would also have a corresponding issue, although this +is less necessary for documentation contributions. + +## Getting Started + +If you are just getting started with Git, GitHub, and/or contributing to Hibernate via +GitHub there are a few pre-requisite steps to follow: + +* Make sure you have a [GitHub account](https://github.com/signup/free) +* [Fork](https://help.github.com/articles/fork-a-repo) the Hibernate Reactive repository. As discussed in +the linked page, this also includes: + * [set up your local git install](https://help.github.com/articles/set-up-git) + * clone your fork +* Instruct git to ignore certain commits when using `git blame`. From the directory of your local clone, run this: `git config blame.ignoreRevsFile .git-blame-ignore-revs` +* See the wiki pages for setting up your IDE, whether you use +[IntelliJ IDEA](https://hibernate.org/community/contribute/intellij-idea/) +or [Eclipse](https://hibernate.org/community/contribute/eclipse-ide/)(1). + + +## Create the working (topic) branch + +Create a [topic branch](https://git-scm.com/book/en/Git-Branching-Branching-Workflows#Topic-Branches) +on which you will work. The convention is to incorporate the JIRA issue key in the name of this branch, +although this is more of a mnemonic strategy than a hard-and-fast rule - but doing so helps: +* Remember what each branch is for +* Isolate the work from other contributions you may be working on + +_If there is not already a GitHub issue covering the work you want to do, [create one](https://github.com/hibernate/hibernate-reactive/issues/new)._ + +Assuming you will be working from the `main` branch and working +on the GitHub issue #123 : `git checkout -b 123 main` + +## Code + +Do your thing! + + +## Commit + +* Make commits of logical units +* Be sure to start each commit message using the ** GitHub issue key **. For example: + ``` + [#1234] Fix some kind of problem + ``` +* Make sure you have added the necessary tests for your changes +* Run _all_ the tests to ensure nothing else was accidentally broken + +_Before committing, if you want to pull in the latest upstream changes (highly +appreciated btw), please use rebasing rather than merging. Merging creates +"merge commits" that invariably muck up the project timeline._ + +## Submit + +* Push your changes to the topic branch in your fork of the repository +* Initiate a [pull request](https://help.github.com/articles/creating-a-pull-request) +* Adding the sentence `Fix #123`, where `#123` is the issue key, will link the pull request to the corresponding issue + and close it accordingly, when the pull request gets merged. + +It is important that this topic branch of your fork: + +* Is isolated to just the work on this one issue, or multiple issues if they are + related and also fixed/implemented by this work. The main point is to not push commits for more than + one PR to a single branch - GitHub PRs are linked to a branch rather than specific commits +* remain until the PR is closed. Once the underlying branch is deleted the corresponding PR will be closed, + if not already, and the changes will be lost. + +# Notes +(1) Gradle `eclipse` plugin is no longer supported, so the recommended way to import the project in your IDE is with the proper IDE tools/plugins. Don't try to run `./gradlew clean eclipse --refresh-dependencies` from the command line as you'll get an error because `eclipse` no longer exists diff --git a/README.md b/README.md index a44faa31b..743540d6c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ [![Hibernate team logo](http://static.jboss.org/hibernate/images/hibernate_logo_whitebkg_200px.png)](https://hibernate.org/reactive) -[![Main branch build status](https://github.com/hibernate/hibernate-reactive/workflows/Hibernate%20Reactive%20CI/badge.svg?style=flat)](https://github.com/hibernate/hibernate-reactive/actions?query=workflow%3A%22Hibernate+Reactive+CI%22) -[![Apache 2.0 license](https://img.shields.io/badge/License-APACHE%202.0-green.svg?logo=APACHE&style=flat)](https://opensource.org/licenses/Apache-2.0) -[![Latest version on Maven Central](https://img.shields.io/maven-central/v/org.hibernate.reactive/hibernate-reactive-core.svg?label=Maven%20Central&logo=apache-maven&style=flat)](https://search.maven.org/search?q=g:org.hibernate.reactive) -[![Developers stream on Zulip](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg?logo=zulip&style=flat)](https://hibernate.zulipchat.com/#narrow/stream/205413-hibernate-reactive-dev) -[![Hibernate Reactive documentation](https://img.shields.io/badge/Hibernate-Documentation-orange.svg?logo=Hibernate&style=flat)](https://hibernate.org/reactive/documentation/) +[![Main branch build status](https://img.shields.io/github/actions/workflow/status/hibernate/hibernate-reactive/build.yml?label=Hibernate%20Reactive%20CI&style=for-the-badge)](https://github.com/hibernate/hibernate-reactive/actions?query=workflow%3A%22Hibernate+Reactive+CI%22) +[![Apache 2.0 license](https://img.shields.io/badge/License-APACHE%202.0-green.svg?logo=APACHE&style=for-the-badge)](https://opensource.org/licenses/Apache-2.0) +[![Latest version on Maven Central](https://img.shields.io/maven-central/v/org.hibernate.reactive/hibernate-reactive-core.svg?label=Maven%20Central&logo=apache-maven&style=for-the-badge)](https://search.maven.org/search?q=g:org.hibernate.reactive) +[![Developers stream on Zulip](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg?logo=zulip&style=for-the-badge)](https://hibernate.zulipchat.com/#narrow/stream/205413-hibernate-reactive-dev) +[![Hibernate Reactive documentation](https://img.shields.io/badge/Hibernate-Documentation-orange.svg?logo=Hibernate&style=for-the-badge)](https://hibernate.org/reactive/documentation/) +[![Reproducible Builds](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/jvm-repo-rebuild/reproducible-central/master/content/org/hibernate/reactive/hibernate-reactive/badge.json&style=for-the-badge)](https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/org/hibernate/reactive/hibernate-reactive/README.md) # Hibernate Reactive @@ -29,20 +30,20 @@ Learn more at . Hibernate Reactive has been tested with: -- Java 11, 17, 21, 23 +- Java 17, 21, 24 - PostgreSQL 16 -- MySQL 8 +- MySQL 9 - MariaDB 11 -- Db2 11 +- Db2 12 - CockroachDB v24 - MS SQL Server 2022 - Oracle 23 -- [Hibernate ORM][] 6.6.3.Final -- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.11 -- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.11 -- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.11 -- [Vert.x Reactive MS SQL Server Client](https://vertx.io/docs/vertx-mssql-client/java/) 4.5.11 -- [Vert.x Reactive Oracle Client](https://vertx.io/docs/vertx-oracle-client/java/) 4.5.11 +- [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 5aee62970..f927371ef 100644 --- a/build.gradle +++ b/build.gradle @@ -12,15 +12,6 @@ group = "org.hibernate.reactive" // leverage the ProjectVersion which comes from the `local.versions` plugin version = project.projectVersion.fullName -ext { - if ( !project.hasProperty( 'hibernatePublishUsername' ) ) { - hibernatePublishUsername = null - } - if ( !project.hasProperty( 'hibernatePublishPassword' ) ) { - hibernatePublishPassword = null - } -} - // Versions which need to be aligned across modules; this also // allows overriding the build using a parameter, which can be // useful to monitor compatibility for upcoming versions on CI: @@ -31,26 +22,14 @@ ext { // Example: // ./gradlew build -PvertxSqlClientVersion=4.0.0-SNAPSHOT if ( !project.hasProperty( 'vertxSqlClientVersion' ) ) { - vertxSqlClientVersion = '4.5.11' + vertxSqlClientVersion = '5.0.0' } - testcontainersVersion = '1.20.4' + testcontainersVersion = '1.21.0' logger.lifecycle "Vert.x SQL Client Version: " + project.vertxSqlClientVersion } -// To release, see task ciRelease in release/build.gradle -// To publish on Sonatype (Maven Central): -// ./gradlew publishToSonatype closeAndReleaseStagingRepository -PhibernatePublishUsername="" -PhibernatePublishPassword="" -nexusPublishing { - repositories { - sonatype { - username = project.hibernatePublishUsername - password = project.hibernatePublishPassword - } - } -} - subprojects { apply plugin: 'java-library' apply plugin: 'com.diffplug.spotless' @@ -87,7 +66,7 @@ subprojects { ext.publishScript = rootProject.rootDir.absolutePath + '/publish.gradle' - tasks.withType( JavaCompile ) { + tasks.withType( JavaCompile ).configureEach { options.encoding = 'UTF-8' } diff --git a/ci/compare-build-results.sh b/ci/compare-build-results.sh new file mode 100644 index 000000000..6b45022ce --- /dev/null +++ b/ci/compare-build-results.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +# This is a simple script to check if builds are reproducible. The steps are: +# 1. Build Hibernate Reactive with `./gradlew --no-daemon clean publishToMavenLocal --no-build-cache -Dmaven.repo.local=some-path/out/build1` +# 2. Build Hibernate Reactive with `./gradlew --no-daemon clean publishToMavenLocal --no-build-cache -Dmaven.repo.local=some-path/out/build2` second time pointing to a different local maven repository to publish +# 3. Compare the build results with sh ./ci/compare-build-results.sh some-path/out/build1 some-path/out/build2 +# 4. The generated .buildcompare file will also contain the diffscope commands to see/compare the problematic build artifacts + +outputDir1=$1 +outputDir2=$2 +outputDir1=${outputDir1%/} +outputDir2=${outputDir2%/} + +ok=() +okFiles=() +ko=() +koFiles=() + +for f in `find ${outputDir1} -type f | grep -v "javadoc.jar$" | grep -v "maven-metadata-local.xml$" | sort` +do + flocal=${f#$outputDir1} + # echo "comparing ${flocal}" + sha1=`shasum -a 512 $f | cut -f 1 -d ' '` + sha2=`shasum -a 512 $outputDir2$flocal | cut -f 1 -d ' '` + # echo "$sha1" + # echo "$sha2" + if [ "$sha1" = "$sha2" ]; then + ok+=($flocal) + okFiles+=(${flocal##*/}) + else + ko+=($flocal) + koFiles+=(${flocal##*/}) + fi +done + +# generate .buildcompare +buildcompare=".buildcompare" +echo "ok=${#ok[@]}" >> ${buildcompare} +echo "ko=${#ko[@]}" >> ${buildcompare} +echo "okFiles=\"${okFiles[@]}\"" >> ${buildcompare} +echo "koFiles=\"${koFiles[@]}\"" >> ${buildcompare} +echo "" >> ${buildcompare} +echo "# see what caused the mismatch in the checksum by executing the following diffscope commands" >> ${buildcompare} +for f in ${ko[@]} +do + echo "# diffoscope $outputDir1$f $outputDir2$f" >> ${buildcompare} +done + +if [ ${#ko[@]} -eq 0 ]; then + exit 0 +else + exit 1 +fi diff --git a/ci/nightly/Jenkinsfile b/ci/nightly/Jenkinsfile new file mode 100644 index 000000000..945f02606 --- /dev/null +++ b/ci/nightly/Jenkinsfile @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ + +@Library('hibernate-jenkins-pipeline-helpers') _ + +pipeline { + agent none + triggers { + cron '@midnight' + } + tools { + jdk 'OpenJDK 17 Latest' + } + options { + buildDiscarder logRotator(daysToKeepStr: '10', numToKeepStr: '3') + disableConcurrentBuilds(abortPrevious: true) + overrideIndexTriggers(false) + } + stages { + stage('Build reproducibility check') { + agent { + label 'Worker&&Containers' + } + steps { + timeout(time: 30, unit: 'MINUTES') { + script { + def tempDir = pwd(tmp: true) + def repo1 = tempDir + '/repo1' + def repo2 = tempDir + '/repo2' + // build Hibernate Reactive two times without any cache and "publish" the resulting artifacts + // to different maven repositories, so that we can compare them afterwards: + sh "./gradlew --no-daemon clean publishToMavenLocal --no-build-cache -Dmaven.repo.local=${repo1}" + sh "./gradlew --no-daemon clean publishToMavenLocal --no-build-cache -Dmaven.repo.local=${repo2}" + + sh "sh ci/compare-build-results.sh ${repo1} ${repo2}" + sh "cat .buildcompare" + } + } + } + } + } + post { + always { + notifyBuildResult maintainers: 'davide@hibernate.org' + } + } +} diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index bb4315fb8..8627950a1 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -58,10 +58,10 @@ def checkoutReleaseScripts() { pipeline { agent { - label 'Worker&&Containers' + label 'Release' } tools { - jdk 'OpenJDK 11 Latest' + jdk 'OpenJDK 17 Latest' } options { buildDiscarder logRotator(daysToKeepStr: '30', numToKeepStr: '10') @@ -73,7 +73,7 @@ pipeline { string( name: 'RELEASE_VERSION', defaultValue: '', - description: 'The version to be released, e.g. 2.4.0.Final. Mandatory for manual releases, to prevent mistakes.', + description: 'The version to be released, e.g. 3.0.0.Beta1. Mandatory for manual releases, to prevent mistakes.', trim: true ) string( @@ -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) @@ -165,24 +168,17 @@ pipeline { configFile(fileId: 'release.config.ssh', targetLocation: "${env.HOME}/.ssh/config"), configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts") ]) { - withCredentials([ - usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USER'), - usernamePassword(credentialsId: 'gradle-plugin-portal-api-key', passwordVariable: 'PLUGIN_PORTAL_PASSWORD', usernameVariable: 'PLUGIN_PORTAL_USERNAME'), - file(credentialsId: 'release.gpg.private-key', variable: 'RELEASE_GPG_PRIVATE_KEY_PATH'), - string(credentialsId: 'release.gpg.passphrase', variable: 'RELEASE_GPG_PASSPHRASE') - ]) { - sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) { - // set release version - // update changelog from JIRA - // 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}" - } + + sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) { + // set release version + // update changelog from JIRA + // tags the version + // changes the version to the provided development version + withEnv([ + // 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 -j -b ${env.GIT_BRANCH} -v ${env.DEVELOPMENT_VERSION} ${env.PROJECT} ${env.RELEASE_VERSION}" } } } @@ -199,16 +195,22 @@ pipeline { configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts") ]) { withCredentials([ - usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USER'), - usernamePassword(credentialsId: 'gradle-plugin-portal-api-key', passwordVariable: 'PLUGIN_PORTAL_PASSWORD', usernameVariable: 'PLUGIN_PORTAL_USERNAME'), + // 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: 'RELEASE_GPG_PASSPHRASE'), - gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default') + 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}" } } } @@ -223,4 +225,4 @@ pipeline { } } } -} \ No newline at end of file +} diff --git a/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile index c95f6663f..fea160b23 100644 --- a/ci/snapshot-publish.Jenkinsfile +++ b/ci/snapshot-publish.Jenkinsfile @@ -10,12 +10,20 @@ 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 'Fedora' + label 'Release' } tools { - jdk 'OpenJDK 11 Latest' + jdk 'OpenJDK 17 Latest' } options { rateLimitBuilds(throttle: [count: 1, durationName: 'hour', userBoost: true]) @@ -30,16 +38,23 @@ pipeline { } stage('Publish') { steps { - withCredentials([ - usernamePassword(credentialsId: 'ossrh.sonatype.org', usernameVariable: 'hibernatePublishUsername', passwordVariable: 'hibernatePublishPassword'), - string(credentialsId: 'release.gpg.passphrase', variable: 'SIGNING_PASS'), - file(credentialsId: 'release.gpg.private-key', variable: 'SIGNING_KEYRING') - ]) { - sh '''./gradlew clean publish \ - -PhibernatePublishUsername=$hibernatePublishUsername \ - -PhibernatePublishPassword=$hibernatePublishPassword \ - --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}" + } } } } @@ -51,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 40b4847bc..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 @@ -578,8 +578,8 @@ custom reactive identifier generator. === JSON Mapping -:orm-json-basic-mapping: https://docs.jboss.org/hibernate/orm/6.6/userguide/html_single/Hibernate_User_Guide.html#basic-mapping-json -:orm-json-embeddable-mapping: https://docs.jboss.org/hibernate/orm/6.6/userguide/html_single/Hibernate_User_Guide.html#_jsonxml_aggregate_embeddable_mapping +:orm-json-basic-mapping: https://docs.jboss.org/hibernate/orm/7.0/userguide/html_single/Hibernate_User_Guide.html#basic-mapping-json +:orm-json-embeddable-mapping: https://docs.jboss.org/hibernate/orm/7.0/userguide/html_single/Hibernate_User_Guide.html#_jsonxml_aggregate_embeddable_mapping :string-to-json-converter: https://github.com/hibernate/hibernate-reactive/blob/main/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/StringToJsonConverter.java Like in Hibernate ORM, it's possible to map a JSON field using the {orm-json-basic-mapping}[SqlTypes.JSON] diff --git a/examples/native-sql-example/build.gradle b/examples/native-sql-example/build.gradle index fabdbc7fa..3b650d34a 100644 --- a/examples/native-sql-example/build.gradle +++ b/examples/native-sql-example/build.gradle @@ -27,7 +27,7 @@ dependencies { implementation project( ':hibernate-reactive-core' ) // Hibernate Validator (optional) - implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final' + implementation 'org.hibernate.validator:hibernate-validator:8.0.2.Final' runtimeOnly 'org.glassfish.expressly:expressly:5.0.0' // JPA metamodel generation for criteria queries (optional) @@ -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/native-sql-example/src/main/resources/META-INF/persistence.xml b/examples/native-sql-example/src/main/resources/META-INF/persistence.xml index aaa9d3687..686442cb2 100644 --- a/examples/native-sql-example/src/main/resources/META-INF/persistence.xml +++ b/examples/native-sql-example/src/main/resources/META-INF/persistence.xml @@ -3,7 +3,7 @@ xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd" version="3.0"> - + org.hibernate.reactive.provider.ReactivePersistenceProvider org.hibernate.reactive.example.nativesql.Author diff --git a/examples/session-example/build.gradle b/examples/session-example/build.gradle index 7cf7fb5f6..a001112d8 100644 --- a/examples/session-example/build.gradle +++ b/examples/session-example/build.gradle @@ -27,7 +27,7 @@ dependencies { implementation project( ':hibernate-reactive-core' ) // Hibernate Validator (optional) - implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final' + implementation 'org.hibernate.validator:hibernate-validator:8.0.2.Final' runtimeOnly 'org.glassfish.expressly:expressly:5.0.0' // JPA metamodel generation for criteria queries (optional) @@ -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/examples/session-example/src/main/resources/META-INF/persistence.xml b/examples/session-example/src/main/resources/META-INF/persistence.xml index 7c2c13900..f3271f251 100644 --- a/examples/session-example/src/main/resources/META-INF/persistence.xml +++ b/examples/session-example/src/main/resources/META-INF/persistence.xml @@ -3,7 +3,7 @@ xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd" version="3.0"> - + org.hibernate.reactive.provider.ReactivePersistenceProvider org.hibernate.reactive.example.session.Author diff --git a/gradle.properties b/gradle.properties index 55ec51fc8..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 = 6.6.3.Final +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 = 6.6.3.Final +#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.11-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.11 -#vertxWebtClientVersion = 4.5.11 +#vertxWebVersion = 5.0.0 +#vertxWebtClientVersion = 5.0.0 diff --git a/gradle/version.properties b/gradle/version.properties index dd20a5fa4..618fb2d72 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=2.4.3-SNAPSHOT \ No newline at end of file +projectVersion=4.0.0-SNAPSHOT \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd4917..a4b76b953 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9355b4155..cea7a793a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a426..f5feea6d6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 6689b85be..9b42019c7 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/hibernate-reactive-core/build.gradle b/hibernate-reactive-core/build.gradle index bbcbc9e6c..26a4de751 100644 --- a/hibernate-reactive-core/build.gradle +++ b/hibernate-reactive-core/build.gradle @@ -10,21 +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.5.0.Final' - compileOnly 'org.jboss.logging:jboss-logging-annotations:2.2.1.Final' + 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.4.Final' + annotationProcessor 'org.jboss.logging:jboss-logging-annotations:3.0.4.Final' + annotationProcessor 'org.jboss.logging:jboss-logging-processor:3.0.4.Final' - annotationProcessor 'org.jboss.logging:jboss-logging:3.5.0.Final' - annotationProcessor 'org.jboss.logging:jboss-logging-annotations:2.2.1.Final' - annotationProcessor 'org.jboss.logging:jboss-logging-processor:2.2.1.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 @@ -38,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" @@ -80,6 +81,15 @@ dependencies { testImplementation "org.testcontainers:oracle-xe:${testcontainersVersion}" } +// Reproducible Builds + +// https://docs.gradle.org/current/userguide/working_with_files.html#sec:reproducible_archives +// Configure archive tasks to produce reproducible archives: +tasks.withType(AbstractArchiveTask).configureEach { + preserveFileTimestamps = false + reproducibleFileOrder = true +} + // Print a summary of the results of the tests (number of failures, successes and skipped) def loggingSummary(db, result, desc) { if ( !desc.parent ) { // will match the outermost suite 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 new file mode 100644 index 000000000..6d87416b4 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/engine/internal/ReactivePersistenceContextAdapter.java @@ -0,0 +1,799 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.engine.internal; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletionStage; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.hibernate.HibernateException; +import org.hibernate.Internal; +import org.hibernate.LockMode; +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.BatchFetchQueue; +import org.hibernate.engine.spi.CollectionEntry; +import org.hibernate.engine.spi.CollectionKey; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.EntityHolder; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.EntityUniqueKey; +import org.hibernate.engine.spi.NaturalIdResolutions; +import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.Status; +import org.hibernate.event.service.spi.EventListenerGroup; +import org.hibernate.event.spi.PostLoadEvent; +import org.hibernate.event.spi.PostLoadEventListener; +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.sql.exec.spi.Callback; +import org.hibernate.sql.results.graph.entity.EntityInitializer; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState; +import org.hibernate.sql.results.spi.LoadContexts; + +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; +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.voidFuture; + +/** + * Add reactive methods to a {@link PersistenceContext}. + */ +public class ReactivePersistenceContextAdapter implements PersistenceContext { + + private static final Log LOG = make( Log.class, lookup() ); + + private final PersistenceContext delegate; + + /** + * Constructs a PersistentContext, bound to the given session. + */ + public ReactivePersistenceContextAdapter(PersistenceContext persistenceContext) { + this.delegate = persistenceContext; + } + + public CompletionStage reactiveInitializeNonLazyCollections() throws HibernateException { + final NonLazyCollectionInitializer initializer = new NonLazyCollectionInitializer(); + delegate.initializeNonLazyCollections( initializer ); + return initializer.stage; + } + + private class NonLazyCollectionInitializer implements Consumer> { + CompletionStage stage = voidFuture(); + + @Override + public void accept(PersistentCollection nonLazyCollection) { + if ( !nonLazyCollection.wasInitialized() ) { + stage = stage.thenCompose( v -> + ( (ReactiveSharedSessionContractImplementor) getSession() ) + .reactiveInitializeCollection( nonLazyCollection, false ) ); + } + } + } + + /** + * @deprecated use {@link #reactiveInitializeNonLazyCollections} instead. + */ + @Deprecated + @Override + public void initializeNonLazyCollections() { + // still called by ResultSetProcessorImpl, so can't throw UnsupportedOperationException + } + + @Override + public void initializeNonLazyCollections(Consumer> initializeAction) { + throw LOG.nonReactiveMethodCall( "reactiveInitializeNonLazyCollection" ); + } + + @Deprecated + @Override + public Object[] getDatabaseSnapshot(Object id, EntityPersister persister) throws HibernateException { + throw LOG.nonReactiveMethodCall( "reactiveGetDatabaseSnapshot" ); + } + + private static final Object[] NO_ROW = new Object[] {StatefulPersistenceContext.NO_ROW}; + + public CompletionStage reactiveGetDatabaseSnapshot(Object id, EntityPersister persister) throws HibernateException { + SessionImplementor session = (SessionImplementor) getSession(); + final EntityKey key = session.generateEntityKey( id, persister ); + final Object[] cached = getEntitySnapshotsByKey() == null + ? null + : (Object[]) getEntitySnapshotsByKey().get( key ); + if ( cached != null ) { + return completedFuture( cached == NO_ROW ? null : cached ); + } + else { + return ( (ReactiveEntityPersister) persister ) + .reactiveGetDatabaseSnapshot( id, session ) + .thenApply( snapshot -> { + getOrInitializeEntitySnapshotsByKey().put( key, snapshot ); + return snapshot; + } ); + } + } + + @Override + public boolean isStateless() { + return delegate.isStateless(); + } + + @Override + public SharedSessionContractImplementor getSession() { + return delegate.getSession(); + } + + @Override + public LoadContexts getLoadContexts() { + return delegate.getLoadContexts(); + } + + @Override + public boolean hasLoadContext() { + return delegate.hasLoadContext(); + } + + @Override + public PersistentCollection useUnownedCollection(CollectionKey key) { + return delegate.useUnownedCollection( key ); + } + + @Override + public BatchFetchQueue getBatchFetchQueue() { + return delegate.getBatchFetchQueue(); + } + + @Override + public void clear() { + delegate.clear(); + } + + @Override + public void setEntryStatus(EntityEntry entry, Status status) { + delegate.setEntryStatus( entry, status ); + } + + @Override + public void afterTransactionCompletion() { + delegate.afterTransactionCompletion(); + } + + @Override + public Object[] getCachedDatabaseSnapshot(EntityKey key) { + return delegate.getCachedDatabaseSnapshot( key ); + } + + @Override + public Object getNaturalIdSnapshot(Object id, EntityPersister persister) { + return delegate.getNaturalIdSnapshot( id, persister ); + } + + @Override + public void addEntity(EntityKey key, Object entity) { + delegate.addEntity( key, entity ); + } + + @Override + public Object getEntity(EntityKey key) { + return delegate.getEntity( key ); + } + + @Override + public boolean containsEntity(EntityKey key) { + return delegate.containsEntity( key ); + } + + @Override + public Object removeEntity(EntityKey key) { + return delegate.removeEntity( key ); + } + + @Override + public void addEntity(EntityUniqueKey euk, Object entity) { + delegate.addEntity( euk, entity ); + } + + @Override + public Object getEntity(EntityUniqueKey euk) { + return delegate.getEntity( euk ); + } + + @Override + public EntityEntry getEntry(Object entity) { + return delegate.getEntry( entity ); + } + + @Override + public EntityEntry removeEntry(Object entity) { + return delegate.removeEntry( entity ); + } + + @Override + public boolean isEntryFor(Object entity) { + return delegate.isEntryFor( entity ); + } + + @Override + public CollectionEntry getCollectionEntry(PersistentCollection coll) { + return delegate.getCollectionEntry( coll ); + } + + @Override + public EntityEntry addEntity( + Object entity, + Status status, + Object[] loadedState, + EntityKey entityKey, + Object version, + LockMode lockMode, + boolean existsInDatabase, + EntityPersister persister, + boolean disableVersionIncrement) { + return delegate.addEntity( + entity, + status, + loadedState, + entityKey, + version, + lockMode, + existsInDatabase, + persister, + disableVersionIncrement + ); + } + + @Override + public EntityEntry addEntry( + Object entity, + Status status, + Object[] loadedState, + Object rowId, + Object id, + Object version, + LockMode lockMode, + boolean existsInDatabase, + EntityPersister persister, + boolean disableVersionIncrement) { + return delegate.addEntry( + entity, + status, + loadedState, + rowId, + id, + version, + lockMode, + existsInDatabase, + persister, + disableVersionIncrement + ); + } + + @Override + public EntityEntry addReferenceEntry(Object entity, Status status) { + return delegate.addReferenceEntry( entity, status ); + } + + @Override + public boolean containsCollection(PersistentCollection collection) { + return delegate.containsCollection( collection ); + } + + @Override + public boolean containsProxy(Object proxy) { + return delegate.containsProxy( proxy ); + } + + @Override + public boolean reassociateIfUninitializedProxy(Object value) { + return delegate.reassociateIfUninitializedProxy( value ); + } + + @Override + public void reassociateProxy(Object value, Object id) { + delegate.reassociateProxy( value, id ); + } + + @Override + public Object unproxy(Object maybeProxy) { + return delegate.unproxy( maybeProxy ); + } + + @Override + public Object unproxyAndReassociate(Object maybeProxy) { + return delegate.unproxyAndReassociate( maybeProxy ); + } + + @Override + public void checkUniqueness(EntityKey key, Object object) { + delegate.checkUniqueness( key, object ); + } + + @Override + public Object narrowProxy(Object proxy, EntityPersister persister, EntityKey key, Object object) { + return delegate.narrowProxy( proxy, persister, key, object ); + } + + @Override + public Object proxyFor(EntityPersister persister, EntityKey key, Object impl) { + return delegate.proxyFor( persister, key, impl ); + } + + @Override + public Object proxyFor(Object impl) { + return delegate.proxyFor( impl ); + } + + @Override + public Object proxyFor(EntityHolder holder, EntityPersister persister) { + return delegate.proxyFor( holder, persister ); + } + + @Override + public void addEnhancedProxy(EntityKey key, PersistentAttributeInterceptable entity) { + delegate.addEnhancedProxy( key, entity ); + } + + @Override + public Object getCollectionOwner(Object key, CollectionPersister collectionPersister) { + return delegate.getCollectionOwner( key, collectionPersister ); + } + + @Override + public Object getLoadedCollectionOwnerOrNull(PersistentCollection collection) { + return delegate.getLoadedCollectionOwnerOrNull( collection ); + } + + @Override + public Object getLoadedCollectionOwnerIdOrNull(PersistentCollection collection) { + return delegate.getLoadedCollectionOwnerIdOrNull( collection ); + } + + @Override + public void addUninitializedCollection(CollectionPersister persister, PersistentCollection collection, Object id) { + delegate.addUninitializedCollection( persister, collection, id ); + } + + @Override + public void addUninitializedDetachedCollection(CollectionPersister persister, PersistentCollection collection) { + delegate.addUninitializedDetachedCollection( persister, collection ); + } + + @Override + public void addNewCollection(CollectionPersister persister, PersistentCollection collection) { + delegate.addNewCollection( persister, collection ); + } + + @Override + public void addInitializedDetachedCollection(CollectionPersister collectionPersister, PersistentCollection collection) { + delegate.addInitializedDetachedCollection( collectionPersister, collection ); + } + + @Override + public void replaceCollection(CollectionPersister persister, PersistentCollection oldCollection, PersistentCollection collection) { + delegate.replaceCollection( persister, oldCollection, collection ); + } + + @Override + public CollectionEntry addInitializedCollection(CollectionPersister persister, PersistentCollection collection, Object id) { + return delegate.addInitializedCollection( persister, collection, id ); + } + + @Override + public PersistentCollection getCollection(CollectionKey collectionKey) { + return delegate.getCollection( collectionKey ); + } + + @Override + public void addNonLazyCollection(PersistentCollection collection) { + delegate.addNonLazyCollection( collection ); + } + + @Override + public PersistentCollection getCollectionHolder(Object array) { + return delegate.getCollectionHolder( array ); + } + + @Override + public void addCollectionHolder(PersistentCollection holder) { + delegate.addCollectionHolder( holder ); + } + + @Override + public PersistentCollection removeCollectionHolder(Object array) { + return delegate.removeCollectionHolder( array ); + } + + @Override + public Serializable getSnapshot(PersistentCollection coll) { + return delegate.getSnapshot( coll ); + } + + @Override + public Object getProxy(EntityKey key) { + return delegate.getProxy( key ); + } + + @Override + public void addProxy(EntityKey key, Object proxy) { + delegate.addProxy( key, proxy ); + } + + @Override + public Object removeProxy(EntityKey key) { + return delegate.removeProxy( key ); + } + + @Override + public EntityHolder claimEntityHolderIfPossible(EntityKey key, Object entity, JdbcValuesSourceProcessingState processingState, EntityInitializer initializer) { + return delegate.claimEntityHolderIfPossible( key, entity, processingState, initializer ); + } + + @Override + public EntityHolder addEntityHolder(EntityKey key, Object entity) { + return delegate.addEntityHolder( key, entity ); + } + + @Override + public EntityHolder getEntityHolder(EntityKey key) { + return delegate.getEntityHolder( key ); + } + + @Override + public boolean containsEntityHolder(EntityKey key) { + return delegate.containsEntityHolder( key ); + } + + @Override + public EntityHolder removeEntityHolder(EntityKey key) { + return delegate.removeEntityHolder( key ); + } + + @Override + public void postLoad(JdbcValuesSourceProcessingState processingState, Consumer loadedConsumer) { + throw LOG.nonReactiveMethodCall( "reactivePostLoad(JdbcValuesSourceProcessingState, Consumer) )" ); + } + + @Internal + @Override + public Map getEntitiesByKey() { + return delegate.getEntitiesByKey(); + } + + @Internal + @Override + public Map getEntityHoldersByKey() { + return delegate.getEntityHoldersByKey(); + } + + @Override + public Map.Entry[] reentrantSafeEntityEntries() { + return delegate.reentrantSafeEntityEntries(); + } + + @Override + public int getNumberOfManagedEntities() { + return delegate.getNumberOfManagedEntities(); + } + + @Internal + @Override + public Map, CollectionEntry> getCollectionEntries() { + return delegate.getCollectionEntries(); + } + + @Override + public void forEachCollectionEntry(BiConsumer, CollectionEntry> action, boolean concurrent) { + delegate.forEachCollectionEntry( action, concurrent ); + } + + @Deprecated + @Override + public Map> getCollectionsByKey() { + return delegate.getCollectionsByKey(); + } + + @Override + public int getCascadeLevel() { + return delegate.getCascadeLevel(); + } + + @Override + public int incrementCascadeLevel() { + return delegate.incrementCascadeLevel(); + } + + @Override + public int decrementCascadeLevel() { + return delegate.decrementCascadeLevel(); + } + + @Override + public boolean isFlushing() { + return delegate.isFlushing(); + } + + @Override + public void setFlushing(boolean flushing) { + delegate.setFlushing( flushing ); + } + + @Override + public void beforeLoad() { + delegate.beforeLoad(); + } + + @Override + public void afterLoad() { + delegate.afterLoad(); + } + + @Override + public boolean isLoadFinished() { + return delegate.isLoadFinished(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + @Override + public Object getOwnerId(String entityName, String propertyName, Object childEntity, Map mergeMap) { + return delegate.getOwnerId( entityName, propertyName, childEntity, mergeMap ); + } + + @Override + public Object getIndexInOwner(String entity, String property, Object childObject, Map mergeMap) { + return delegate.getIndexInOwner( entity, property, childObject, mergeMap ); + } + + @Override + public void addNullProperty(EntityKey ownerKey, String propertyName) { + delegate.addNullProperty( ownerKey, propertyName ); + } + + @Override + public boolean isPropertyNull(EntityKey ownerKey, String propertyName) { + return delegate.isPropertyNull( ownerKey, propertyName ); + } + + @Override + public boolean isDefaultReadOnly() { + return delegate.isDefaultReadOnly(); + } + + @Override + public void setDefaultReadOnly(boolean readOnly) { + delegate.setDefaultReadOnly( readOnly ); + } + + @Override + public boolean isReadOnly(Object entityOrProxy) { + return delegate.isReadOnly( entityOrProxy ); + } + + @Override + public void setReadOnly(Object entityOrProxy, boolean readOnly) { + delegate.setReadOnly( entityOrProxy, readOnly ); + } + + @Override + public boolean isRemovingOrphanBeforeUpdates() { + return delegate.isRemovingOrphanBeforeUpdates(); + } + + @Override + public void beginRemoveOrphanBeforeUpdates() { + delegate.beginRemoveOrphanBeforeUpdates(); + } + + @Override + public void endRemoveOrphanBeforeUpdates() { + delegate.endRemoveOrphanBeforeUpdates(); + } + + @Override + public void replaceDelayedEntityIdentityInsertKeys(EntityKey oldKey, Object generatedId) { + delegate.replaceDelayedEntityIdentityInsertKeys( oldKey, generatedId ); + } + + @Internal + @Override + public void replaceEntityEntryRowId(Object entity, Object rowId) { + delegate.replaceEntityEntryRowId( entity, rowId ); + } + + @Override + public void addChildParent(Object child, Object parent) { + delegate.addChildParent( child, parent ); + } + + @Override + public void removeChildParent(Object child) { + delegate.removeChildParent( child ); + } + + @Override + public void registerInsertedKey(EntityPersister persister, Object id) { + delegate.registerInsertedKey( persister, id ); + } + + @Override + public boolean wasInsertedDuringTransaction(EntityPersister persister, Object id) { + return delegate.wasInsertedDuringTransaction( persister, id ); + } + + @Override + public boolean containsNullifiableEntityKey(Supplier sek) { + return delegate.containsNullifiableEntityKey( sek ); + } + + @Override + public void registerNullifiableEntityKey(EntityKey key) { + delegate.registerNullifiableEntityKey( key ); + } + + @Override + public boolean isNullifiableEntityKeysEmpty() { + return delegate.isNullifiableEntityKeysEmpty(); + } + + @Override + public boolean containsDeletedUnloadedEntityKey(EntityKey ek) { + return delegate.containsDeletedUnloadedEntityKey( ek ); + } + + @Override + public void registerDeletedUnloadedEntityKey(EntityKey key) { + delegate.registerDeletedUnloadedEntityKey( key ); + } + + @Override + public void removeDeletedUnloadedEntityKey(EntityKey key) { + delegate.removeDeletedUnloadedEntityKey( key ); + } + + @Override + public boolean containsDeletedUnloadedEntityKeys() { + return delegate.containsDeletedUnloadedEntityKeys(); + } + + @Override + public int getCollectionEntriesSize() { + return delegate.getCollectionEntriesSize(); + } + + @Override + public CollectionEntry removeCollectionEntry(PersistentCollection collection) { + return delegate.removeCollectionEntry( collection ); + } + + @Override + public void clearCollectionsByKey() { + delegate.clearCollectionsByKey(); + } + + @Override + public PersistentCollection addCollectionByKey(CollectionKey collectionKey, PersistentCollection persistentCollection) { + return delegate.addCollectionByKey( collectionKey, persistentCollection ); + } + + @Override + public void removeCollectionByKey(CollectionKey collectionKey) { + delegate.removeCollectionByKey( collectionKey ); + } + + @Internal + @Override + public Map getEntitySnapshotsByKey() { + return delegate.getEntitySnapshotsByKey(); + } + + @Override + @Internal + public Map getOrInitializeEntitySnapshotsByKey() { + return delegate.getOrInitializeEntitySnapshotsByKey(); + } + + @Override + public Iterator managedEntitiesIterator() { + return delegate.managedEntitiesIterator(); + } + + @Override + public NaturalIdResolutions getNaturalIdResolutions() { + return delegate.getNaturalIdResolutions(); + } + + /** + * Reactive version of {@link StatefulPersistenceContext#postLoad(JdbcValuesSourceProcessingState, Consumer)} + */ + public CompletionStage reactivePostLoad( + JdbcValuesSourceProcessingState processingState, + Consumer holderConsumer) { + final ReactiveCallbackImpl callback = (ReactiveCallbackImpl) processingState + .getExecutionContext().getCallback(); + return processHolders( + holderConsumer, + processingState.getLoadingEntityHolders(), + getSession().getFactory().getEventListenerGroups().eventListenerGroup_POST_LOAD, + processingState.getPostLoadEvent(), + callback + ).thenCompose( v -> processHolders( + holderConsumer, + processingState.getReloadedEntityHolders(), + null, + null, + callback + ) ); + } + + private CompletionStage processHolders( + Consumer holderConsumer, + List loadingEntityHolders, + EventListenerGroup listenerGroup, + PostLoadEvent postLoadEvent, + ReactiveCallbackImpl callback) { + if ( loadingEntityHolders != null ) { + return loop( loadingEntityHolders, + holder -> processLoadedEntityHolder( + holder, + listenerGroup, + postLoadEvent, + callback, + holderConsumer + ) + ).thenAccept( v -> loadingEntityHolders.clear() ); + } + return voidFuture(); + } + + /** + * Reactive version of {@link StatefulPersistenceContext#processLoadedEntityHolder(EntityHolder, EventListenerGroup, PostLoadEvent, Callback, Consumer)} + */ + private CompletionStage processLoadedEntityHolder( + EntityHolder holder, + EventListenerGroup listenerGroup, + PostLoadEvent postLoadEvent, + ReactiveCallbackImpl callback, + Consumer holderConsumer) { + if ( holderConsumer != null ) { + holderConsumer.accept( holder ); + } + if ( holder.getEntity() == null ) { + // It's possible that we tried to load an entity and found out it doesn't exist, + // in which case we added an entry with a null proxy and entity. + // Remove that empty entry on post load to avoid unwanted side effects + getEntitiesByKey().remove( holder.getEntityKey() ); + } + else { + if ( postLoadEvent != null ) { + postLoadEvent.reset(); + postLoadEvent.setEntity( holder.getEntity() ) + .setId( holder.getEntityKey().getIdentifier() ) + .setPersister( holder.getDescriptor() ); + listenerGroup.fireEventOnEachListener( postLoadEvent, PostLoadEventListener::onPostLoad ); + if ( callback != null ) { + return callback + .invokeReactiveLoadActions( holder.getEntity(), holder.getDescriptor(), getSession() ) + .thenAccept( v -> holder.resetEntityInitialier() ); + } + } + } + return voidFuture(); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/PreparedStatementAdaptor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/PreparedStatementAdaptor.java index 8ff274857..066ec9de6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/PreparedStatementAdaptor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/PreparedStatementAdaptor.java @@ -5,11 +5,6 @@ */ package org.hibernate.reactive.adaptor.impl; -import io.vertx.core.buffer.Buffer; - -import org.hibernate.AssertionFailure; -import org.hibernate.HibernateException; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -39,12 +34,23 @@ import java.util.Calendar; import java.util.function.Function; +import org.hibernate.AssertionFailure; +import org.hibernate.HibernateException; +import org.hibernate.reactive.logging.impl.Log; + +import io.vertx.core.buffer.Buffer; + +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; + /** * Collects parameter bindings from Hibernate core code * that expects a JDBC {@link PreparedStatement}. */ public class PreparedStatementAdaptor implements PreparedStatement { + private static final Log LOG = make( Log.class, lookup() ); + @FunctionalInterface public interface Binder { void bind(PreparedStatement statement) throws SQLException; @@ -315,7 +321,7 @@ public void setNClob(int parameterIndex, Reader reader, long length) { @Override public void setSQLXML(int parameterIndex, SQLXML xmlObject) { - throw new UnsupportedOperationException(); + throw LOG.unsupportedXmlType(); } @Override @@ -540,7 +546,7 @@ public int[] executeBatch() { @Override public Connection getConnection() { - throw new UnsupportedOperationException(); + throw LOG.unexpectedConnectionRequest(); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/ResultSetAdaptor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/ResultSetAdaptor.java index 0bfcd89d5..b75133699 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/ResultSetAdaptor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/ResultSetAdaptor.java @@ -36,8 +36,9 @@ import java.util.NoSuchElementException; import java.util.function.Function; -import org.hibernate.engine.jdbc.BlobProxy; -import org.hibernate.engine.jdbc.ClobProxy; +import org.hibernate.engine.jdbc.proxy.BlobProxy; +import org.hibernate.engine.jdbc.proxy.ClobProxy; +import org.hibernate.reactive.logging.impl.Log; import org.hibernate.type.descriptor.jdbc.JdbcType; import io.vertx.core.buffer.Buffer; @@ -48,8 +49,10 @@ import io.vertx.sqlclient.desc.ColumnDescriptor; import io.vertx.sqlclient.impl.RowBase; +import static java.lang.invoke.MethodHandles.lookup; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; /** * An adaptor that allows Hibernate core code which expects a JDBC @@ -57,6 +60,8 @@ */ public class ResultSetAdaptor implements ResultSet { + private static final Log LOG = make( Log.class, lookup() ); + private final Iterator iterator; private final List columnDescriptors; @@ -866,12 +871,12 @@ public NClob getNClob(String columnLabel) { @Override public SQLXML getSQLXML(int columnIndex) { - throw new UnsupportedOperationException(); + throw LOG.unsupportedXmlType(); } @Override public SQLXML getSQLXML(String columnLabel) { - throw new UnsupportedOperationException(); + throw LOG.unsupportedXmlType(); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/boot/spi/ReactiveMetadataImplementor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/boot/spi/ReactiveMetadataImplementor.java index fb72b2624..e63d3215d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/boot/spi/ReactiveMetadataImplementor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/boot/spi/ReactiveMetadataImplementor.java @@ -7,7 +7,6 @@ import org.hibernate.boot.spi.AbstractDelegatingMetadata; import org.hibernate.boot.spi.MetadataImplementor; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.named.NamedObjectRepository; import org.hibernate.reactive.query.internal.ReactiveNamedObjectRepositoryImpl; @@ -18,7 +17,7 @@ public ReactiveMetadataImplementor(MetadataImplementor delegate) { } @Override - public NamedObjectRepository buildNamedQueryRepository(SessionFactoryImplementor sessionFactory) { - return new ReactiveNamedObjectRepositoryImpl( delegate().buildNamedQueryRepository( sessionFactory ) ); + public NamedObjectRepository buildNamedQueryRepository() { + return new ReactiveNamedObjectRepositoryImpl( super.buildNamedQueryRepository() ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java index 4448c9b6c..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 @@ -8,7 +8,7 @@ import java.lang.invoke.MethodHandles; import io.vertx.core.Vertx; -import io.vertx.core.impl.ContextInternal; +import io.vertx.core.internal.ContextInternal; import org.hibernate.reactive.context.Context; import org.hibernate.reactive.logging.impl.Log; @@ -36,7 +36,7 @@ public void injectServices(ServiceRegistryImplementor serviceRegistry) { @Override public void put(Key key, T instance) { - final io.vertx.core.Context context = Vertx.currentContext(); + final ContextInternal context = currentContext(); if ( context != null ) { if ( trace ) LOG.tracef( "Putting key,value in context: [%1$s, %2$s]", key, instance ); context.putLocal( key, instance ); @@ -47,9 +47,13 @@ public void put(Key key, T instance) { } } + private static ContextInternal currentContext() { + return (ContextInternal) Vertx.currentContext(); + } + @Override public T get(Key key) { - final io.vertx.core.Context context = Vertx.currentContext(); + final ContextInternal context = currentContext(); if ( context != null ) { T local = context.getLocal( key ); if ( trace ) LOG.tracef( "Getting value %2$s from context for key %1$s", key, local ); @@ -63,7 +67,7 @@ public T get(Key key) { @Override public void remove(Key key) { - final io.vertx.core.Context context = Vertx.currentContext(); + final ContextInternal context = currentContext(); if ( context != null ) { boolean removed = context.removeLocal( key ); if ( trace ) LOG.tracef( "Key %s removed from context: %s", key, removed ); @@ -75,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(); @@ -83,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() ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/dialect/ReactiveOracleSqlAstTranslator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/dialect/ReactiveOracleSqlAstTranslator.java deleted file mode 100644 index 0773ab033..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/dialect/ReactiveOracleSqlAstTranslator.java +++ /dev/null @@ -1,43 +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.dialect; - -import org.hibernate.dialect.OracleSqlAstTranslator; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.persister.entity.mutation.EntityTableMapping; -import org.hibernate.reactive.sql.model.ReactiveDeleteOrUpsertOperation; -import org.hibernate.sql.ast.tree.Statement; -import org.hibernate.sql.exec.spi.JdbcOperation; -import org.hibernate.sql.model.MutationOperation; -import org.hibernate.sql.model.internal.OptionalTableUpdate; -import org.hibernate.sql.model.jdbc.UpsertOperation; - -public class ReactiveOracleSqlAstTranslator extends OracleSqlAstTranslator { - public ReactiveOracleSqlAstTranslator( - SessionFactoryImplementor sessionFactory, - Statement statement) { - super( sessionFactory, statement ); - } - - @Override - public MutationOperation createMergeOperation(OptionalTableUpdate optionalTableUpdate) { - renderUpsertStatement( optionalTableUpdate ); - - final UpsertOperation upsertOperation = new UpsertOperation( - optionalTableUpdate.getMutatingTable().getTableMapping(), - optionalTableUpdate.getMutationTarget(), - getSql(), - getParameterBinders() - ); - - return new ReactiveDeleteOrUpsertOperation( - optionalTableUpdate.getMutationTarget(), - (EntityTableMapping) optionalTableUpdate.getMutatingTable().getTableMapping(), - upsertOperation, - optionalTableUpdate - ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/ReactiveActionQueue.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/ReactiveActionQueue.java index 034c9f583..3c815b2a8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/ReactiveActionQueue.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/ReactiveActionQueue.java @@ -21,6 +21,8 @@ import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; import org.hibernate.PropertyValueException; +import org.hibernate.TransientObjectException; +import org.hibernate.action.internal.AbstractEntityInsertAction; import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.action.internal.EntityDeleteAction; import org.hibernate.action.internal.UnresolvedEntityInsertActions; @@ -28,13 +30,13 @@ import org.hibernate.action.spi.BeforeTransactionCompletionProcess; import org.hibernate.action.spi.Executable; import org.hibernate.cache.CacheException; +import org.hibernate.engine.internal.NonNullableTransientDependencies; import org.hibernate.engine.spi.ActionQueue; import org.hibernate.engine.spi.ComparableExecutable; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.ExecutableList; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metadata.ClassMetadata; import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; @@ -59,7 +61,6 @@ import static java.lang.invoke.MethodHandles.lookup; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; -import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.loop; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @@ -520,11 +521,21 @@ public CompletionStage executeInserts() { */ public CompletionStage executeActions() { if ( hasUnresolvedEntityInsertActions() ) { - return failedFuture( new IllegalStateException( "About to execute actions, but there are unresolved entity insert actions." ) ); + final AbstractEntityInsertAction insertAction = unresolvedInsertions + .getDependentEntityInsertActions() + .iterator() + .next(); + final NonNullableTransientDependencies transientEntities = insertAction.findNonNullableTransientEntities(); + final Object transientEntity = transientEntities.getNonNullableTransientEntities().iterator().next(); + final String path = transientEntities.getNonNullableTransientPropertyPaths(transientEntity).iterator().next(); + //TODO: should be TransientPropertyValueException + throw new TransientObjectException( "Persistent instance of '" + insertAction.getEntityName() + + "' with id '" + insertAction.getId() + + "' references an unsaved transient instance via attribute '" + path + + "' (save the transient instance before flushing)" ); } CompletionStage ret = voidFuture(); - for ( OrderedActions action : ORDERED_OPERATIONS ) { ret = ret.thenCompose( v -> executeActions( action.getActions( this ) ) ); } @@ -738,26 +749,6 @@ public int numberOfInsertions() { return insertions.size(); } -// public TransactionCompletionProcesses getTransactionCompletionProcesses() { -// return new TransactionCompletionProcesses( beforeTransactionProcesses(), afterTransactionProcesses() ); -// } -// -// /** -// * Bind transaction completion processes to make them shared between primary and secondary session. -// * Transaction completion processes are always executed by transaction owner (primary session), -// * but can be registered using secondary session too. -// * -// * @param processes Transaction completion processes. -// * @param isTransactionCoordinatorShared Flag indicating shared transaction context. -// */ -// public void setTransactionCompletionProcesses( -// TransactionCompletionProcesses processes, -// boolean isTransactionCoordinatorShared) { -// this.isTransactionCoordinatorShared = isTransactionCoordinatorShared; -// this.beforeTransactionProcesses = processes.beforeTransactionCompletionProcesses; -// this.afterTransactionProcesses = processes.afterTransactionCompletionProcesses; -// } - public void sortCollectionActions() { if ( isOrderUpdatesEnabled() ) { // sort the updates by fk @@ -864,32 +855,6 @@ public void unScheduleDeletion(EntityEntry entry, Object rescuedEntity) { throw new AssertionFailure( "Unable to perform un-delete for instance " + entry.getEntityName() ); } -// /** -// * Used by the owning session to explicitly control serialization of the action queue -// * -// * @param oos The stream to which the action queue should get written -// * -// * @throws IOException Indicates an error writing to the stream -// */ -// public void serialize(ObjectOutputStream oos) throws IOException { -// LOG.trace( "Serializing action-queue" ); -// if ( unresolvedInsertions == null ) { -// unresolvedInsertions = new UnresolvedEntityInsertActions(); -// } -// unresolvedInsertions.serialize( oos ); -// -// for ( ListProvider p : EXECUTABLE_LISTS_MAP.values() ) { -// ExecutableList l = p.get( this ); -// if ( l == null ) { -// oos.writeBoolean( false ); -// } -// else { -// oos.writeBoolean( true ); -// l.writeExternal( oos ); -// } -// } -// } - private abstract static class AbstractTransactionCompletionProcessQueue { final ReactiveSession session; @@ -994,21 +959,6 @@ public CompletionStage afterTransactionCompletion(boolean success) { } } -// /** -// * Wrapper class allowing to bind the same transaction completion process queues in different sessions. -// */ -// public static class TransactionCompletionProcesses { -// private final BeforeTransactionCompletionProcessQueue beforeTransactionCompletionProcesses; -// private final AfterTransactionCompletionProcessQueue afterTransactionCompletionProcesses; -// -// private TransactionCompletionProcesses( -// BeforeTransactionCompletionProcessQueue beforeTransactionCompletionProcessQueue, -// AfterTransactionCompletionProcessQueue afterTransactionCompletionProcessQueue) { -// this.beforeTransactionCompletionProcesses = beforeTransactionCompletionProcessQueue; -// this.afterTransactionCompletionProcesses = afterTransactionCompletionProcessQueue; -// } -// } - /** * Order the {@link #insertions} queue such that we group inserts against the same entity together (without * violating constraints). The original order is generated by cascade order, which in turn is based on the @@ -1152,26 +1102,23 @@ public void sort(List insertions) { */ private void addParentChildEntityNames(ReactiveEntityInsertAction action, BatchIdentifier batchIdentifier) { Object[] propertyValues = action.getState(); - ClassMetadata classMetadata = action.getPersister().getClassMetadata(); - if ( classMetadata != null ) { - Type[] propertyTypes = classMetadata.getPropertyTypes(); - Type identifierType = classMetadata.getIdentifierType(); - - for ( int i = 0; i < propertyValues.length; i++ ) { - Object value = propertyValues[i]; - if (value!=null) { - Type type = propertyTypes[i]; - addParentChildEntityNameByPropertyAndValue( action, batchIdentifier, type, value ); - } + Type[] propertyTypes = action.getPersister().getPropertyTypes(); + Type identifierType = action.getPersister().getIdentifierType(); + + for ( int i = 0; i < propertyValues.length; i++ ) { + Object value = propertyValues[i]; + if (value!=null) { + Type type = propertyTypes[i]; + addParentChildEntityNameByPropertyAndValue( action, batchIdentifier, type, value ); } + } - if ( identifierType.isComponentType() ) { - CompositeType compositeType = (CompositeType) identifierType; - Type[] compositeIdentifierTypes = compositeType.getSubtypes(); + if ( identifierType.isComponentType() ) { + CompositeType compositeType = (CompositeType) identifierType; + Type[] compositeIdentifierTypes = compositeType.getSubtypes(); - for ( Type type : compositeIdentifierTypes ) { - addParentChildEntityNameByPropertyAndValue( action, batchIdentifier, type, null ); - } + for ( Type type : compositeIdentifierTypes ) { + addParentChildEntityNameByPropertyAndValue( action, batchIdentifier, type, null ); } } } @@ -1275,10 +1222,9 @@ public boolean equals(Object o) { if ( this == o ) { return true; } - if ( !( o instanceof BatchIdentifier ) ) { + if ( !( o instanceof BatchIdentifier that ) ) { return false; } - BatchIdentifier that = (BatchIdentifier) o; return Objects.equals( entityName, that.entityName ); } @@ -1315,9 +1261,7 @@ boolean hasAnyChildEntityNames(BatchIdentifier batchIdentifier) { /** * Check if this {@link BatchIdentifier} has a parent or grandparent * matching the given {@link BatchIdentifier reference. - * * @param batchIdentifier {@link BatchIdentifier} reference - * * @return this {@link BatchIdentifier} has a parent matching the given {@link BatchIdentifier reference */ boolean hasParent(BatchIdentifier batchIdentifier) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/Cascade.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/Cascade.java index 36466b556..a13426966 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/Cascade.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/Cascade.java @@ -235,10 +235,7 @@ else if ( action.performOnLazyProperty() && type.isEntityType() ) { false ) ); } - else { - // Nothing to do, so just skip cascading to this lazy attribute. - continue; - } + // Nothing to do, so just skip cascading to this lazy attribute. } else { Object child = persister.getValue( parent, i ); @@ -447,7 +444,7 @@ private static CompletionStage cascadeLogicalOneToOneOrphanRemoval( * * @param type The type representing the attribute metadata * - * @return True if the attribute represents a logical one to one association + * @return True if the attribute represents a logical one-to-one association */ private static boolean isLogicalOneToOne(Type type) { return type.isEntityType() && ( (EntityType) type ).isLogicalOneToOne(); 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 new file mode 100644 index 000000000..9ce6a7c5b --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java @@ -0,0 +1,482 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.engine.impl; + +import java.io.Serializable; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.concurrent.CompletionStage; + +import org.hibernate.Hibernate; +import org.hibernate.HibernateException; +import org.hibernate.collection.spi.AbstractPersistentCollection; +import org.hibernate.collection.spi.PersistentArrayHolder; +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.CollectionEntry; +import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.type.ArrayType; +import org.hibernate.type.CollectionType; +import org.hibernate.type.CustomCollectionType; +import org.hibernate.type.EntityType; +import org.hibernate.type.ForeignKeyDirection; +import org.hibernate.type.MapType; +import org.hibernate.type.Type; + +import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY; +import static org.hibernate.internal.util.collections.CollectionHelper.mapOfSize; +import static org.hibernate.pretty.MessageHelper.collectionInfoString; +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.voidFuture; + +/** + * Reactive operations that really belong to {@link CollectionType} + * + */ +public class CollectionTypes { + private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + + /** + * @see org.hibernate.type.AbstractType#replace(Object, Object, SharedSessionContractImplementor, Object, Map, ForeignKeyDirection) + */ + public static CompletionStage replace( + CollectionType type, + Object original, + Object target, + SharedSessionContractImplementor session, + Object owner, + Map copyCache, + ForeignKeyDirection foreignKeyDirection) + throws HibernateException { + // Collection and OneToOne are the only associations that could be TO_PARENT + return type.getForeignKeyDirection() == foreignKeyDirection + ? replace( type, original, target, session, owner, copyCache ) + : completedFuture( target ); + } + + /** + * @see CollectionType#replace(Object, Object, SharedSessionContractImplementor, Object, Map) + */ + public static CompletionStage replace( + CollectionType type, + Object original, + Object target, + SharedSessionContractImplementor session, + Object owner, + Map copyCache) throws HibernateException { + if ( original == null ) { + return completedFuture( replaceNullOriginal( target, session ) ); + } + else if ( !Hibernate.isInitialized( original ) ) { + return completedFuture( replaceUninitializedOriginal( type, original, target, session, copyCache ) ); + } + else { + return replaceOriginal( type, original, target, session, owner, copyCache ); + } + } + + // todo: make org.hibernate.type.CollectionType#replaceNullOriginal public ? + /** + * @see CollectionType#replaceNullOriginal(Object, SharedSessionContractImplementor) + */ + private static Object replaceNullOriginal( + Object target, + SharedSessionContractImplementor session) { + if ( target == null ) { + return null; + } + else if ( target instanceof Collection collection ) { + collection.clear(); + return collection; + } + else if ( target instanceof Map map ) { + map.clear(); + return map; + } + else { + final PersistenceContext persistenceContext = session.getPersistenceContext(); + 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; + } + + // todo: make org.hibernate.type.CollectionType#replaceUninitializedOriginal public + private static Object replaceUninitializedOriginal( + CollectionType type, + Object original, + Object target, + SharedSessionContractImplementor session, + Map copyCache) { + final PersistentCollection persistentCollection = (PersistentCollection) original; + if ( persistentCollection.hasQueuedOperations() ) { + if ( original == target ) { + // 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 collection = + (AbstractPersistentCollection) original; + collection.replaceQueuedOperationValues( + session.getFactory() + .getMappingMetamodel() + .getCollectionDescriptor( type.getRole() ), copyCache + ); + } + else { + // original is a detached copy of the collection; + // it contains queued operations, which will be ignored + LOG.ignoreQueuedOperationsOnMerge( + collectionInfoString( type.getRole(), persistentCollection.getKey() ) ); + } + } + return target; + } + + /** + * @see CollectionType#replaceOriginal(Object, Object, SharedSessionContractImplementor, Object, Map) + */ + private static CompletionStage replaceOriginal( + CollectionType type, + Object original, + Object target, + SharedSessionContractImplementor session, + Object owner, + Map copyCache) { + + //for arrays, replaceElements() may return a different reference, since + //the array length might not match + return replaceElements( + type, + original, + instantiateResultIfNecessary( type, original, target ), + owner, + copyCache, + session + ).thenCompose( result -> { + if ( original == target ) { + // get the elements back into the target making sure to handle dirty flag + //TODO: this is a little inefficient, don't need to do a whole + // deep replaceElements() call + 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 ); + } + } ); + } + + /** + * @see CollectionType#replaceElements(Object, Object, Object, Map, SharedSessionContractImplementor) + */ + private static CompletionStage replaceElements( + CollectionType type, + Object original, + Object target, + Object owner, + Map copyCache, + SharedSessionContractImplementor session) { + if ( type instanceof ArrayType ) { + return replaceArrayTypeElements( type, original, target, owner, copyCache, session ); + } + else if ( type instanceof CustomCollectionType ) { + return completedFuture( type.replaceElements( original, target, owner, copyCache, session ) ); + } + else if ( type instanceof MapType ) { + return replaceMapTypeElements( + type, + (Map) original, + (Map) target, + owner, + copyCache, + session + ); + } + else { + return replaceCollectionTypeElements( + type, + original, + (Collection) target, + owner, + copyCache, + session + ); + } + } + + private static CompletionStage replaceCollectionTypeElements( + CollectionType type, + Object original, + final Collection result, + Object owner, + Map copyCache, + 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( 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 target, + Object owner, + Map copyCache, + SharedSessionContractImplementor session) { + final CollectionPersister persister = + session.getFactory().getRuntimeMetamodels().getMappingMetamodel() + .getCollectionDescriptor( type.getRole() ); + target.clear(); + return loop( + original.entrySet(), entry -> { + final Map.Entry me = entry; + return getReplace( persister.getIndexType(), me.getKey(), owner, session, copyCache ) + .thenCompose( key -> getReplace( + persister.getElementType(), + me.getValue(), + owner, + session, + copyCache + ).thenAccept( value -> target.put( key, value ) ) + ); + } + ).thenApply( unused -> target); + } + + private static CompletionStage replaceArrayTypeElements( + CollectionType type, + Object original, + Object target, + Object owner, + Map copyCache, + SharedSessionContractImplementor session) { + final Object result; + final int length = Array.getLength( original ); + if ( length != Array.getLength( target ) ) { + //note: this affects the return value! + result = ( (ArrayType) type ).instantiateResult( original ); + } + else { + result = target; + } + + final Type elemType = type.getElementType( session.getFactory() ); + return loop( 0, length, + i -> getReplace( elemType, Array.get( original, i ), owner, session, copyCache ) + .thenApply( o -> { + Array.set( result, i, o ); + return result; + } ) + ).thenApply( unused -> result ); + } + + private static CompletionStage getReplace( + Type elemType, + Object o, + Object owner, + SharedSessionContractImplementor session, + Map copyCache) { + return getReplace( elemType, o, null, owner, session, copyCache ); + } + + private static CompletionStage getReplace( + Type elemType, + Object object, + Object target, + Object owner, + SharedSessionContractImplementor session, + Map copyCache) { + return elemType instanceof EntityType entityType + ? EntityTypes.replace( entityType, object, target, session, owner, copyCache ) + : completedFuture( elemType.replace( object, target, session, owner, copyCache) ); + } + + /** + * @see CollectionType#preserveSnapshot(PersistentCollection, PersistentCollection, Type, Object, Map, SharedSessionContractImplementor) + */ + private static CompletionStage preserveSnapshot( + PersistentCollection original, + PersistentCollection result, + Type elemType, + Object owner, + Map copyCache, + 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 ) ); + } + else { + return voidFuture(); + } + } + + /** + * @see CollectionType#createSnapshot(PersistentCollection, PersistentCollection, Type, Object, Map, SharedSessionContractImplementor) + */ + private static CompletionStage createSnapshot( + PersistentCollection original, + PersistentCollection result, + Type elemType, + Object owner, + Map copyCache, + SharedSessionContractImplementor session) { + final Serializable originalSnapshot = original.getStoredSnapshot(); + if ( originalSnapshot instanceof List list ) { + return createListSnapshot( list, elemType, owner, copyCache, session ); + } + else if ( originalSnapshot instanceof Map map ) { + return createMapSnapshot( map, result, elemType, owner, copyCache, session ); + } + else if ( originalSnapshot instanceof Object[] array ) { + return createArraySnapshot( array, elemType, owner, copyCache, session ); + } + else { + // retain the same snapshot + return completedFuture( result.getStoredSnapshot() ); + } + } + + /** + * @see CollectionType#createArraySnapshot(Object[], Type, Object, Map, SharedSessionContractImplementor) + */ + private static CompletionStage createArraySnapshot( + Object[] array, + Type elemType, + Object owner, + Map copyCache, + SharedSessionContractImplementor session) { + return loop( 0, array.length, + i -> getReplace( elemType, array[i], owner, session, copyCache ) + .thenAccept( o -> array[i] = o ) + ).thenApply( unused -> array ); + } + + /** + * @see CollectionType#createMapSnapshot(Map, PersistentCollection, Type, Object, Map, SharedSessionContractImplementor) + */ + private static CompletionStage createMapSnapshot( + Map map, + PersistentCollection result, + Type elemType, + Object owner, + Map copyCache, + SharedSessionContractImplementor session) { + final Map resultSnapshot = (Map) result.getStoredSnapshot(); + 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 ); + } + + /** + * @see CollectionType#createListSnapshot(List, Type, Object, Map, SharedSessionContractImplementor) + */ + private static CompletionStage createListSnapshot( + List list, + Type elemType, + Object owner, + Map copyCache, + SharedSessionContractImplementor session) { + final ArrayList targetList = new ArrayList<>( list.size() ); + return loop( list, + obj -> getReplace( elemType, obj, owner, session, copyCache ) + .thenAccept( targetList::add ) + ).thenApply( unused -> targetList ); + } + + /** + * @see CollectionType#instantiateResultIfNecessary(Object, Object) + */ + private static Object instantiateResultIfNecessary(CollectionType type, Object original, Object target) { + // for a null target, or a target which is the same as the original, + // we need to put the merged elements in a new collection + // 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 ) + ? 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 879f7ddc8..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,16 @@ 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; import org.hibernate.type.OneToOneType; @@ -75,8 +76,7 @@ public static CompletionStage resolve(EntityType entityType, Object idOr * @see OneToOneType#isNull(Object, SharedSessionContractImplementor) */ static boolean isNull(EntityType entityType, Object owner, SharedSessionContractImplementor session) { - if ( entityType instanceof OneToOneType ) { - OneToOneType type = (OneToOneType) entityType; + if ( entityType instanceof OneToOneType type ) { String propertyName = type.getPropertyName(); if ( propertyName != null ) { final EntityPersister ownerPersister = session.getFactory() @@ -125,7 +125,7 @@ static CompletionStage loadByUniqueKey( entityName, uniqueKeyPropertyName, key, - entityType.getIdentifierOrUniqueKeyType( factory ), + entityType.getIdentifierOrUniqueKeyType( factory.getRuntimeMetamodels() ), factory ); @@ -137,7 +137,7 @@ static CompletionStage loadByUniqueKey( else { return persister .reactiveLoadByUniqueKey( uniqueKeyPropertyName, key, session ) - .thenApply( ukResult -> loadHibernateProxyEntity( ukResult, session ) + .thenCompose( ukResult -> loadHibernateProxyEntity( ukResult, session ) .thenApply( targetUK -> { persistenceContext.addEntity( euk, targetUK ); return targetUK; @@ -154,46 +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]; - for ( int i = 0; i < types.length; i++ ) { - if ( original[i] == UNFETCHED_PROPERTY || original[i] == UNKNOWN ) { - copied[i] = target[i]; - } - else { - if ( !( types[i] instanceof EntityType ) ) { - copied[i] = types[i].replace( - original[i], - target[i] == UNFETCHED_PROPERTY ? null : target[i], - session, - owner, - copyCache - ); - } - } - } return loop( 0, types.length, - i -> original[i] != UNFETCHED_PROPERTY && original[i] != UNKNOWN - && types[i] instanceof EntityType, - i -> replace( - (EntityType) types[i], - original[i], - target[i] == UNFETCHED_PROPERTY ? null : target[i], - session, - owner, - copyCache - ).thenCompose( copy -> { - if ( copy instanceof CompletionStage ) { - return ( (CompletionStage) copy ) - .thenAccept( nonStageCopy -> copied[i] = nonStageCopy ); - } - else { - copied[i] = copy; - return voidFuture(); - } - } ) + i -> replace( original, target, types, session, owner, copyCache, i, copied ) ).thenApply( v -> copied ); } @@ -204,48 +170,13 @@ 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]; - for ( int i = 0; i < types.length; i++ ) { - if ( original[i] == UNFETCHED_PROPERTY || original[i] == UNKNOWN ) { - copied[i] = target[i]; - } - else { - if ( !( types[i] instanceof EntityType ) ) { - copied[i] = types[i].replace( - original[i], - target[i] == UNFETCHED_PROPERTY ? null : target[i], - session, - owner, - copyCache, - foreignKeyDirection - ); - } - } - } return loop( 0, types.length, - i -> original[i] != UNFETCHED_PROPERTY && original[i] != UNKNOWN - && types[i] instanceof EntityType, - i -> replace( - (EntityType) types[i], - original[i], - target[i] == UNFETCHED_PROPERTY ? null : target[i], - session, - owner, - copyCache, - foreignKeyDirection - ).thenCompose( copy -> { - if ( copy instanceof CompletionStage ) { - return ( (CompletionStage) copy ).thenAccept( nonStageCopy -> copied[i] = nonStageCopy ); - } - else { - copied[i] = copy; - return voidFuture(); - } - } ) + i -> replace( original, target, types, session, owner, copyCache, foreignKeyDirection, i, copied ) ).thenApply( v -> copied ); } @@ -256,7 +187,7 @@ private static CompletionStage replace( EntityType entityType, Object original, Object target, - SessionImplementor session, + SharedSessionContractImplementor session, Object owner, Map copyCache, ForeignKeyDirection foreignKeyDirection) @@ -272,11 +203,11 @@ private static CompletionStage replace( /** * @see EntityType#replace(Object, Object, SharedSessionContractImplementor, Object, Map) */ - private static CompletionStage replace( + protected static CompletionStage replace( EntityType entityType, Object original, Object target, - SessionImplementor session, + SharedSessionContractImplementor session, Object owner, Map copyCache) { if ( original == null ) { @@ -322,7 +253,7 @@ private static CompletionStage replace( private static CompletionStage resolveIdOrUniqueKey( EntityType entityType, Object original, - SessionImplementor session, + SharedSessionContractImplementor session, Object owner, Map copyCache) { return getIdentifier( entityType, original, session ) @@ -336,15 +267,16 @@ 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 ).reactiveFetch( id, true ) + return ( (ReactiveQueryProducer) session ) + .reactiveFetch( id, true ) .thenCompose( fetched -> { - Object idOrUniqueKey = entityType.getIdentifierOrUniqueKeyType( session.getFactory() ) + Object idOrUniqueKey = entityType + .getIdentifierOrUniqueKeyType( session.getFactory().getRuntimeMetamodels() ) .replace( fetched, null, session, owner, copyCache ); if ( idOrUniqueKey instanceof CompletionStage ) { return ( (CompletionStage) idOrUniqueKey ) .thenCompose( key -> resolve( entityType, key, owner, session ) ); } - return resolve( entityType, idOrUniqueKey, owner, session ); } ); } ); @@ -356,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 ); @@ -407,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 ); @@ -424,11 +357,11 @@ 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 completedFuture( propertyValue ); + return propertyValue; } - return nullFuture(); + return null; } ); } @@ -439,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; @@ -450,4 +384,100 @@ private static CompletionStage loadHibernateProxyEntity( } } + private static CompletionStage replace( + Object[] original, + Object[] target, + Type[] types, + SharedSessionContractImplementor session, + Object owner, + Map copyCache, + int i, + Object[] copied) { + if ( original[i] == UNFETCHED_PROPERTY || original[i] == UNKNOWN ) { + copied[i] = target[i]; + return voidFuture(); + } + else if ( types[i] instanceof CollectionType ) { + return CollectionTypes.replace( + (CollectionType) types[i], + original[i], + target[i] == UNFETCHED_PROPERTY ? null : target[i], + session, + owner, + copyCache + ).thenAccept( copy -> copied[i] = copy ); + } + else if ( types[i] instanceof EntityType ) { + return replace( + (EntityType) types[i], + original[i], + target[i] == UNFETCHED_PROPERTY ? null : target[i], + session, + owner, + copyCache + ).thenAccept( copy -> copied[i] = copy ); + } + else { + final Type type = types[i]; + copied[i] = type.replace( + original[i], + target[i] == UNFETCHED_PROPERTY ? null : target[i], + session, + owner, + copyCache + ); + return voidFuture(); + } + } + + private static CompletionStage replace( + Object[] original, + Object[] target, + Type[] types, + SharedSessionContractImplementor session, + Object owner, + Map copyCache, + ForeignKeyDirection foreignKeyDirection, + int i, + Object[] copied) { + if ( original[i] == UNFETCHED_PROPERTY || original[i] == UNKNOWN ) { + copied[i] = target[i]; + return voidFuture(); + } + else if ( types[i] instanceof CollectionType ) { + return CollectionTypes.replace( + (CollectionType) types[i], + original[i], + target[i] == UNFETCHED_PROPERTY ? null : target[i], + session, + owner, + copyCache, + foreignKeyDirection + ).thenAccept( copy -> copied[i] = copy ); + } + else if ( types[i] instanceof EntityType ) { + return replace( + (EntityType) types[i], + original[i], + target[i] == UNFETCHED_PROPERTY ? null : target[i], + session, + owner, + copyCache, + foreignKeyDirection + ).thenAccept( copy -> copied[i] = copy ); + } + else { + copied[i] = types[i].replace( + original[i], + target[i] == UNFETCHED_PROPERTY ? null : target[i], + session, + owner, + copyCache, + foreignKeyDirection + ); + return voidFuture(); + } + } + + } 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 5a77bd9f6..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 @@ -11,10 +11,10 @@ import org.hibernate.TransientObjectException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.engine.internal.NonNullableTransientDependencies; +import org.hibernate.engine.internal.ReactivePersistenceContextAdapter; 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; @@ -47,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; @@ -64,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; @@ -269,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(); } @@ -296,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 @@ -349,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(); } @@ -386,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/ReactiveCallbackImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCallbackImpl.java new file mode 100644 index 000000000..609f021ba --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCallbackImpl.java @@ -0,0 +1,70 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.engine.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletionStage; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.loader.ast.spi.AfterLoadAction; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.reactive.loader.ast.spi.ReactiveAfterLoadAction; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.sql.exec.spi.Callback; + +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; +import static org.hibernate.reactive.util.impl.CompletionStages.loop; + +/** + * Reactive equivalent of {@link org.hibernate.sql.exec.internal.CallbackImpl} + */ +public class ReactiveCallbackImpl implements Callback { + private static final Log LOG = make( Log.class, lookup() ); + + private final List afterLoadActions; + + public ReactiveCallbackImpl() { + this.afterLoadActions = new ArrayList<>( 1 ); + } + + @Override + public void registerAfterLoadAction(AfterLoadAction afterLoadAction) { + throw LOG.nonReactiveMethodCall( "registerReactiveAfterLoadAction(ReactiveCallbackImpl)" ); + } + + public void registerReactiveAfterLoadAction(ReactiveAfterLoadAction afterLoadAction) { + afterLoadActions.add( afterLoadAction ); + } + + @Override + public void invokeAfterLoadActions( + Object entity, + EntityMappingType entityMappingType, + SharedSessionContractImplementor session) { + throw LOG.nonReactiveMethodCall( "invokeAfterLoadActions(Object, EntityMappingType, SharedSessionContractImplementor)" ); + } + + /** + * Reactive version of {@link org.hibernate.sql.exec.internal.CallbackImpl#invokeAfterLoadActions(Object, EntityMappingType, SharedSessionContractImplementor)} + */ + public CompletionStage invokeReactiveLoadActions( + Object entity, + EntityMappingType entityMappingType, + SharedSessionContractImplementor session) { + return loop( + afterLoadActions, afterLoadAction -> + afterLoadAction.reactiveAfterLoad( entity, entityMappingType, session ) + ); + } + + @Override + public boolean hasAfterLoadActions() { + return !afterLoadActions.isEmpty(); + } + +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRecreateAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRecreateAction.java index 008755086..37f8eaa7e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRecreateAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRecreateAction.java @@ -56,7 +56,7 @@ public void execute() throws HibernateException { } private void preRecreate() { - getFastSessionServices() + getEventListenerGroups() .eventListenerGroup_PRE_COLLECTION_RECREATE .fireLazyEventOnEachListener( this::newPreCollectionRecreateEvent, PreCollectionRecreateEventListener::onPreRecreateCollection ); } @@ -66,7 +66,7 @@ private PreCollectionRecreateEvent newPreCollectionRecreateEvent() { } private void postRecreate() { - getFastSessionServices() + getEventListenerGroups() .eventListenerGroup_POST_COLLECTION_RECREATE .fireLazyEventOnEachListener( this::newPostCollectionRecreateEvent, PostCollectionRecreateEventListener::onPostRecreateCollection ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRemoveAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRemoveAction.java index 4b086cf34..0f2bdefea 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRemoveAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRemoveAction.java @@ -100,7 +100,8 @@ public void execute() throws HibernateException { } private void preRemove() { - getFastSessionServices().eventListenerGroup_PRE_COLLECTION_REMOVE + getEventListenerGroups() + .eventListenerGroup_PRE_COLLECTION_REMOVE .fireLazyEventOnEachListener( this::newPreCollectionRemoveEvent, PreCollectionRemoveEventListener::onPreRemoveCollection ); } @@ -115,7 +116,8 @@ private PreCollectionRemoveEvent newPreCollectionRemoveEvent() { } private void postRemove() { - getFastSessionServices().eventListenerGroup_POST_COLLECTION_REMOVE + getEventListenerGroups() + .eventListenerGroup_POST_COLLECTION_REMOVE .fireLazyEventOnEachListener( this::newPostCollectionRemoveEvent, PostCollectionRemoveEventListener::onPostRemoveCollection ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionUpdateAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionUpdateAction.java index eae872d56..8806b3e9d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionUpdateAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionUpdateAction.java @@ -118,7 +118,8 @@ public void execute() throws HibernateException { } private void preUpdate() { - getFastSessionServices().eventListenerGroup_PRE_COLLECTION_UPDATE + getEventListenerGroups() + .eventListenerGroup_PRE_COLLECTION_UPDATE .fireLazyEventOnEachListener( this::newPreCollectionUpdateEvent, PreCollectionUpdateEventListener::onPreUpdateCollection ); } @@ -132,7 +133,8 @@ private PreCollectionUpdateEvent newPreCollectionUpdateEvent() { } private void postUpdate() { - getFastSessionServices().eventListenerGroup_POST_COLLECTION_UPDATE + getEventListenerGroups() + .eventListenerGroup_POST_COLLECTION_UPDATE .fireLazyEventOnEachListener( this::newPostCollectionUpdateEvent, PostCollectionUpdateEventListener::onPostUpdateCollection ); } 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/impl/ReactiveEntityRegularInsertAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityRegularInsertAction.java index 062f0374f..977179c73 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityRegularInsertAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityRegularInsertAction.java @@ -63,7 +63,7 @@ public CompletionStage reactiveExecute() throws HibernateException { final ReactiveEntityPersister reactivePersister = (ReactiveEntityPersister) persister; final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); return stage - .thenCompose( v -> reactivePersister.insertReactive( id, getState(), instance, session, false ) ) + .thenCompose( v -> reactivePersister.insertReactive( id, getState(), instance, session ) ) .thenCompose( generatedValues -> { final EntityEntry entry = persistenceContext.getEntry( instance ); if ( entry == null ) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactivePersistenceContextAdapter.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactivePersistenceContextAdapter.java deleted file mode 100644 index 33750cc0d..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactivePersistenceContextAdapter.java +++ /dev/null @@ -1,134 +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.engine.impl; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.concurrent.CompletionStage; -import java.util.function.Consumer; - -import org.hibernate.HibernateException; -import org.hibernate.collection.spi.PersistentCollection; -import org.hibernate.engine.internal.StatefulPersistenceContext; -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.persister.entity.EntityPersister; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; -import org.hibernate.reactive.session.ReactiveSession; - -import static java.lang.invoke.MethodHandles.lookup; -import static org.hibernate.pretty.MessageHelper.infoString; -import static org.hibernate.reactive.logging.impl.LoggerFactory.make; -import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - -/** - * Add reactive methods to a {@link PersistenceContext}. - */ -public class ReactivePersistenceContextAdapter extends StatefulPersistenceContext { - - private static final Log LOG = make( Log.class, lookup() ); - - private HashMap entitySnapshotsByKey; - - /** - * Constructs a PersistentContext, bound to the given session. - * - * @param session The session "owning" this context. - */ - public ReactivePersistenceContextAdapter(SharedSessionContractImplementor session) { - super( session ); - } - - public CompletionStage reactiveInitializeNonLazyCollections() throws HibernateException { - final NonLazyCollectionInitializer initializer = new NonLazyCollectionInitializer(); - initializeNonLazyCollections( initializer ); - return initializer.stage; - } - - private class NonLazyCollectionInitializer implements Consumer> { - CompletionStage stage = voidFuture(); - - @Override - public void accept(PersistentCollection nonLazyCollection) { - if ( !nonLazyCollection.wasInitialized() ) { - stage = stage.thenCompose( v -> ( (ReactiveSession) getSession() ) - .reactiveInitializeCollection( nonLazyCollection, false ) ); - } - } - } - - /** - * @deprecated use {@link #reactiveInitializeNonLazyCollections} instead. - */ - @Deprecated - @Override - public void initializeNonLazyCollections() { - // still called by ResultSetProcessorImpl, so can't throw UnsupportedOperationException - } - - @Deprecated - @Override - public Object[] getDatabaseSnapshot(Object id, EntityPersister persister) throws HibernateException { - throw LOG.nonReactiveMethodCall( "reactiveGetDatabaseSnapshot" ); - } - - private static final Object[] NO_ROW = new Object[]{ StatefulPersistenceContext.NO_ROW }; - - public CompletionStage reactiveGetDatabaseSnapshot(Object id, EntityPersister persister) - throws HibernateException { - - SessionImplementor session = (SessionImplementor) getSession(); - final EntityKey key = session.generateEntityKey( id, persister ); - final Object[] cached = entitySnapshotsByKey == null ? null : entitySnapshotsByKey.get( key ); - if ( cached != null ) { - return completedFuture( cached == NO_ROW ? null : cached ); - } - else { - return ( (ReactiveEntityPersister) persister ) - .reactiveGetDatabaseSnapshot( id, session ) - .thenApply( snapshot -> { - if ( entitySnapshotsByKey == null ) { - entitySnapshotsByKey = new HashMap<>( 8 ); - } - entitySnapshotsByKey.put( key, snapshot == null ? NO_ROW : snapshot ); - return snapshot; - } ); - } - } - - //All below methods copy/pasted from superclass because entitySnapshotsByKey is private: - - @Override - public Object[] getCachedDatabaseSnapshot(EntityKey key) { - final Object[] snapshot = entitySnapshotsByKey == null ? null : entitySnapshotsByKey.get( key ); - if ( snapshot == NO_ROW ) { - throw new IllegalStateException( - "persistence context reported no row snapshot for " - + infoString( key.getEntityName(), key.getIdentifier() ) - ); - } - return snapshot; - } - - @Override - public void clear() { - super.clear(); - entitySnapshotsByKey = null; - } - - @Override - public Object removeEntity(EntityKey key) { - Object result = super.removeEntity(key); - if (entitySnapshotsByKey != null ) { - entitySnapshotsByKey.remove(key); - } - return result; - } -} 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 9964ffe93..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 @@ -8,61 +8,25 @@ import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.Database; import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.DialectDelegateWrapper; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.engine.jdbc.dialect.spi.DialectResolver; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.persister.entity.mutation.EntityMutationTarget; -import org.hibernate.reactive.dialect.ReactiveOracleSqlAstTranslator; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.SqlAstTranslatorFactory; -import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; -import org.hibernate.sql.ast.tree.Statement; -import org.hibernate.sql.exec.spi.JdbcOperation; -import org.hibernate.sql.model.MutationOperation; -import org.hibernate.sql.model.internal.OptionalTableUpdate; -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 ) ) { - Dialect dialect = database.createDialect( info ); - if ( info.getDatabaseName().toUpperCase().startsWith( "ORACLE" ) ) { - return new DialectDelegateWrapper( dialect ) { - @Override - public MutationOperation createOptionalTableUpdateOperation( - EntityMutationTarget mutationTarget, - OptionalTableUpdate optionalTableUpdate, - SessionFactoryImplementor factory) { - return new ReactiveOracleSqlAstTranslator<>( factory, optionalTableUpdate ) - .createMergeOperation( optionalTableUpdate ); - } - - @Override - public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { - return new StandardSqlAstTranslatorFactory() { - @Override - protected SqlAstTranslator buildTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { - return new ReactiveOracleSqlAstTranslator<>( sessionFactory, statement ); - } - }; - } - }; - } - return dialect; + return database.createDialect( info ); } } - return null; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/env/internal/ReactiveJdbcEnvironment.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/env/internal/ReactiveJdbcEnvironment.java deleted file mode 100644 index a8fd73a6a..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/env/internal/ReactiveJdbcEnvironment.java +++ /dev/null @@ -1,31 +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.engine.jdbc.env.internal; - -import java.sql.DatabaseMetaData; -import java.sql.SQLException; - -import org.hibernate.dialect.Dialect; -import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentImpl; -import org.hibernate.service.spi.ServiceRegistryImplementor; -import org.hibernate.sql.ast.SqlAstTranslatorFactory; - -public class ReactiveJdbcEnvironment extends JdbcEnvironmentImpl { - - public ReactiveJdbcEnvironment(ServiceRegistryImplementor registry, Dialect dialect) { - super( registry, dialect ); - } - - @Deprecated - public ReactiveJdbcEnvironment(ServiceRegistryImplementor registry, Dialect dialect, DatabaseMetaData metaData) throws SQLException { - super( registry, dialect, metaData ); - } - - @Override - public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { - return super.getSqlAstTranslatorFactory(); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/env/internal/ReactiveMutationExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/env/internal/ReactiveMutationExecutor.java index fe9873791..3253be9dd 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/env/internal/ReactiveMutationExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/env/internal/ReactiveMutationExecutor.java @@ -10,7 +10,6 @@ import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.DialectDelegateWrapper; import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.SQLServerDialect; @@ -104,15 +103,14 @@ default CompletionStage performReactiveBatchedOperations( private static String createInsert(String insertSql, String identifierColumnName, Dialect dialect) { String sql = insertSql; final String sqlEnd = " returning " + identifierColumnName; - Dialect realDialect = DialectDelegateWrapper.extractRealDialect( dialect ); - if ( realDialect instanceof MySQLDialect ) { + if ( dialect instanceof MySQLDialect ) { // For some reason ORM generates a query with an invalid syntax int index = sql.lastIndexOf( sqlEnd ); return index > -1 ? sql.substring( 0, index ) : sql; } - if ( realDialect instanceof SQLServerDialect ) { + if ( dialect instanceof SQLServerDialect ) { int index = sql.lastIndexOf( sqlEnd ); // FIXME: this is a hack for HHH-16365 if ( index > -1 ) { @@ -128,12 +126,12 @@ private static String createInsert(String insertSql, String identifierColumnName } return sql; } - if ( realDialect instanceof DB2Dialect ) { + if ( dialect instanceof DB2Dialect ) { // ORM query: select id from new table ( insert into IntegerTypeEntity values ( )) // Correct : select id from new table ( insert into LongTypeEntity (id) values (default)) return sql.replace( " values ( ))", " (" + identifierColumnName + ") values (default))" ); } - if ( realDialect instanceof OracleDialect ) { + if ( dialect instanceof OracleDialect ) { final String valuesStr = " values ( )"; int index = sql.lastIndexOf( sqlEnd ); // remove "returning id" since it's added via diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorStandard.java index 80e24fac7..0a09ff3a1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorStandard.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorStandard.java @@ -5,10 +5,7 @@ */ package org.hibernate.reactive.engine.jdbc.mutation.internal; -import java.lang.invoke.MethodHandles; -import java.sql.SQLException; -import java.util.concurrent.CompletionStage; - +import org.hibernate.engine.jdbc.batch.spi.Batch; import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; import org.hibernate.engine.jdbc.mutation.OperationResultChecker; import org.hibernate.engine.jdbc.mutation.ParameterUsage; @@ -24,6 +21,7 @@ import org.hibernate.persister.entity.mutation.EntityTableMapping; import org.hibernate.reactive.adaptor.impl.PrepareStatementDetailsAdaptor; import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor; +import org.hibernate.reactive.engine.jdbc.ResultsCheckerUtil; import org.hibernate.reactive.engine.jdbc.env.internal.ReactiveMutationExecutor; import org.hibernate.reactive.generator.values.ReactiveGeneratedValuesMutationDelegate; import org.hibernate.reactive.logging.impl.Log; @@ -36,9 +34,16 @@ import org.hibernate.sql.model.TableMapping; import org.hibernate.sql.model.ValuesAnalysis; +import java.lang.invoke.MethodHandles; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletionStage; + import static org.hibernate.engine.jdbc.mutation.internal.ModelMutationHelper.checkResults; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.loop; import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER; @@ -72,10 +77,64 @@ private ReactiveConnection connection(SharedSessionContractImplementor session) @Override public CompletionStage performReactiveBatchedOperations( ValuesAnalysis valuesAnalysis, - TableInclusionChecker inclusionChecker, OperationResultChecker resultChecker, + TableInclusionChecker inclusionChecker, + OperationResultChecker resultChecker, SharedSessionContractImplementor session) { - return ReactiveMutationExecutor.super - .performReactiveBatchedOperations( valuesAnalysis, inclusionChecker, resultChecker, session); + final PreparedStatementGroup batchedMutationOperationGroup = getBatchedPreparedStatementGroup(); + if ( batchedMutationOperationGroup != null ) { + final List preparedStatementDetailsList = new ArrayList<>( + batchedMutationOperationGroup.getNumberOfStatements() ); + batchedMutationOperationGroup.forEachStatement( (tableName, statementDetails) -> preparedStatementDetailsList + .add( statementDetails ) ); + return loop( preparedStatementDetailsList, statementDetails -> { + if ( statementDetails == null ) { + return voidFuture(); + } + final JdbcValueBindings valueBindings = getJdbcValueBindings(); + final TableMapping tableDetails = statementDetails.getMutatingTableDetails(); + if ( inclusionChecker != null && !inclusionChecker.include( tableDetails ) ) { + if ( MODEL_MUTATION_LOGGER.isTraceEnabled() ) { + MODEL_MUTATION_LOGGER.tracef( + "Skipping execution of secondary insert : %s", + tableDetails.getTableName() + ); + } + return voidFuture(); + } + + // If we get here the statement is needed - make sure it is resolved + final Object[] paramValues = PreparedStatementAdaptor.bind( statement -> { + PreparedStatementDetails details = new PrepareStatementDetailsAdaptor( + statementDetails, + statement, + session.getJdbcServices() + ); + valueBindings.beforeStatement( details ); + } ); + + final ReactiveConnection reactiveConnection = ( (ReactiveConnectionSupplier) session ).getReactiveConnection(); + final String sql = statementDetails.getSqlString(); + return reactiveConnection.update( + sql, + paramValues, + true, + (rowCount, batchPosition, query) -> ResultsCheckerUtil.checkResults( + session, + statementDetails, + resultChecker, + rowCount, + batchPosition + ) + ).whenComplete( (o, throwable) -> { //TODO: is this part really needed? + if ( statementDetails.getStatement() != null ) { + statementDetails.releaseStatement( session ); + } + valueBindings.afterStatement( tableDetails ); + } ); + } + ); + } + return voidFuture(); } @Override @@ -99,7 +158,8 @@ protected void performSelfExecutingOperations( @Override protected void performBatchedOperations( ValuesAnalysis valuesAnalysis, - TableInclusionChecker inclusionChecker) { + TableInclusionChecker inclusionChecker, + Batch.StaleStateMapper staleStateMapper) { throw LOG.nonReactiveMethodCall( "performReactiveBatchedOperations" ); } @@ -157,6 +217,23 @@ public CompletionStage performReactiveNonBatchedOperations( } } + @Override + public CompletionStage performReactiveSelfExecutingOperations( + ValuesAnalysis valuesAnalysis, + TableInclusionChecker inclusionChecker, + SharedSessionContractImplementor session) { + if ( getSelfExecutingMutations() == null || getSelfExecutingMutations().isEmpty() ) { + return voidFuture(); + } + + return loop( getSelfExecutingMutations(), operation -> { + if ( inclusionChecker.include( operation.getTableDetails() ) ) { + operation.performMutation( getJdbcValueBindings(), valuesAnalysis, session ); + } + return voidFuture(); + }); + } + private class OperationsForEach { private final Object id; @@ -208,6 +285,7 @@ public CompletionStage buildLoop() { return loop; } } + @Override public CompletionStage performReactiveNonBatchedMutation( PreparedStatementDetails statementDetails, 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/ReactiveResolveNaturalIdEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/ReactiveResolveNaturalIdEventListener.java deleted file mode 100644 index fe0fbb96a..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/ReactiveResolveNaturalIdEventListener.java +++ /dev/null @@ -1,29 +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.event; - -import org.hibernate.event.spi.ResolveNaturalIdEvent; - -import java.util.concurrent.CompletionStage; - -/** - * Defines the contract for handling of resolve natural id events generated from a session. - * - * @author Eric Dalquist - * @author Steve Ebersole - * - * @see org.hibernate.event.spi.ResolveNaturalIdEventListener - */ -public interface ReactiveResolveNaturalIdEventListener { - - /** - * Handle the given resolve natural id event. - * - * @param event The resolve natural id event to be handled. - */ - CompletionStage onReactiveResolveNaturalId(ResolveNaturalIdEvent event); - -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveFlushingEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveFlushingEventListener.java index 2772cc15d..9e4ac049d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveFlushingEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveFlushingEventListener.java @@ -74,7 +74,7 @@ private ReactiveActionQueue actionQueue(EventSource session) { /** * Coordinates the processing necessary to get things ready for executions - * as db calls by preping the session caches and moving the appropriate + * as db calls by prepping the session caches and moving the appropriate * entities and collections to their respective execution queues. * * @param event The flush event. @@ -139,9 +139,8 @@ protected void logFlushResults(FlushEvent event) { session.getActionQueue().numberOfCollectionRemovals(), persistenceContext.getCollectionEntriesSize() ); - new EntityPrinter( session.getFactory() ).toString( - persistenceContext.getEntityHoldersByKey().entrySet() - ); + new EntityPrinter( session.getFactory() ) + .logEntities( persistenceContext.getEntityHoldersByKey().entrySet() ); } /** @@ -227,7 +226,7 @@ private int flushEntities(final FlushEvent event, final PersistenceContext persi final EventSource source = event.getSession(); final Iterable flushListeners = source.getFactory() - .getFastSessionServices() + .getEventListenerGroups() .eventListenerGroup_FLUSH_ENTITY .listeners(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java index e9d68b00e..6ca8a8ec2 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java @@ -25,7 +25,7 @@ import org.hibernate.event.spi.EventSource; import org.hibernate.generator.BeforeExecutionGenerator; import org.hibernate.generator.Generator; -import org.hibernate.id.Assigned; +import org.hibernate.id.CompositeNestedGeneratedValueGenerator; import org.hibernate.id.IdentifierGenerationException; import org.hibernate.jpa.event.spi.CallbackRegistry; import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; @@ -134,7 +134,7 @@ protected CompletionStage reactiveSaveWithGeneratedId( // and is not yet available generatedId = null; } - else if ( generator instanceof Assigned ) { + else if ( !generator.generatesOnInsert() ) { // get it from the entity later, since we need // the @PrePersist callback to happen first generatedId = null; @@ -144,55 +144,57 @@ else if ( generator instanceof Assigned ) { // the entity instance, so it will be available // to the entity in the @PrePersist callback if ( generator instanceof ReactiveIdentifierGenerator ) { - return ( (ReactiveIdentifierGenerator) generator ) - .generate( ( ReactiveConnectionSupplier ) source, entity ) - .thenApply( id -> castToIdentifierType( id, persister ) ) - .thenCompose( gid -> performSaveWithId( - entity, - context, - source, - persister, - generator, - gid, - requiresImmediateIdAccess, - false - ) ); + return generateId( entity, source, (ReactiveIdentifierGenerator) generator, persister ) + .thenCompose( gid -> { + if ( gid == SHORT_CIRCUIT_INDICATOR ) { + source.getIdentifier( entity ); + return voidFuture(); + } + persister.setIdentifier( entity, gid, source ); + return reactivePerformSave( + entity, + gid, + persister, + generatedOnExecution, + context, + source, + false + ); + } ); } generatedId = ( (BeforeExecutionGenerator) generator ).generate( source, entity, null, INSERT ); + if ( generatedId == SHORT_CIRCUIT_INDICATOR ) { + source.getIdentifier( entity ); + return voidFuture(); + } + persister.setIdentifier( entity, generatedId, source ); } final Object id = castToIdentifierType( generatedId, persister ); - return reactivePerformSave( entity, id, persister, generatedOnExecution, context, source, requiresImmediateIdAccess ); + final boolean delayIdentityInserts = !source.isTransactionInProgress() && !requiresImmediateIdAccess && generatedOnExecution; + return reactivePerformSave( entity, id, persister, generatedOnExecution, context, source, delayIdentityInserts ); } - private CompletionStage performSaveWithId( + private CompletionStage generateId( Object entity, - C context, EventSource source, - EntityPersister persister, - Generator generator, - Object generatedId, - boolean requiresImmediateIdAccess, - boolean generatedOnExecution) { - if ( generatedId == null ) { - throw new IdentifierGenerationException( "null id generated for: " + entity.getClass() ); - } - if ( generatedId == SHORT_CIRCUIT_INDICATOR ) { - source.getIdentifier( entity ); - return voidFuture(); - } - if ( LOG.isDebugEnabled() ) { - LOG.debugf( - "Generated identifier: %s, using strategy: %s", - persister.getIdentifierType().toLoggableString( generatedId, source.getFactory() ), - generator.getClass().getName() - ); - } - final boolean delayIdentityInserts = - !source.isTransactionInProgress() - && !requiresImmediateIdAccess - && generatedOnExecution; - return reactivePerformSave( entity, generatedId, persister, false, context, source, delayIdentityInserts ); + ReactiveIdentifierGenerator generator, + EntityPersister persister) { + return generator.generate( (ReactiveConnectionSupplier) source, entity ) + .thenApply( id -> { + final Object generatedId = castToIdentifierType( id, persister ); + if ( generatedId == null ) { + throw new IdentifierGenerationException( "null id generated for: " + entity.getClass() ); + } + if ( LOG.isDebugEnabled() ) { + LOG.debugf( + "Generated identifier: %s, using strategy: %s", + persister.getIdentifierType().toLoggableString( generatedId, source.getFactory() ), + generator.getClass().getName() + ); + } + return generatedId; + } ); } /** @@ -229,13 +231,11 @@ protected CompletionStage reactivePerformSave( processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes ); processIfManagedEntity( entity, managedEntity -> managedEntity.$$_hibernate_setUseTracker( true ) ); - if ( persister.getGenerator() instanceof Assigned ) { + final Generator generator = persister.getGenerator(); + if ( !generator.generatesOnInsert() || generator instanceof CompositeNestedGeneratedValueGenerator ) { id = persister.getIdentifier( entity, source ); if ( id == null ) { - throw new IdentifierGenerationException( - "Identifier of entity '" + persister.getEntityName() - + "' must be manually assigned before calling 'persist()'" - ); + return failedFuture( new IdentifierGenerationException( "Identifier of entity '" + persister.getEntityName() + "' must be manually assigned before calling 'persist()'" ) ); } } @@ -420,7 +420,7 @@ private CompletionStage addInsertAction( boolean useIdentityColumn, EventSource source, boolean shouldDelayIdentityInserts) { - final ReactiveActionQueue actionQueue = source.unwrap( ReactiveSession.class ).getReactiveActionQueue(); + final ReactiveActionQueue actionQueue = source.unwrap(ReactiveSession.class).getReactiveActionQueue(); if ( useIdentityColumn ) { final ReactiveEntityIdentityInsertAction insert = new ReactiveEntityIdentityInsertAction( values, entity, persister, false, source, shouldDelayIdentityInserts 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 66151f03b..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 @@ -13,7 +13,6 @@ import org.hibernate.TransientObjectException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; -import org.hibernate.classic.Lifecycle; import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.internal.Nullability; import org.hibernate.engine.spi.EntityEntry; @@ -22,13 +21,13 @@ import org.hibernate.engine.spi.Status; import org.hibernate.event.internal.OnUpdateVisitor; import org.hibernate.event.internal.PostDeleteEventListenerStandardImpl; +import org.hibernate.event.service.spi.EventListenerGroups; import org.hibernate.event.service.spi.JpaBootstrapSensitive; import org.hibernate.event.spi.DeleteContext; import org.hibernate.event.spi.DeleteEvent; import org.hibernate.event.spi.DeleteEventListener; import org.hibernate.event.spi.EventSource; import org.hibernate.internal.EmptyInterceptor; -import org.hibernate.internal.FastSessionServices; import org.hibernate.jpa.event.spi.CallbackRegistry; import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; import org.hibernate.jpa.event.spi.CallbackType; @@ -48,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; @@ -199,23 +199,12 @@ 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 ) ); } - protected boolean invokeDeleteLifecycle(EventSource session, Object entity, EntityPersister persister) { - if ( persister.implementsLifecycle() ) { - LOG.debug( "Calling onDelete()" ); - if ( ( (Lifecycle) entity ).onDelete( session ) ) { - LOG.debug( "Deletion vetoed by onDelete()" ); - return true; - } - } - return false; - } - private CompletionStage deleteTransientInstance(DeleteEvent event, DeleteContext transientEntities, Object entity) { LOG.trace( "Entity was not persistent in delete processing" ); @@ -291,23 +280,20 @@ private CompletionStage delete( Object version, EntityEntry entry) { callbackRegistry.preRemove( entity ); - if ( !invokeDeleteLifecycle( source, entity, persister ) ) { - return deleteEntity( - source, - entity, - entry, - event.isCascadeDeleteEnabled(), - event.isOrphanRemovalBeforeUpdates(), - persister, - transientEntities - ) - .thenAccept( v -> { - if ( source.getFactory().getSessionFactoryOptions().isIdentifierRollbackEnabled() ) { - persister.resetIdentifier( entity, id, version, source ); - } - } ); - } - return voidFuture(); + return deleteEntity( + source, + entity, + entry, + event.isCascadeDeleteEnabled(), + event.isOrphanRemovalBeforeUpdates(), + persister, + transientEntities + ) + .thenAccept( v -> { + if ( source.getFactory().getSessionFactoryOptions().isIdentifierRollbackEnabled() ) { + persister.resetIdentifier( entity, id, version, source ); + } + } ); } /** @@ -315,7 +301,6 @@ private CompletionStage delete( */ private boolean canBeDeletedWithoutLoading(EventSource source, EntityPersister persister) { return source.getInterceptor() == EmptyInterceptor.INSTANCE - && !persister.implementsLifecycle() && !persister.hasSubclasses() //TODO: should be unnecessary, using EntityPersister.getSubclassPropertyTypeClosure(), etc && !persister.hasCascadeDelete() && !persister.hasNaturalIdentifier() @@ -325,7 +310,7 @@ private boolean canBeDeletedWithoutLoading(EventSource source, EntityPersister p } private static boolean hasCustomEventListeners(EventSource source) { - final FastSessionServices fss = source.getFactory().getFastSessionServices(); + final EventListenerGroups fss = source.getFactory().getEventListenerGroups(); // Bean Validation adds a PRE_DELETE listener // and Envers adds a POST_DELETE listener return fss.eventListenerGroup_PRE_DELETE.count() > 0 @@ -452,11 +437,8 @@ protected CompletionStage deleteEntity( persister ).nullifyTransientReferences( entityEntry.getDeletedState() ) .thenAccept( vv -> { - new Nullability( session ).checkNullability( - entityEntry.getDeletedState(), - persister, - Nullability.NullabilityCheckType.DELETE - ); + new Nullability( session, Nullability.NullabilityCheckType.DELETE ) + .checkNullability( entityEntry.getDeletedState(), persister ); persistenceContext.registerNullifiableEntityKey( key ); final ReactiveActionQueue actionQueue = actionQueue( session ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/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/DefaultReactiveLoadEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLoadEventListener.java index 8f821a986..9804a02f6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLoadEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLoadEventListener.java @@ -27,8 +27,7 @@ import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.LoadEvent; import org.hibernate.event.spi.LoadEventListener; -import org.hibernate.loader.ast.internal.CacheEntityLoaderHelper; -import org.hibernate.loader.ast.internal.CacheEntityLoaderHelper.PersistenceContextEntry; +import org.hibernate.loader.internal.CacheLoadHelper; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMappingsList; import org.hibernate.metamodel.mapping.CompositeIdentifierMapping; @@ -40,7 +39,6 @@ import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import org.hibernate.reactive.event.ReactiveLoadEventListener; -import org.hibernate.reactive.loader.entity.ReactiveCacheEntityLoaderHelper; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; @@ -49,7 +47,10 @@ import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; +import static org.hibernate.loader.internal.CacheLoadHelper.loadFromSecondLevelCache; +import static org.hibernate.loader.internal.CacheLoadHelper.loadFromSessionCache; import static org.hibernate.pretty.MessageHelper.infoString; +import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; import static org.hibernate.reactive.session.impl.SessionUtil.checkEntityFound; import static org.hibernate.reactive.session.impl.SessionUtil.throwEntityNotFound; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; @@ -399,7 +400,7 @@ private static CompletionStage loadWithProxyFactory(LoadEvent event, Ent if ( proxy != null ) { LOG.trace( "Entity proxy found in session cache" ); - if ( LOG.isDebugEnabled() && HibernateProxy.extractLazyInitializer( proxy ).isUnwrap() ) { + if ( LOG.isDebugEnabled() && extractLazyInitializer( proxy ).isUnwrap() ) { LOG.debug( "Ignoring NO_PROXY to honor laziness" ); } @@ -429,7 +430,7 @@ private static PersistentAttributeInterceptable createBatchLoadableEnhancedProxy } private static Object proxyOrCached(LoadEvent event, EntityPersister persister, EntityKey keyToLoad) { - final Object cachedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( + final Object cachedEntity = loadFromSecondLevelCache( event.getSession(), null, LockMode.NONE, @@ -450,7 +451,7 @@ private static Object proxyOrCached(LoadEvent event, EntityPersister persister, return options.isCheckDeleted() && wasDeleted( persistenceContext, existing ) ? null : existing; } if ( persister.hasSubclasses() ) { - final Object cachedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( + final Object cachedEntity = loadFromSecondLevelCache( event.getSession(), null, LockMode.NONE, @@ -652,9 +653,9 @@ private CompletionStage doLoad( return nullFuture(); } else { - final PersistenceContextEntry persistenceContextEntry = - ReactiveCacheEntityLoaderHelper.INSTANCE.loadFromSessionCache( event, keyToLoad, options ); - final Object entity = persistenceContextEntry.getEntity(); + final CacheLoadHelper.PersistenceContextEntry persistenceContextEntry = + loadFromSessionCache( keyToLoad, event.getLockOptions(), options, event.getSession() ); + final Object entity = persistenceContextEntry.entity(); if ( entity != null ) { return persistenceContextEntry.isManaged() ? initializeIfNecessary( entity ) : nullFuture(); } @@ -668,9 +669,7 @@ private static CompletionStage initializeIfNecessary(Object entity) { if ( isPersistentAttributeInterceptable( entity ) ) { final PersistentAttributeInterceptable interceptable = asPersistentAttributeInterceptable( entity ); final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); - if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor) { - final EnhancementAsProxyLazinessInterceptor lazinessInterceptor = - (EnhancementAsProxyLazinessInterceptor) interceptor; + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor lazinessInterceptor ) { final SharedSessionContractImplementor session = lazinessInterceptor.getLinkedSession(); if ( session == null ) { throw LOG.sessionClosedLazyInitializationException(); @@ -691,7 +690,8 @@ private CompletionStage loadFromCacheOrDatasource( EntityPersister persister, EntityKey keyToLoad) { final EventSource session = event.getSession(); - final Object entity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache(event, persister, keyToLoad); + final Object entity = session + .loadFromSecondLevelCache( persister, keyToLoad, event.getInstanceToLoad(), event.getLockMode() ); if ( entity != null ) { if ( LOG.isTraceEnabled() ) { LOG.tracev( @@ -740,6 +740,10 @@ private void cacheNaturalId(LoadEvent event, EntityPersister persister, EventSou * @return The object loaded from the datasource, or null if not found. */ protected CompletionStage loadFromDatasource(LoadEvent event, EntityPersister persister) { + if ( LOG.isTraceEnabled() ) { + LOG.trace( "Entity not resolved in any cache, loading from datastore: " + + infoString( persister, event.getEntityId(), event.getFactory() ) ); + } return ( (ReactiveEntityPersister) persister ) .reactiveLoad( event.getEntityId(), @@ -754,16 +758,14 @@ protected CompletionStage loadFromDatasource(LoadEvent event, EntityPers // persister/loader/initializer sensitive to this fact - possibly // passing LoadType along - final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( entity ); - if ( lazyInitializer != null ) { - entity = lazyInitializer.getImplementation(); - } + final LazyInitializer lazyInitializer = extractLazyInitializer( entity ); + final Object impl = lazyInitializer != null ? lazyInitializer.getImplementation() : entity; final StatisticsImplementor statistics = event.getSession().getFactory().getStatistics(); if ( event.isAssociationFetch() && statistics.isStatisticsEnabled() ) { statistics.fetchEntity( event.getEntityClassName() ); } - return entity; + return impl; } ); } } 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 7832298e0..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 @@ -5,7 +5,6 @@ */ package org.hibernate.reactive.event.impl; -import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; import org.hibernate.HibernateException; @@ -20,7 +19,7 @@ import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; -import org.hibernate.event.internal.AbstractReassociateEventListener; +import org.hibernate.event.internal.DefaultLockEventListener; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.LockEvent; import org.hibernate.event.spi.LockEventListener; @@ -33,19 +32,25 @@ import org.hibernate.reactive.engine.impl.ReactiveEntityVerifyVersionProcess; import org.hibernate.reactive.event.ReactiveLockEventListener; import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; 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; import static org.hibernate.pretty.MessageHelper.infoString; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; 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.voidFuture; -public class DefaultReactiveLockEventListener extends AbstractReassociateEventListener - implements LockEventListener, ReactiveLockEventListener { +public class DefaultReactiveLockEventListener extends DefaultLockEventListener implements LockEventListener, ReactiveLockEventListener { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + private static final Log LOG = make( Log.class, lookup() ); + + @Override + public void onLock(LockEvent event) throws HibernateException { + throw LOG.nonReactiveMethodCall( "reactiveOnLock" ); + } @Override public CompletionStage reactiveOnLock(LockEvent event) throws HibernateException { @@ -76,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 ) ); } @@ -96,11 +101,13 @@ private CompletionStage lockEntry( if ( entry == null ) { final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); final Object id = persister.getIdentifier( entity, source ); - return ForeignKeys.isNotTransient( event.getEntityName(), entity, Boolean.FALSE, source ) + return ForeignKeys + .isNotTransient( event.getEntityName(), entity, Boolean.FALSE, source ) .thenCompose( trans -> { if ( !trans ) { return failedFuture( new TransientObjectException( - "cannot lock an unsaved transient instance: " + persister.getEntityName() ) ); + "Cannot lock unsaved transient instance of entity '" + persister.getEntityName() + "'" + ) ); } final EntityEntry e = reassociate( event, entity, id, persister ); @@ -227,9 +234,4 @@ private CompletionStage doUpgradeLock( throw he; } } - - @Override - public void onLock(LockEvent event) throws HibernateException { - throw new UnsupportedOperationException(); - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java index 7f258a1d4..e6cc4cd95 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java @@ -94,7 +94,9 @@ protected Map getMergeMap(MergeContext context) { @Override public CompletionStage reactiveOnMerge(MergeEvent event) throws HibernateException { final EventSource session = event.getSession(); - final EntityCopyObserver entityCopyObserver = createEntityCopyObserver( session ); + final EntityCopyObserver entityCopyObserver = session.getFactory() + .getEntityCopyObserver() + .createEntityCopyObserver(); final MergeContext mergeContext = new MergeContext( session, entityCopyObserver ); return reactiveOnMerge( event, mergeContext ) .thenAccept( v -> entityCopyObserver.topLevelMergeComplete( session ) ) @@ -104,10 +106,6 @@ public CompletionStage reactiveOnMerge(MergeEvent event) throws HibernateE } ); } - private EntityCopyObserver createEntityCopyObserver(final EventSource session) { - return session.getFactory().getFastSessionServices().entityCopyObserverFactory.createEntityCopyObserver(); - } - /** * Handle the given merge event. * diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactivePostLoadEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactivePostLoadEventListener.java index 053c8465f..e1af57441 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactivePostLoadEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactivePostLoadEventListener.java @@ -6,7 +6,6 @@ package org.hibernate.reactive.event.impl; import org.hibernate.AssertionFailure; -import org.hibernate.classic.Lifecycle; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.PostLoadEvent; @@ -21,7 +20,6 @@ /** * We do two things here: *
    - *
  • Call {@link Lifecycle} interface if necessary
  • *
  • Perform needed {@link EntityEntry#getLockMode()} related processing
  • *
* 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/event/impl/DefaultReactiveResolveNaturalIdEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveResolveNaturalIdEventListener.java deleted file mode 100644 index 1d80f3634..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveResolveNaturalIdEventListener.java +++ /dev/null @@ -1,137 +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.event.impl; - -import java.lang.invoke.MethodHandles; -import java.util.concurrent.CompletionStage; - -import org.hibernate.HibernateException; -import org.hibernate.engine.spi.NaturalIdResolutions; -import org.hibernate.event.internal.AbstractLockUpgradeEventListener; -import org.hibernate.event.spi.EventSource; -import org.hibernate.event.spi.ResolveNaturalIdEvent; -import org.hibernate.event.spi.ResolveNaturalIdEventListener; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.reactive.event.ReactiveResolveNaturalIdEventListener; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; -import org.hibernate.stat.spi.StatisticsImplementor; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.NANOSECONDS; -import static org.hibernate.pretty.MessageHelper.infoString; -import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; - -/** - * A reactific {@link org.hibernate.event.internal.DefaultResolveNaturalIdEventListener}. - */ -public class DefaultReactiveResolveNaturalIdEventListener extends AbstractLockUpgradeEventListener - implements ReactiveResolveNaturalIdEventListener, ResolveNaturalIdEventListener { - - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - - @Override - public void onResolveNaturalId(ResolveNaturalIdEvent event) throws HibernateException { - throw new UnsupportedOperationException(); - } - - @Override - public CompletionStage onReactiveResolveNaturalId(ResolveNaturalIdEvent event) throws HibernateException { - return resolveNaturalId( event ).thenAccept( event::setEntityId ); - } - - /** - * Coordinates the efforts to load a given entity. First, an attempt is - * made to load the entity from the session-level cache. If not found there, - * an attempt is made to locate it in second-level cache. Lastly, an - * attempt is made to load it directly from the datasource. - * - * @param event The load event - * - * @return The loaded entity, or null. - */ - protected CompletionStage resolveNaturalId(ResolveNaturalIdEvent event) { - final EntityPersister persister = event.getEntityPersister(); - - if ( LOG.isTraceEnabled() ) { - LOG.tracev( - "Attempting to resolve: {0}#{1}", - infoString( persister ), - event.getNaturalIdValues() - ); - } - - final Object entityId = resolveFromCache( event ); - if ( entityId != null ) { - if ( LOG.isTraceEnabled() ) { - LOG.tracev( - "Resolved object in cache: {0}#{1}", - infoString( persister ), - event.getNaturalIdValues() ); - } - return completedFuture( entityId ); - } - - if ( LOG.isTraceEnabled() ) { - LOG.tracev( - "Object not resolved in any cache: {0}#{1}", - infoString( persister ), - event.getNaturalIdValues() - ); - } - - return loadFromDatasource( event ); - } - - /** - * Attempts to resolve the entity id corresponding to the event's natural id values from the session - * - * @param event The load event - * @return The entity from the cache, or null. - */ - protected Object resolveFromCache(ResolveNaturalIdEvent event) { - return getNaturalIdResolutions( event ) - .findCachedIdByNaturalId( event.getOrderedNaturalIdValues(), event.getEntityPersister() ); - } - - /** - * Performs the process of loading an entity from the configured - * underlying datasource. - * - * @param event The load event - * - * @return The object loaded from the datasource, or null if not found. - */ - protected CompletionStage loadFromDatasource(ResolveNaturalIdEvent event) { - final EventSource session = event.getSession(); - final EntityPersister entityPersister = event.getEntityPersister(); - final StatisticsImplementor statistics = session.getFactory().getStatistics(); - final boolean statisticsEnabled = statistics.isStatisticsEnabled(); - final long startTime = statisticsEnabled ? System.nanoTime() : 0; - - return ( (ReactiveEntityPersister) entityPersister ) - .reactiveLoadEntityIdByNaturalId( event.getOrderedNaturalIdValues(), event.getLockOptions(), session ) - .thenApply( pk -> { - if ( statisticsEnabled ) { - long milliseconds = MILLISECONDS.convert( System.nanoTime() - startTime, NANOSECONDS ); - statistics.naturalIdQueryExecuted( entityPersister.getRootEntityName(), milliseconds ); - } - - //PK can be null if the entity doesn't exist - if ( pk != null ) { - getNaturalIdResolutions( event ) - .cacheResolutionFromLoad( pk, event.getOrderedNaturalIdValues(), entityPersister ); - } - - return pk; - } ); - } - - private static NaturalIdResolutions getNaturalIdResolutions(ResolveNaturalIdEvent event) { - return event.getSession().getPersistenceContextInternal().getNaturalIdResolutions(); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/ReactiveInsertGeneratedIdentifierDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/ReactiveInsertGeneratedIdentifierDelegate.java deleted file mode 100644 index 36d85f353..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/ReactiveInsertGeneratedIdentifierDelegate.java +++ /dev/null @@ -1,116 +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.generator.values; - -import java.sql.PreparedStatement; -import java.util.concurrent.CompletionStage; - -import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; -import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.generator.EventType; -import org.hibernate.generator.values.GeneratedValues; -import org.hibernate.id.insert.Binder; -import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate; -import org.hibernate.jdbc.Expectation; -import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; -import org.hibernate.sql.model.ast.builder.TableInsertBuilder; -import org.hibernate.sql.model.ast.builder.TableMutationBuilder; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer; - -public class ReactiveInsertGeneratedIdentifierDelegate implements InsertGeneratedIdentifierDelegate, ReactiveGeneratedValuesMutationDelegate { - private final InsertGeneratedIdentifierDelegate delegate; - - public ReactiveInsertGeneratedIdentifierDelegate(InsertGeneratedIdentifierDelegate delegate) { - this.delegate = delegate; - } - - @Override - public TableInsertBuilder createTableInsertBuilder( - BasicEntityIdentifierMapping identifierMapping, - Expectation expectation, - SessionFactoryImplementor sessionFactory) { - return delegate.createTableInsertBuilder( identifierMapping, expectation, sessionFactory ); - } - - @Override - public PreparedStatement prepareStatement(String insertSql, SharedSessionContractImplementor session) { - return delegate.prepareStatement( insertSql, session ); - } - - @Override - public Object performInsert( - PreparedStatementDetails insertStatementDetails, - JdbcValueBindings valueBindings, - Object entity, - SharedSessionContractImplementor session) { - return delegate.performInsert( insertStatementDetails, valueBindings, entity, session ); - } - - @Override - public String prepareIdentifierGeneratingInsert(String insertSQL) { - return delegate.prepareIdentifierGeneratingInsert( insertSQL ); - } - - @Override - public Object performInsert(String insertSQL, SharedSessionContractImplementor session, Binder binder) { - return delegate.performInsert( insertSQL, session, binder ); - } - - @Override - public GeneratedValues performInsertReturning( - String insertSQL, - SharedSessionContractImplementor session, - Binder binder) { - return delegate.performInsertReturning( insertSQL, session, binder ); - } - - @Override - public TableMutationBuilder createTableMutationBuilder( - Expectation expectation, - SessionFactoryImplementor sessionFactory) { - return delegate.createTableMutationBuilder( expectation, sessionFactory ); - } - - @Override - public GeneratedValues performMutation( - PreparedStatementDetails statementDetails, - JdbcValueBindings valueBindings, - Object entity, - SharedSessionContractImplementor session) { - return delegate.performMutation( statementDetails, valueBindings, entity, session ); - } - - @Override - public EventType getTiming() { - return delegate.getTiming(); - } - - @Override - public boolean supportsArbitraryValues() { - return delegate.supportsArbitraryValues(); - } - - @Override - public boolean supportsRowId() { - return delegate.supportsRowId(); - } - - @Override - public JdbcValuesMappingProducer getGeneratedValuesMappingProducer() { - return delegate.getGeneratedValuesMappingProducer(); - } - - @Override - public CompletionStage reactivePerformMutation( - PreparedStatementDetails singleStatementDetails, - JdbcValueBindings jdbcValueBindings, - Object modelReference, - SharedSessionContractImplementor session) { - return null; - } -} 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 8854f22d8..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; /** @@ -63,14 +63,14 @@ public class ReactiveGeneratedValuesHelper { * * @see GeneratedValuesHelper#getGeneratedValuesDelegate(EntityPersister, EventType) */ - public static GeneratedValuesMutationDelegate getGeneratedValuesDelegate( - EntityPersister persister, - EventType timing) { + public static GeneratedValuesMutationDelegate getGeneratedValuesDelegate(EntityPersister persister, EventType timing) { final boolean hasGeneratedProperties = !persister.getGeneratedProperties( timing ).isEmpty(); final boolean hasRowId = timing == EventType.INSERT && persister.getRowIdMapping() != null; final Dialect dialect = persister.getFactory().getJdbcServices().getDialect(); - if ( hasRowId && dialect.supportsInsertReturning() && dialect.supportsInsertReturningRowId() + if ( hasRowId + && dialect.supportsInsertReturning() + && dialect.supportsInsertReturningRowId() && noCustomSql( persister, timing ) ) { // Special case for RowId on INSERT, since GetGeneratedKeysDelegate doesn't support it // make InsertReturningDelegate the preferred method if the dialect supports it diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveIdentifierGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveIdentifierGenerator.java index 4e44eb7d0..7121ea5c4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveIdentifierGenerator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveIdentifierGenerator.java @@ -6,6 +6,7 @@ package org.hibernate.reactive.id; import org.hibernate.Incubating; +import org.hibernate.generator.Generator; import org.hibernate.id.IdentifierGenerator; import org.hibernate.reactive.session.ReactiveConnectionSupplier; @@ -24,7 +25,7 @@ * @see IdentifierGenerator */ @Incubating -public interface ReactiveIdentifierGenerator { +public interface ReactiveIdentifierGenerator extends Generator { /** * Returns a generated identifier, via a {@link CompletionStage}. diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/factory/spi/ReactiveIdentifierGeneratorFactoryInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/factory/spi/ReactiveIdentifierGeneratorFactoryInitiator.java deleted file mode 100644 index 78eed431d..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/factory/spi/ReactiveIdentifierGeneratorFactoryInitiator.java +++ /dev/null @@ -1,27 +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.id.factory.spi; - -import java.util.Map; - -import org.hibernate.boot.registry.StandardServiceInitiator; -import org.hibernate.id.factory.IdentifierGeneratorFactory; -import org.hibernate.reactive.id.impl.ReactiveIdentifierGeneratorFactory; -import org.hibernate.service.spi.ServiceRegistryImplementor; - -public class ReactiveIdentifierGeneratorFactoryInitiator implements StandardServiceInitiator { - public static final ReactiveIdentifierGeneratorFactoryInitiator INSTANCE = new ReactiveIdentifierGeneratorFactoryInitiator(); - - @Override - public Class getServiceInitiated() { - return IdentifierGeneratorFactory.class; - } - - @Override - public IdentifierGeneratorFactory initiateService(Map configurationValues, ServiceRegistryImplementor registry) { - return new ReactiveIdentifierGeneratorFactory( registry ); - } -} 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 224410853..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 @@ -5,18 +5,18 @@ */ package org.hibernate.reactive.id.impl; -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 java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import org.hibernate.reactive.id.ReactiveIdentifierGenerator; import org.hibernate.reactive.session.ReactiveConnectionSupplier; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; +import io.vertx.core.Context; +import io.vertx.core.Vertx; +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,14 +44,14 @@ 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 */ protected abstract CompletionStage nextHiValue(ReactiveConnectionSupplier session); - //Not strictly necessary to put these fields into a dedicated class, but it help + //Not strictly necessary to put these fields into a dedicated class, but it helps //to reason about what the current state is and what the CombinerExecutor is //supposed to work on. private static class GeneratorState { @@ -138,7 +138,6 @@ public Task execute(GeneratorState state) { // value in the table, so just increment the lo // value and return the next id in the block completedFuture( local ).whenComplete( this::acceptAsReturnValue ); - return null; } else { nextHiValue( connectionSupplier ) @@ -155,8 +154,8 @@ public Task execute(GeneratorState state) { } ); } } ); - return null; } + return null; } private void acceptAsReturnValue(final Long aLong, final Throwable throwable) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/EmulatedSequenceReactiveIdentifierGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/EmulatedSequenceReactiveIdentifierGenerator.java index 889f3d69a..b973c869b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/EmulatedSequenceReactiveIdentifierGenerator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/EmulatedSequenceReactiveIdentifierGenerator.java @@ -5,14 +5,20 @@ */ package org.hibernate.reactive.id.impl; +import org.hibernate.dialect.CockroachDialect; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.dialect.SQLServerDialect; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.id.enhanced.SequenceStyleGenerator; +import org.hibernate.id.enhanced.TableStructure; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.Type; import java.util.Properties; -import static org.hibernate.internal.util.config.ConfigurationHelper.getInt; import static org.hibernate.internal.util.config.ConfigurationHelper.getString; /** @@ -25,6 +31,10 @@ */ public class EmulatedSequenceReactiveIdentifierGenerator extends TableReactiveIdentifierGenerator { + public EmulatedSequenceReactiveIdentifierGenerator(TableStructure structure, RuntimeModelCreationContext runtimeModelCreationContext) { + super( structure, runtimeModelCreationContext ); + } + @Override public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) { super.configure( type, params, serviceRegistry ); @@ -60,16 +70,6 @@ protected String determineSegmentValue(Properties params) { return null; } - @Override - protected int determineInitialValue(Properties params) { - return getInt( SequenceStyleGenerator.INITIAL_PARAM, params, SequenceStyleGenerator.DEFAULT_INITIAL_VALUE ); - } - - @Override - protected int determineIncrement(Properties params) { - return getInt( SequenceStyleGenerator.INCREMENT_PARAM, params, SequenceStyleGenerator.DEFAULT_INCREMENT_SIZE ); - } - @Override protected Object[] updateParameters(long currentValue, long updatedValue) { return new Object[] { updatedValue, currentValue }; @@ -86,20 +86,44 @@ protected Object[] selectParameters() { } @Override - protected String buildSelectQuery() { + protected String buildSelectQuery(Dialect dialect) { return "select tbl." + valueColumnName + " from " + renderedTableName + " tbl"; } @Override - protected String buildUpdateQuery() { + protected String buildUpdateQuery(Dialect dialect) { + if ( dialect instanceof PostgreSQLDialect || dialect instanceof CockroachDialect ) { + return "update " + renderedTableName + " set " + valueColumnName + "=$1" + + " where " + valueColumnName + "=$2"; + + } + if ( dialect instanceof SQLServerDialect ) { + return "update " + renderedTableName + " set " + valueColumnName + "=@P1" + + " where " + valueColumnName + "=@P2"; + + } + if ( dialect instanceof OracleDialect ) { + return "update " + renderedTableName + " set " + valueColumnName + "=:1" + + " where " + valueColumnName + "=:2"; + + } return "update " + renderedTableName + " set " + valueColumnName + "=?" + " where " + valueColumnName + "=?"; } @Override - protected String buildInsertQuery() { - return "insert into " + renderedTableName + " (" + valueColumnName + ") " - + " values (?)"; + protected String buildInsertQuery(Dialect dialect) { + final String sql = "insert into " + renderedTableName + " (" + valueColumnName + ") values "; + if ( dialect instanceof PostgreSQLDialect || dialect instanceof CockroachDialect ) { + return sql + "($1)"; + } + if ( dialect instanceof SQLServerDialect ) { + return sql + "(@P1)"; + } + if ( dialect instanceof OracleDialect ) { + return sql + "(:1)"; + } + return sql + "(?)"; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveIdentifierGeneratorFactory.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveIdentifierGeneratorFactory.java deleted file mode 100644 index 1e3596294..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveIdentifierGeneratorFactory.java +++ /dev/null @@ -1,143 +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.id.impl; - -import java.lang.invoke.MethodHandles; -import java.util.Properties; - -import org.hibernate.MappingException; -import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; -import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; -import org.hibernate.engine.config.spi.ConfigurationService; -import org.hibernate.engine.config.spi.StandardConverters; -import org.hibernate.generator.BeforeExecutionGenerator; -import org.hibernate.generator.Generator; -import org.hibernate.generator.GeneratorCreationContext; -import org.hibernate.generator.OnExecutionGenerator; -import org.hibernate.id.Configurable; -import org.hibernate.id.IdentifierGenerator; -import org.hibernate.id.PersistentIdentifierGenerator; -import org.hibernate.id.SelectGenerator; -import org.hibernate.id.enhanced.DatabaseStructure; -import org.hibernate.id.enhanced.SequenceStructure; -import org.hibernate.id.enhanced.SequenceStyleGenerator; -import org.hibernate.id.enhanced.TableGenerator; -import org.hibernate.id.enhanced.TableStructure; -import org.hibernate.id.factory.internal.StandardIdentifierGeneratorFactory; -import org.hibernate.reactive.id.ReactiveIdentifierGenerator; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.provider.Settings; -import org.hibernate.service.ServiceRegistry; -import org.hibernate.type.Type; - -public class ReactiveIdentifierGeneratorFactory extends StandardIdentifierGeneratorFactory { - - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - - private final ServiceRegistry serviceRegistry; - - public ReactiveIdentifierGeneratorFactory(ServiceRegistry serviceRegistry) { - super( serviceRegistry ); - this.serviceRegistry = serviceRegistry; - } - - @Override - public Generator createIdentifierGenerator(String strategy, Type type, GeneratorCreationContext creationContext, Properties config) { - Object generator; - try { - generator = super.createIdentifierGenerator( strategy, type, creationContext, config ); - } - catch ( MappingException ignored ) { - try { - final Class clazz = generatorClassForName( strategy ); - generator = clazz.getConstructor().newInstance(); - if ( generator instanceof Configurable ) { - ( (Configurable) generator ).configure( type, config, serviceRegistry ); - } - } - catch ( Exception e ) { - final String entityName = config.getProperty( IdentifierGenerator.ENTITY_NAME ); - throw new MappingException( String.format( "Could not instantiate id generator [entity-name=%s]", entityName ), e ); - } - } - - //FIXME: Not sure why we need all these instanceof - if ( generator instanceof BeforeExecutionGenerator ) { - return augmentWithReactiveGenerator( (BeforeExecutionGenerator) generator, type, config ); - } - - if ( generator instanceof OnExecutionGenerator ) { - return augmentWithReactiveGenerator( (OnExecutionGenerator) generator, type, config ); - } - - if ( generator instanceof ReactiveIdentifierGenerator ) { - return new ReactiveGeneratorWrapper( (ReactiveIdentifierGenerator) generator ); - } - - final String entityName = config.getProperty( IdentifierGenerator.ENTITY_NAME ); - throw new MappingException( String.format( "Not an id generator [entity-name=%s]", entityName ) ); - } - - protected Class generatorClassForName(String strategy) { - try { - return serviceRegistry.getService( ClassLoaderService.class ).classForName( strategy ); - } - catch ( ClassLoadingException e ) { - throw new MappingException( String.format( "Could not interpret id generator strategy [%s]", strategy ) ); - } - } - - public Generator augmentWithReactiveGenerator(Generator generator, Type type, Properties params) { - return augmentWithReactiveGenerator( serviceRegistry, generator, type, params ); - } - - public static Generator augmentWithReactiveGenerator(ServiceRegistry serviceRegistry, Generator generator, Type type, Properties params) { - final ReactiveIdentifierGenerator reactiveGenerator; - if ( generator instanceof SequenceStyleGenerator ) { - final DatabaseStructure structure = ( (SequenceStyleGenerator) generator ).getDatabaseStructure(); - if ( structure instanceof TableStructure ) { - reactiveGenerator = new EmulatedSequenceReactiveIdentifierGenerator(); - } - else if ( structure instanceof SequenceStructure ) { - reactiveGenerator = new ReactiveSequenceIdentifierGenerator(); - } - else { - throw LOG.unknownStructureType(); - } - } - else if ( generator instanceof TableGenerator ) { - reactiveGenerator = new TableReactiveIdentifierGenerator(); - } - else if ( generator instanceof SelectGenerator ) { - throw LOG.selectGeneratorIsNotSupportedInHibernateReactive(); - } - else { - //nothing to do - return generator; - } - - //this is not the way ORM does this: instead it passes a - //SqlStringGenerationContext to IdentifierGenerator.initialize() - final ConfigurationService cs = serviceRegistry.getService( ConfigurationService.class ); - if ( !params.containsKey( PersistentIdentifierGenerator.SCHEMA ) ) { - final String schema = cs.getSetting( Settings.DEFAULT_SCHEMA, StandardConverters.STRING ); - if ( schema != null ) { - params.put( PersistentIdentifierGenerator.SCHEMA, schema ); - } - } - if ( !params.containsKey( PersistentIdentifierGenerator.CATALOG ) ) { - final String catalog = cs.getSetting( Settings.DEFAULT_CATALOG, StandardConverters.STRING ); - if ( catalog != null ) { - params.put( PersistentIdentifierGenerator.CATALOG, catalog ); - } - } - - ( (Configurable) reactiveGenerator ).configure( type, params, serviceRegistry ); - return new ReactiveGeneratorWrapper( reactiveGenerator, (IdentifierGenerator) generator ); - } - -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveSequenceIdentifierGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveSequenceIdentifierGenerator.java index 6331d178b..325a5b662 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveSequenceIdentifierGenerator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveSequenceIdentifierGenerator.java @@ -19,11 +19,13 @@ import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.IdentifierGenerator; +import org.hibernate.id.enhanced.DatabaseStructure; import org.hibernate.id.enhanced.ImplicitDatabaseObjectNamingStrategy; import org.hibernate.id.enhanced.SequenceStyleGenerator; import org.hibernate.id.enhanced.StandardNamingStrategy; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.Type; @@ -51,6 +53,16 @@ public class ReactiveSequenceIdentifierGenerator extends BlockingIdentifierGener private String sql; private int increment; + + public ReactiveSequenceIdentifierGenerator() { + } + + public ReactiveSequenceIdentifierGenerator(DatabaseStructure structure, RuntimeModelCreationContext creationContext) { + qualifiedName = structure.getPhysicalName(); + increment = structure.getIncrementSize(); + dialect = creationContext.getDialect(); + } + @Override protected int getBlockSize() { return increment; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/TableReactiveIdentifierGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/TableReactiveIdentifierGenerator.java index a49a04289..21a863b0b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/TableReactiveIdentifierGenerator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/TableReactiveIdentifierGenerator.java @@ -12,19 +12,22 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.LockOptions; -import org.hibernate.boot.model.relational.Database; -import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.dialect.SQLServerDialect; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.enhanced.TableGenerator; +import org.hibernate.id.enhanced.TableStructure; import org.hibernate.internal.util.StringHelper; import org.hibernate.jdbc.TooManyRowsAffectedException; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.reactive.pool.ReactiveConnection; -import org.hibernate.reactive.pool.impl.Parameters; import org.hibernate.reactive.provider.Settings; import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.service.ServiceRegistry; @@ -65,6 +68,60 @@ public class TableReactiveIdentifierGenerator extends BlockingIdentifierGenerato private String insertQuery; private String updateQuery; + public TableReactiveIdentifierGenerator(TableGenerator generator, RuntimeModelCreationContext runtimeModelCreationContext) { + ServiceRegistry serviceRegistry = runtimeModelCreationContext.getServiceRegistry(); + segmentColumnName = generator.getSegmentColumnName(); + valueColumnName = generator.getValueColumnName(); + segmentValue = generator.getSegmentValue(); + initialValue = generator.getInitialValue(); + increment = generator.getIncrementSize(); + storeLastUsedValue = determineStoreLastUsedValue( serviceRegistry ); + renderedTableName = generator.getTableName(); + + JdbcEnvironment jdbcEnvironment = serviceRegistry.getService( JdbcEnvironment.class ); + Dialect dialect = jdbcEnvironment.getDialect(); + selectQuery = applyLocksToSelect( dialect, "tbl", buildSelectQuery( dialect ) ); + updateQuery = buildUpdateQuery( dialect ); + insertQuery = buildInsertQuery( dialect ); + } + + public TableReactiveIdentifierGenerator( + TableStructure structure, + RuntimeModelCreationContext runtimeModelCreationContext) { + ServiceRegistry serviceRegistry = runtimeModelCreationContext.getServiceRegistry(); + JdbcEnvironment jdbcEnvironment = serviceRegistry.getService( JdbcEnvironment.class ); + Dialect dialect = jdbcEnvironment.getDialect(); + + valueColumnName = structure.getLogicalValueColumnNameIdentifier().render( dialect ); + initialValue = structure.getInitialValue(); + increment = structure.getIncrementSize(); + storeLastUsedValue = determineStoreLastUsedValue( serviceRegistry ); + renderedTableName = structure.getPhysicalName().render(); + segmentColumnName = null; + segmentValue = null; + + selectQuery = applyLocksToSelect( dialect, "tbl", buildSelectQuery( dialect ) ); + updateQuery = buildUpdateQuery( dialect ); + insertQuery = buildInsertQuery( dialect ); + } + + @Override + public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) { + JdbcEnvironment jdbcEnvironment = serviceRegistry.getService( JdbcEnvironment.class ); + segmentColumnName = determineSegmentColumnName( params, jdbcEnvironment ); + valueColumnName = determineValueColumnNameForTable( params, jdbcEnvironment ); + segmentValue = determineSegmentValue( params ); + initialValue = determineInitialValue( params ); + increment = determineIncrement( params ); + storeLastUsedValue = determineStoreLastUsedValue( serviceRegistry ); + renderedTableName = determineTableName( type, params, serviceRegistry ); + + Dialect dialect = jdbcEnvironment.getDialect(); + selectQuery = applyLocksToSelect( dialect, "tbl", buildSelectQuery( dialect ) ); + updateQuery = buildUpdateQuery( dialect ); + insertQuery = buildInsertQuery( dialect ); + } + @Override protected int getBlockSize() { return increment; @@ -130,32 +187,6 @@ protected CompletionStage nextHiValue(ReactiveConnectionSupplier session) } ); } - @Override - public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) { - JdbcEnvironment jdbcEnvironment = serviceRegistry.getService( JdbcEnvironment.class ); - segmentColumnName = determineSegmentColumnName( params, jdbcEnvironment ); - valueColumnName = determineValueColumnNameForTable( params, jdbcEnvironment ); - segmentValue = determineSegmentValue( params ); - initialValue = determineInitialValue( params ); - increment = determineIncrement( params ); - storeLastUsedValue = determineStoreLastUsedValue( serviceRegistry ); - renderedTableName = determineTableName( type, params, serviceRegistry ); - - Dialect dialect = jdbcEnvironment.getDialect(); - Parameters parameters = Parameters.instance( dialect ); - selectQuery = parameters.process( applyLocksToSelect( dialect, "tbl", buildSelectQuery() ) ); - updateQuery = parameters.process( buildUpdateQuery() ); - insertQuery = parameters.process( buildInsertQuery() ); - } - - @Override - public void registerExportables(Database database) { - } - - @Override - public void initialize(SqlStringGenerationContext context) { - } - @Override public Object generate(SharedSessionContractImplementor session, Object object) throws HibernateException { throw new UnsupportedOperationException(); @@ -224,19 +255,48 @@ protected Object[] selectParameters() { return new Object[]{ segmentValue }; } - protected String buildSelectQuery() { - return "select tbl." + valueColumnName + " from " + renderedTableName + " tbl" - + " where tbl." + segmentColumnName + "=?"; + protected String buildSelectQuery(Dialect dialect) { + final String sql = "select tbl." + valueColumnName + " from " + renderedTableName + " tbl where tbl." + segmentColumnName; + if ( dialect instanceof PostgreSQLDialect || dialect instanceof CockroachDialect ) { + return sql + "=$1"; + } + if ( dialect instanceof SQLServerDialect ) { + return sql + "=@P1"; + } + if ( dialect instanceof OracleDialect ) { + return sql + "=:1"; + } + return sql + "=?"; } - protected String buildUpdateQuery() { + protected String buildUpdateQuery(Dialect dialect) { + if ( dialect instanceof PostgreSQLDialect || dialect instanceof CockroachDialect ) { + return "update " + renderedTableName + " set " + valueColumnName + "=$1" + + " where " + valueColumnName + "=$2 and " + segmentColumnName + "=$3"; + } + if ( dialect instanceof SQLServerDialect ) { + return "update " + renderedTableName + " set " + valueColumnName + "=@P1" + + " where " + valueColumnName + "=@P2 and " + segmentColumnName + "=@P3"; + } + if ( dialect instanceof OracleDialect ) { + return "update " + renderedTableName + " set " + valueColumnName + "=:1" + + " where " + valueColumnName + "=:2 and " + segmentColumnName + "=:3"; + } return "update " + renderedTableName + " set " + valueColumnName + "=?" + " where " + valueColumnName + "=? and " + segmentColumnName + "=?"; } - protected String buildInsertQuery() { - return "insert into " + renderedTableName + " (" + segmentColumnName + ", " + valueColumnName + ") " - + " values (?, ?)"; + protected String buildInsertQuery(Dialect dialect) { + final String sql = "insert into " + renderedTableName + " (" + segmentColumnName + ", " + valueColumnName + ") "; + if ( dialect instanceof PostgreSQLDialect || dialect instanceof CockroachDialect ) { + return sql + " values ($1, $2)"; + } + if ( dialect instanceof SQLServerDialect ) { + return sql + " values (@P1, @P2)"; + } + if ( dialect instanceof OracleDialect ) { + return sql + " values (:1, :2)"; + } + return sql + " values (?, ?)"; } - } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveAbstractReturningDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveAbstractReturningDelegate.java index 42dfddef0..aed2555fe 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveAbstractReturningDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveAbstractReturningDelegate.java @@ -12,7 +12,6 @@ import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.DialectDelegateWrapper; import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.SQLServerDialect; @@ -21,8 +20,8 @@ import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.generator.values.GeneratedValues; -import org.hibernate.id.PostInsertIdentityPersister; import org.hibernate.id.insert.Binder; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.adaptor.impl.PrepareStatementDetailsAdaptor; import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor; import org.hibernate.reactive.logging.impl.Log; @@ -35,7 +34,7 @@ public interface ReactiveAbstractReturningDelegate extends ReactiveInsertGenerat @Override PreparedStatement prepareStatement(String insertSql, SharedSessionContractImplementor session); - PostInsertIdentityPersister getPersister(); + EntityPersister getPersister(); @Override default CompletionStage reactivePerformInsertReturning(String sql, SharedSessionContractImplementor session, Binder binder) { @@ -89,15 +88,14 @@ && getPersister().getFactory().getJdbcServices().getDialect() instanceof Cockroa private static String createInsert(String insertSql, String identifierColumnName, Dialect dialect) { String sql = insertSql; final String sqlEnd = " returning " + identifierColumnName; - Dialect realDialect = DialectDelegateWrapper.extractRealDialect( dialect ); - if ( realDialect instanceof MySQLDialect ) { + if ( dialect instanceof MySQLDialect ) { // For some reason ORM generates a query with an invalid syntax int index = sql.lastIndexOf( sqlEnd ); return index > -1 ? sql.substring( 0, index ) : sql; } - if ( realDialect instanceof SQLServerDialect ) { + if ( dialect instanceof SQLServerDialect ) { int index = sql.lastIndexOf( sqlEnd ); // FIXME: this is a hack for HHH-16365 if ( index > -1 ) { @@ -113,12 +111,12 @@ private static String createInsert(String insertSql, String identifierColumnName } return sql; } - if ( realDialect instanceof DB2Dialect ) { + if ( dialect instanceof DB2Dialect ) { // ORM query: select id from new table ( insert into IntegerTypeEntity values ( )) // Correct : select id from new table ( insert into LongTypeEntity (id) values (default)) return sql.replace( " values ( ))", " (" + identifierColumnName + ") values (default))" ); } - if ( realDialect instanceof OracleDialect ) { + if ( dialect instanceof OracleDialect ) { final String valuesStr = " values ( )"; int index = sql.lastIndexOf( sqlEnd ); // remove "returning id" since it's added via diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java index 79460c546..2b4063173 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java @@ -21,7 +21,6 @@ import org.hibernate.generator.values.GeneratedValueBasicResultBuilder; import org.hibernate.generator.values.GeneratedValues; import org.hibernate.generator.values.internal.TableUpdateReturningBuilder; -import org.hibernate.id.PostInsertIdentityPersister; import org.hibernate.id.insert.AbstractReturningDelegate; import org.hibernate.id.insert.InsertReturningDelegate; import org.hibernate.id.insert.TableInsertReturningBuilder; @@ -36,6 +35,7 @@ import org.hibernate.sql.model.ast.builder.TableMutationBuilder; import static java.sql.Statement.NO_GENERATED_KEYS; +import static org.hibernate.generator.EventType.INSERT; import static org.hibernate.generator.values.internal.GeneratedValuesHelper.getActualGeneratedModelPart; import static org.hibernate.reactive.generator.values.internal.ReactiveGeneratedValuesHelper.getGeneratedValues; @@ -46,27 +46,26 @@ public class ReactiveInsertReturningDelegate extends AbstractReturningDelegate i private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - private final PostInsertIdentityPersister persister; + private final EntityPersister persister; private final MutatingTableReference tableReference; private final List generatedColumns; public ReactiveInsertReturningDelegate(EntityPersister persister, EventType timing) { - this( (PostInsertIdentityPersister) persister, timing, false ); - + this( persister, timing, false ); } - public ReactiveInsertReturningDelegate(PostInsertIdentityPersister persister, Dialect dialect) { + public ReactiveInsertReturningDelegate(EntityPersister persister, Dialect dialect) { // With JDBC it's possible to enabled GetGeneratedKeys for identity generation. // Vert.x doesn't have this option, so we always use the same strategy for all database. // But MySQL requires setting supportsArbitraryValues to false or it's not going to work. - this( persister, EventType.INSERT, supportsArbitraryValues( dialect ) ); + this( persister, INSERT, supportsArbitraryValues( dialect ) ); } private static boolean supportsArbitraryValues( Dialect dialect) { return !( dialect instanceof MySQLDialect ); } - private ReactiveInsertReturningDelegate(PostInsertIdentityPersister persister, EventType timing, boolean supportsArbitraryValues) { + private ReactiveInsertReturningDelegate(EntityPersister persister, EventType timing, boolean supportsArbitraryValues) { super( persister, timing, @@ -88,7 +87,7 @@ private ReactiveInsertReturningDelegate(PostInsertIdentityPersister persister, E public TableMutationBuilder createTableMutationBuilder( Expectation expectation, SessionFactoryImplementor sessionFactory) { - if ( getTiming() == EventType.INSERT ) { + if ( getTiming() == INSERT ) { return new TableInsertReturningBuilder( persister, tableReference, generatedColumns, sessionFactory ); } else { @@ -110,7 +109,7 @@ public PreparedStatement prepareStatement(String sql, SharedSessionContractImple } @Override - public PostInsertIdentityPersister getPersister() { + public EntityPersister getPersister() { return persister; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java index 3fdd5acf1..33e8465b2 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java @@ -79,7 +79,7 @@ class DatabaseSnapshotExecutor { DatabaseSnapshotExecutor::visitEmptyFetchList, true, new LoadQueryInfluencers( sessionFactory ), - sessionFactory + sessionFactory.getSqlTranslationEngine() ); final NavigablePath rootPath = new NavigablePath( entityDescriptor.getEntityName() ); 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/ReactiveCollectionBatchLoaderArrayParam.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionBatchLoaderArrayParam.java index efeafdd02..0960a2ebc 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionBatchLoaderArrayParam.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionBatchLoaderArrayParam.java @@ -18,9 +18,9 @@ import org.hibernate.loader.ast.internal.LoaderSelectBuilder; import org.hibernate.loader.ast.internal.MultiKeyLoadHelper; import org.hibernate.loader.ast.spi.SqlArrayMultiKeyLoader; +import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.PluralAttributeMapping; -import org.hibernate.metamodel.mapping.internal.SimpleForeignKeyDescriptor; import org.hibernate.query.spi.QueryOptions; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; @@ -34,7 +34,6 @@ import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; -import org.hibernate.type.BasicType; import static org.hibernate.loader.ast.internal.MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER; @@ -65,18 +64,14 @@ public ReactiveCollectionBatchLoaderArrayParam( ); } - final SimpleForeignKeyDescriptor keyDescriptor = (SimpleForeignKeyDescriptor) getLoadable().getKeyDescriptor(); - + final ForeignKeyDescriptor keyDescriptor = getLoadable().getKeyDescriptor(); + final JdbcMapping jdbcMapping = keyDescriptor.getSingleJdbcMapping(); + final Class jdbcJavaTypeClass = jdbcMapping.getJdbcJavaType().getJavaTypeClass(); arrayElementType = keyDescriptor.getJavaType().getJavaTypeClass(); - Class arrayClass = Array.newInstance( arrayElementType, 0 ).getClass(); - final BasicType arrayBasicType = getSessionFactory().getTypeConfiguration() - .getBasicTypeRegistry() - .getRegisteredType( arrayClass ); arrayJdbcMapping = MultiKeyLoadHelper.resolveArrayJdbcMapping( - arrayBasicType, - keyDescriptor.getJdbcMapping(), - arrayClass, + keyDescriptor.getSingleJdbcMapping(), + jdbcJavaTypeClass, getSessionFactory() ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveEntityBatchLoaderArrayParam.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveEntityBatchLoaderArrayParam.java index 9ff3c2ba7..04f971ffd 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveEntityBatchLoaderArrayParam.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveEntityBatchLoaderArrayParam.java @@ -10,7 +10,6 @@ import java.util.concurrent.CompletionStage; import org.hibernate.LockOptions; -import org.hibernate.engine.internal.BatchFetchQueueHelper; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -63,12 +62,10 @@ public ReactiveEntityBatchLoaderArrayParam( } identifierMapping = (BasicEntityIdentifierMapping) getLoadable().getIdentifierMapping(); - final Class arrayClass = - Array.newInstance( identifierMapping.getJavaType().getJavaTypeClass(), 0 ).getClass(); + final Class idClass = identifierMapping.getJavaType().getJavaTypeClass(); arrayJdbcMapping = MultiKeyLoadHelper.resolveArrayJdbcMapping( - sessionFactory.getTypeConfiguration().getBasicTypeRegistry().getRegisteredType( arrayClass ), identifierMapping.getJdbcMapping(), - arrayClass, + idClass, sessionFactory ); @@ -162,7 +159,8 @@ private CompletionStage initializeEntities( continue; } // found or not, remove the key from the batch-fetch queue - BatchFetchQueueHelper.removeBatchLoadableEntityKey( id, getLoadable(), session ); + session.getPersistenceContextInternal().getBatchFetchQueue() + .removeBatchLoadableEntityKey( session.generateEntityKey( id, getLoadable().getEntityPersister() ) ); } } ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderArrayParam.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderArrayParam.java index fd7cb8794..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 @@ -13,25 +13,22 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; -import org.hibernate.engine.internal.BatchFetchQueueHelper; import org.hibernate.engine.spi.BatchFetchQueue; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; 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.event.spi.LoadEvent; -import org.hibernate.event.spi.LoadEventListener; -import org.hibernate.loader.ast.internal.CacheEntityLoaderHelper; -import org.hibernate.loader.ast.internal.LoaderHelper; import org.hibernate.loader.ast.internal.LoaderSelectBuilder; import org.hibernate.loader.ast.internal.MultiIdEntityLoaderArrayParam; import org.hibernate.loader.ast.internal.MultiKeyLoadLogging; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; +import org.hibernate.loader.internal.CacheLoadHelper.PersistenceContextEntry; import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.QueryOptions; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; @@ -44,11 +41,12 @@ import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; -import org.hibernate.type.BasicType; +import static org.hibernate.event.spi.LoadEventListener.GET; import static org.hibernate.internal.util.collections.CollectionHelper.arrayList; import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty; import static org.hibernate.loader.ast.internal.MultiKeyLoadHelper.resolveArrayJdbcMapping; +import static org.hibernate.loader.internal.CacheLoadHelper.loadFromSessionCache; import static org.hibernate.reactive.loader.ast.internal.ReactiveLoaderHelper.loadByArrayParameter; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.loop; @@ -66,16 +64,12 @@ public ReactiveMultiIdEntityLoaderArrayParam( EntityMappingType entityDescriptor, SessionFactoryImplementor sessionFactory) { super( entityDescriptor, sessionFactory ); - final Class arrayClass = createTypedArray( 0 ).getClass(); - JdbcMapping jdbcMapping = getIdentifierMapping().getJdbcMapping(); - BasicType registeredType = getSessionFactory().getTypeConfiguration() - .getBasicTypeRegistry() - .getRegisteredType( arrayClass ); - JdbcMapping arrayJdbcMapping1 = resolveArrayJdbcMapping( registeredType, jdbcMapping, arrayClass, getSessionFactory() ); -// JavaType objectJavaType = getSessionFactory().getTypeConfiguration() -// .getJavaTypeRegistry() -// .resolveDescriptor( ReactiveArrayJdbcType.class ); - arrayJdbcMapping = arrayJdbcMapping1; + final Class idClass = getIdentifierMapping().getJavaType().getJavaTypeClass(); + arrayJdbcMapping = resolveArrayJdbcMapping( + getIdentifierMapping().getJdbcMapping(), + idClass, + getSessionFactory() + ); jdbcParameter = new JdbcParameterImpl( arrayJdbcMapping ); } @@ -88,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", @@ -96,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(); @@ -113,24 +107,17 @@ protected CompletionStage> performOrderedMultiLoad( final EntityKey entityKey = new EntityKey( id, getLoadable().getEntityPersister() ); if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) { - LoadEvent loadEvent = new LoadEvent( - id, - getLoadable().getJavaType().getJavaTypeClass().getName(), - lockOptions, - session, - LoaderHelper.getReadOnlyFromLoadQueryInfluencers( session ) - ); - Object managedEntity = null; if ( loadOptions.isSessionCheckingEnabled() ) { // look for it in the Session first - final CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper.loadFromSessionCacheStatic( - loadEvent, + final PersistenceContextEntry persistenceContextEntry = loadFromSessionCache( entityKey, - LoadEventListener.GET + lockOptions, + GET, + session ); - managedEntity = persistenceContextEntry.getEntity(); + managedEntity = persistenceContextEntry.entity(); if ( managedEntity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() @@ -142,12 +129,9 @@ protected CompletionStage> performOrderedMultiLoad( } if ( managedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) { + final EntityPersister persister = getLoadable().getEntityPersister(); // look for it in the SessionFactory - managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( - loadEvent, - getLoadable().getEntityPersister(), - entityKey - ); + managedEntity = session.loadFromSecondLevelCache( persister, entityKey, null, lockOptions.getLockMode() ); } if ( managedEntity != null ) { @@ -217,13 +201,11 @@ protected CompletionStage> performOrderedMultiLoad( } ) .thenApply( ignore -> { final PersistenceContext persistenceContext = session.getPersistenceContext(); - for ( int i = 0; i < idsToLoadFromDatabaseResultIndexes.size(); i++ ) { - final Integer resultIndex = idsToLoadFromDatabaseResultIndexes.get( i ); - + for ( final Integer resultIndex : idsToLoadFromDatabaseResultIndexes ) { // the element value at this position in the result List should be // the EntityKey for that entity - reuse it final EntityKey entityKey = (EntityKey) result.get( resultIndex ); - BatchFetchQueueHelper.removeBatchLoadableEntityKey( entityKey, session ); + session.getPersistenceContextInternal().getBatchFetchQueue().removeBatchLoadableEntityKey( entityKey ); Object entity = persistenceContext.getEntity( entityKey ); if ( entity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() ) { // make sure it is not DELETED @@ -245,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", @@ -310,7 +292,8 @@ protected CompletionStage> performUnorderedMultiLoad( continue; } // found or not, remove the key from the batch-fetch queue - BatchFetchQueueHelper.removeBatchLoadableEntityKey( id, getLoadable(), session ); + session.getPersistenceContextInternal().getBatchFetchQueue() + .removeBatchLoadableEntityKey( session.generateEntityKey( id, getLoadable().getEntityPersister() ) ); } return result; @@ -322,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; @@ -337,32 +320,24 @@ protected final K[] processResolvableEntities( for ( int i = 0; i < ids.length; i++ ) { final Object id; if ( coerce ) { - //noinspection unchecked - id = (K) getLoadable().getIdentifierMapping().getJavaType().coerce( ids[i], session ); + id = getLoadable().getIdentifierMapping().getJavaType().coerce( ids[i], session ); } else { id = ids[i]; } final EntityKey entityKey = new EntityKey( id, getLoadable().getEntityPersister() ); - final LoadEvent loadEvent = new LoadEvent( - id, - getLoadable().getJavaType().getJavaTypeClass().getName(), - lockOptions, - session, - LoaderHelper.getReadOnlyFromLoadQueryInfluencers( session ) - ); - Object resolvedEntity = null; // look for it in the Session first - final CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper.loadFromSessionCacheStatic( - loadEvent, + final PersistenceContextEntry persistenceContextEntry = loadFromSessionCache( entityKey, - LoadEventListener.GET + lockOptions, + GET, + session ); if ( loadOptions.isSessionCheckingEnabled() ) { - resolvedEntity = persistenceContextEntry.getEntity(); + resolvedEntity = persistenceContextEntry.entity(); if ( resolvedEntity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() @@ -374,11 +349,8 @@ protected final K[] processResolvableEntities( } if ( resolvedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) { - resolvedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( - loadEvent, - getLoadable().getEntityPersister(), - entityKey - ); + final EntityPersister persister = getLoadable().getEntityPersister(); + resolvedEntity = session.loadFromSecondLevelCache( persister, entityKey, null, lockOptions.getLockMode() ); } if ( resolvedEntity != null ) { @@ -391,7 +363,6 @@ protected final K[] processResolvableEntities( if ( nonResolvedIds == null ) { nonResolvedIds = new ArrayList<>(); } - //noinspection unchecked,CastCanBeRemovedNarrowingVariableType nonResolvedIds.add( (K) id ); } } 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 9a87f2359..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 @@ -20,19 +20,14 @@ import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; -import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SubselectFetch; -import org.hibernate.event.spi.EventSource; -import org.hibernate.event.spi.LoadEvent; -import org.hibernate.event.spi.LoadEventListener; import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.loader.ast.internal.CacheEntityLoaderHelper; -import org.hibernate.loader.ast.internal.LoaderHelper; import org.hibernate.loader.ast.internal.LoaderSelectBuilder; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; +import org.hibernate.loader.internal.CacheLoadHelper.PersistenceContextEntry; import org.hibernate.mapping.PersistentClass; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.QueryOptions; @@ -50,6 +45,8 @@ import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; +import static org.hibernate.event.spi.LoadEventListener.GET; +import static org.hibernate.loader.internal.CacheLoadHelper.loadFromSessionCache; 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.voidFuture; @@ -77,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() ); } @@ -106,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 ) @@ -114,24 +111,17 @@ protected CompletionStage> performOrderedMultiLoad( final EntityKey entityKey = new EntityKey( id, getLoadable().getEntityPersister() ); if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) { - LoadEvent loadEvent = new LoadEvent( - id, - getLoadable().getJavaType().getJavaTypeClass().getName(), - lockOptions, - session, - LoaderHelper.getReadOnlyFromLoadQueryInfluencers( session ) - ); - Object managedEntity = null; if ( loadOptions.isSessionCheckingEnabled() ) { // look for it in the Session first - CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper.INSTANCE.loadFromSessionCache( - loadEvent, + PersistenceContextEntry persistenceContextEntry = loadFromSessionCache( entityKey, - LoadEventListener.GET + lockOptions, + GET, + session ); - managedEntity = persistenceContextEntry.getEntity(); + managedEntity = persistenceContextEntry.entity(); if ( managedEntity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() && !persistenceContextEntry.isManaged() ) { // put a null in the result @@ -141,12 +131,10 @@ protected CompletionStage> performOrderedMultiLoad( } if ( managedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) { + final EntityPersister persister = getLoadable().getEntityPersister(); // look for it in the SessionFactory - managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( - loadEvent, - getLoadable().getEntityPersister(), - entityKey - ); + managedEntity = session + .loadFromSecondLevelCache( persister, entityKey, null, lockOptions.getLockMode() ); } if ( managedEntity != null ) { @@ -300,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; @@ -323,35 +311,27 @@ protected CompletionStage> performUnorderedMultiLoad( boolean foundAnyManagedEntities = false; final List nonManagedIds = new ArrayList<>(); - final boolean coerce = !getSessionFactory().getJpaMetamodel().getJpaCompliance().isLoadByIdComplianceEnabled(); - for ( int i = 0; i < ids.length; i++ ) { + final boolean coerce = isIdCoercionEnabled(); + for ( Object o : ids ) { final Object id = coerce - ? getEntityDescriptor().getIdentifierMapping().getJavaType().coerce( ids[i], session ) - : ids[i]; + ? getEntityDescriptor().getIdentifierMapping().getJavaType().coerce( o, session ) + : o; final EntityKey entityKey = new EntityKey( id, getLoadable().getEntityPersister() ); - LoadEvent loadEvent = new LoadEvent( - id, - getLoadable().getJavaType().getJavaTypeClass().getName(), - lockOptions, - session, - getReadOnlyFromLoadQueryInfluencers( session ) - ); - - Object managedEntity = null; + Object cachedEntity = null; // look for it in the Session first - CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper.INSTANCE - .loadFromSessionCache( - loadEvent, - entityKey, - LoadEventListener.GET - ); + PersistenceContextEntry persistenceContextEntry = loadFromSessionCache( + entityKey, + lockOptions, + GET, + session + ); if ( loadOptions.isSessionCheckingEnabled() ) { - managedEntity = persistenceContextEntry.getEntity(); + cachedEntity = persistenceContextEntry.entity(); - if ( managedEntity != null + if ( cachedEntity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() && !persistenceContextEntry.isManaged() ) { foundAnyManagedEntities = true; @@ -360,18 +340,20 @@ protected CompletionStage> performUnorderedMultiLoad( } } - if ( managedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) { - managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( - loadEvent, - getLoadable().getEntityPersister(), - entityKey + if ( cachedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) { + final EntityPersister persister = getLoadable().getEntityPersister(); + cachedEntity = session.loadFromSecondLevelCache( + persister, + entityKey, + null, + lockOptions.getLockMode() ); } - if ( managedEntity != null ) { + if ( cachedEntity != null ) { foundAnyManagedEntities = true; //noinspection unchecked - result.add( (T) managedEntity ); + result.add( (T) cachedEntity ); } else { nonManagedIds.add( id ); @@ -427,14 +409,4 @@ protected CompletionStage> performUnorderedMultiLoad( } ) .thenApply( v -> result ); } - - private Boolean getReadOnlyFromLoadQueryInfluencers(SharedSessionContractImplementor session) { - Boolean readOnly = null; - final LoadQueryInfluencers loadQueryInfluencers = session.getLoadQueryInfluencers(); - if ( loadQueryInfluencers != null ) { - readOnly = loadQueryInfluencers.getReadOnly(); - } - return readOnly; - } - } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveNaturalIdLoaderDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveNaturalIdLoaderDelegate.java index 56fef5be9..08b83cd47 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveNaturalIdLoaderDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveNaturalIdLoaderDelegate.java @@ -27,6 +27,7 @@ import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.query.internal.SimpleQueryOptions; import org.hibernate.query.spi.QueryOptions; +import org.hibernate.reactive.engine.impl.ReactiveCallbackImpl; import org.hibernate.reactive.loader.ast.spi.ReactiveNaturalIdLoader; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; @@ -38,7 +39,6 @@ import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.BaseExecutionContext; -import org.hibernate.sql.exec.internal.CallbackImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.Callback; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; @@ -238,7 +238,7 @@ public CompletionStage reactiveSelectByNaturalId( fetchProcessor, true, new LoadQueryInfluencers( sessionFactory ), - sessionFactory + sessionFactory.getSqlTranslationEngine() ); final TableGroup rootTableGroup = entityDescriptor().createRootTableGroup( @@ -335,7 +335,7 @@ public NaturalIdLoaderWithOptionsExecutionContext( QueryOptions queryOptions) { super( session ); this.queryOptions = queryOptions; - callback = new CallbackImpl(); + callback = new ReactiveCallbackImpl(); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderProvidedQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderProvidedQueryImpl.java index 6b849d346..a2b3a384e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderProvidedQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderProvidedQueryImpl.java @@ -7,17 +7,18 @@ import java.util.concurrent.CompletionStage; -import org.hibernate.FlushMode; import org.hibernate.LockOptions; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.query.Query; +import org.hibernate.query.QueryFlushMode; import org.hibernate.query.named.NamedQueryMemento; -import org.hibernate.query.spi.QueryImplementor; import org.hibernate.reactive.loader.ast.spi.ReactiveSingleIdEntityLoader; import org.hibernate.reactive.query.ReactiveSelectionQuery; import jakarta.persistence.Parameter; +import org.hibernate.type.descriptor.java.JavaType; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; @@ -30,9 +31,9 @@ public class ReactiveSingleIdEntityLoaderProvidedQueryImpl implements Reactiv private static final CompletionStage EMPTY_ARRAY_STAGE = completedFuture( ArrayHelper.EMPTY_OBJECT_ARRAY ); private final EntityMappingType entityDescriptor; - private final NamedQueryMemento namedQueryMemento; + private final NamedQueryMemento namedQueryMemento; - public ReactiveSingleIdEntityLoaderProvidedQueryImpl(EntityMappingType entityDescriptor, NamedQueryMemento namedQueryMemento) { + public ReactiveSingleIdEntityLoaderProvidedQueryImpl(EntityMappingType entityDescriptor, NamedQueryMemento namedQueryMemento) { this.entityDescriptor = entityDescriptor; this.namedQueryMemento = namedQueryMemento; } @@ -42,17 +43,13 @@ public EntityMappingType getLoadable() { return entityDescriptor; } - @Override + @Override @SuppressWarnings("unchecked") public CompletionStage load(Object pkValue, LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session) { - // noinspection unchecked - final QueryImplementor query = namedQueryMemento - .toQuery( session, (Class) entityDescriptor.getMappedJavaType().getJavaTypeClass() ); - - //noinspection unchecked + final JavaType mappedJavaType = (JavaType) entityDescriptor.getMappedJavaType(); + final Query query = namedQueryMemento.toQuery( session, mappedJavaType.getJavaTypeClass() ); query.setParameter( (Parameter) query.getParameters().iterator().next(), pkValue ); - query.setHibernateFlushMode( FlushMode.MANUAL ); - - return ( (ReactiveSelectionQuery) query ).reactiveUnique(); + query.setQueryFlushMode( QueryFlushMode.NO_FLUSH ); + return ( (ReactiveSelectionQuery) query ).reactiveUnique(); } @Override 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/internal/ReactiveSingleIdLoadPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdLoadPlan.java index 83c3292f5..3d636eed9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdLoadPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdLoadPlan.java @@ -20,16 +20,19 @@ import org.hibernate.query.internal.SimpleQueryOptions; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryParameterBindings; +import org.hibernate.reactive.engine.impl.ReactiveCallbackImpl; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.resource.jdbc.spi.LogicalConnectionImplementor; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.internal.CallbackImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.Callback; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.results.spi.RowTransformer; + +import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture; public class ReactiveSingleIdLoadPlan extends SingleIdLoadPlan> { @@ -61,7 +64,7 @@ public CompletionStage load(Object restrictedValue, Object entityInstance, Bo } assert offset == getJdbcParameters().size(); final QueryOptions queryOptions = new SimpleQueryOptions( getLockOptions(), readOnly ); - final Callback callback = new CallbackImpl(); + final ReactiveCallbackImpl callback = new ReactiveCallbackImpl(); EntityMappingType loadable = (EntityMappingType) getLoadable(); ExecutionContext executionContext = executionContext( restrictedValue, @@ -73,21 +76,27 @@ public CompletionStage load(Object restrictedValue, Object entityInstance, Bo ); // FIXME: Should we get this from jdbcServices.getSelectExecutor()? return StandardReactiveSelectExecutor.INSTANCE - .list( getJdbcSelect(), jdbcParameterBindings, executionContext, getRowTransformer(), resultConsumer( singleResultExpected ) ) + .list( getJdbcSelect(), jdbcParameterBindings, executionContext, rowTransformer(), resultConsumer( singleResultExpected ) ) .thenApply( this::extractEntity ) - .thenApply( entity -> { - invokeAfterLoadActions( callback, session, entity ); - return (T) entity; - } ); + .thenCompose( entity -> invokeAfterLoadActions( callback, session, entity ) ); + } + + private RowTransformer rowTransformer() { + // Because of the generics, the compiler expect this to return RowTransformer> + // but it actually returns RowTransformer. I don't know at the moment how to fix this in a cleaner way + return (RowTransformer) getRowTransformer(); } - private void invokeAfterLoadActions(Callback callback, SharedSessionContractImplementor session, T entity) { - if ( entity != null && getLoadable() != null) { - callback.invokeAfterLoadActions( entity, (EntityMappingType) getLoadable(), session ); + private CompletionStage invokeAfterLoadActions(ReactiveCallbackImpl callback, SharedSessionContractImplementor session, T entity) { + if ( entity != null && getLoadable() != null ) { + return callback + .invokeReactiveLoadActions( entity, (EntityMappingType) getLoadable(), session ) + .thenApply( v -> entity ); } + return nullFuture(); } - private Object extractEntity(List list) { + private T extractEntity(List list) { return list.isEmpty() ? null : list.get( 0 ); } @@ -156,7 +165,7 @@ public CollectionKey getCollectionKey() { @Override public QueryParameterBindings getQueryParameterBindings() { - return QueryParameterBindings.NO_PARAM_BINDINGS; + return QueryParameterBindings.empty(); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleUniqueKeyEntityLoaderStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleUniqueKeyEntityLoaderStandard.java index 62e5d0d44..09490cdad 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleUniqueKeyEntityLoaderStandard.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleUniqueKeyEntityLoaderStandard.java @@ -24,13 +24,13 @@ import org.hibernate.metamodel.mapping.SingularAttributeMapping; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.query.spi.QueryOptions; +import org.hibernate.reactive.engine.impl.ReactiveCallbackImpl; import org.hibernate.reactive.loader.ast.spi.ReactiveSingleUniqueKeyEntityLoader; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.BaseExecutionContext; -import org.hibernate.sql.exec.internal.CallbackImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.Callback; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; @@ -179,7 +179,7 @@ public SingleUKEntityLoaderExecutionContext(SharedSessionContractImplementor ses super( session ); //Careful, readOnly is possibly null this.queryOptions = readOnly == null ? QueryOptions.NONE : readOnly ? QueryOptions.READ_ONLY : QueryOptions.READ_WRITE; - callback = new CallbackImpl(); + callback = new ReactiveCallbackImpl(); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveStandardBatchLoaderFactory.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveStandardBatchLoaderFactory.java index 5d5c03fb5..fa5d075ba 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveStandardBatchLoaderFactory.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveStandardBatchLoaderFactory.java @@ -33,7 +33,7 @@ public EntityBatchLoader createEntityBatchLoader( // NOTE : don't use the EntityIdentifierMapping here because it will not be known until later final Type identifierType = entityDescriptor.getEntityPersister().getIdentifierType(); - final int idColumnCount = identifierType.getColumnSpan( factory ); + final int idColumnCount = identifierType.getColumnSpan( factory.getRuntimeMetamodels() ); if ( idColumnCount == 1 && MultiKeyLoadHelper.supportsSqlArrayType( dialect ) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/spi/ReactiveAfterLoadAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/spi/ReactiveAfterLoadAction.java new file mode 100644 index 000000000..e7e20d2e8 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/spi/ReactiveAfterLoadAction.java @@ -0,0 +1,26 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.loader.ast.spi; + +import java.util.concurrent.CompletionStage; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.metamodel.mapping.EntityMappingType; + +/** + * Reactive version of {@link org.hibernate.loader.ast.spi.AfterLoadAction} + */ +public interface ReactiveAfterLoadAction { + /** + * @see org.hibernate.loader.ast.spi.AfterLoadAction#afterLoad(Object, EntityMappingType, SharedSessionContractImplementor) + * + * The action trigger - the {@code entity} is being loaded + */ + CompletionStage reactiveAfterLoad( + Object entity, + EntityMappingType entityMappingType, + SharedSessionContractImplementor 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/loader/entity/ReactiveCacheEntityLoaderHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/entity/ReactiveCacheEntityLoaderHelper.java deleted file mode 100644 index 703148cd7..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/entity/ReactiveCacheEntityLoaderHelper.java +++ /dev/null @@ -1,148 +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.loader.entity; - -import static org.hibernate.loader.ast.internal.LoaderHelper.upgradeLock; -import org.hibernate.HibernateException; -import org.hibernate.LockMode; -import org.hibernate.LockOptions; -import org.hibernate.ObjectDeletedException; -import org.hibernate.cache.spi.access.EntityDataAccess; -import org.hibernate.cache.spi.access.SoftLock; -import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.EntityKey; -import org.hibernate.engine.spi.SessionImplementor; -import org.hibernate.engine.spi.Status; -import org.hibernate.event.spi.EventSource; -import org.hibernate.event.spi.LoadEvent; -import org.hibernate.event.spi.LoadEventListener; -import org.hibernate.internal.CoreLogging; -import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.loader.ast.internal.CacheEntityLoaderHelper.EntityStatus; -import org.hibernate.loader.ast.internal.CacheEntityLoaderHelper.PersistenceContextEntry; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; -import org.hibernate.sql.results.LoadingLogger; - -/** - * @author Gavin King - * - * @see org.hibernate.loader.ast.internal.CacheEntityLoaderHelper - */ -public class ReactiveCacheEntityLoaderHelper { - - public static final ReactiveCacheEntityLoaderHelper INSTANCE = new ReactiveCacheEntityLoaderHelper(); - - private static final CoreMessageLogger LOG = CoreLogging.messageLogger( ReactiveCacheEntityLoaderHelper.class ); - - private ReactiveCacheEntityLoaderHelper() {} - - /** - * Attempts to locate the entity in the session-level cache. - *

- * If allowed to return nulls, then if the entity happens to be found in - * the session cache, we check the entity type for proper handling - * of entity hierarchies. - *

- * If checkDeleted was set to true, then if the entity is found in the - * session-level cache, its current status within the session cache - * is checked to see if it has previously been scheduled for deletion. - * - * @param event The load event - * @param keyToLoad The EntityKey representing the entity to be loaded. - * @param options The load options. - * - * @return The entity from the session-level cache, or null. - * - * @throws HibernateException Generally indicates problems applying a lock-mode. - */ - public PersistenceContextEntry loadFromSessionCache( - final LoadEvent event, - final EntityKey keyToLoad, - final LoadEventListener.LoadType options) throws HibernateException { - - SessionImplementor session = event.getSession(); - Object old = session.getEntityUsingInterceptor( keyToLoad ); - - if ( old != null ) { - // this object was already loaded - EntityEntry oldEntry = session.getPersistenceContext().getEntry( old ); - if ( options.isCheckDeleted() ) { - if ( oldEntry.getStatus().isDeletedOrGone() ) { - LoadingLogger.LOGGER.debug( - "Load request found matching entity in context, but it is scheduled for removal; returning null" ); - return new PersistenceContextEntry( old, EntityStatus.REMOVED_ENTITY_MARKER ); - } - } - if ( options.isAllowNulls() ) { - final EntityPersister persister = event.getSession() - .getFactory() - .getRuntimeMetamodels() - .getMappingMetamodel() - .getEntityDescriptor( keyToLoad.getEntityName() ); - if ( !persister.isInstance( old ) ) { - LOG.debug( - "Load request found matching entity in context, but the matched entity was of an inconsistent return type; returning null" - ); - return new PersistenceContextEntry( old, EntityStatus.INCONSISTENT_RTN_CLASS_MARKER ); - } - } - upgradeLock( old, oldEntry, event.getLockOptions(), event.getSession() ); - } - - return new PersistenceContextEntry( old, EntityStatus.MANAGED ); - } - - /** - * see org.hibernate.event.internal.AbstractLockUpgradeEventListener#upgradeLock(Object, EntityEntry, LockOptions, EventSource) - */ - private static void upgradeLock(Object object, EntityEntry entry, LockOptions lockOptions, EventSource source) { - - LockMode requestedLockMode = lockOptions.getLockMode(); - if ( requestedLockMode.greaterThan( entry.getLockMode() ) ) { - // The user requested a "greater" (i.e. more restrictive) form of pessimistic lock - - if ( entry.getStatus() != Status.MANAGED ) { - throw new ObjectDeletedException( - "attempted to lock a deleted instance", - entry.getId(), - entry.getPersister().getEntityName() - ); - } - - final EntityPersister persister = entry.getPersister(); - final boolean cachingEnabled = persister.canWriteToCache(); - SoftLock lock = null; - Object ck = null; - try { - if ( cachingEnabled ) { - EntityDataAccess cache = persister.getCacheAccessStrategy(); - ck = cache.generateCacheKey( entry.getId(), persister, source.getFactory(), source.getTenantIdentifier() ); - lock = cache.lockItem( source, ck, entry.getVersion() ); - } - - if ( persister.isVersioned() && requestedLockMode == LockMode.PESSIMISTIC_FORCE_INCREMENT ) { - // todo : should we check the current isolation mode explicitly? - Object nextVersion = persister.forceVersionIncrement( entry.getId(), entry.getVersion(), source ); - entry.forceLocked( object, nextVersion ); - } - else { - ( (ReactiveEntityPersister) persister ) - .reactiveLock( entry.getId(), entry.getVersion(), object, lockOptions, source ); - } - entry.setLockMode( requestedLockMode ); - } - finally { - // the database now holds a lock + the object is flushed from the cache, - // so release the soft lock - if ( cachingEnabled ) { - persister.getCacheAccessStrategy().unlockItem( source, ck, lock ); - } - } - - } - } -} 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 72760a26b..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") @@ -258,6 +261,15 @@ public interface Log extends BasicLogger { @Message(id = 80, value = "No results were returned by the query (you can try running it with '.executeUpdate()'): %1$s") HibernateException noResultException(String sql); + @Message(id = 81, value = "The Vert.x SQL client doesn't support the SQL XML data type. If it's the mapping of an array, you can also try setting the property `hibernate.type.preferred_array_jdbc_type`") + HibernateException unsupportedXmlType(); + + @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!" ) @@ -278,6 +290,11 @@ public interface Log extends BasicLogger { @Message(id = 245, value = "Manipulation query [%s] resulted in [%s] split queries" ) void splitQueries(String sourceQuery, int length); + // Same method that exists in CoreMessageLogger + @LogMessage(level = INFO) + @Message(value = "Could not find any META-INF/persistence.xml file in the classpath", id = 318) + void unableToFindPersistenceXmlInClasspath(); + // Same method that exists in CoreMessageLogger @LogMessage(level = INFO) @Message(id = 327, value = "Error performing load command") @@ -300,4 +317,16 @@ public interface Log extends BasicLogger { @LogMessage(level = WARN) @Message(id = 448, value = "Warnings creating temp table : %s") void warningsCreatingTempTable(SQLWarning warning); + + @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/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java new file mode 100644 index 000000000..876312b47 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java @@ -0,0 +1,128 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.metamodel.mapping.internal; + +import java.util.Map; + +import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.boot.spi.BootstrapContext; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.boot.spi.SessionFactoryOptions; +import org.hibernate.cache.spi.CacheImplementor; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.generator.Generator; +import org.hibernate.mapping.GeneratorSettings; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.metamodel.spi.MappingMetamodelImplementor; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.query.sqm.function.SqmFunctionRegistry; +import org.hibernate.reactive.tuple.entity.ReactiveEntityMetamodel; +import org.hibernate.service.ServiceRegistry; +import org.hibernate.tuple.entity.EntityMetamodel; +import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; +import org.hibernate.type.spi.TypeConfiguration; + +public class ReactiveRuntimeModelCreationContext implements RuntimeModelCreationContext { + + private final RuntimeModelCreationContext delegate; + + public ReactiveRuntimeModelCreationContext(RuntimeModelCreationContext delegate) { + this.delegate = delegate; + } + + @Override + public EntityMetamodel createEntityMetamodel(PersistentClass persistentClass, EntityPersister persister) { + return new ReactiveEntityMetamodel( persistentClass, persister, delegate ); + } + + @Override + public SessionFactoryImplementor getSessionFactory() { + return delegate.getSessionFactory(); + } + + @Override + public BootstrapContext getBootstrapContext() { + return delegate.getBootstrapContext(); + } + + @Override + public MetadataImplementor getBootModel() { + return delegate.getBootModel(); + } + + @Override + public MappingMetamodelImplementor getDomainModel() { + return delegate.getDomainModel(); + } + + @Override + public TypeConfiguration getTypeConfiguration() { + return delegate.getTypeConfiguration(); + } + + @Override + public JavaTypeRegistry getJavaTypeRegistry() { + return delegate.getJavaTypeRegistry(); + } + + @Override + public MetadataImplementor getMetadata() { + return delegate.getMetadata(); + } + + @Override + public SqmFunctionRegistry getFunctionRegistry() { + return delegate.getFunctionRegistry(); + } + + @Override + public Map getSettings() { + return delegate.getSettings(); + } + + @Override + public Dialect getDialect() { + return delegate.getDialect(); + } + + @Override + public CacheImplementor getCache() { + return delegate.getCache(); + } + + @Override + public SessionFactoryOptions getSessionFactoryOptions() { + return delegate.getSessionFactoryOptions(); + } + + @Override + public JdbcServices getJdbcServices() { + return delegate.getJdbcServices(); + } + + @Override + public SqlStringGenerationContext getSqlStringGenerationContext() { + return delegate.getSqlStringGenerationContext(); + } + + @Override + public ServiceRegistry getServiceRegistry() { + return delegate.getServiceRegistry(); + } + + @Override + public Map getGenerators() { + return delegate.getGenerators(); + } + + @Override + public GeneratorSettings getGeneratorSettings() { + return delegate.getGeneratorSettings(); + } +} 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 65b0ad906..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; @@ -18,14 +13,14 @@ import org.hibernate.LockMode; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.collection.spi.AbstractPersistentCollection; -import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; 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; import org.hibernate.reactive.common.AffectedEntities; import org.hibernate.reactive.common.Identifier; import org.hibernate.reactive.common.ResultSetMapping; @@ -38,15 +33,21 @@ import jakarta.persistence.CacheRetrieveMode; import jakarta.persistence.CacheStoreMode; import jakarta.persistence.EntityGraph; +import jakarta.persistence.FindOption; 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; @@ -358,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. @@ -515,239 +485,576 @@ 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.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 entity 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 entity); + SelectionQuery createQuery(CriteriaQuery criteriaQuery); /** - * Persist multiple transient entity instances at once. + * Create an instance of {@link MutationQuery} for the given criteria update. * - * @see #persist(Object) + * @param criteriaUpdate The {@link CriteriaUpdate} + * + * @return The {@link MutationQuery} instance for manipulation and execution */ - Uni persistAll(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());}
-		 * 
+ * Create an instance of {@link MutationQuery} for the given criteria delete. * - * @param entity the managed persistent instance to be removed + * @param criteriaDelete The {@link CriteriaDelete} * - * @throws IllegalArgumentException if the given instance is not managed - * @see jakarta.persistence.EntityManager#remove(Object) + * @return The {@link MutationQuery} instance for manipulation and execution */ - Uni 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) */ - Uni removeAll(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) */ - Uni 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, + * 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 #merge(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 */ - Uni mergeAll(Object... entities); + Query createNativeQuery(String queryString); /** - * 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 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 + * + * @return an updated persistent instance + * + * @see jakarta.persistence.EntityManager#merge(Object) + */ + Uni merge(T entity); + + /** + * Merge multiple entity instances at once. + * + * @see #merge(Object) + */ + Uni mergeAll(Object... entities); + + /** + * 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: * *

    *
  • where a database trigger alters the object state after insert or @@ -858,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))}
     		 * 
    @@ -915,246 +1224,7 @@ default Uni lock(Object entity, LockModeType lockModeType) { /** * 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 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 - * - * @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); + boolean contains(Object entity); /** * Set the {@link FlushMode flush mode} for this session. @@ -1226,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. @@ -1504,7 +1553,7 @@ default Session setCacheRetrieveMode(CacheRetrieveMode cacheRetrieveMode) { * * @see org.hibernate.StatelessSession */ - interface StatelessSession extends Closeable { + interface StatelessSession extends QueryProducer, Closeable { /** * Retrieve a row. @@ -1518,6 +1567,18 @@ interface StatelessSession extends Closeable { */ Uni get(Class entityClass, Object id); + /** + * Retrieve multiple rows. + * + * @param entityClass The class of the entity to retrieve + * @param ids The ids of the entities to retrieve + * + * @return a list of detached entity instances, via a {@code Uni} + * + * @see org.hibernate.StatelessSession#getMultiple(Class, List) + */ + Uni> get(Class entityClass, Object... ids); + /** * Retrieve a row, obtaining the specified lock mode. * @@ -1557,135 +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); - /** * Insert a row. * @@ -1696,7 +1628,8 @@ default Uni get(Class entityClass, Object id, LockModeType lockModeTyp Uni insert(Object entity); /** - * Insert multiple rows. + * Insert multiple rows, using the number of the + * given entities as the batch size. * * @param entities new transient instances * @@ -1714,6 +1647,16 @@ default Uni get(Class entityClass, Object id, LockModeType lockModeTyp */ Uni insertAll(int batchSize, Object... entities); + /** + * Insert multiple rows, using the size of the + * given list as the batch size. + * + * @param entities new transient instances + * + * @see org.hibernate.StatelessSession#insert(Object) + */ + Uni insertMultiple(List entities); + /** * Delete a row. * @@ -1724,7 +1667,8 @@ default Uni get(Class entityClass, Object id, LockModeType lockModeTyp Uni delete(Object entity); /** - * Delete multiple rows. + * Delete multiple rows, using the number of the + * given entities as the batch size. * * @param entities detached entity instances * @@ -1742,6 +1686,16 @@ default Uni get(Class entityClass, Object id, LockModeType lockModeTyp */ Uni deleteAll(int batchSize, Object... entities); + /** + * Delete multiple rows, using the size of the + * given list as the batch size. + * + * @param entities detached entity instances + * + * @see org.hibernate.StatelessSession#delete(Object) + */ + Uni deleteMultiple(List entities); + /** * Update a row. * @@ -1752,7 +1706,8 @@ default Uni get(Class entityClass, Object id, LockModeType lockModeTyp Uni update(Object entity); /** - * Update multiple rows. + * Update multiple rows, using the number of the + * given entities as the batch size. * * @param entities detached entity instances * @@ -1771,13 +1726,14 @@ default Uni get(Class entityClass, Object id, LockModeType lockModeTyp Uni updateAll(int batchSize, Object... entities); /** - * Refresh the entity instance state from the database. + * Update multiple rows, using the size of the + * given list as the batch size. * - * @param entity The entity to be refreshed. + * @param entities detached entity instances * - * @see org.hibernate.StatelessSession#refresh(Object) + * @see org.hibernate.StatelessSession#update(Object) */ - Uni refresh(Object entity); + Uni updateMultiple(List entities); /** * Use a SQL {@code merge into} statement to perform an upsert. @@ -1790,20 +1746,54 @@ default Uni get(Class entityClass, Object id, LockModeType lockModeTyp Uni upsert(Object entity); /** - * Use a SQL {@code merge into} statement to perform an upsert. + * Use a SQL {@code merge into} statement to perform + * an upsert on multiple rows using the size of the given array + * as batch size. * - * @param entityName The entityName for the entity to be merged - * @param entity a detached entity instance - * @throws org.hibernate.TransientObjectException is the entity is transient + * @param entities the entities to upsert + * + * @see org.hibernate.StatelessSession#upsert(Object) + */ + @Incubating + Uni upsertAll(Object... entities); + + /** + * Use a SQL {@code merge into} statement to perform + * an upsert on multiple rows using the specified batch size. + * + * @param batchSize the batch size + * @param entities the list of entities to upsert + * + * @see org.hibernate.StatelessSession#upsert(Object) + */ + @Incubating + Uni upsertAll(int batchSize, Object... entities); + + /** + * Use a SQL {@code merge into} statement to perform + * an upsert on multiple rows using the size of the given list + * as batch size. + * + * @param entities the entities to upsert * - * @see org.hibernate.StatelessSession#upsert(String, Object) + * @see org.hibernate.StatelessSession#upsert(Object) */ @Incubating - Uni upsert(String entityName, Object entity); + Uni upsertMultiple(List entities); /** * Refresh the entity instance state from the database. * + * @param entity The entity to be refreshed. + * + * @see org.hibernate.StatelessSession#refresh(Object) + */ + Uni refresh(Object entity); + + /** + * Refresh the entity instance state from the database, using the number of the + * given entities as the batch size. + * * @param entities The entities to be refreshed. * * @see org.hibernate.StatelessSession#refresh(Object) @@ -1821,6 +1811,16 @@ default Uni get(Class entityClass, Object id, LockModeType lockModeTyp */ Uni refreshAll(int batchSize, Object... entities); + /** + * Refresh the entity instance state from the database + * using the size of the given list as the batch size. + * + * @param entities The entities to be refreshed. + * + * @see org.hibernate.StatelessSession#refresh(Object) + */ + Uni refreshMultiple(List entities); + /** * Refresh the entity instance state from the database. * @@ -1862,25 +1862,15 @@ default Uni refresh(Object entity, LockModeType lockModeType) { Uni fetch(T association); /** - * 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} + * Return the identifier value of the given entity, which may be detached. + * + * @param entity a persistent instance associated with this session + * + * @return the identifier + * + * @since 3.0 */ - EntityGraph createEntityGraph(Class rootType, String graphName); + Object getIdentifier(Object entity); /** * Performs the given work within the scope of a database transaction, @@ -2040,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 @@ -2059,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. *

    * @@ -2079,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. @@ -2102,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. @@ -2127,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 @@ -2138,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) @@ -2151,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 @@ -2162,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}. @@ -2173,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 @@ -2191,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. @@ -2210,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 @@ -2234,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 @@ -2260,7 +2258,7 @@ default Uni withStatelessTransaction(Function> w * @return an instance of {@link CriteriaBuilder} for creating * criteria queries. */ - CriteriaBuilder getCriteriaBuilder(); + HibernateCriteriaBuilder getCriteriaBuilder(); /** * Obtain the JPA {@link Metamodel} for the persistence unit. @@ -2279,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. */ @@ -2317,18 +2340,18 @@ static Uni fetch(T association) { } final SharedSessionContractImplementor session; - if ( association instanceof HibernateProxy ) { - session = ( (HibernateProxy) association ).getHibernateLazyInitializer().getSession(); + if ( association instanceof HibernateProxy proxy ) { + session = proxy.getHibernateLazyInitializer().getSession(); } - else if ( association instanceof PersistentCollection ) { + else if ( association instanceof AbstractPersistentCollection collection ) { //this unfortunately doesn't work for stateless session because the session ref gets set to null - session = ( (AbstractPersistentCollection) association ).getSession(); + session = collection.getSession(); } else if ( isPersistentAttributeInterceptable( association ) ) { final PersistentAttributeInterceptable interceptable = asPersistentAttributeInterceptable( association ); final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); - if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { - session = ( (EnhancementAsProxyLazinessInterceptor) interceptor ).getLinkedSession(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor lazinessInterceptor ) { + session = lazinessInterceptor.getLinkedSession(); } else { return Uni.createFrom().item( association ); 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 aecc99e37..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 @@ -15,6 +15,7 @@ import org.hibernate.Cache; import org.hibernate.internal.SessionCreationOptions; import org.hibernate.internal.SessionFactoryImpl; +import org.hibernate.query.criteria.HibernateCriteriaBuilder; import org.hibernate.reactive.common.spi.Implementor; import org.hibernate.reactive.context.Context; import org.hibernate.reactive.context.impl.BaseKey; @@ -30,7 +31,6 @@ import org.hibernate.stat.Statistics; import io.smallrye.mutiny.Uni; -import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.metamodel.Metamodel; import static org.hibernate.reactive.common.InternalStateAssertions.assertUseOnEventLoop; @@ -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" ); @@ -245,7 +255,7 @@ public Uni withStatelessTransaction(String tenantId, BiFunction( delegate.createReactiveQuery( queryString ), factory ); } + @Override + public MutationQuery createMutationQuery(CriteriaUpdate updateQuery) { + return new MutinyMutationQueryImpl<>( delegate.createReactiveMutationQuery( updateQuery ), factory ); + } + + @Override + public MutationQuery createMutationQuery(CriteriaDelete deleteQuery) { + return new MutinyMutationQueryImpl<>( delegate.createReactiveMutationQuery( deleteQuery ), factory ); + } + + @Override + 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 ); @@ -157,7 +181,7 @@ public MutationQuery createQuery(CriteriaDelete criteriaDelete) { @Override public Query createNamedQuery(String queryName) { - return new MutinyQueryImpl<>( delegate.createReactiveNamedQuery( queryName, null ), factory ); + return new MutinyQueryImpl<>( delegate.createReactiveNamedQuery( queryName ), factory ); } @Override @@ -236,6 +260,11 @@ public Uni persist(Object entity) { return uni( () -> delegate.reactivePersist( entity ) ); } + @Override + public Uni persist(String entityName, Object entity) { + return uni( () -> delegate.reactivePersist( entityName, entity ) ); + } + @Override public Uni persistAll(Object... entity) { return uni( () -> applyToAll( delegate::reactivePersist, entity ) ); @@ -303,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; } @@ -487,7 +502,8 @@ Uni execute(Function> work) { * roll back) and an error thrown by the work. */ Uni executeInTransaction(Function> work) { - return work.apply( this ) + return Uni.createFrom() + .deferred( () -> work.apply( this ) ) // only flush() if the work completed with no exception .call( this::flush ).call( this::beforeCompletion ) // in the case of an exception or cancellation @@ -548,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 b7dc5c26c..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,24 +5,31 @@ */ package org.hibernate.reactive.mutiny.impl; -import io.smallrye.mutiny.Uni; -import jakarta.persistence.EntityGraph; -import jakarta.persistence.criteria.CriteriaQuery; 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 @@ -42,7 +49,6 @@ public ReactiveConnection getReactiveConnection() { return delegate.getReactiveConnection(); } - Uni uni(Supplier> stageSupplier) { return factory.uni( stageSupplier ); } @@ -52,6 +58,11 @@ public Uni get(Class entityClass, Object id) { return uni( () -> delegate.reactiveGet( entityClass, id ) ); } + @Override + public Uni> get(Class entityClass, Object... ids) { + return uni( () -> delegate.reactiveGet( entityClass, ids ) ); + } + @Override public Uni get(Class entityClass, Object id, LockMode lockMode) { return uni( () -> delegate.reactiveGet( entityClass, id, lockMode, null ) ); @@ -63,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 ); @@ -75,17 +92,47 @@ 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 public Query createNamedQuery(String queryName) { - return new MutinyQueryImpl<>( delegate.createReactiveNamedQuery( queryName, null ), factory ); + return new MutinyQueryImpl<>( delegate.createReactiveNamedQuery( queryName ), factory ); } @Override @@ -113,6 +160,16 @@ public SelectionQuery createQuery(CriteriaQuery criteriaQuery) { return new MutinySelectionQueryImpl<>( delegate.createReactiveQuery( criteriaQuery ), factory ); } + @Override + public Mutiny.MutationQuery createQuery(CriteriaUpdate criteriaUpdate) { + return new MutinyMutationQueryImpl<>( delegate.createReactiveMutationQuery( criteriaUpdate ), factory ); + } + + @Override + public Mutiny.MutationQuery createQuery(CriteriaDelete criteriaDelete) { + return new MutinyMutationQueryImpl<>( delegate.createReactiveMutationQuery( criteriaDelete ), factory ); + } + @Override public Uni insert(Object entity) { return uni( () -> delegate.reactiveInsert( entity ) ); @@ -120,7 +177,7 @@ public Uni insert(Object entity) { @Override public Uni insertAll(Object... entities) { - return uni( () -> delegate.reactiveInsertAll( entities ) ); + return uni( () -> delegate.reactiveInsertAll( entities.length, entities ) ); } @Override @@ -128,6 +185,11 @@ public Uni insertAll(int batchSize, Object... entities) { return uni( () -> delegate.reactiveInsertAll( batchSize, entities ) ); } + @Override + public Uni insertMultiple(List entities) { + return insertAll( entities.size(), entities.toArray() ); + } + @Override public Uni delete(Object entity) { return uni( () -> delegate.reactiveDelete( entity ) ); @@ -135,12 +197,17 @@ public Uni delete(Object entity) { @Override public Uni deleteAll(Object... entities) { - return uni( () -> delegate.reactiveDeleteAll( entities ) ); + return uni( () -> delegate.reactiveDeleteAll( entities.length, entities ) ); } @Override public Uni deleteAll(int batchSize, Object... entities) { - return uni( () -> delegate.reactiveDeleteAll( entities ) ); + return uni( () -> delegate.reactiveDeleteAll( batchSize, entities ) ); + } + + @Override + public Uni deleteMultiple(List entities) { + return deleteAll( entities.size(), entities.toArray() ); } @Override @@ -150,7 +217,7 @@ public Uni update(Object entity) { @Override public Uni updateAll(Object... entities) { - return uni( () -> delegate.reactiveUpdateAll( entities ) ); + return uni( () -> delegate.reactiveUpdateAll( entities.length, entities ) ); } @Override @@ -158,6 +225,11 @@ public Uni updateAll(int batchSize, Object... entities) { return uni( () -> delegate.reactiveUpdateAll( batchSize, entities ) ); } + @Override + public Uni updateMultiple(List entities) { + return updateAll( entities.size(), entities.toArray() ); + } + @Override public Uni refresh(Object entity) { return uni( () -> delegate.reactiveRefresh( entity ) ); @@ -169,13 +241,23 @@ public Uni upsert(Object entity) { } @Override - public Uni upsert(String entityName, Object entity) { - return uni( () -> delegate.reactiveUpsert( entityName, entity ) ); + public Uni upsertAll(Object... entities) { + return uni( () -> delegate.reactiveUpsertAll( entities.length, entities ) ); + } + + @Override + public Uni upsertAll(int batchSize, Object... entities) { + return uni( () -> delegate.reactiveUpsertAll( batchSize, entities ) ); + } + + @Override + public Uni upsertMultiple(List entities) { + return uni( () -> delegate.reactiveUpsertAll( entities.size(), entities.toArray() ) ); } @Override public Uni refreshAll(Object... entities) { - return uni( () -> delegate.reactiveRefreshAll( entities ) ); + return uni( () -> delegate.reactiveRefreshAll( entities.length, entities ) ); } @Override @@ -183,6 +265,11 @@ public Uni refreshAll(int batchSize, Object... entities) { return uni( () -> delegate.reactiveRefreshAll( batchSize, entities ) ); } + @Override + public Uni refreshMultiple(List entities) { + return refreshAll( entities.size(), entities.toArray() ); + } + @Override public Uni refresh(Object entity, LockMode lockMode) { return uni( () -> delegate.reactiveRefresh( entity, lockMode ) ); @@ -193,25 +280,10 @@ public Uni fetch(T association) { return uni( () -> delegate.reactiveFetch( association, false ) ); } -// @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 Object getIdentifier(Object entity) { + return delegate.getIdentifier(entity); + } @Override public Uni withTransaction(Function> work) { @@ -228,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() @@ -236,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 work.apply( this ) + 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, @@ -293,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/collection/mutation/ReactiveUpdateRowsCoordinatorOneToMany.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/collection/mutation/ReactiveUpdateRowsCoordinatorOneToMany.java index 99bec6f5a..8b7478efa 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/collection/mutation/ReactiveUpdateRowsCoordinatorOneToMany.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/collection/mutation/ReactiveUpdateRowsCoordinatorOneToMany.java @@ -7,6 +7,7 @@ import java.util.Iterator; import java.util.concurrent.CompletionStage; +import java.util.function.Function; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey; @@ -28,9 +29,9 @@ import static java.lang.invoke.MethodHandles.lookup; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; -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.voidFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.zeroFuture; import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER; import static org.hibernate.sql.model.MutationType.DELETE; import static org.hibernate.sql.model.MutationType.INSERT; @@ -70,15 +71,18 @@ public CompletionStage reactiveUpdateRows(Object key, PersistentCollection } private CompletionStage doReactiveUpdate(Object key, PersistentCollection collection, SharedSessionContractImplementor session) { - if ( rowMutationOperations.hasDeleteRow() ) { - deleteRows( key, collection, session ); - } + final Function> insertRowsFun = v -> { + if ( rowMutationOperations.hasInsertRow() ) { + return insertRows( key, collection, session ); + } - if ( rowMutationOperations.hasInsertRow() ) { - return insertRows( key, collection, session ); + return zeroFuture(); + }; + if ( rowMutationOperations.hasDeleteRow() ) { + return deleteRows( key, collection, session ) + .thenCompose( insertRowsFun ); } - - return completedFuture( 0 ); + return insertRowsFun.apply( null ); } private CompletionStage insertRows(Object key, PersistentCollection collection, SharedSessionContractImplementor session) { 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 30c383331..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 @@ -5,11 +5,14 @@ */ package org.hibernate.reactive.persister.entity.impl; -import java.lang.invoke.MethodHandles; -import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.CompletionStage; import org.hibernate.AssertionFailure; @@ -20,35 +23,46 @@ import org.hibernate.MappingException; import org.hibernate.StaleObjectStateException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; -import org.hibernate.bytecode.enhance.spi.interceptor.*; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeDescriptor; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.internal.ManagedTypeHelper; -import org.hibernate.engine.spi.*; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.EventSource; -import org.hibernate.event.spi.LoadEvent; import org.hibernate.generator.OnExecutionGenerator; import org.hibernate.generator.values.GeneratedValuesMutationDelegate; import org.hibernate.internal.util.collections.ArrayHelper; -import org.hibernate.jdbc.Expectation; -import org.hibernate.loader.ast.internal.CacheEntityLoaderHelper; import org.hibernate.loader.ast.internal.LoaderSelectBuilder; import org.hibernate.loader.ast.spi.NaturalIdLoader; +import org.hibernate.loader.ast.spi.SingleIdEntityLoader; import org.hibernate.mapping.PersistentClass; -import org.hibernate.metamodel.mapping.*; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.AttributeMappingsList; +import org.hibernate.metamodel.mapping.EntityVersionMapping; +import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.NaturalIdMapping; +import org.hibernate.metamodel.mapping.SingularAttributeMapping; 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; import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; 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.pool.impl.Parameters; -import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.session.impl.ReactiveQueryExecutorLookup; import org.hibernate.sql.SimpleSelect; import org.hibernate.sql.Update; @@ -58,11 +72,13 @@ import jakarta.persistence.metamodel.Attribute; +import static java.lang.invoke.MethodHandles.lookup; import static java.util.Collections.emptyMap; import static org.hibernate.generator.EventType.INSERT; import static org.hibernate.generator.EventType.UPDATE; import static org.hibernate.internal.util.collections.CollectionHelper.setOfSize; import static org.hibernate.pretty.MessageHelper.infoString; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; 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.logSqlException; @@ -78,7 +94,7 @@ * three flavors of {@link ReactiveEntityPersister}. Therefore, this * interface is defined as a mixin. This design avoid duplicating * the code in this class in the three different subclasses. - * + *

    * Concrete implementations of this interface _must_ also extend * {@code AbstractEntityPersister} or one of its concrete * subclasses. @@ -90,12 +106,6 @@ */ public interface ReactiveAbstractEntityPersister extends ReactiveEntityPersister { - Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - - default Parameters parameters() { - return Parameters.instance( getFactory().getJdbcServices().getDialect() ); - } - /** * A self-reference of type {@code AbstractEntityPersister}. * @@ -121,13 +131,6 @@ default ReactiveConnection getReactiveConnection(SharedSessionContractImplemento return ReactiveQueryExecutorLookup.extract( session ).getReactiveConnection(); } - boolean check( - int rows, - Object id, - int tableNumber, - Expectation expectation, - PreparedStatement statement, String sql) throws HibernateException; - default String generateSelectLockString(LockOptions lockOptions) { final SessionFactoryImplementor factory = getFactory(); final SimpleSelect select = new SimpleSelect( factory ) @@ -141,20 +144,20 @@ default String generateSelectLockString(LockOptions lockOptions) { if ( factory.getSessionFactoryOptions().isCommentsEnabled() ) { select.setComment( lockOptions.getLockMode() + " lock " + getEntityName() ); } - return parameters().process( select.toStatementString() ); + return select.toStatementString(); } default String generateUpdateLockString(LockOptions lockOptions) { final SessionFactoryImplementor factory = getFactory(); final Update update = new Update( factory ); - update.setTableName( getEntityMappingType().getMappedTableDetails().getTableName() ); + update.setTableName( getEntityMappingType().getMappedTableDetails().getTableName() ); update.addAssignment( getVersionMapping().getVersionAttribute().getAttributeName() ); update.addRestriction( getIdentifierPropertyName() ); update.addRestriction( getVersionMapping().getVersionAttribute().getAttributeName() ); if ( factory.getSessionFactoryOptions().isCommentsEnabled() ) { update.setComment( lockOptions.getLockMode() + " lock " + getEntityName() ); } - return parameters().process( update.toStatementString() ); + return update.toStatementString(); } @Override @@ -172,7 +175,7 @@ default CompletionStage reactiveLock( String sql; boolean writeLock; - switch (lockMode) { + switch ( lockMode ) { // 0) noop case NONE: return voidFuture(); @@ -201,14 +204,14 @@ default CompletionStage reactiveLock( // locks obtained in the before completion phase case OPTIMISTIC: case OPTIMISTIC_FORCE_INCREMENT: - throw new AssertionFailure("optimistic lock mode is not supported here"); + throw new AssertionFailure( "optimistic lock mode is not supported here" ); // 7) READ and WRITE are obtained implicitly by // other operations case READ: case WRITE: - throw new AssertionFailure("implicit lock mode is not supported here"); + throw new AssertionFailure( "implicit lock mode is not supported here" ); default: - throw new AssertionFailure("illegal lock mode"); + throw new AssertionFailure( "illegal lock mode" ); } Object[] arguments = PreparedStatementAdaptor.bind( statement -> { @@ -218,7 +221,7 @@ default CompletionStage reactiveLock( offset++; } getIdentifierType().nullSafeSet( statement, id, offset, session ); - offset += getIdentifierType().getColumnSpan( getFactory() ); + offset += getIdentifierType().getColumnSpan( getFactory().getRuntimeMetamodels() ); if ( isVersioned() ) { getVersionType().nullSafeSet( statement, version, offset, session ); } @@ -259,6 +262,7 @@ default Object nextVersionForLock(LockMode lockMode, Object id, Object currentVe final Object nextVersion = getVersionJavaType() .next( currentVersion, versionMapping.getLength(), versionMapping.getPrecision(), versionMapping.getScale(), session ); + Log LOG = make( Log.class, lookup() ); if ( LOG.isTraceEnabled() ) { LOG.trace( "Forcing version increment [" + infoString( this, id, getFactory() ) + "; " + versionType.toLoggableString( currentVersion, getFactory() ) + " -> " @@ -289,6 +293,7 @@ default ReactiveSingleIdEntityLoader getReactiveSingleIdEntityLoader() { */ @Override default CompletionStage reactiveGetCurrentVersion(Object id, SharedSessionContractImplementor session) { + Log LOG = make( Log.class, lookup() ); if ( LOG.isTraceEnabled() ) { LOG.tracev( "Getting version: {0}", infoString( this, id, getFactory() ) ); } @@ -339,7 +344,7 @@ else if ( result instanceof PersistentCollection ) { // collection. That's inconsistent with what happens // for other lazy fields, so let's set the field here final String[] propertyNames = getPropertyNames(); - for ( int index=0; index collection = (PersistentCollection) result; return collection.wasInitialized() ? completedFuture( (T) collection ) - : ((ReactiveSession) session).reactiveInitializeCollection( collection, false ) + : ( (ReactiveSharedSessionContractImplementor) session ) + .reactiveInitializeCollection( collection, false ) .thenApply( v -> (T) result ); } else { @@ -379,7 +385,7 @@ default CompletionStage reactiveInitializeLazyPropertiesFromDatastore( throw new AssertionFailure( "Expecting bytecode interceptor to be non-null" ); } - LOG.tracef( "Initializing lazy properties from datastore (triggered for `%s`)", fieldName ); + make( Log.class, lookup() ).tracef( "Initializing lazy properties from datastore (triggered for `%s`)", fieldName ); final String fetchGroup = getEntityPersister().getBytecodeEnhancementMetadata() .getLazyAttributesMetadata() @@ -418,7 +424,7 @@ default CompletionStage initLazyProperty( Object[] values) { // Load all the lazy properties that are in the same fetch group CompletionStage resultStage = nullFuture(); int i = 0; - for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor: fetchGroupAttributeDescriptors ) { + for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) { if ( initializedLazyAttributeNames.contains( fetchGroupAttributeDescriptor.getName() ) ) { // Already initialized if ( fetchGroupAttributeDescriptor.getName().equals( fieldName ) ) { @@ -427,7 +433,7 @@ default CompletionStage initLazyProperty( continue; } - final Object selectedValue = values[i++]; + final Object selectedValue = values[i++]; if ( selectedValue instanceof CompletionStage ) { // This happens with a lazy one-to-one (bytecode enhancement enabled) CompletionStage selectedValueStage = (CompletionStage) selectedValue; @@ -459,7 +465,7 @@ default CompletionStage initLazyProperty( } return resultStage.thenApply( result -> { - LOG.trace( "Done initializing lazy properties" ); + make( Log.class, lookup() ).trace( "Done initializing lazy properties" ); return result; } ); } @@ -471,7 +477,7 @@ default CompletionStage reactiveInitializeEnhancedEntityUsedAsProxy( final BytecodeEnhancementMetadata enhancementMetadata = getEntityPersister().getBytecodeEnhancementMetadata(); final BytecodeLazyAttributeInterceptor currentInterceptor = enhancementMetadata.extractLazyInterceptor( entity ); - if ( currentInterceptor instanceof EnhancementAsProxyLazinessInterceptor) { + if ( currentInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { final EnhancementAsProxyLazinessInterceptor proxyInterceptor = (EnhancementAsProxyLazinessInterceptor) currentInterceptor; @@ -515,36 +521,25 @@ private CompletionStage loadFromDatabaseOrCache( EntityKey entityKey, Object identifier) { - // note that stateless sessions don't interact with second-level cache - if ( session instanceof EventSource && canReadFromCache() ) { - Object cached = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( - new LoadEvent( identifier, entity, (EventSource) session, false ), - this, - entityKey - ); - if ( cached != null ) { - return completedFuture( cached ); + if ( canReadFromCache() && session.isEventSource() ) { + final EventSource eventSource = (EventSource) session; + Object loaded = eventSource.loadFromSecondLevelCache( this, entityKey, entity, LockMode.NONE ); + if ( loaded != null ) { + return completedFuture( loaded ); } } - - return getReactiveSingleIdEntityLoader().load( - identifier, - entity, - LockOptions.NONE, - session - ); + return ( (ReactiveSingleIdEntityLoader) determineLoaderToUse( session ) ) + .load( identifier, entity, LockOptions.NONE, session ); } + SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session); + boolean initializeLazyProperty(String fieldName, Object entity, EntityEntry entry, int lazyIndex, Object selectedValue); Object initializeLazyProperty(String fieldName, Object entity, SharedSessionContractImplementor session); - String[][] getLazyPropertyColumnAliases(); - ReactiveSingleIdArrayLoadPlan reactiveGetSQLLazySelectLoadPlan(String fetchGroup); - boolean isBatchable(); - /** * @see AbstractEntityPersister#generateNaturalIdMapping(MappingModelCreationProcess, PersistentClass) */ @@ -558,7 +553,9 @@ default NaturalIdMapping generateNaturalIdMapping( assert naturalIdAttributeIndexes.length > 0; if ( naturalIdAttributeIndexes.length == 1 ) { - final String propertyName = getEntityPersister().getAttributeMappings().get(naturalIdAttributeIndexes[0]).getAttributeName(); + final String propertyName = getEntityPersister().getAttributeMappings() + .get( naturalIdAttributeIndexes[0] ) + .getAttributeName(); final AttributeMapping attributeMapping = findAttributeMapping( propertyName ); final SingularAttributeMapping singularAttributeMapping = (SingularAttributeMapping) attributeMapping; return new ReactiveSimpleNaturalIdMapping( singularAttributeMapping, this, creationProcess ); @@ -597,14 +594,14 @@ default NaturalIdLoader getNaturalIdLoader() { /** * @see AbstractEntityPersister#getLazyLoadPlanByFetchGroup() */ - default Map getLazyLoadPlanByFetchGroup(String[] subclassPropertyNameClosure ) { + default Map getLazyLoadPlanByFetchGroup(String[] subclassPropertyNameClosure) { final BytecodeEnhancementMetadata metadata = delegate().getEntityPersister().getBytecodeEnhancementMetadata(); return metadata.isEnhancedForLazyLoading() && metadata.getLazyAttributesMetadata().hasLazyAttributes() ? createLazyLoadPlanByFetchGroup( metadata, subclassPropertyNameClosure ) : emptyMap(); } - default Map createLazyLoadPlanByFetchGroup(BytecodeEnhancementMetadata metadata, String[] subclassPropertyNameClosure ) { + default Map createLazyLoadPlanByFetchGroup(BytecodeEnhancementMetadata metadata, String[] subclassPropertyNameClosure) { final Map result = new HashMap<>(); final LazyAttributesMetadata attributesMetadata = metadata.getLazyAttributesMetadata(); for ( String groupName : attributesMetadata.getFetchGroupNames() ) { @@ -617,7 +614,7 @@ default Map createLazyLoadPlanByFetchGrou return result; } - default ReactiveSingleIdArrayLoadPlan createLazyLoadPlan(List fetchGroupAttributeDescriptors, String[] subclassPropertyNameClosure ) { + default ReactiveSingleIdArrayLoadPlan createLazyLoadPlan(List fetchGroupAttributeDescriptors, String[] subclassPropertyNameClosure) { final List partsToSelect = new ArrayList<>( fetchGroupAttributeDescriptors.size() ); for ( LazyAttributeDescriptor lazyAttributeDescriptor : fetchGroupAttributeDescriptors ) { // all this only really needs to consider properties 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 ba365a463..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; @@ -32,16 +31,7 @@ public interface ReactiveEntityPersister extends EntityPersister { * * @see EntityPersister#insert(Object, Object[], Object, SharedSessionContractImplementor) */ - default CompletionStage insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session) { - return insertReactive( id, fields, object, session ); - }; - - /** - * Insert the given instance state without blocking, but it allows to specify if it's an identity insert or a regular one. - * - * @see EntityPersister#insert(Object, Object[], Object, SharedSessionContractImplementor) - */ - CompletionStage insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session, boolean isIdentityType); + CompletionStage insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session); /** * Insert the given instance state without blocking. @@ -101,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/ReactiveIdentityGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveIdentityGenerator.java index 20d32814d..d37f07eec 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveIdentityGenerator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveIdentityGenerator.java @@ -9,8 +9,8 @@ import org.hibernate.dialect.Dialect; import org.hibernate.dialect.identity.CockroachDBIdentityColumnSupport; import org.hibernate.id.IdentityGenerator; -import org.hibernate.id.PostInsertIdentityPersister; import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.id.insert.ReactiveInsertReturningDelegate; /** @@ -22,7 +22,7 @@ public class ReactiveIdentityGenerator extends IdentityGenerator { * @see CockroachDBIdentityColumnSupport#supportsIdentityColumns() for some limitations related to CockroachDB */ @Override - public InsertGeneratedIdentifierDelegate getGeneratedIdentifierDelegate(PostInsertIdentityPersister persister) { + public InsertGeneratedIdentifierDelegate getGeneratedIdentifierDelegate(EntityPersister persister) { Dialect dialect = persister.getFactory().getJdbcServices().getDialect(); // Hibernate ORM allows the selection of different strategies based on the property `hibernate.jdbc.use_get_generated_keys`. // But that's a specific JDBC property and with Vert.x we only have one viable option for each supported database. 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 8eca0a6f4..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 @@ -5,7 +5,6 @@ */ package org.hibernate.reactive.persister.entity.impl; -import java.sql.PreparedStatement; import java.util.List; import java.util.concurrent.CompletionStage; @@ -18,10 +17,8 @@ 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.jdbc.Expectation; import org.hibernate.loader.ast.spi.MultiIdEntityLoader; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.loader.ast.spi.SingleIdEntityLoader; @@ -44,6 +41,8 @@ import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.reactive.loader.ast.internal.ReactiveSingleIdArrayLoadPlan; import org.hibernate.reactive.loader.ast.spi.ReactiveSingleUniqueKeyEntityLoader; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.metamodel.mapping.internal.ReactiveRuntimeModelCreationContext; import org.hibernate.reactive.persister.entity.mutation.ReactiveDeleteCoordinator; import org.hibernate.reactive.persister.entity.mutation.ReactiveInsertCoordinatorStandard; import org.hibernate.reactive.persister.entity.mutation.ReactiveUpdateCoordinator; @@ -54,6 +53,9 @@ import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.type.EntityType; +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; + /** * An {@link ReactiveEntityPersister} backed by {@link JoinedSubclassEntityPersister} * and {@link ReactiveAbstractEntityPersister}. @@ -61,6 +63,8 @@ public class ReactiveJoinedSubclassEntityPersister extends JoinedSubclassEntityPersister implements ReactiveAbstractEntityPersister { + private static final Log LOG = make( Log.class, lookup() ); + private final ReactiveAbstractPersisterDelegate reactiveDelegate; public ReactiveJoinedSubclassEntityPersister( @@ -68,8 +72,8 @@ public ReactiveJoinedSubclassEntityPersister( final EntityDataAccess cacheAccessStrategy, final NaturalIdDataAccess naturalIdRegionAccessStrategy, final RuntimeModelCreationContext creationContext) throws HibernateException { - super( persistentClass, cacheAccessStrategy, naturalIdRegionAccessStrategy, creationContext ); - reactiveDelegate = new ReactiveAbstractPersisterDelegate( this, persistentClass, creationContext ); + super( persistentClass, cacheAccessStrategy, naturalIdRegionAccessStrategy, new ReactiveRuntimeModelCreationContext( creationContext ) ); + reactiveDelegate = new ReactiveAbstractPersisterDelegate( this, persistentClass, new ReactiveRuntimeModelCreationContext( creationContext ) ); } @Override @@ -78,7 +82,7 @@ protected SingleIdEntityLoader buildSingleIdEntityLoader() { } @Override - protected MultiIdEntityLoader buildMultiIdLoader() { + protected MultiIdEntityLoader buildMultiIdLoader() { return reactiveDelegate.buildMultiIdEntityLoader(); } @@ -135,9 +139,8 @@ protected AttributeMapping buildPluralAttributeMapping( } @Override - public String generateSelectVersionString() { - String sql = super.generateSelectVersionString(); - return parameters().process( sql ); + public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session) { + return super.determineLoaderToUse( session ); } @Override @@ -179,12 +182,7 @@ public NaturalIdMapping generateNaturalIdMapping(MappingModelCreationProcess cre @Override public CompletionStage insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session) { - return insertReactive( id, fields, object, session, true ); - } - - @Override - public CompletionStage insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session, boolean isIdentityInsert) { - return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() ).coordinateReactiveInsert( object, id, fields, session, isIdentityInsert ); + return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() ).coordinateReactiveInsert( object, id, fields, session, false ); } @Override @@ -240,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 ); } @@ -281,21 +279,11 @@ public void merge( throw LOG.nonReactiveMethodCall( "mergeReactive" ); } - @Override - public boolean check(int rows, Object id, int tableNumber, Expectation expectation, PreparedStatement statement, String sql) throws HibernateException { - return super.check(rows, id, tableNumber, expectation, statement, sql); - } - @Override public boolean initializeLazyProperty(String fieldName, Object entity, EntityEntry entry, int lazyIndex, Object selectedValue) { return super.initializeLazyProperty( fieldName, entity, entry, lazyIndex, selectedValue ); } - @Override - public String[][] getLazyPropertyColumnAliases() { - return super.getLazyPropertyColumnAliases(); - } - /** * Process properties generated with an insert * @@ -324,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 ); } @@ -334,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 ); } @@ -344,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 ); } @@ -382,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/ReactiveMergeCoordinatorStandardScopeFactory.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveMergeCoordinatorStandardScopeFactory.java index 73441f382..062edd9d6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveMergeCoordinatorStandardScopeFactory.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveMergeCoordinatorStandardScopeFactory.java @@ -12,15 +12,13 @@ import org.hibernate.reactive.persister.entity.mutation.ReactiveMergeCoordinator; import org.hibernate.reactive.persister.entity.mutation.ReactiveScopedUpdateCoordinator; import org.hibernate.reactive.persister.entity.mutation.ReactiveUpdateCoordinator; +import org.hibernate.reactive.sql.model.ReactiveDeleteOrUpsertOperation; import org.hibernate.reactive.sql.model.ReactiveOptionalTableUpdateOperation; -import org.hibernate.sql.model.ModelMutationLogging; import org.hibernate.sql.model.MutationOperation; -import org.hibernate.sql.model.MutationOperationGroup; import org.hibernate.sql.model.ValuesAnalysis; -import org.hibernate.sql.model.ast.MutationGroup; import org.hibernate.sql.model.ast.TableMutation; -import org.hibernate.sql.model.internal.MutationOperationGroupFactory; import org.hibernate.sql.model.internal.OptionalTableUpdate; +import org.hibernate.sql.model.jdbc.DeleteOrUpsertOperation; import org.hibernate.sql.model.jdbc.OptionalTableUpdateOperation; public class ReactiveMergeCoordinatorStandardScopeFactory extends MergeCoordinator @@ -44,61 +42,15 @@ public ReactiveScopedUpdateCoordinator makeScopedCoordinator() { ); } - // We override the whole method but we just need to plug in our custom createOperation(...) method @Override - protected MutationOperationGroup createOperationGroup(ValuesAnalysis valuesAnalysis, MutationGroup mutationGroup) { - final int numberOfTableMutations = mutationGroup.getNumberOfTableMutations(); - switch ( numberOfTableMutations ) { - case 0: - return MutationOperationGroupFactory.noOperations( mutationGroup ); - case 1: { - MutationOperation operation = createOperation( valuesAnalysis, mutationGroup.getSingleTableMutation() ); - return operation == null - ? MutationOperationGroupFactory.noOperations( mutationGroup ) - : MutationOperationGroupFactory.singleOperation( mutationGroup, operation ); - } - default: { - MutationOperation[] operations = new MutationOperation[numberOfTableMutations]; - int outputIndex = 0; - int skipped = 0; - for ( int i = 0; i < mutationGroup.getNumberOfTableMutations(); i++ ) { - final TableMutation tableMutation = mutationGroup.getTableMutation( i ); - MutationOperation operation = createOperation( valuesAnalysis, tableMutation ); - if ( operation != null ) { - operations[outputIndex++] = operation; - } - else { - skipped++; - ModelMutationLogging.MODEL_MUTATION_LOGGER.debugf( - "Skipping table update - %s", - tableMutation.getTableName() - ); - } - } - if ( skipped != 0 ) { - final MutationOperation[] trimmed = new MutationOperation[outputIndex]; - System.arraycopy( operations, 0, trimmed, 0, outputIndex ); - operations = trimmed; - } - return MutationOperationGroupFactory.manyOperations( - mutationGroup.getMutationType(), - entityPersister, - operations - ); - } - } - } - - // FIXME: We could add this method in ORM and override only this code protected MutationOperation createOperation(ValuesAnalysis valuesAnalysis, TableMutation singleTableMutation) { MutationOperation operation = singleTableMutation.createMutationOperation( valuesAnalysis, factory() ); if ( operation instanceof OptionalTableUpdateOperation ) { // We need to plug in our own reactive operation - return new ReactiveOptionalTableUpdateOperation( - operation.getMutationTarget(), - (OptionalTableUpdate) singleTableMutation, - factory() - ); + return new ReactiveOptionalTableUpdateOperation( operation.getMutationTarget(), (OptionalTableUpdate) singleTableMutation, factory() ); + } + if ( operation instanceof DeleteOrUpsertOperation ) { + return new ReactiveDeleteOrUpsertOperation( (DeleteOrUpsertOperation) operation ); } return operation; } 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 e16523523..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 @@ -5,7 +5,6 @@ */ package org.hibernate.reactive.persister.entity.impl; -import java.sql.PreparedStatement; import java.util.List; import java.util.concurrent.CompletionStage; import java.util.function.Supplier; @@ -19,12 +18,9 @@ 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; -import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate; -import org.hibernate.jdbc.Expectation; import org.hibernate.loader.ast.spi.MultiIdEntityLoader; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.loader.ast.spi.SingleIdEntityLoader; @@ -47,9 +43,10 @@ import org.hibernate.persister.entity.mutation.UpdateCoordinator; import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.reactive.generator.values.GeneratedValuesMutationDelegateAdaptor; -import org.hibernate.reactive.generator.values.ReactiveInsertGeneratedIdentifierDelegate; import org.hibernate.reactive.loader.ast.internal.ReactiveSingleIdArrayLoadPlan; import org.hibernate.reactive.loader.ast.spi.ReactiveSingleUniqueKeyEntityLoader; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.metamodel.mapping.internal.ReactiveRuntimeModelCreationContext; import org.hibernate.reactive.persister.entity.mutation.ReactiveAbstractDeleteCoordinator; import org.hibernate.reactive.persister.entity.mutation.ReactiveInsertCoordinatorStandard; import org.hibernate.reactive.persister.entity.mutation.ReactiveUpdateCoordinator; @@ -60,6 +57,9 @@ import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.type.EntityType; +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; + /** * A {@link ReactiveEntityPersister} backed by {@link SingleTableEntityPersister} @@ -67,15 +67,17 @@ */ public class ReactiveSingleTableEntityPersister extends SingleTableEntityPersister implements ReactiveAbstractEntityPersister { - private ReactiveAbstractPersisterDelegate reactiveDelegate; + private static final Log LOG = make( Log.class, lookup() ); + + private final ReactiveAbstractPersisterDelegate reactiveDelegate; public ReactiveSingleTableEntityPersister( final PersistentClass persistentClass, final EntityDataAccess cacheAccessStrategy, final NaturalIdDataAccess naturalIdRegionAccessStrategy, final RuntimeModelCreationContext creationContext) throws HibernateException { - super( persistentClass, cacheAccessStrategy, naturalIdRegionAccessStrategy, creationContext ); - reactiveDelegate = new ReactiveAbstractPersisterDelegate( this, persistentClass, creationContext ); + super( persistentClass, cacheAccessStrategy, naturalIdRegionAccessStrategy, new ReactiveRuntimeModelCreationContext( creationContext ) ); + reactiveDelegate = new ReactiveAbstractPersisterDelegate( this, persistentClass, new ReactiveRuntimeModelCreationContext( creationContext ) ); } @Override @@ -83,6 +85,11 @@ public GeneratedValuesMutationDelegate createInsertDelegate() { return ReactiveAbstractEntityPersister.super.createReactiveInsertDelegate(); } + @Override + public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session) { + return super.determineLoaderToUse( session ); + } + @Override protected GeneratedValuesMutationDelegate createUpdateDelegate() { return ReactiveAbstractEntityPersister.super.createReactiveUpdateDelegate(); @@ -94,16 +101,10 @@ protected SingleIdEntityLoader buildSingleIdEntityLoader() { } @Override - protected MultiIdEntityLoader buildMultiIdLoader() { + protected MultiIdEntityLoader buildMultiIdLoader() { return reactiveDelegate.buildMultiIdEntityLoader(); } - @Override - public String generateSelectVersionString() { - String sql = super.generateSelectVersionString(); - return parameters().process( sql ); - } - @Override protected UpdateCoordinator buildUpdateCoordinator() { return ReactiveCoordinatorFactory.buildUpdateCoordinator( this, getFactory() ); @@ -147,15 +148,6 @@ public GeneratedValuesMutationDelegate getUpdateDelegate() { return new GeneratedValuesMutationDelegateAdaptor( updateDelegate ); } - @Override - public InsertGeneratedIdentifierDelegate getIdentityInsertDelegate() { - final GeneratedValuesMutationDelegate insertDelegate = super.getInsertDelegate(); - if ( insertDelegate instanceof InsertGeneratedIdentifierDelegate ) { - return new ReactiveInsertGeneratedIdentifierDelegate( (InsertGeneratedIdentifierDelegate) insertDelegate ); - } - return null; - } - @Override public DomainResult createDomainResult( NavigablePath navigablePath, @@ -217,11 +209,6 @@ protected AttributeMapping buildPluralAttributeMapping( ); } - @Override - public boolean check(int rows, Object id, int tableNumber, Expectation expectation, PreparedStatement statement, String sql) throws HibernateException { - return super.check( rows, id, tableNumber, expectation,statement, sql ); - } - @Override public boolean initializeLazyProperty(String fieldName, Object entity, EntityEntry entry, int lazyIndex, Object selectedValue) { return super.initializeLazyProperty(fieldName, entity, entry, lazyIndex, selectedValue); @@ -232,11 +219,6 @@ public Object initializeLazyPropertiesFromDatastore(final Object entity, final O return reactiveInitializeLazyPropertiesFromDatastore( entity, id, entry, fieldName, session ); } - @Override - public String[][] getLazyPropertyColumnAliases() { - return super.getLazyPropertyColumnAliases(); - } - @Override public Object insert(Object[] fields, Object object, SharedSessionContractImplementor session) { throw LOG.nonReactiveMethodCall( "insertReactive" ); @@ -319,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 ); } @@ -335,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 ); } @@ -345,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 ); } @@ -360,12 +342,7 @@ public CompletionStage insertReactive(Object[] fields, Object e @Override public CompletionStage insertReactive(Object id, Object[] fields, Object entity, SharedSessionContractImplementor session) { - return insertReactive( id, fields, entity, session, true ); - } - - @Override - public CompletionStage insertReactive(Object id, Object[] fields, Object entity, SharedSessionContractImplementor session, boolean isIdentityInsert) { - return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() ).coordinateReactiveInsert( entity, id, fields, session, isIdentityInsert ); + return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() ).coordinateReactiveInsert( entity, id, fields, session, false ); } @Override @@ -419,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 51eb33fad..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 @@ -6,7 +6,6 @@ package org.hibernate.reactive.persister.entity.impl; import java.lang.invoke.MethodHandles; -import java.sql.PreparedStatement; import java.util.List; import java.util.concurrent.CompletionStage; @@ -20,11 +19,9 @@ 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; -import org.hibernate.jdbc.Expectation; import org.hibernate.loader.ast.spi.MultiIdEntityLoader; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.loader.ast.spi.SingleIdEntityLoader; @@ -49,6 +46,7 @@ import org.hibernate.reactive.loader.ast.spi.ReactiveSingleUniqueKeyEntityLoader; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.metamodel.mapping.internal.ReactiveRuntimeModelCreationContext; import org.hibernate.reactive.persister.entity.mutation.ReactiveDeleteCoordinator; import org.hibernate.reactive.persister.entity.mutation.ReactiveInsertCoordinatorStandard; import org.hibernate.reactive.persister.entity.mutation.ReactiveUpdateCoordinator; @@ -74,8 +72,8 @@ public ReactiveUnionSubclassEntityPersister( final EntityDataAccess cacheAccessStrategy, final NaturalIdDataAccess naturalIdRegionAccessStrategy, final RuntimeModelCreationContext creationContext) throws HibernateException { - super( persistentClass, cacheAccessStrategy, naturalIdRegionAccessStrategy, creationContext ); - reactiveDelegate = new ReactiveAbstractPersisterDelegate( this, persistentClass, creationContext ); + super( persistentClass, cacheAccessStrategy, naturalIdRegionAccessStrategy, new ReactiveRuntimeModelCreationContext( creationContext ) ); + reactiveDelegate = new ReactiveAbstractPersisterDelegate( this, persistentClass, new ReactiveRuntimeModelCreationContext( creationContext ) ); } @Override @@ -84,7 +82,7 @@ protected SingleIdEntityLoader buildSingleIdEntityLoader() { } @Override - protected MultiIdEntityLoader buildMultiIdLoader() { + protected MultiIdEntityLoader buildMultiIdLoader() { return reactiveDelegate.buildMultiIdEntityLoader(); } @@ -141,21 +139,13 @@ protected AttributeMapping buildPluralAttributeMapping( } @Override - public NaturalIdMapping generateNaturalIdMapping(MappingModelCreationProcess creationProcess, PersistentClass bootEntityDescriptor) { - return ReactiveAbstractEntityPersister.super.generateNaturalIdMapping(creationProcess, bootEntityDescriptor); - } - - @Override - public String generateSelectVersionString() { - String sql = super.generateSelectVersionString(); - return parameters().process( sql ); + public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session) { + return super.determineLoaderToUse( session ); } @Override - protected void validateGenerator() { - if ( super.getGenerator() instanceof IdentityGenerator ) { - throw new MappingException( "Cannot use identity column key generation with mapping for: " + getEntityName() ); - } + public NaturalIdMapping generateNaturalIdMapping(MappingModelCreationProcess creationProcess, PersistentClass bootEntityDescriptor) { + return ReactiveAbstractEntityPersister.super.generateNaturalIdMapping(creationProcess, bootEntityDescriptor); } @Override @@ -183,18 +173,15 @@ public DomainResult createDomainResult( } @Override - public Generator getGenerator() throws HibernateException { - return reactiveDelegate.reactive( super.getGenerator() ); - } - - @Override - public String[][] getLazyPropertyColumnAliases() { - return super.getLazyPropertyColumnAliases(); + protected void validateGenerator() { + if ( super.getGenerator() instanceof IdentityGenerator) { + throw new MappingException( "Cannot use identity column key generation with mapping for: " + getEntityName() ); + } } @Override - public boolean check(int rows, Object id, int tableNumber, Expectation expectation, PreparedStatement statement, String sql) throws HibernateException { - return super.check(rows, id, tableNumber, expectation, statement, sql); + public Generator getGenerator() throws HibernateException { + return reactiveDelegate.reactive( super.getGenerator() ); } @Override @@ -291,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 ); } @@ -301,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 ); } @@ -311,23 +298,17 @@ 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 ); } @Override public CompletionStage insertReactive(Object id, Object[] fields, Object entity, SharedSessionContractImplementor session) { - return insertReactive( id, fields, entity, session, true ); - } - - @Override - public CompletionStage insertReactive(Object id, Object[] fields, Object entity, SharedSessionContractImplementor session, boolean isIdentityInsert) { - return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() ) - .coordinateReactiveInsert( entity, id, fields, session, isIdentityInsert ); + return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() ).coordinateReactiveInsert( entity, id, fields, session, false ); } @Override @@ -382,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 ); } @@ -390,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/ReactiveInsertCoordinatorStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveInsertCoordinatorStandard.java index 96f5813d4..3fa811b99 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveInsertCoordinatorStandard.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveInsertCoordinatorStandard.java @@ -69,10 +69,7 @@ public ReactiveInsertCoordinatorStandard(AbstractEntityPersister entityPersister batchKey = null; } else { - batchKey = new BasicBatchKey( - entityPersister.getEntityName() + "#INSERT", - null - ); + batchKey = new BasicBatchKey( entityPersister.getEntityName() + "#INSERT" ); } if ( entityPersister.getEntityMetamodel().isDynamicInsert() ) { @@ -193,7 +190,7 @@ protected CompletionStage decomposeForReactiveInsert( mutationGroup.forEachOperation( (position, jdbcOperation) -> { if ( id == null ) { - assert entityPersister().getIdentityInsertDelegate() != null; + assert entityPersister().getInsertDelegate() != null; } else { final EntityTableMapping tableDetails = (EntityTableMapping) jdbcOperation.getTableDetails(); @@ -438,14 +435,12 @@ protected void breakDownJdbcValue( final String tableName = tableDetails.getTableName(); tableDetails.getKeyMapping().breakDownKeyJdbcValues( id, - (jdbcValue, columnMapping) -> { - jdbcValueBindings.bindValue( - jdbcValue, - tableName, - columnMapping.getColumnName(), - ParameterUsage.SET - ); - }, + (jdbcValue, columnMapping) -> jdbcValueBindings.bindValue( + jdbcValue, + tableName, + columnMapping.getColumnName(), + ParameterUsage.SET + ), session ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveMergeCoordinator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveMergeCoordinator.java index 815842ea8..aa3165b25 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveMergeCoordinator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveMergeCoordinator.java @@ -7,7 +7,7 @@ import org.hibernate.engine.jdbc.batch.spi.BatchKey; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.persister.entity.AbstractEntityPersister; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.mutation.EntityTableMapping; import org.hibernate.sql.model.MutationOperation; import org.hibernate.sql.model.MutationOperationGroup; @@ -20,7 +20,7 @@ */ public class ReactiveMergeCoordinator extends ReactiveUpdateCoordinatorStandard { public ReactiveMergeCoordinator( - AbstractEntityPersister entityPersister, + EntityPersister entityPersister, SessionFactoryImplementor factory, MutationOperationGroup staticUpdateGroup, BatchKey batchKey, 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 d3237cf72..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 @@ -7,7 +7,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; - import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; @@ -22,7 +21,7 @@ import org.hibernate.generator.values.GeneratedValues; import org.hibernate.metamodel.mapping.EntityVersionMapping; import org.hibernate.metamodel.mapping.SingularAttributeMapping; -import org.hibernate.persister.entity.AbstractEntityPersister; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.mutation.AttributeAnalysis; import org.hibernate.persister.entity.mutation.EntityTableMapping; import org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard; @@ -31,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; @@ -47,7 +46,7 @@ public class ReactiveUpdateCoordinatorStandard extends UpdateCoordinatorStandard private CompletableFuture updateResultStage; public ReactiveUpdateCoordinatorStandard( - AbstractEntityPersister entityPersister, + EntityPersister entityPersister, SessionFactoryImplementor factory, MutationOperationGroup staticUpdateGroup, BatchKey batchKey, @@ -107,8 +106,8 @@ public CompletionStage reactiveUpdate( return updateResultStage; } - CompletionStage s = voidFuture(); - return s.thenCompose( v -> reactivePreUpdateInMemoryValueGeneration(entity, values, session) ) + return voidFuture() + .thenCompose( v -> reactivePreUpdateInMemoryValueGeneration( entity, values, session ) ) .thenCompose( preUpdateGeneratedAttributeIndexes -> { final int[] dirtyAttributeIndexes = dirtyAttributeIndexes( incomingDirtyAttributeIndexes, preUpdateGeneratedAttributeIndexes ); @@ -129,7 +128,7 @@ else if ( !isModifiableEntity( entry ) ) { } else if ( dirtyAttributeIndexes != null && entityPersister().hasUninitializedLazyProperties( entity ) - && entityPersister().hasLazyDirtyFields( dirtyAttributeIndexes ) ) { + && hasLazyDirtyFields( entityPersister(), dirtyAttributeIndexes ) ) { // we have an entity with dirty lazy attributes. we need to use dynamic // delete and add the dirty, lazy attributes plus the non-lazy attributes forceDynamicUpdate = true; @@ -194,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 35c3980e0..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,14 +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; -import static java.util.stream.Collectors.toList; - /** * A pool of reactive connections backed by a Vert.x {@link Pool}. * The {@code Pool} itself is backed by an instance of {@link Vertx} @@ -128,7 +128,7 @@ public void injectServices(ServiceRegistryImplementor serviceRegistry) { } @Override - public void configure(Map configuration) { + public void configure(Map configuration) { uri = jdbcUrl( configuration ); } @@ -190,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. @@ -199,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 ); } } @@ -223,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 ); @@ -247,7 +247,7 @@ private Driver findDriver(URI uri, ServiceConfigurationError originalError) { if ( selected.size() > 1 ) { List driverClasses = selected.stream() .map( driver -> driver.getClass().getCanonicalName() ) - .collect( toList() ); + .toList(); throw new ConfigurationException( "Multiple drivers found matching for URI scheme \"" + scheme + "\". Please, pick one: " + driverClasses, originalError ); } return selected.get( 0 ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/DefaultSqlClientPoolConfiguration.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/DefaultSqlClientPoolConfiguration.java index b60600bb8..e0613b78c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/DefaultSqlClientPoolConfiguration.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/DefaultSqlClientPoolConfiguration.java @@ -232,15 +232,15 @@ private int oraclePort(URI uri) { if ( s.indexOf( '/' ) != -1 ) { // Example: 1234/ s = s.substring( 0, s.indexOf( '/' ) ); - return Integer.valueOf( s ); + return Integer.parseInt( s ); } if ( s.indexOf( '?' ) != -1 ) { // Example: 1234?param=value s = s.substring( 0, s.indexOf( '?' ) ); - return Integer.valueOf( s ); + return Integer.parseInt( s ); } // Example: 1234 - return Integer.valueOf( s ); + return Integer.parseInt( s ); } return -1; } @@ -330,7 +330,7 @@ private int extractPort(URI uri) { if ( startOfPort == -1 ) { return -1; } - return Integer.valueOf( hostPortString.substring( startOfPort + 1 ) ); + return Integer.parseInt( hostPortString.substring( startOfPort + 1 ) ); } private String findHost(URI uri, String scheme) { @@ -348,7 +348,7 @@ private String findHost(URI uri, String scheme) { } private int findPort(URI uri, String scheme) { - int port = -1; + int port; if ( "oracle".equals( scheme ) ) { port = oraclePort( uri ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/ExternalSqlClientPool.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/ExternalSqlClientPool.java index 602ecd7b0..86067882a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/ExternalSqlClientPool.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/ExternalSqlClientPool.java @@ -59,7 +59,7 @@ public final class ExternalSqlClientPool extends SqlClientPool { private final Pool pool; private final SqlStatementLogger sqlStatementLogger; - private SqlExceptionHelper sqlExceptionHelper; + private final SqlExceptionHelper sqlExceptionHelper; public ExternalSqlClientPool(Pool pool, SqlStatementLogger sqlStatementLogger, SqlExceptionHelper sqlExceptionHelper) { this.pool = pool; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/Parameters.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/Parameters.java index bf9ddfccb..d30826846 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/Parameters.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/Parameters.java @@ -9,7 +9,6 @@ import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.DialectDelegateWrapper; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.dialect.SQLServerDialect; @@ -40,9 +39,6 @@ protected Parameters(String paramPrefix) { } public static Parameters instance(Dialect dialect) { - if ( dialect instanceof DialectDelegateWrapper ) { - dialect = ( (DialectDelegateWrapper) dialect ).getWrappedDialect(); - } if ( dialect instanceof PostgreSQLDialect || dialect instanceof CockroachDialect ) { return PostgresParameters.INSTANCE; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/ReactiveConnectionPoolInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/ReactiveConnectionPoolInitiator.java index 15c517acc..cf57e94f0 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/ReactiveConnectionPoolInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/ReactiveConnectionPoolInitiator.java @@ -19,10 +19,10 @@ import java.util.Map; /** - * A Hibernate {@link StandardServiceInitiator service initiator} that + * A Hibernate {@linkplain StandardServiceInitiator service initiator} that * integrates our {@link ReactiveConnectionPool}. By default, the pool - * implementation is {@link DefaultSqlClientPool}. A custom implementation may - * be specified via {@link Settings#SQL_CLIENT_POOL}. + * implementation is {@link DefaultSqlClientPool}. A custom implementation + * may be specified via {@link Settings#SQL_CLIENT_POOL}. * * @see ReactiveConnectionPool * @see DefaultSqlClientPool diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientConnection.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientConnection.java index 2b5fd2c3e..3e8647f6c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientConnection.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientConnection.java @@ -149,8 +149,7 @@ private T convertException(T rows, String sql, Throwable sqlException) { if ( sqlException == null ) { return rows; } - if ( sqlException instanceof DatabaseException ) { - DatabaseException de = (DatabaseException) sqlException; + if ( sqlException instanceof DatabaseException de ) { sqlException = sqlExceptionHelper .convert( new SQLException( de.getMessage(), de.getSqlState(), de.getErrorCode() ), "error executing SQL statement", sql ); } @@ -287,8 +286,12 @@ private SqlConnection client() { @Override public CompletionStage beginTransaction() { + if ( transaction != null ) { + throw new IllegalStateException( "Can't begin a new transaction as an active transaction is already associated to this connection" ); + } return connection.begin() .onSuccess( tx -> LOG.tracef( "Transaction started: %s", tx ) ) + .onFailure( v -> LOG.errorf( "Failed to start a transaction: %s", transaction ) ) .toCompletionStage() .thenAccept( this::setTransaction ); } @@ -297,6 +300,7 @@ public CompletionStage beginTransaction() { public CompletionStage commitTransaction() { return transaction.commit() .onSuccess( v -> LOG.tracef( "Transaction committed: %s", transaction ) ) + .onFailure( v -> LOG.errorf( "Failed to commit transaction: %s", transaction ) ) .toCompletionStage() .whenComplete( this::clearTransaction ); } @@ -304,6 +308,7 @@ public CompletionStage commitTransaction() { @Override public CompletionStage rollbackTransaction() { return transaction.rollback() + .onFailure( v -> LOG.errorf( "Failed to rollback transaction: %s", transaction ) ) .onSuccess( v -> LOG.tracef( "Transaction rolled back: %s", transaction ) ) .toCompletionStage() .whenComplete( this::clearTransaction ); @@ -311,8 +316,12 @@ public CompletionStage rollbackTransaction() { @Override public CompletionStage close() { + if ( transaction != null ) { + throw new IllegalStateException( "Connection being closed with a live transaction associated to it" ); + } return connection.close() .onSuccess( event -> LOG.tracef( "Connection closed: %s", connection ) ) + .onFailure( v -> LOG.errorf( "Failed to close a connection: %s", connection ) ) .toCompletionStage(); } @@ -358,6 +367,7 @@ private void setTransaction(Transaction tx) { } private void clearTransaction(Void v, Throwable x) { + LOG.tracef( "Clearing current transaction instance from connection: %s", transaction ); transaction = null; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientPoolConfigurationInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientPoolConfigurationInitiator.java index e23656d21..1c12cc8b6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientPoolConfigurationInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientPoolConfigurationInitiator.java @@ -16,7 +16,7 @@ import java.util.Map; /** - * A Hibernate {@link StandardServiceInitiator service initiator} that + * A Hibernate {@linkplain StandardServiceInitiator service initiator} that * allows the user to define their own {@link SqlClientPoolConfiguration} * strategy. */ diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/ReactivePersistenceProvider.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/ReactivePersistenceProvider.java index df25197b2..de4f1db37 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/ReactivePersistenceProvider.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/ReactivePersistenceProvider.java @@ -6,15 +6,16 @@ package org.hibernate.reactive.provider; import java.lang.invoke.MethodHandles; +import java.net.URL; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; -import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor; import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor; -import org.hibernate.jpa.boot.internal.PersistenceXmlParser; import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; +import org.hibernate.jpa.boot.spi.PersistenceXmlParser; import org.hibernate.jpa.internal.util.PersistenceUtilHelper; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; @@ -22,6 +23,8 @@ import org.hibernate.reactive.provider.impl.ReactiveProviderChecker; import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.PersistenceConfiguration; +import jakarta.persistence.PersistenceException; import jakarta.persistence.spi.LoadState; import jakarta.persistence.spi.PersistenceProvider; import jakarta.persistence.spi.PersistenceUnitInfo; @@ -38,6 +41,12 @@ public class ReactivePersistenceProvider implements PersistenceProvider { private final PersistenceUtilHelper.MetadataCache cache = new PersistenceUtilHelper.MetadataCache(); + @Override + public EntityManagerFactory createEntityManagerFactory(PersistenceConfiguration persistenceConfiguration) { + // Same as ORM + throw log.notYetImplemented(); + } + /** * {@inheritDoc} *

    @@ -55,21 +64,11 @@ public EntityManagerFactory createEntityManagerFactory(String persistenceUnitNam return builder.build(); } - protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull( - String persistenceUnitName, - Map properties) { - log.tracef( - "Attempting to obtain correct EntityManagerFactoryBuilder for persistenceUnitName : %s", - persistenceUnitName - ); + protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String persistenceUnitName, Map properties) { + log.tracef( "Attempting to obtain correct EntityManagerFactoryBuilder for persistenceUnitName : %s", persistenceUnitName ); - final List units; - try { - units = PersistenceXmlParser.locatePersistenceUnits( properties ); - } - catch (Exception e) { - throw log.unableToLocatePersistenceUnits( e ); - } + final Map integration = immutable( properties ); + final Collection units = locatePersistenceUnits( integration ); log.debugf( "Located and parsed %s persistence units; checking each", units.size() ); @@ -78,7 +77,7 @@ protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull( throw log.noNameProvidedAndMultiplePersistenceUnitsFound(); } - for ( ParsedPersistenceXmlDescriptor persistenceUnit : units ) { + for ( PersistenceUnitDescriptor persistenceUnit : units ) { log.debugf( "Checking persistence-unit [name=%s, explicit-provider=%s] against incoming persistence unit name [%s]", persistenceUnit.getName(), @@ -106,6 +105,24 @@ protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull( return null; } + // Check before changing: may be overridden in Quarkus + // This is basically a copy and paste of the method in HibernatePersistenceProvider + protected Collection locatePersistenceUnits(Map integration) { + try { + var parser = PersistenceXmlParser.create( integration, null, null ); + final List xmlUrls = parser.getClassLoaderService().locateResources( "META-INF/persistence.xml" ); + if ( xmlUrls.isEmpty() ) { + log.unableToFindPersistenceXmlInClasspath(); + return List.of(); + } + return parser.parse( xmlUrls ).values(); + } + catch (Exception e) { + log.debug( "Unable to locate persistence units", e ); + throw new PersistenceException( "Unable to locate persistence units", e ); + } + } + private static Map immutable(Map properties) { return properties == null ? Collections.emptyMap() : Collections.unmodifiableMap( properties ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveEntityManagerFactoryBuilder.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveEntityManagerFactoryBuilder.java index 9752df913..9d243d741 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveEntityManagerFactoryBuilder.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveEntityManagerFactoryBuilder.java @@ -6,7 +6,6 @@ package org.hibernate.reactive.provider.impl; import org.hibernate.boot.internal.MetadataImpl; -import org.hibernate.boot.internal.SessionFactoryBuilderImpl; import org.hibernate.boot.internal.SessionFactoryOptionsBuilder; import org.hibernate.boot.registry.BootstrapServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; @@ -20,6 +19,8 @@ import org.hibernate.reactive.provider.service.ReactiveSessionFactoryBuilder; import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.PersistenceException; + import java.util.Map; /** @@ -44,33 +45,24 @@ protected StandardServiceRegistryBuilder getStandardServiceRegistryBuilder(Boots @Override public EntityManagerFactory build() { final MetadataImplementor metadata = metadata(); - SessionFactoryOptionsBuilder optionsBuilder = new SessionFactoryOptionsBuilder( metadata.getMetadataBuildingOptions().getServiceRegistry(), - ( (MetadataImpl) metadata).getBootstrapContext() + ( (MetadataImpl) metadata ).getBootstrapContext() ); optionsBuilder.enableCollectionInDefaultFetchGroup(true); - // FIXME [ORM-6]: This method does not exists anymore -// optionsBuilder.applyMultiTableBulkIdStrategy( new ReactiveBulkIdStrategy( metadata ) ); int batchSize = ConfigurationHelper.getInt( Settings.STATEMENT_BATCH_SIZE, getConfigurationValues(), 0 ); optionsBuilder.applyJdbcBatchSize(batchSize); - final SessionFactoryBuilderImpl defaultBuilder = new SessionFactoryBuilderImpl( + final SessionFactoryBuilderImplementor reactiveSessionFactoryBuilder = new ReactiveSessionFactoryBuilder( metadata, - optionsBuilder, - metadata.getTypeConfiguration() - .getMetadataBuildingContext() - .getBootstrapContext() - ); - final SessionFactoryBuilderImplementor reactiveSessionFactoryBuilder = new ReactiveSessionFactoryBuilder( metadata, defaultBuilder ); - populateSfBuilder( reactiveSessionFactoryBuilder, getStandardServiceRegistry() ); + (SessionFactoryBuilderImplementor) populateSessionFactoryBuilder() + ); try { return reactiveSessionFactoryBuilder.build(); } catch (Exception e) { - throw persistenceException( "Unable to build Hibernate SessionFactory", e ); + throw new PersistenceException( "Unable to build Hibernate SessionFactory ", e ); } } - } 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 0d1876320..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 @@ -25,11 +25,10 @@ import org.hibernate.reactive.event.impl.DefaultReactivePersistOnFlushEventListener; import org.hibernate.reactive.event.impl.DefaultReactivePostLoadEventListener; import org.hibernate.reactive.event.impl.DefaultReactiveRefreshEventListener; -import org.hibernate.reactive.event.impl.DefaultReactiveResolveNaturalIdEventListener; 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; -import org.hibernate.service.spi.SessionFactoryServiceRegistry; /** * Integrates Hibernate Reactive with Hibernate ORM by @@ -48,13 +47,9 @@ public void integrate(Metadata metadata, BootstrapContext bootstrapContext, Sess attachEventContextManagingListenersIfRequired( sessionFactory.getServiceRegistry() ); } - @Override - public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) { - } - 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 ); @@ -70,7 +65,6 @@ private void attachEventContextManagingListenersIfRequired(ServiceRegistry servi eventListenerRegistry.getEventListenerGroup( EventType.LOAD ).appendListener( new DefaultReactiveLoadEventListener() ); eventListenerRegistry.getEventListenerGroup( EventType.INIT_COLLECTION ).appendListener( new DefaultReactiveInitializeCollectionEventListener() ); eventListenerRegistry.getEventListenerGroup( EventType.POST_LOAD ).appendListener( new DefaultReactivePostLoadEventListener() ); - eventListenerRegistry.getEventListenerGroup( EventType.RESOLVE_NATURAL_ID ).appendListener( new DefaultReactiveResolveNaturalIdEventListener() ); } } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveServiceInitiators.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveServiceInitiators.java index f385b8064..2343a9644 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveServiceInitiators.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveServiceInitiators.java @@ -26,7 +26,6 @@ import org.hibernate.property.access.internal.PropertyAccessStrategyResolverInitiator; import org.hibernate.reactive.context.impl.VertxContextInitiator; import org.hibernate.reactive.engine.jdbc.mutation.internal.ReactiveMutationExecutorServiceInitiator; -import org.hibernate.reactive.id.factory.spi.ReactiveIdentifierGeneratorFactoryInitiator; import org.hibernate.reactive.loader.ast.internal.ReactiveBatchLoaderFactoryInitiator; import org.hibernate.reactive.pool.impl.ReactiveConnectionPoolInitiator; import org.hibernate.reactive.pool.impl.SqlClientPoolConfigurationInitiator; @@ -70,9 +69,6 @@ private static List> buildInitialServiceInitiatorLis // Custom for Hibernate Reactive: SessionFactoryBuilderService serviceInitiators.add( ReactiveSessionFactoryBuilderInitiator.INSTANCE ); - // Custom for Hibernate Reactive: IdentifierGeneratorFactory - serviceInitiators.add( ReactiveIdentifierGeneratorFactoryInitiator.INSTANCE); - // [standard] BytecodeProvider serviceInitiators.add( BytecodeProviderInitiator.INSTANCE ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveTypeContributor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveTypeContributor.java index e278bd393..4fcd4a46c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveTypeContributor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveTypeContributor.java @@ -21,14 +21,16 @@ import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.DialectDelegateWrapper; +import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor; import org.hibernate.reactive.type.descriptor.jdbc.ReactiveArrayJdbcTypeConstructor; +import org.hibernate.reactive.type.descriptor.jdbc.ReactiveJsonArrayJdbcTypeConstructor; import org.hibernate.reactive.type.descriptor.jdbc.ReactiveJsonJdbcType; +import org.hibernate.reactive.type.descriptor.jdbc.ReactiveXmlArrayJdbcTypeConstructor; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.AbstractSingleColumnStandardBasicType; import org.hibernate.type.BasicTypeRegistry; @@ -47,6 +49,7 @@ import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.descriptor.jdbc.ObjectJdbcType; +import org.hibernate.type.descriptor.jdbc.ObjectNullAsBinaryTypeJdbcType; import org.hibernate.type.descriptor.jdbc.TimestampJdbcType; import org.hibernate.type.descriptor.jdbc.TimestampUtcAsJdbcTimestampJdbcType; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; @@ -85,8 +88,17 @@ private void registerReactiveChanges(TypeContributions typeContributions, Servic JdbcTypeRegistry jdbcTypeRegistry = typeConfiguration.getJdbcTypeRegistry(); jdbcTypeRegistry.addTypeConstructor( ReactiveArrayJdbcTypeConstructor.INSTANCE ); + jdbcTypeRegistry.addTypeConstructor( ReactiveXmlArrayJdbcTypeConstructor.INSTANCE ); jdbcTypeRegistry.addDescriptor( SqlTypes.JSON, ReactiveJsonJdbcType.INSTANCE ); + if ( !( dialect instanceof MariaDBDialect ) && dialect instanceof MySQLDialect ) { + jdbcTypeRegistry.addTypeConstructor( ReactiveJsonArrayJdbcTypeConstructor.INSTANCE ); + } + + if ( dialect instanceof MySQLDialect ) { + jdbcTypeRegistry.addDescriptor( ObjectNullAsBinaryTypeJdbcType.INSTANCE ); + } + if ( dialect instanceof MySQLDialect || dialect instanceof DB2Dialect || dialect instanceof OracleDialect ) { jdbcTypeRegistry.addDescriptor( TimestampAsLocalDateTimeJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( TimestampUtcAsLocalDateTimeJdbcType.INSTANCE ); @@ -101,7 +113,7 @@ private void registerReactiveChanges(TypeContributions typeContributions, Servic } private Dialect dialect(ServiceRegistry serviceRegistry) { - return DialectDelegateWrapper.extractRealDialect( serviceRegistry.getService( JdbcEnvironment.class ).getDialect() ); + return serviceRegistry.getService( JdbcEnvironment.class ).getDialect(); } /** @@ -116,7 +128,8 @@ private static class TimestampAsLocalDateTimeJdbcType extends TimestampJdbcType public ValueBinder getBinder(final JavaType javaType) { return new BasicBinder<>( javaType, this ) { @Override - protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { final Timestamp timestamp = javaType.unwrap( value, Timestamp.class, options ); if ( value instanceof Calendar ) { ( (PreparedStatementAdaptor) st ) @@ -124,7 +137,12 @@ protected void doBind(PreparedStatement st, X value, int index, WrapperOptions o } else if ( options.getJdbcTimeZone() != null ) { ( (PreparedStatementAdaptor) st ) - .setTimestamp( index, timestamp, Calendar.getInstance( options.getJdbcTimeZone() ), ZonedDateTime::toLocalDateTime ); + .setTimestamp( + index, + timestamp, + Calendar.getInstance( options.getJdbcTimeZone() ), + ZonedDateTime::toLocalDateTime + ); } else { st.setTimestamp( index, timestamp ); @@ -141,7 +159,12 @@ protected void doBind(CallableStatement st, X value, String name, WrapperOptions } else if ( options.getJdbcTimeZone() != null ) { ( (PreparedStatementAdaptor) st ) - .setTimestamp( name, timestamp, Calendar.getInstance( options.getJdbcTimeZone() ), ZonedDateTime::toLocalDateTime ); + .setTimestamp( + name, + timestamp, + Calendar.getInstance( options.getJdbcTimeZone() ), + ZonedDateTime::toLocalDateTime + ); } else { st.setTimestamp( name, timestamp ); @@ -165,16 +188,25 @@ private static class TimestampUtcAsLocalDateTimeJdbcType extends TimestampUtcAsJ public ValueBinder getBinder(final JavaType javaType) { return new BasicBinder<>( javaType, this ) { @Override - protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) { final Instant instant = javaType.unwrap( value, Instant.class, options ); - ( (PreparedStatementAdaptor) st).setTimestamp( index, Timestamp.from( instant ), UTC_CALENDAR, ZonedDateTime::toLocalDateTime ); + ( (PreparedStatementAdaptor) st ).setTimestamp( + index, + Timestamp.from( instant ), + UTC_CALENDAR, + ZonedDateTime::toLocalDateTime + ); } @Override - protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) - throws SQLException { + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) { final Instant instant = javaType.unwrap( value, Instant.class, options ); - ( (PreparedStatementAdaptor) st).setTimestamp( name, Timestamp.from( instant ), UTC_CALENDAR, ZonedDateTime::toLocalDateTime ); + ( (PreparedStatementAdaptor) st ).setTimestamp( + name, + Timestamp.from( instant ), + UTC_CALENDAR, + ZonedDateTime::toLocalDateTime + ); } }; } @@ -203,7 +235,12 @@ public String getCastTypeName(JdbcType jdbcType, JavaType javaType) { } @Override - public String getCastTypeName(JdbcType jdbcType, JavaType javaType, Long length, Integer precision, Integer scale) { + public String getCastTypeName( + JdbcType jdbcType, + JavaType javaType, + Long length, + Integer precision, + Integer scale) { return "json"; } } @@ -253,7 +290,7 @@ public String getName() { @Override public String[] getRegistrationKeys() { - return new String[] { "json", JsonObject.class.getName() }; + return new String[] {"json", JsonObject.class.getName()}; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/AbstractReactiveInformationSchemaBasedExtractorImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/AbstractReactiveInformationSchemaBasedExtractorImpl.java index 8ba013cb5..e1846d1bf 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/AbstractReactiveInformationSchemaBasedExtractorImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/AbstractReactiveInformationSchemaBasedExtractorImpl.java @@ -12,14 +12,12 @@ import java.util.List; import java.util.StringTokenizer; -import org.hibernate.boot.model.TruthValue; import org.hibernate.boot.model.naming.DatabaseIdentifier; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.tool.schema.extract.internal.AbstractInformationExtractorImpl; import org.hibernate.tool.schema.extract.internal.ColumnInformationImpl; -import org.hibernate.tool.schema.extract.spi.ColumnInformation; import org.hibernate.tool.schema.extract.spi.ExtractionContext; import org.hibernate.tool.schema.extract.spi.InformationExtractor; import org.hibernate.tool.schema.extract.spi.TableInformation; @@ -194,15 +192,19 @@ protected boolean appendClauseAndParameterIfNotNullOrEmpty( StringBuilder sb, List parameters) { - if ( parameter != null && ( ! String.class.isInstance( parameter ) || ! ( (String) parameter ).isEmpty() ) ) { + if ( parameter != null && ( !( parameter instanceof String string ) || !string.isEmpty() ) ) { parameters.add( parameter ); sb.append( clause ); - sb.append( "?"); + sb.append( parameterMarker( parameters.size() ) ); return true; } return false; } + protected String parameterMarker(int pos) { + return "?"; + } + @Override protected T processTableResultSet( String catalog, @@ -349,7 +351,7 @@ protected String getDatabaseCatalogColumnName(String catalogColumnName, String s * used for storing the schema name, or , if there * is no valid column containing the schema name. *

    - * MySQL, for example, does not have a valid column in + * MySQL, for example, does not have a valid column * in the information_schema to store the schema name. * (@see MySqlReactiveInformationExtractorImpl) * @@ -364,9 +366,9 @@ protected String getDatabaseSchemaColumnName(String catalogColumnName, String sc } @Override - protected void addExtractedColumnInformation(TableInformation tableInformation, ResultSet resultSet) throws SQLException { + protected ColumnInformationImpl columnInformation(TableInformation tableInformation, ResultSet resultSet) throws SQLException { final String typeName = new StringTokenizer( resultSet.getString( getResultSetTypeNameLabel() ), "() " ).nextToken(); - final ColumnInformation columnInformation = new ColumnInformationImpl( + return new ColumnInformationImpl( tableInformation, DatabaseIdentifier.toIdentifier( resultSet.getString( getResultSetColumnNameLabel() ) ), dataTypeCode( typeName ), @@ -375,7 +377,6 @@ protected void addExtractedColumnInformation(TableInformation tableInformation, resultSet.getInt( getResultSetDecimalDigitsLabel() ), interpretTruthValue( resultSet.getString( getResultSetIsNullableLabel() ) ) ); - tableInformation.addColumn( columnInformation ); } /** @@ -384,14 +385,4 @@ protected void addExtractedColumnInformation(TableInformation tableInformation, protected int dataTypeCode(String typeName) { return 0; } - - private TruthValue interpretTruthValue(String nullable) { - if ( "yes".equalsIgnoreCase( nullable ) ) { - return TruthValue.TRUE; - } - else if ( "no".equalsIgnoreCase( nullable ) ) { - return TruthValue.FALSE; - } - return TruthValue.UNKNOWN; - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/MySqlReactiveInformationExtractorImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/MySqlReactiveInformationExtractorImpl.java index 6cc016dda..0504e44ee 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/MySqlReactiveInformationExtractorImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/MySqlReactiveInformationExtractorImpl.java @@ -46,11 +46,6 @@ protected int dataTypeCode(String typeName) { } } - @Override - protected String getResultSetTableTypesPhysicalTableConstant() { - return "BASE TABLE"; - } - protected String getDatabaseCatalogColumnName(String catalogColumnName, String schemaColumnName ) { return schemaColumnName; } @@ -64,7 +59,7 @@ protected T processPrimaryKeysResultSet( String catalogFilter, String schemaFilter, Identifier tableName, - ExtractionContext.ResultSetProcessor processor) throws SQLException { + ExtractionContext.ResultSetProcessor processor) { // This functionality is not used by ORM. throw new UnsupportedOperationException(); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NativeParametersHandling.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NativeParametersHandling.java index 1db528258..fb151a3aa 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NativeParametersHandling.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NativeParametersHandling.java @@ -11,7 +11,6 @@ import org.hibernate.boot.registry.StandardServiceInitiator; import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.DialectDelegateWrapper; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.dialect.SQLServerDialect; import org.hibernate.engine.jdbc.spi.JdbcServices; @@ -39,8 +38,7 @@ public class NativeParametersHandling implements StandardServiceInitiator configurationValues, ServiceRegistryImplementor registry) { final Dialect dialect = registry.getService( JdbcServices.class ).getDialect(); - final Dialect realDialect = DialectDelegateWrapper.extractRealDialect( dialect ); - final ParameterMarkerStrategy renderer = recommendRendered( realDialect ); + final ParameterMarkerStrategy renderer = recommendRendered( dialect ); LOG.debugf( "Initializing service JdbcParameterRenderer with implementation: %s", renderer.getClass() ); return renderer; } 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/NoJdbcConnectionProviderInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProviderInitiator.java index 6ecdffe2f..a57f72b16 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProviderInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProviderInitiator.java @@ -14,8 +14,8 @@ import java.util.Map; /** - * A Hibernate {@link StandardServiceInitiator service initiator} that - * wraps the Hibernate {@link ConnectionProvider} in an instance of + * A Hibernate {@linkplain StandardServiceInitiator service initiator} + * that wraps the Hibernate {@link ConnectionProvider} in an instance of * {@link NoJdbcConnectionProvider}. * * @author Gavin King diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcEnvironmentInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcEnvironmentInitiator.java index deafed172..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,34 +5,39 @@ */ 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.engine.jdbc.env.internal.ReactiveJdbcEnvironment; 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; /** - * A Hibernate {@link StandardServiceInitiator service initiator} that - * provides an implementation of {@link JdbcEnvironment} that infers + * A Hibernate {@linkplain StandardServiceInitiator service initiator} + * that provides an implementation of {@link JdbcEnvironment} that infers * the Hibernate {@link org.hibernate.dialect.Dialect} from the JDBC URL. */ -public class NoJdbcEnvironmentInitiator implements StandardServiceInitiator { +public class NoJdbcEnvironmentInitiator extends JdbcEnvironmentInitiator + implements StandardServiceInitiator { public static final NoJdbcEnvironmentInitiator INSTANCE = new NoJdbcEnvironmentInitiator(); @@ -42,15 +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 ); - Dialect dialect = dialectFactory.buildDialect( configurationValues, null ); - return new ReactiveJdbcEnvironment( registry, dialect ); - } + 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 ReactiveJdbcEnvironment( 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 { @@ -63,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() ) { @@ -97,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 ); } @@ -123,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; } } @@ -180,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(); @@ -197,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/provider/service/NoJdbcMultiTenantConnectionProviderInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcMultiTenantConnectionProviderInitiator.java index 60a6cf599..50e614e2e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcMultiTenantConnectionProviderInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcMultiTenantConnectionProviderInitiator.java @@ -13,8 +13,8 @@ import org.hibernate.service.spi.ServiceRegistryImplementor; /** - * A Hibernate {@link StandardServiceInitiator service initiator} for - * {@link NoJdbcMultiTenantConnectionProvider}. + * A Hibernate {@linkplain StandardServiceInitiator service initiator} + * for {@link NoJdbcMultiTenantConnectionProvider}. * * @author Gavin King */ diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJtaPlatformInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJtaPlatformInitiator.java index 30add8dae..8fd12fa10 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJtaPlatformInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJtaPlatformInitiator.java @@ -13,7 +13,8 @@ import java.util.Map; /** - * A Hibernate {@link StandardServiceInitiator service initiator} for the non-configured form of JTA platform. + * A Hibernate {@linkplain StandardServiceInitiator service initiator} + * for the non-configured form of JTA platform. * * @see NoJtaPlatform */ diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java index 67852da79..d85b74d1e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java @@ -61,10 +61,15 @@ protected T processIndexInfoResultSet( appendClauseAndParameterIfNotNullOrEmpty( " and ui.table_owner = ", schema, sb, parameters ); appendClauseAndParameterIfNotNullOrEmpty( " and ui.table_name = ", table, sb, parameters ); - return getExtractionContext().getQueryResults( sb.toString(), parameters.toArray(), processor ); } + @Override + protected String parameterMarker(int pos) { + return ":" + pos; + } + + @Override protected T processImportedKeysResultSet( String catalog, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/PostgreSqlReactiveInformationExtractorImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/PostgreSqlReactiveInformationExtractorImpl.java index d6bcb00a3..62fff7ecc 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/PostgreSqlReactiveInformationExtractorImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/PostgreSqlReactiveInformationExtractorImpl.java @@ -32,17 +32,12 @@ public PostgreSqlReactiveInformationExtractorImpl(ExtractionContext extractionCo super( extractionContext ); } - @Override - protected String getResultSetTableTypesPhysicalTableConstant() { - return "BASE TABLE"; - } - @Override protected T processPrimaryKeysResultSet( String catalogFilter, String schemaFilter, Identifier tableName, - ExtractionContext.ResultSetProcessor processor) throws SQLException { + ExtractionContext.ResultSetProcessor processor) { // This functionality is not used by ORM. throw new UnsupportedOperationException(); } @@ -100,6 +95,11 @@ protected T processIndexInfoResultSet( ); } + @Override + protected String parameterMarker(int pos) { + return "$" + pos; + } + @Override protected T processImportedKeysResultSet( String catalog, @@ -135,7 +135,6 @@ protected T processImportedKeysResultSet( // No need to order by catalog since it is always null. sb.append( " order by pkn.nspname, pkc.relname, con.conname, pos.n" ); - return getExtractionContext().getQueryResults( sb.toString(), parameterValues.toArray(), processor ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveGenerationTarget.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveGenerationTarget.java index 0919a3358..1f89a0490 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveGenerationTarget.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveGenerationTarget.java @@ -19,7 +19,7 @@ import org.hibernate.reactive.pool.ReactiveConnectionPool; import org.hibernate.reactive.vertx.VertxInstance; import org.hibernate.service.ServiceRegistry; -import org.hibernate.tool.schema.internal.exec.GenerationTarget; +import org.hibernate.tool.schema.spi.GenerationTarget; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveImprovedExtractionContextImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveImprovedExtractionContextImpl.java index 362444a00..160c61c52 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveImprovedExtractionContextImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveImprovedExtractionContextImpl.java @@ -39,7 +39,6 @@ import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.reactive.pool.ReactiveConnectionPool; -import org.hibernate.reactive.pool.impl.Parameters; import org.hibernate.resource.transaction.spi.DdlTransactionIsolator; import org.hibernate.service.ServiceRegistry; import org.hibernate.tool.schema.internal.exec.ImprovedExtractionContextImpl; @@ -79,8 +78,7 @@ private ResultSet getQueryResultSet( String queryString, Object[] positionalParameters) { final Object[] parametersToUse = positionalParameters != null ? positionalParameters : new Object[0]; - final Parameters parametersDialectSpecific = Parameters.instance( getJdbcEnvironment().getDialect() ); - final String queryToUse = parametersDialectSpecific.process( queryString, parametersToUse.length ); + final String queryToUse = queryString; return connectionPool // DDL needs to run outside the current transaction. For example: // - increment on a table-based id generator should happen outside the current tx. diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveMarkerServiceInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveMarkerServiceInitiator.java index e7e92c270..46b4549d8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveMarkerServiceInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveMarkerServiceInitiator.java @@ -11,7 +11,8 @@ import org.hibernate.service.spi.ServiceRegistryImplementor; /** - * A Hibernate {@link StandardServiceInitiator service initiator} for {@link ReactiveMarkerService}. + * A Hibernate {@linkplain StandardServiceInitiator service initiator} for + * {@link ReactiveMarkerService}. */ public final class ReactiveMarkerServiceInitiator implements StandardServiceInitiator { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactivePersisterClassResolverInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactivePersisterClassResolverInitiator.java index 8e9670860..8edd7b4b1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactivePersisterClassResolverInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactivePersisterClassResolverInitiator.java @@ -12,8 +12,9 @@ import org.hibernate.service.spi.ServiceRegistryImplementor; /** - * A Hibernate {@link StandardServiceInitiator service initiator} that creates a {@link ReactivePersisterClassResolver} to register - * the persisters Hibernate Reactive needs. + * A Hibernate {@linkplain StandardServiceInitiator service initiator} + * that creates a {@link ReactivePersisterClassResolver} to register the + * persisters needed by Hibernate Reactive. */ public class ReactivePersisterClassResolverInitiator implements StandardServiceInitiator { public static final ReactivePersisterClassResolverInitiator INSTANCE = new ReactivePersisterClassResolverInitiator(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveSchemaManagementTool.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveSchemaManagementTool.java index 347848443..0d7dce19c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveSchemaManagementTool.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveSchemaManagementTool.java @@ -9,8 +9,6 @@ import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.DialectDelegateWrapper; -import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.PostgreSQLDialect; @@ -60,15 +58,11 @@ public ExtractionContext createExtractionContext( public InformationExtractor createInformationExtractor(ExtractionContext extractionContext) { Dialect dialect = extractionContext.getJdbcEnvironment().getDialect(); - //Allow for wrapped cases: - if ( dialect instanceof DialectDelegateWrapper ) { - dialect = ( (DialectDelegateWrapper) dialect ).getWrappedDialect(); - } //Now detect the kind of Dialect: if ( dialect instanceof PostgreSQLDialect || dialect instanceof CockroachDialect ) { return new PostgreSqlReactiveInformationExtractorImpl( extractionContext ); } - if ( dialect instanceof MySQLDialect || dialect instanceof MariaDBDialect ) { + if ( dialect instanceof MySQLDialect ) { return new MySqlReactiveInformationExtractorImpl( extractionContext ); } if ( dialect instanceof SQLServerDialect ) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveSessionFactoryBuilderInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveSessionFactoryBuilderInitiator.java index 768c36ec6..14805057f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveSessionFactoryBuilderInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveSessionFactoryBuilderInitiator.java @@ -12,9 +12,9 @@ import org.hibernate.service.spi.ServiceRegistryImplementor; /** - * A Hibernate {@link StandardServiceInitiator service initiator} that - * wraps the Hibernate {@link org.hibernate.engine.jdbc.connections.spi.ConnectionProvider} in an instance of - * {@link NoJdbcConnectionProvider}. + * A Hibernate {@linkplain StandardServiceInitiator service initiator} that wraps + * the Hibernate {@link org.hibernate.engine.jdbc.connections.spi.ConnectionProvider} + * in an instance of {@link NoJdbcConnectionProvider}. */ public class ReactiveSessionFactoryBuilderInitiator implements StandardServiceInitiator { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveValuesMappingProducerProvider.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveValuesMappingProducerProvider.java index 984271279..0f968bd9a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveValuesMappingProducerProvider.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveValuesMappingProducerProvider.java @@ -7,7 +7,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.results.ResultSetMapping; -import org.hibernate.query.results.ResultSetMappingImpl; +import org.hibernate.query.results.internal.ResultSetMappingImpl; import org.hibernate.reactive.sql.results.ReactiveResultSetMapping; import org.hibernate.reactive.sql.results.internal.ReactiveStandardValuesMappingProducer; import org.hibernate.sql.ast.tree.select.SelectStatement; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/SqlServerReactiveInformationExtractorImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/SqlServerReactiveInformationExtractorImpl.java index e2d277e2d..eea7fc477 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/SqlServerReactiveInformationExtractorImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/SqlServerReactiveInformationExtractorImpl.java @@ -93,6 +93,12 @@ protected T processTableResultSet( return getExtractionContext().getQueryResults( sb.toString(), parameterValues.toArray(), processor ); } + + @Override + protected String parameterMarker(int pos) { + return "@P" + pos; + } + @Override protected T processColumnsResultSet( String catalog, @@ -152,7 +158,7 @@ protected T processPrimaryKeysResultSet( String catalogFilter, String schemaFilter, Identifier tableName, - ExtractionContext.ResultSetProcessor processor) throws SQLException { + ExtractionContext.ResultSetProcessor processor) { throw new UnsupportedOperationException(); } 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 7c7588a7c..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 @@ -5,14 +5,12 @@ */ package org.hibernate.reactive.query; -import java.io.Serializable; import java.time.Instant; import java.util.Calendar; import java.util.Collection; 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; @@ -20,15 +18,10 @@ import jakarta.persistence.Parameter; import jakarta.persistence.TemporalType; +import jakarta.persistence.metamodel.Type; public interface ReactiveQueryImplementor extends ReactiveQuery { - void setOptionalId(Serializable id); - - void setOptionalEntityName(String entityName); - - void setOptionalObject(Object optionalObject); - QueryParameterBindings getParameterBindings(); @Override @@ -44,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); @@ -62,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); @@ -80,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); @@ -98,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); @@ -107,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); @@ -119,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); @@ -128,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); @@ -143,7 +136,7 @@

    ReactiveQueryImplementor setParameterList(

    ReactiveQueryImplementor setParameterList( QueryParameter

    parameter, Collection values, - BindableType

    type); + Type

    type); @Override

    ReactiveQueryImplementor setParameterList(QueryParameter

    parameter, P[] values); @@ -152,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 2b347b3de..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 @@ -119,17 +118,10 @@ default CompletionStage> getReactiveResultList() { ReactiveSelectionQuery setLockMode(String alias, LockMode lockMode); - @Deprecated - ReactiveSelectionQuery setAliasSpecificLockMode(String alias, LockMode lockMode); - ReactiveSelectionQuery setFollowOnLocking(boolean enable); void applyGraph(RootGraphImplementor graph, GraphSemantic semantic); - ReactiveSelectionQuery setOrder(List> orderList); - - ReactiveSelectionQuery setOrder(Order order); - ReactiveSelectionQuery enableFetchProfile(String profileName); @Override @@ -139,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); @@ -157,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); @@ -175,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); @@ -193,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); @@ -202,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); @@ -211,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); @@ -220,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); @@ -229,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); @@ -238,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 52d778e1a..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; @@ -22,6 +23,9 @@ import org.hibernate.reactive.query.sql.spi.ReactiveNamedNativeQueryMemento; import org.hibernate.reactive.query.sql.spi.ReactiveNamedSqmQueryMemento; +import jakarta.persistence.Query; +import jakarta.persistence.TypedQueryReference; + public class ReactiveNamedObjectRepositoryImpl implements NamedObjectRepository { private final NamedObjectRepository delegate; @@ -31,12 +35,27 @@ public ReactiveNamedObjectRepositoryImpl(NamedObjectRepository delegate) { } @Override - public NamedSqmQueryMemento getSqmQueryMemento(String queryName) { + public Map> getNamedQueries(Class resultType) { + return delegate.getNamedQueries( resultType ); + } + + @Override + public NamedSqmQueryMemento getSqmQueryMemento(String queryName) { return wrapSqmQueryMemento( delegate.getSqmQueryMemento( queryName ) ); } @Override - public void visitSqmQueryMementos(Consumer action) { + 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 ); } @@ -46,12 +65,12 @@ public void registerSqmQueryMemento(String name, NamedSqmQueryMemento descriptor } @Override - public NamedNativeQueryMemento getNativeQueryMemento(String queryName) { + public NamedNativeQueryMemento getNativeQueryMemento(String queryName) { return wrapNativeQueryMemento( delegate.getNativeQueryMemento( queryName ) ); } @Override - public void visitNativeQueryMementos(Consumer action) { + public void visitNativeQueryMementos(Consumer> action) { delegate.visitNativeQueryMementos( action ); } @@ -101,11 +120,11 @@ public void validateNamedQueries(QueryEngine queryEngine) { } @Override - public NamedQueryMemento resolve( + public NamedQueryMemento resolve( SessionFactoryImplementor sessionFactory, MetadataImplementor bootMetamodel, String registrationName) { - return wrap(delegate.resolve( sessionFactory, bootMetamodel, registrationName )); + return wrap( delegate.resolve( sessionFactory, bootMetamodel, registrationName ) ); } @Override @@ -118,17 +137,19 @@ public void close() { delegate.close(); } - private static NamedQueryMemento wrap(final NamedQueryMemento namedQueryMemento) { + private static NamedQueryMemento wrap(final NamedQueryMemento namedQueryMemento) { if ( namedQueryMemento == null ) { return null; - } else if( namedQueryMemento instanceof NamedSqmQueryMemento ) { - return wrapSqmQueryMemento( (NamedSqmQueryMemento) namedQueryMemento ); - } else { - return wrapNativeQueryMemento( (NamedNativeQueryMemento) namedQueryMemento ); + } + else if ( namedQueryMemento instanceof NamedSqmQueryMemento ) { + return wrapSqmQueryMemento( (NamedSqmQueryMemento) namedQueryMemento ); + } + else { + return wrapNativeQueryMemento( (NamedNativeQueryMemento) namedQueryMemento ); } } - private static NamedSqmQueryMemento wrapSqmQueryMemento(final NamedSqmQueryMemento sqmQueryMemento) { + private static NamedSqmQueryMemento wrapSqmQueryMemento(final NamedSqmQueryMemento sqmQueryMemento) { if ( sqmQueryMemento == null ) { return null; } @@ -141,7 +162,7 @@ else if ( sqmQueryMemento instanceof ReactiveNamedSqmQueryMemento ) { } } - private static NamedNativeQueryMemento wrapNativeQueryMemento(final NamedNativeQueryMemento nativeQueryMemento) { + private static NamedNativeQueryMemento wrapNativeQueryMemento(final NamedNativeQueryMemento nativeQueryMemento) { if ( nativeQueryMemento == null ) { return null; } 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 f01b5d315..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 @@ -6,7 +6,10 @@ package org.hibernate.reactive.query.spi; import java.lang.invoke.MethodHandles; -import java.util.*; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; import java.util.function.Consumer; @@ -27,15 +30,17 @@ 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; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.internal.AggregatedSelectReactiveQueryPlan; import org.hibernate.reactive.query.sqm.internal.ConcreteSqmSelectReactiveQueryPlan; import org.hibernate.reactive.query.sqm.spi.ReactiveSelectQueryPlan; import org.hibernate.reactive.sql.results.spi.ReactiveSingleResultConsumer; +import org.hibernate.sql.exec.spi.Callback; import org.hibernate.sql.results.internal.TupleMetadata; import jakarta.persistence.NoResultException; @@ -76,6 +81,8 @@ public class ReactiveAbstractSelectionQuery { private final Function, R> uniqueElement; private final InterpretationsKeySource interpretationsKeySource; + private Callback callback; + // I'm sure we can avoid some of this by making some methods public in ORM, // but this allows me to prototype faster. We can refactor the code later. public ReactiveAbstractSelectionQuery( @@ -363,4 +370,11 @@ public void enableFetchProfile(String profileName) { } fetchProfiles.add( profileName ); } + + public Callback getCallback() { + if ( callback == null ) { + callback = new ReactiveCallbackImpl(); + } + return callback; + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeNonSelectQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeNonSelectQueryPlan.java index efed93fad..be5541c5d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeNonSelectQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeNonSelectQueryPlan.java @@ -20,6 +20,7 @@ import org.hibernate.query.sql.spi.ParameterOccurrence; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; +import org.hibernate.reactive.pool.impl.Parameters; import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; @@ -71,8 +72,9 @@ public CompletionStage executeReactiveUpdate(DomainQueryExecutionContex final SQLQueryParser parser = new SQLQueryParser( sql, null, session.getFactory() ); + Parameters parameters = Parameters.instance( session.getDialect() ); final JdbcOperationQueryMutation jdbcMutation = new JdbcOperationQueryMutationNative( - parser.process(), + parameters.process( parser.process() ), jdbcParameterBinders, affectedTableNames ); 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 1c97f847a..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 @@ -26,14 +26,12 @@ import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.RootGraph; import org.hibernate.graph.spi.RootGraphImplementor; -import org.hibernate.internal.AbstractSharedSessionContract; import org.hibernate.metamodel.model.domain.BasicDomainType; -import org.hibernate.query.BindableType; -import org.hibernate.query.Order; import org.hibernate.query.QueryParameter; import org.hibernate.query.ResultListTransformer; import org.hibernate.query.TupleTransformer; import org.hibernate.query.named.NamedResultSetMappingMemento; +import org.hibernate.query.results.internal.dynamic.DynamicResultBuilderEntityStandard; import org.hibernate.query.spi.AbstractSelectionQuery; import org.hibernate.query.spi.NonSelectQueryPlan; import org.hibernate.query.spi.QueryInterpretationCache; @@ -45,6 +43,7 @@ import org.hibernate.reactive.query.sql.spi.ReactiveNativeQueryImplementor; import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; import org.hibernate.reactive.query.sqm.spi.ReactiveSelectQueryPlan; +import org.hibernate.sql.exec.spi.Callback; import org.hibernate.type.BasicTypeReference; import jakarta.persistence.AttributeConverter; @@ -54,17 +53,28 @@ 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 - implements ReactiveNativeQueryImplementor { + implements ReactiveNativeQueryImplementor { private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); private final ReactiveAbstractSelectionQuery selectionQueryDelegate; - public ReactiveNativeQueryImpl(String memento, SharedSessionContractImplementor session) { - super( memento, session ); + public ReactiveNativeQueryImpl(String sql, SharedSessionContractImplementor session) { + super( sql, null, session ); + this.selectionQueryDelegate = createSelectionQueryDelegate( session ); + } + + public ReactiveNativeQueryImpl(String sql, Class resultClass, SharedSessionContractImplementor session) { + super( sql, resultClass, session ); + this.selectionQueryDelegate = createSelectionQueryDelegate( session ); + } + + public ReactiveNativeQueryImpl(String sql, NamedResultSetMappingMemento resultSetMappingMemento, Class resultClass, SharedSessionContractImplementor session) { + super( sql, resultSetMappingMemento, resultClass, session); this.selectionQueryDelegate = createSelectionQueryDelegate( session ); } @@ -73,21 +83,22 @@ public ReactiveNativeQueryImpl(NamedNativeQueryMemento memento, SharedSessionCon this.selectionQueryDelegate = createSelectionQueryDelegate( session ); } - public ReactiveNativeQueryImpl(NamedNativeQueryMemento memento, Class resultJavaType, SharedSessionContractImplementor session) { + public ReactiveNativeQueryImpl( + NamedNativeQueryMemento memento, + Class resultJavaType, + SharedSessionContractImplementor session) { super( memento, resultJavaType, session ); this.selectionQueryDelegate = createSelectionQueryDelegate( session ); } - public ReactiveNativeQueryImpl(NamedNativeQueryMemento memento, String resultSetMappingName, SharedSessionContractImplementor session) { + public ReactiveNativeQueryImpl( + NamedNativeQueryMemento memento, + String resultSetMappingName, + SharedSessionContractImplementor session) { super( memento, resultSetMappingName, session ); this.selectionQueryDelegate = createSelectionQueryDelegate( session ); } - public ReactiveNativeQueryImpl(String sqlString, NamedResultSetMappingMemento resultSetMappingMemento, AbstractSharedSessionContract session) { - super( sqlString, resultSetMappingMemento, session ); - this.selectionQueryDelegate = createSelectionQueryDelegate( session ); - } - // Convenient for passing parameters to ReactiveAbstractSelectionQuery using method reference private T getNull() { return null; @@ -119,6 +130,7 @@ private ReactiveAbstractSelectionQuery createSelectionQueryDelegate(SharedSes null ); } + private CompletionStage> doReactiveList() { return reactiveSelectPlan().reactivePerformList( this ); } @@ -138,7 +150,11 @@ private ReactiveNonSelectQueryPlan reactiveNonSelectPlan() { } final String sqlString = expandParameterLists(); - ReactiveNonSelectQueryPlan queryPlan = new ReactiveNativeNonSelectQueryPlan( sqlString, getQuerySpaces(), getParameterOccurrences() ); + ReactiveNonSelectQueryPlan queryPlan = new ReactiveNativeNonSelectQueryPlan( + sqlString, + getQuerySpaces(), + getParameterOccurrences() + ); if ( cacheKey != null ) { getSession().getFactory().getQueryEngine().getInterpretationCache() .cacheNonSelectQueryPlan( cacheKey, queryPlan ); @@ -177,6 +193,11 @@ public R getSingleResultOrNull() { return selectionQueryDelegate.getSingleResultOrNull(); } + @Override + public Callback getCallback() { + return selectionQueryDelegate.getCallback(); + } + @Override public CompletionStage getReactiveSingleResultOrNull() { return selectionQueryDelegate.getReactiveSingleResultOrNull(); @@ -224,9 +245,19 @@ public ReactiveNativeQueryImpl applyFetchGraph(RootGraph graph) { return this; } + @Override + public DynamicResultBuilderEntityStandard addRoot(String tableAlias, Class entityType) { + return super.addRoot( tableAlias, entityType ); + } + + @Override + public DynamicResultBuilderEntityStandard addRoot(String tableAlias, String entityName) { + return super.addRoot( tableAlias, entityName ); + } + @Override public void addResultTypeClass(Class resultClass) { - super.addResultTypeClass(resultClass); + super.addResultTypeClass( resultClass ); } @Override @@ -260,31 +291,48 @@ public ReactiveNativeQueryImpl addScalar(String columnAlias, BasicTypeReferen } @Override - public ReactiveNativeQueryImpl addScalar(String columnAlias, Class relationalJavaType, AttributeConverter converter) { + public ReactiveNativeQueryImpl addScalar( + String columnAlias, + Class relationalJavaType, + AttributeConverter converter) { super.addScalar( columnAlias, relationalJavaType, converter ); return this; } @Override - public ReactiveNativeQueryImpl addScalar(String columnAlias, Class domainJavaType, Class jdbcJavaType, AttributeConverter converter) { + public ReactiveNativeQueryImpl addScalar( + String columnAlias, + Class domainJavaType, + Class jdbcJavaType, + AttributeConverter converter) { super.addScalar( columnAlias, domainJavaType, jdbcJavaType, converter ); return this; } @Override - public ReactiveNativeQueryImpl addScalar(String columnAlias, Class relationalJavaType, Class> converter) { + public ReactiveNativeQueryImpl addScalar( + String columnAlias, + Class relationalJavaType, + Class> converter) { super.addScalar( columnAlias, relationalJavaType, converter ); return this; } @Override - public ReactiveNativeQueryImpl addScalar(String columnAlias, Class domainJavaType, Class jdbcJavaType, Class> converter) { + public ReactiveNativeQueryImpl addScalar( + String columnAlias, + Class domainJavaType, + Class jdbcJavaType, + Class> converter) { super.addScalar( columnAlias, domainJavaType, jdbcJavaType, converter ); return this; } @Override - public ReactiveNativeQueryImpl addAttributeResult(String columnAlias, Class entityJavaType, String attributePath) { + public ReactiveNativeQueryImpl addAttributeResult( + String columnAlias, + Class entityJavaType, + String attributePath) { super.addAttributeResult( columnAlias, entityJavaType, attributePath ); return this; } @@ -318,6 +366,7 @@ public ReactiveNativeQueryImpl addEntity(String tableAlias, String entityName super.addEntity( tableAlias, entityName, lockMode ); return this; } + @Override public ReactiveNativeQueryImpl addEntity(Class entityType) { super.addEntity( entityType ); @@ -382,12 +431,6 @@ public ReactiveNativeQueryImpl applyLoadGraph(RootGraph graph) { return this; } - @Override @Deprecated - public ReactiveNativeQueryImpl setAliasSpecificLockMode(String alias, LockMode lockMode) { - super.setAliasSpecificLockMode( alias, lockMode ); - return this; - } - @Override public ReactiveNativeQueryImpl setHint(String hintName, Object value) { super.setHint( hintName, value ); @@ -484,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 ); @@ -539,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; } @@ -581,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; } @@ -617,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; } @@ -635,7 +666,10 @@ public ReactiveNativeQueryImpl setParameter(Parameter param, Date value } @Override - public ReactiveNativeQueryImpl setParameter(Parameter param, Calendar value, TemporalType temporalType) { + public ReactiveNativeQueryImpl setParameter( + Parameter param, + Calendar value, + TemporalType temporalType) { super.setParameter( param, value, temporalType ); return this; } @@ -653,7 +687,10 @@ public

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

    type) { + public

    ReactiveNativeQueryImpl setParameterList( + String name, + Collection values, + Type

    type) { super.setParameterList( name, values, type ); return this; } @@ -671,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; } @@ -683,13 +720,19 @@ public ReactiveNativeQueryImpl setParameterList(int position, Collection valu } @Override - public

    ReactiveNativeQueryImpl setParameterList(int position, Collection values, Class

    type) { + public

    ReactiveNativeQueryImpl setParameterList( + int position, + Collection values, + Class

    type) { super.setParameterList( position, values, type ); return this; } @Override - public

    ReactiveNativeQueryImpl setParameterList(int position, Collection values, BindableType

    type) { + public

    ReactiveNativeQueryImpl setParameterList( + int position, + Collection values, + Type

    type) { super.setParameterList( position, values, type ); return this; } @@ -707,25 +750,33 @@ 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; } @Override - public

    ReactiveNativeQueryImpl setParameterList(QueryParameter

    parameter, Collection values) { + public

    ReactiveNativeQueryImpl setParameterList( + QueryParameter

    parameter, + Collection values) { super.setParameterList( parameter, values ); return this; } @Override - public

    ReactiveNativeQueryImpl setParameterList(QueryParameter

    parameter, Collection values, Class

    javaType) { + public

    ReactiveNativeQueryImpl setParameterList( + QueryParameter

    parameter, + Collection values, + Class

    javaType) { super.setParameterList( parameter, values, javaType ); return this; } @Override - public

    ReactiveNativeQueryImpl setParameterList(QueryParameter

    parameter, Collection values, BindableType

    type) { + public

    ReactiveNativeQueryImpl setParameterList( + QueryParameter

    parameter, + Collection values, + Type

    type) { super.setParameterList( parameter, values, type ); return this; } @@ -743,7 +794,10 @@ public

    ReactiveNativeQueryImpl setParameterList(QueryParameter

    paramet } @Override - public

    ReactiveNativeQueryImpl setParameterList(QueryParameter

    parameter, P[] values, BindableType

    type) { + public

    ReactiveNativeQueryImpl setParameterList( + QueryParameter

    parameter, + P[] values, + Type

    type) { super.setParameterList( parameter, values, type ); return this; } @@ -767,6 +821,6 @@ public void applyGraph(RootGraphImplementor graph, GraphSemantic semantic) { @Override public ReactiveNativeQueryImpl enableFetchProfile(String profileName) { - throw new UnsupportedOperationException("A native SQL query cannot use fetch profiles"); + throw new UnsupportedOperationException( "A native SQL query cannot use fetch profiles" ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeSelectQueryPlanImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeSelectQueryPlanImpl.java index 28753683e..f1eca36df 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeSelectQueryPlanImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeSelectQueryPlanImpl.java @@ -6,12 +6,12 @@ package org.hibernate.reactive.query.sql.internal; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletionStage; +import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.results.ResultSetMapping; import org.hibernate.query.spi.DomainQueryExecutionContext; @@ -23,6 +23,7 @@ import org.hibernate.query.sql.spi.ParameterOccurrence; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; +import org.hibernate.reactive.pool.impl.Parameters; import org.hibernate.reactive.query.internal.ReactiveResultSetMappingProcessor; import org.hibernate.reactive.query.spi.ReactiveNativeSelectQueryPlan; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; @@ -60,7 +61,8 @@ public ReactiveNativeSelectQueryPlanImpl( resultSetMapping.addAffectedTableNames( affectedTableNames, sessionFactory ); } this.affectedTableNames = affectedTableNames; - this.sql = parser.process(); + Dialect dialect = sessionFactory.getJdbcServices().getDialect(); + this.sql = Parameters.instance( dialect ).process( parser.process() ); this.parameterList = parameterList; } @@ -90,29 +92,28 @@ public CompletionStage> reactivePerformList(DomainQueryExecutionContext ); } - final ReactiveSharedSessionContractImplementor reactiveSession = (ReactiveSharedSessionContractImplementor) executionContext.getSession(); - return reactiveSession.reactiveAutoFlushIfRequired( affectedTableNames ) - .thenCompose( aBoolean -> { - final JdbcOperationQuerySelect jdbcSelect = new JdbcOperationQuerySelect( - sql, - jdbcParameterBinders, - resultSetMapping, - affectedTableNames, - Collections.emptySet() - ); + return ( (ReactiveSharedSessionContractImplementor) executionContext.getSession() ) + .reactiveAutoFlushIfRequired( affectedTableNames ) + .thenCompose( aBoolean -> { + final JdbcOperationQuerySelect jdbcSelect = new JdbcOperationQuerySelect( + sql, + jdbcParameterBinders, + resultSetMapping, + affectedTableNames + ); - return StandardReactiveSelectExecutor.INSTANCE - .list( - jdbcSelect, - jdbcParameterBindings, - SqmJdbcExecutionContextAdapter.usingLockingAndPaging( executionContext ), - null, - queryOptions.getUniqueSemantic() == null - ? ReactiveListResultsConsumer.UniqueSemantic.NEVER - : reactiveUniqueSemantic( queryOptions ) - ); + return StandardReactiveSelectExecutor.INSTANCE + .list( + jdbcSelect, + jdbcParameterBindings, + SqmJdbcExecutionContextAdapter.usingLockingAndPaging( executionContext ), + null, + queryOptions.getUniqueSemantic() == null + ? ReactiveListResultsConsumer.UniqueSemantic.NEVER + : reactiveUniqueSemantic( queryOptions ) + ); - } ); + } ); } private static ReactiveListResultsConsumer.UniqueSemantic reactiveUniqueSemantic(QueryOptions queryOptions) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedNativeQueryMemento.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedNativeQueryMemento.java index fc1493d02..7bc480637 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedNativeQueryMemento.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedNativeQueryMemento.java @@ -19,14 +19,19 @@ /** * @see NamedNativeQueryMemento */ -public class ReactiveNamedNativeQueryMemento implements NamedNativeQueryMemento { +public class ReactiveNamedNativeQueryMemento implements NamedNativeQueryMemento { - private final NamedNativeQueryMemento delegate; + private final NamedNativeQueryMemento delegate; - public ReactiveNamedNativeQueryMemento(NamedNativeQueryMemento delegate) { + public ReactiveNamedNativeQueryMemento(NamedNativeQueryMemento delegate) { this.delegate = delegate; } + @Override + public Class getResultType() { + return delegate.getResultType(); + } + @Override public String getSqlString() { return delegate.getSqlString(); @@ -63,23 +68,23 @@ public Integer getMaxResults() { } @Override - public NativeQueryImplementor toQuery(SharedSessionContractImplementor session) { - return new ReactiveNativeQueryImpl( this, session ); + public NativeQueryImplementor toQuery(SharedSessionContractImplementor session) { + return new ReactiveNativeQueryImpl<>( this, session ); } @Override public NativeQueryImplementor toQuery(SharedSessionContractImplementor session, Class resultType) { - return new ReactiveNativeQueryImpl( this, resultType, session ); + return new ReactiveNativeQueryImpl<>( this, resultType, session ); } @Override public NativeQueryImplementor toQuery(SharedSessionContractImplementor session, String resultSetMapping) { - return new ReactiveNativeQueryImpl( this, resultSetMapping, session ); + return new ReactiveNativeQueryImpl<>( this, resultSetMapping, session ); } @Override - public NamedNativeQueryMemento makeCopy(String name) { - return new ReactiveNamedNativeQueryMemento( delegate.makeCopy( name ) ); + public NamedNativeQueryMemento makeCopy(String name) { + return new ReactiveNamedNativeQueryMemento<>( delegate.makeCopy( name ) ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedSqmQueryMemento.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedSqmQueryMemento.java index af7a9ba7c..388488b26 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedSqmQueryMemento.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedSqmQueryMemento.java @@ -26,17 +26,22 @@ /** * @see org.hibernate.query.sql.spi.NamedNativeQueryMemento */ -public class ReactiveNamedSqmQueryMemento implements NamedSqmQueryMemento { +public class ReactiveNamedSqmQueryMemento implements NamedSqmQueryMemento { - private final NamedSqmQueryMemento delegate; + private final NamedSqmQueryMemento delegate; - public ReactiveNamedSqmQueryMemento(NamedSqmQueryMemento delegate) { + public ReactiveNamedSqmQueryMemento(NamedSqmQueryMemento delegate) { Objects.requireNonNull( delegate ); this.delegate = delegate; } @Override - public SqmQueryImplementor toQuery(SharedSessionContractImplementor session) { + public Class getResultType() { + return delegate.getResultType(); + } + + @Override + public SqmQueryImplementor toQuery(SharedSessionContractImplementor session) { return toQuery( session, null ); } @@ -73,7 +78,7 @@ public String getHqlString() { } @Override - public SqmStatement getSqmStatement() { + public SqmStatement getSqmStatement() { return delegate.getSqmStatement(); } @@ -98,8 +103,8 @@ public Map getParameterTypes() { } @Override - public NamedSqmQueryMemento makeCopy(String name) { - return new ReactiveNamedSqmQueryMemento( delegate.makeCopy( name ) ); + public NamedSqmQueryMemento makeCopy(String name) { + return new ReactiveNamedSqmQueryMemento<>( delegate.makeCopy( name ) ); } @Override 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 11e943631..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,17 +11,17 @@ 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; import org.hibernate.query.named.NameableQuery; -import org.hibernate.query.results.dynamic.DynamicResultBuilderEntityStandard; +import org.hibernate.query.results.internal.dynamic.DynamicResultBuilderEntityStandard; import org.hibernate.query.sql.spi.NamedNativeQueryMemento; import org.hibernate.reactive.query.ReactiveNativeQuery; import org.hibernate.reactive.query.ReactiveQueryImplementor; @@ -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 e0c71f7e3..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> orders); - - @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/ConcreteSqmSelectReactiveQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java index 821f2c7f3..7c7cfd062 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java @@ -228,22 +228,20 @@ private CompletionStage withCacheableSqmInterpretation(DomainQueryExec return interpreter.interpret( context, executionContext, localCopy, jdbcParameterBindings ); } + // Copy and paste from ORM private JdbcParameterBindings createJdbcParameterBindings(CacheableSqmInterpretation sqmInterpretation, DomainQueryExecutionContext executionContext) { - final SharedSessionContractImplementor session = executionContext.getSession(); return SqmUtil.createJdbcParameterBindings( executionContext.getQueryParameterBindings(), domainParameterXref, sqmInterpretation.getJdbcParamsXref(), - session.getFactory().getRuntimeMetamodels().getMappingMetamodel(), - sqmInterpretation.getTableGroupAccess()::findTableGroup, new SqmParameterMappingModelResolutionAccess() { - @Override - @SuppressWarnings("unchecked") + //this is pretty ugly! + @Override @SuppressWarnings("unchecked") public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmInterpretation.getSqmParameterMappingModelTypes().get( parameter ); + return (MappingModelExpressible) sqmInterpretation.getSqmParameterMappingModelTypes().get(parameter); } }, - session + executionContext.getSession() ); } @@ -263,7 +261,7 @@ private static CacheableSqmInterpretation buildCacheableSqmInterpretation( domainParameterXref, executionContext.getQueryParameterBindings(), executionContext.getSession().getLoadQueryInfluencers(), - sessionFactory, + sessionFactory.getSqlTranslationEngine(), true ); @@ -281,19 +279,15 @@ private static CacheableSqmInterpretation buildCacheableSqmInterpretation( executionContext.getQueryParameterBindings(), domainParameterXref, jdbcParamsXref, - session.getFactory().getRuntimeMetamodels().getMappingMetamodel(), - tableGroupAccess::findTableGroup, new SqmParameterMappingModelResolutionAccess() { - @Override - @SuppressWarnings("unchecked") + @Override @SuppressWarnings("unchecked") public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmInterpretation - .getSqmParameterMappingModelTypeResolutions() - .get( parameter ); + return (MappingModelExpressible) sqmInterpretation.getSqmParameterMappingModelTypeResolutions().get(parameter); } }, session ); + final JdbcOperationQuerySelect jdbcSelect = selectTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() 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 fbe3295d6..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 @@ -23,19 +23,16 @@ import org.hibernate.LockOptions; import org.hibernate.TypeMismatchException; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.Generator; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.RootGraph; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.id.BulkInsertionCapableIdentifierGenerator; -import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.OptimizableGenerator; import org.hibernate.id.enhanced.Optimizer; import org.hibernate.metamodel.model.domain.EntityDomainType; -import org.hibernate.persister.entity.AbstractEntityPersister; 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; @@ -62,6 +59,7 @@ import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; import org.hibernate.reactive.query.sqm.spi.ReactiveSelectQueryPlan; import org.hibernate.reactive.session.ReactiveSqmQueryImplementor; +import org.hibernate.sql.exec.spi.Callback; import org.hibernate.transform.ResultTransformer; import jakarta.persistence.CacheRetrieveMode; @@ -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} @@ -175,6 +174,11 @@ public R getSingleResultOrNull() { return selectionQueryDelegate.getSingleResultOrNull(); } + @Override + public Callback getCallback() { + return selectionQueryDelegate.getCallback(); + } + @Override public R uniqueResult() { return selectionQueryDelegate.uniqueResult(); @@ -414,22 +418,23 @@ private ReactiveNonSelectQueryPlan buildUpdateQueryPlan() { ? new ReactiveSimpleUpdateQueryPlan( sqmUpdate, getDomainParameterXref() ) : new ReactiveMultiTableUpdateQueryPlan( sqmUpdate, getDomainParameterXref(), multiTableStrategy ); } + private ReactiveNonSelectQueryPlan buildInsertQueryPlan() { //noinspection rawtypes final SqmInsertStatement sqmInsert = (SqmInsertStatement) getSqmStatement(); final String entityNameToInsert = sqmInsert.getTarget().getModel().getHibernateEntityName(); - final AbstractEntityPersister entityDescriptor = (AbstractEntityPersister) getSessionFactory().getRuntimeMetamodels() - .getMappingMetamodel() - .getEntityDescriptor( entityNameToInsert ); + final EntityPersister persister = getSessionFactory() + .getMappingMetamodel().getEntityDescriptor( entityNameToInsert ); + + boolean useMultiTableInsert = persister.hasMultipleTables(); + if ( !useMultiTableInsert && !isSimpleValuesInsert( sqmInsert, persister ) ) { + final Generator identifierGenerator = persister.getGenerator(); - boolean useMultiTableInsert = entityDescriptor.isMultiTable(); - if ( !useMultiTableInsert && !isSimpleValuesInsert( sqmInsert, entityDescriptor ) ) { - final IdentifierGenerator identifierGenerator = entityDescriptor.getIdentifierGenerator(); if ( identifierGenerator instanceof BulkInsertionCapableIdentifierGenerator && identifierGenerator instanceof OptimizableGenerator ) { final Optimizer optimizer = ( (OptimizableGenerator) identifierGenerator ).getOptimizer(); if ( optimizer != null && optimizer.getIncrementSize() > 1 ) { - useMultiTableInsert = !hasIdentifierAssigned( sqmInsert, entityDescriptor ); + useMultiTableInsert = !hasIdentifierAssigned( sqmInsert, persister ); } } } @@ -440,7 +445,7 @@ private ReactiveNonSelectQueryPlan buildInsertQueryPlan() { return new ReactiveMultiTableInsertQueryPlan( sqmInsert, getDomainParameterXref(), - (ReactiveSqmMultiTableInsertStrategy) entityDescriptor.getSqmMultiTableInsertStrategy() + (ReactiveSqmMultiTableInsertStrategy) persister.getSqmMultiTableInsertStrategy() ); } } @@ -472,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(); @@ -508,7 +501,7 @@ public ReactiveQuerySqmImpl setMaxResults(int maxResult) { @Override public ReactiveQuerySqmImpl setFirstResult(int startPosition) { - applyFirstResult( startPosition ); + super.setFirstResult( startPosition ); return this; } @@ -533,13 +526,6 @@ public ReactiveQuerySqmImpl setLockMode(LockModeType lockMode) { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // covariance - - @Override @Deprecated - public ReactiveQuerySqmImpl setAliasSpecificLockMode(String alias, LockMode lockMode) { - super.setAliasSpecificLockMode( alias, lockMode ); - return this; - } - @Override public ReactiveQuerySqmImpl applyGraph(RootGraph graph, GraphSemantic semantic) { super.applyGraph( graph, semantic ); @@ -643,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; } @@ -667,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; } @@ -691,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; } @@ -751,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; } @@ -769,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; } @@ -787,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; } @@ -805,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; } @@ -823,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; } @@ -841,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/ReactiveSimpleDeleteQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java index ca82ff284..eb055a38a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java @@ -37,9 +37,7 @@ import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement; import org.hibernate.sql.ast.tree.MutationStatement; -import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcLiteral; import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper; import org.hibernate.sql.ast.tree.from.NamedTableReference; import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; @@ -82,7 +80,7 @@ protected SqlAstTranslator createTranslato domainParameterXref, executionContext.getQueryParameterBindings(), executionContext.getSession().getLoadQueryInfluencers(), - factory + factory.getSqlTranslationEngine() ); sqmInterpretation = (SqmTranslation) translator.translate(); @@ -95,29 +93,28 @@ protected SqlAstTranslator createTranslato return factory.getJdbcServices() .getJdbcEnvironment() .getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, mutationStatement() ); + .buildMutationTranslator( factory, createDeleteAst() ); } - private MutationStatement mutationStatement() { + // Copy and paste from superclass + private MutationStatement createDeleteAst() { + final MutationStatement ast; if ( entityDescriptor.getSoftDeleteMapping() == null ) { - return sqmInterpretation.getSqlAst(); + ast = sqmInterpretation.getSqlAst(); } - final AbstractUpdateOrDeleteStatement sqlDeleteAst = sqmInterpretation.getSqlAst(); - final NamedTableReference targetTable = sqlDeleteAst.getTargetTable(); - final SoftDeleteMapping columnMapping = getEntityDescriptor().getSoftDeleteMapping(); - final ColumnReference columnReference = new ColumnReference( targetTable, columnMapping ); - //noinspection rawtypes,unchecked - final JdbcLiteral jdbcLiteral = new JdbcLiteral( - columnMapping.getDeletedLiteralValue(), - columnMapping.getJdbcMapping() - ); - final Assignment assignment = new Assignment( columnReference, jdbcLiteral ); - - return new UpdateStatement( - targetTable, - Collections.singletonList( assignment ), - sqlDeleteAst.getRestriction() - ); + else { + final AbstractUpdateOrDeleteStatement sqlDeleteAst = sqmInterpretation.getSqlAst(); + final NamedTableReference targetTable = sqlDeleteAst.getTargetTable(); + final SoftDeleteMapping columnMapping = getEntityDescriptor().getSoftDeleteMapping(); + final Assignment assignment = columnMapping.createSoftDeleteAssignment( targetTable ); + + ast = new UpdateStatement( + targetTable, + Collections.singletonList( assignment ), + sqlDeleteAst.getRestriction() + ); + } + return ast; } @Override @@ -134,14 +131,10 @@ public CompletionStage executeReactiveUpdate(DomainQueryExecutionContex executionContext.getQueryParameterBindings(), domainParameterXref, jdbcParamsXref, - factory.getRuntimeMetamodels().getMappingMetamodel(), - sqmInterpretation.getFromClauseAccess()::findTableGroup, new SqmParameterMappingModelResolutionAccess() { - @Override - @SuppressWarnings("unchecked") + @Override @SuppressWarnings("unchecked") public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmInterpretation.getSqmParameterMappingModelTypeResolutions() - .get( parameter ); + return (MappingModelExpressible) sqmInterpretation.getSqmParameterMappingModelTypeResolutions().get(parameter); } }, session diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleInsertQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleInsertQueryPlan.java index 5e4710f93..bf67d16ea 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleInsertQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleInsertQueryPlan.java @@ -15,23 +15,19 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.query.sqm.internal.SqmUtil; import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.sql.SqmTranslation; -import org.hibernate.query.sqm.sql.SqmTranslator; -import org.hibernate.query.sqm.sql.SqmTranslatorFactory; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.spi.FromClauseAccess; -import org.hibernate.sql.ast.tree.insert.InsertStatement; -import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert; +import org.hibernate.sql.ast.tree.MutationStatement; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; @@ -46,8 +42,7 @@ public class ReactiveSimpleInsertQueryPlan implements ReactiveNonSelectQueryPlan private Map, MappingModelExpressible> paramTypeResolutions; - private JdbcOperationQueryInsert jdbcInsert; - private FromClauseAccess tableGroupAccess; + private JdbcOperationQueryMutation jdbcInsert; private Map, Map, List>> jdbcParamsXref; public ReactiveSimpleInsertQueryPlan( @@ -61,17 +56,14 @@ public ReactiveSimpleInsertQueryPlan( public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmInsert ); final SharedSessionContractImplementor session = executionContext.getSession(); - SqlAstTranslator insertTranslator = null; - if ( jdbcInsert == null ) { - insertTranslator = createInsertTranslator( executionContext ); - } + SqlAstTranslator insertTranslator = jdbcInsert == null + ? createInsertTranslator( executionContext ) + : null; final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( executionContext.getQueryParameterBindings(), domainParameterXref, jdbcParamsXref, - session.getFactory().getRuntimeMetamodels().getMappingMetamodel(), - tableGroupAccess::findTableGroup, new SqmParameterMappingModelResolutionAccess() { @Override @SuppressWarnings("unchecked") public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { @@ -81,10 +73,7 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter MappingModelExpressible getResolvedMappingModelType(SqmParameter createInsertTranslator(DomainQueryExecutionContext executionContext) { + // Copied from Hibernate ORM SimpleInsertQueryPlan#createInsertTranslator + private SqlAstTranslator createInsertTranslator(DomainQueryExecutionContext executionContext) { final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final QueryEngine queryEngine = factory.getQueryEngine(); - - final SqmTranslatorFactory translatorFactory = queryEngine.getSqmTranslatorFactory(); - final SqmTranslator translator = translatorFactory.createInsertTranslator( - sqmInsert, - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getSession().getLoadQueryInfluencers(), - factory - ); - - final SqmTranslation sqmInterpretation = translator.translate(); - tableGroupAccess = sqmInterpretation.getFromClauseAccess(); + final SqmTranslation sqmInterpretation = factory.getQueryEngine().getSqmTranslatorFactory() + .createMutationTranslator( + sqmInsert, + executionContext.getQueryOptions(), + domainParameterXref, + executionContext.getQueryParameterBindings(), + executionContext.getSession().getLoadQueryInfluencers(), + factory.getSqlTranslationEngine() + ) + .translate(); this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( domainParameterXref, @@ -131,6 +116,6 @@ private SqlAstTranslator createInsertTranslator(Domain return factory.getJdbcServices() .getJdbcEnvironment() .getSqlAstTranslatorFactory() - .buildInsertTranslator( factory, sqmInterpretation.getSqlAst() ); + .buildMutationTranslator( factory, sqmInterpretation.getSqlAst() ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleUpdateQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleUpdateQueryPlan.java index c3bc21dc5..f49f848f5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleUpdateQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleUpdateQueryPlan.java @@ -29,8 +29,8 @@ import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.spi.FromClauseAccess; -import org.hibernate.sql.ast.tree.update.UpdateStatement; -import org.hibernate.sql.exec.spi.JdbcOperationQueryUpdate; +import org.hibernate.sql.ast.tree.MutationStatement; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; @@ -43,7 +43,7 @@ public class ReactiveSimpleUpdateQueryPlan implements ReactiveNonSelectQueryPlan private final SqmUpdateStatement sqmUpdate; private final DomainParameterXref domainParameterXref; - private JdbcOperationQueryUpdate jdbcUpdate; + private JdbcOperationQueryMutation jdbcUpdate; private FromClauseAccess tableGroupAccess; private Map, Map, List>> jdbcParamsXref; private Map, MappingModelExpressible> sqmParamMappingTypeResolutions; @@ -57,22 +57,17 @@ public ReactiveSimpleUpdateQueryPlan(SqmUpdateStatement sqmUpdate, DomainPara public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmUpdate ); final SharedSessionContractImplementor session = executionContext.getSession(); - SqlAstTranslator updateTranslator = null; - if ( jdbcUpdate == null ) { - updateTranslator = createUpdateTranslator( executionContext ); - } - + SqlAstTranslator updateTranslator = jdbcUpdate == null + ? createUpdateTranslator( executionContext ) + : null; final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( executionContext.getQueryParameterBindings(), domainParameterXref, jdbcParamsXref, - session.getFactory().getRuntimeMetamodels().getMappingMetamodel(), - tableGroupAccess::findTableGroup, new SqmParameterMappingModelResolutionAccess() { - @Override - @SuppressWarnings("unchecked") + @Override @SuppressWarnings("unchecked") public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmParamMappingTypeResolutions.get( parameter ); + return (MappingModelExpressible) sqmParamMappingTypeResolutions.get(parameter); } }, session @@ -97,27 +92,26 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter createUpdateTranslator(DomainQueryExecutionContext executionContext) { + private SqlAstTranslator createUpdateTranslator(DomainQueryExecutionContext executionContext) { final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); final QueryEngine queryEngine = factory.getQueryEngine(); final SqmTranslatorFactory translatorFactory = queryEngine.getSqmTranslatorFactory(); - final SqmTranslator translator = translatorFactory.createSimpleUpdateTranslator( + final SqmTranslator translator = translatorFactory.createMutationTranslator( sqmUpdate, executionContext.getQueryOptions(), domainParameterXref, executionContext.getQueryParameterBindings(), executionContext.getSession().getLoadQueryInfluencers(), - factory + factory.getSqlTranslationEngine() ); - final SqmTranslation sqmInterpretation = translator.translate(); - + final SqmTranslation sqmInterpretation = translator.translate(); tableGroupAccess = sqmInterpretation.getFromClauseAccess(); this.jdbcParamsXref = SqmUtil .generateJdbcParamsXref( domainParameterXref, sqmInterpretation::getJdbcParamsBySqmParam ); this.sqmParamMappingTypeResolutions = sqmInterpretation.getSqmParameterMappingModelTypeResolutions(); return factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory() - .buildUpdateTranslator( factory, sqmInterpretation.getSqlAst() ); + .buildMutationTranslator( factory, sqmInterpretation.getSqlAst() ); } } 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 13e167697..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; @@ -39,6 +26,7 @@ import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.reactive.query.spi.ReactiveAbstractSelectionQuery; import org.hibernate.reactive.query.sqm.ReactiveSqmSelectionQuery; +import org.hibernate.sql.exec.spi.Callback; import jakarta.persistence.CacheRetrieveMode; import jakarta.persistence.CacheStoreMode; @@ -46,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; @@ -218,6 +218,11 @@ public CompletionStage getReactiveResultCount() { .getReactiveResultsCount( getSqmStatement().createCountQuery(), this ); } + @Override + public Callback getCallback() { + return selectionQueryDelegate.getCallback(); + } + @Override public List getResultList() { return selectionQueryDelegate.getResultList(); @@ -240,12 +245,6 @@ public ReactiveSqmSelectionQueryImpl setHibernateLockMode(LockMode lockMode) return this; } - @Override @Deprecated - public ReactiveSqmSelectionQueryImpl setAliasSpecificLockMode(String alias, LockMode lockMode) { - super.setAliasSpecificLockMode( alias, lockMode ); - return this; - } - @Override public ReactiveSqmSelectionQueryImpl setLockMode(String alias, LockMode lockMode) { super.setLockMode( alias, lockMode ); @@ -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/ReactiveSqmMutationStrategyHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveSqmMutationStrategyHelper.java index 37689a33f..b2e36a296 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveSqmMutationStrategyHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveSqmMutationStrategyHelper.java @@ -6,7 +6,6 @@ package org.hibernate.reactive.query.sqm.mutation.internal; import java.sql.PreparedStatement; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.BiFunction; @@ -17,14 +16,16 @@ import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; import org.hibernate.reactive.util.impl.CompletionStages; +import org.hibernate.reactive.util.impl.CompletionStages.Completable; import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.from.NamedTableReference; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQueryDelete; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** @@ -35,74 +36,6 @@ public class ReactiveSqmMutationStrategyHelper { private ReactiveSqmMutationStrategyHelper() { } -// public static CompletionStage visitCollectionTables(EntityMappingType entityDescriptor, Consumer consumer) { -// if ( !entityDescriptor.getEntityPersister().hasCollections() ) { -// // none to clean-up -// return voidFuture(); -// } -// -// final CompletableFuture stage = new CompletableFuture<>(); -// try { -// entityDescriptor.visitSubTypeAttributeMappings( -// attributeMapping -> { -// if ( attributeMapping instanceof PluralAttributeMapping ) { -// try { -// consumer.accept( (PluralAttributeMapping) attributeMapping ); -// complete( stage, null ); -// } -// catch (Throwable throwable) { -// complete( stage, throwable ); -// } -// } -// else if ( attributeMapping instanceof EmbeddedAttributeMapping ) { -// visitCollectionTables( (EmbeddedAttributeMapping) attributeMapping, consumer ) -// .whenComplete( (v, throwable) -> complete( stage, throwable ) ); -// } -// else { -// complete( stage, null ); -// } -// } ); -// return stage; -// } -// catch (Throwable throwable) { -// complete( stage, throwable ); -// return stage; -// } -// } - -// private static CompletionStage visitCollectionTables(EmbeddedAttributeMapping attributeMapping, Consumer consumer) { -// final CompletableFuture stage = new CompletableFuture<>(); -// -// try { -// attributeMapping.visitSubParts( -// modelPart -> { -// if ( modelPart instanceof PluralAttributeMapping ) { -// try { -// consumer.accept( (PluralAttributeMapping) modelPart ); -// complete( stage, null ); -// } -// catch (Throwable throwable) { -// complete( stage, throwable ); -// } -// } -// else if ( modelPart instanceof EmbeddedAttributeMapping ) { -// visitCollectionTables( (EmbeddedAttributeMapping) modelPart, consumer ) -// .whenComplete( (v, throwable) -> complete( stage, throwable ) ); -// } -// else { -// complete( stage, null ); -// } -// }, -// null -// ); -// return stage; -// } -// catch (Throwable t) { -// complete( stage, t ); -// return stage; -// } -// } - public static CompletionStage cleanUpCollectionTables( EntityMappingType entityDescriptor, BiFunction restrictionProducer, @@ -113,83 +46,75 @@ public static CompletionStage cleanUpCollectionTables( return voidFuture(); } - final CompletableFuture stage = new CompletableFuture<>(); try { - entityDescriptor.visitSubTypeAttributeMappings( - attributeMapping -> { + final Completable stage = new Completable<>(); + entityDescriptor + .visitSubTypeAttributeMappings( attributeMapping -> { if ( attributeMapping instanceof PluralAttributeMapping ) { cleanUpCollectionTable( (PluralAttributeMapping) attributeMapping, - entityDescriptor, restrictionProducer, jdbcParameterBindings, executionContext - ).whenComplete( (v, throwable) -> complete( stage, throwable ) ); + ).handle( stage::complete ); } else if ( attributeMapping instanceof EmbeddedAttributeMapping ) { cleanUpCollectionTables( (EmbeddedAttributeMapping) attributeMapping, - entityDescriptor, restrictionProducer, jdbcParameterBindings, executionContext - ).whenComplete( (v, throwable) -> complete( stage, throwable ) ); + ).handle( stage::complete ); } else { - complete( stage, null ); + stage.complete( null, null ); } } ); - return stage; + return stage.getStage(); } catch (Throwable throwable) { - complete( stage, throwable ); - return stage; + return failedFuture( throwable ); } } private static CompletionStage cleanUpCollectionTables( EmbeddedAttributeMapping attributeMapping, - EntityMappingType entityDescriptor, BiFunction restrictionProducer, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { - final CompletableFuture stage = new CompletableFuture<>(); try { + final Completable completable = new Completable<>(); attributeMapping.visitSubParts( modelPart -> { if ( modelPart instanceof PluralAttributeMapping ) { cleanUpCollectionTable( (PluralAttributeMapping) modelPart, - entityDescriptor, restrictionProducer, jdbcParameterBindings, executionContext - ); + ).handle( completable::complete ); } else if ( modelPart instanceof EmbeddedAttributeMapping ) { cleanUpCollectionTables( (EmbeddedAttributeMapping) modelPart, - entityDescriptor, restrictionProducer, jdbcParameterBindings, executionContext - ); + ).handle( completable::complete ); } }, null ); - return stage; + return completable.getStage(); } catch (Throwable throwable) { - complete( stage, throwable ); - return stage; + return failedFuture( throwable ); } } private static CompletionStage cleanUpCollectionTable( PluralAttributeMapping attributeMapping, - EntityMappingType entityDescriptor, BiFunction restrictionProducer, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { @@ -216,9 +141,9 @@ private static CompletionStage cleanUpCollectionTable( restrictionProducer.apply( tableReference, attributeMapping ) ); - JdbcOperationQueryDelete jdbcDelete = jdbcServices.getJdbcEnvironment() + JdbcOperationQueryMutation jdbcDelete = jdbcServices.getJdbcEnvironment() .getSqlAstTranslatorFactory() - .buildDeleteTranslator( sessionFactory, sqlAstDelete ) + .buildMutationTranslator( sessionFactory, sqlAstDelete ) .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); return StandardReactiveJdbcMutationExecutor.INSTANCE .executeReactive( @@ -231,15 +156,6 @@ private static CompletionStage cleanUpCollectionTable( .thenCompose( CompletionStages::voidFuture ); } - private static void complete(CompletableFuture stage, Throwable throwable) { - if ( throwable == null ) { - stage.complete( null ); - } - else { - stage.completeExceptionally( throwable ); - } - } - private static void doNothing(Integer i, PreparedStatement ps) { } } 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 d1ea9026f..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; @@ -96,7 +96,7 @@ default CompletionStage reactiveExecute(DomainQueryExecutionContext exe executionContext.getQueryOptions(), executionContext.getSession().getLoadQueryInfluencers(), executionContext.getQueryParameterBindings(), - factory + factory.getSqlTranslationEngine() ); final Map, List> parameterResolutions; if ( getDomainParameterXref().getSqmParameterCount() == 0 ) { @@ -117,8 +117,7 @@ default CompletionStage reactiveExecute(DomainQueryExecutionContext exe true, restriction, sqmConverter, - executionContext, - factory + executionContext ), // The id-select cte will be reused multiple times CteMaterialization.MATERIALIZED @@ -159,8 +158,6 @@ default CompletionStage reactiveExecute(DomainQueryExecutionContext exe executionContext.getQueryParameterBindings(), getDomainParameterXref(), SqmUtil.generateJdbcParamsXref( getDomainParameterXref(), sqmConverter ), - factory.getRuntimeMetamodels().getMappingMetamodel(), - navigablePath -> sqmConverter.getMutatingTableGroup(), new SqmParameterMappingModelResolutionAccess() { @Override @SuppressWarnings("unchecked") public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { @@ -179,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 aaf1be3e9..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 @@ -8,8 +8,6 @@ import java.lang.invoke.MethodHandles; import java.util.AbstractMap; import java.util.ArrayList; -import java.util.Collections; -import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletionStage; @@ -25,7 +23,7 @@ import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.SqlExpressible; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.query.results.TableGroupImpl; +import org.hibernate.query.results.internal.TableGroupImpl; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.BinaryArithmeticOperator; import org.hibernate.query.sqm.ComparisonOperator; @@ -43,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; @@ -62,7 +60,6 @@ import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.expression.QueryLiteral; import org.hibernate.sql.ast.tree.expression.SelfRenderingSqlFragmentExpression; import org.hibernate.sql.ast.tree.from.NamedTableReference; @@ -86,12 +83,15 @@ public class ReactiveCteInsertHandler extends CteInsertHandler implements Reacti private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + private final SessionFactoryImplementor sessionFactory; + public ReactiveCteInsertHandler( CteTable cteTable, SqmInsertStatement sqmStatement, DomainParameterXref domainParameterXref, SessionFactoryImplementor sessionFactory) { super( cteTable, sqmStatement, domainParameterXref, sessionFactory ); + this.sessionFactory = sessionFactory; } @Override @@ -123,18 +123,10 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec executionContext.getQueryOptions(), executionContext.getSession().getLoadQueryInfluencers(), executionContext.getQueryParameterBindings(), - factory + factory.getSqlTranslationEngine() ); final TableGroup insertingTableGroup = sqmConverter.getMutatingTableGroup(); - final Map, List>> parameterResolutions; - if ( getDomainParameterXref().getSqmParameterCount() == 0 ) { - parameterResolutions = Collections.emptyMap(); - } - else { - parameterResolutions = new IdentityHashMap<>(); - } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // visit the insertion target using our special converter, collecting // information about the target paths @@ -193,7 +185,7 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec querySpec -> { // This returns true if the insertion target uses a sequence with an optimizer // in which case we will fill the row_number column instead of the id column - if ( additionalInsertValues.applySelections( querySpec, getSessionFactory() ) ) { + if ( additionalInsertValues.applySelections( querySpec, sessionFactory ) ) { final CteColumn rowNumberColumn = getCteTable().getCteColumns() .get( getCteTable().getCteColumns().size() - 1 ); final ColumnReference columnReference = new ColumnReference( @@ -218,7 +210,7 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec 0, SqmInsertStrategyHelper.createRowNumberingExpression( querySpec, - getSessionFactory() + sessionFactory ) ) ); @@ -288,11 +280,7 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec targetPathCteColumns.add( rowNumberColumn ); } - final CteTable entityCteTable = createCteTable( - getCteTable(), - targetPathCteColumns, - factory - ); + final CteTable entityCteTable = createCteTable( getCteTable(), targetPathCteColumns ); // Create the main query spec that will return the count of rows final QuerySpec querySpec = new QuerySpec( true, 1 ); @@ -357,7 +345,7 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec ); final String fragment = ( (BulkInsertionCapableIdentifierGenerator) entityDescriptor.getGenerator() ) .determineBulkInsertionIdentifierGenerationSelectFragment( - getSessionFactory().getSqlStringGenerationContext() + sessionFactory.getSqlStringGenerationContext() ); rowsWithSequenceQuery.getSelectClause().addSqlSelection( new SqlSelectionImpl( @@ -463,11 +451,7 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec } else { targetPathCteColumns.add( 0, getCteTable().getCteColumns().get( 0 ) ); - finalEntityCteTable = createCteTable( - getCteTable(), - targetPathCteColumns, - factory - ); + finalEntityCteTable = createCteTable( getCteTable(), targetPathCteColumns ); } final List cteColumns = finalEntityCteTable.getCteColumns(); for ( int i = 1; i < cteColumns.size(); i++ ) { @@ -506,11 +490,7 @@ else if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) ); statement.addCteStatement( baseEntityCte ); targetPathCteColumns.add( 0, getCteTable().getCteColumns().get( 0 ) ); - final CteTable finalEntityCteTable = createCteTable( - getCteTable(), - targetPathCteColumns, - factory - ); + final CteTable finalEntityCteTable = createCteTable( getCteTable(), targetPathCteColumns ); final QuerySpec finalQuerySpec = new QuerySpec( true ); final SelectStatement finalQueryStatement = new SelectStatement( finalQuerySpec ); entityCte = new CteStatement( @@ -537,7 +517,6 @@ else if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) targetPathColumns, assignsId, sqmConverter, - parameterResolutions, factory ); @@ -564,18 +543,18 @@ else if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) executionContext.getQueryParameterBindings(), getDomainParameterXref(), SqmUtil.generateJdbcParamsXref( getDomainParameterXref(), sqmConverter ), - factory.getRuntimeMetamodels().getMappingMetamodel(), - navigablePath -> sqmConverter.getMutatingTableGroup(), new SqmParameterMappingModelResolutionAccess() { @Override + @SuppressWarnings("unchecked") public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmConverter.getSqmParameterMappingModelExpressibleResolutions().get( parameter ); + return (MappingModelExpressible) sqmConverter.getSqmParameterMappingModelExpressibleResolutions() + .get( parameter ); } }, executionContext.getSession() ); final JdbcOperationQuerySelect select = translator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - return ( (ReactiveSession) executionContext.getSession() ) + return ( (ReactiveSharedSessionContractImplementor) executionContext.getSession() ) .reactiveAutoFlushIfRequired( select.getAffectedTableNames() ) .thenCompose( v -> StandardReactiveSelectExecutor.INSTANCE.list( select, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java index 3644af942..33acc38f6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java @@ -15,9 +15,8 @@ import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.temptable.AfterUseAction; import org.hibernate.query.sqm.mutation.internal.temptable.InsertExecutionDelegate; -import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveTableBasedInsertHandler; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.from.TableGroup; @@ -32,9 +31,7 @@ */ public class ReactiveInsertExecutionDelegate extends InsertExecutionDelegate implements ReactiveTableBasedInsertHandler.ReactiveExecutionDelegate { - public ReactiveInsertExecutionDelegate( - SqmInsertStatement sqmInsert, MultiTableSqmMutationConverter sqmConverter, TemporaryTable entityTable, AfterUseAction afterUseAction, @@ -48,7 +45,6 @@ public ReactiveInsertExecutionDelegate( JdbcParameter sessionUidParameter, DomainQueryExecutionContext executionContext) { super( - sqmInsert, sqmConverter, entityTable, afterUseAction, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java index 7f1444496..e8f7bbea6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java @@ -26,8 +26,8 @@ import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.query.sqm.ComparisonOperator; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.temptable.AfterUseAction; -import org.hibernate.query.sqm.mutation.internal.temptable.BeforeUseAction; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; +import org.hibernate.query.sqm.mutation.spi.BeforeUseAction; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveTemporaryTableHelper.TemporaryTableCreationWork; @@ -47,7 +47,7 @@ import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.results.internal.SqlSelectionImpl; @@ -162,7 +162,7 @@ public static CompletionStage saveIntoTemporaryTable( ) ); } - final JdbcOperationQueryInsert jdbcInsert = sqlAstTranslatorFactory.buildInsertTranslator( factory, temporaryTableInsert ) + final JdbcOperationQueryMutation jdbcInsert = sqlAstTranslatorFactory.buildMutationTranslator( factory, temporaryTableInsert ) .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); lockOptions.setLockMode( lockMode ); @@ -210,7 +210,7 @@ public static QuerySpec createIdTableSelectQuerySpec( querySpec.getFromClause().addRoot( idTableGroup ); - applyIdTableSelections( querySpec, idTableReference, idTable, fkModelPart, executionContext ); + applyIdTableSelections( querySpec, idTableReference, idTable, fkModelPart ); applyIdTableRestrictions( querySpec, idTableReference, idTable, sessionUidAccess, executionContext ); return querySpec; @@ -221,8 +221,7 @@ private static void applyIdTableSelections( QuerySpec querySpec, TableReference tableReference, TemporaryTable idTable, - ModelPart fkModelPart, - ExecutionContext executionContext) { + ModelPart fkModelPart) { if ( fkModelPart == null ) { final int size = idTable.getEntityDescriptor().getIdentifierMapping().getJdbcTypeCount(); for ( int i = 0; i < size; i++ ) { @@ -245,20 +244,18 @@ private static void applyIdTableSelections( } else { fkModelPart.forEachSelectable( - (i, selectableMapping) -> { - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - i, - new ColumnReference( - tableReference, - selectableMapping.getSelectionExpression(), - false, - null, - selectableMapping.getJdbcMapping() - ) - ) - ); - } + (i, selectableMapping) -> querySpec.getSelectClause() + .addSqlSelection( new SqlSelectionImpl( + i, + new ColumnReference( + tableReference, + selectableMapping.getSelectionExpression(), + false, + null, + selectableMapping.getJdbcMapping() + ) + ) + ) ); } } @@ -275,8 +272,7 @@ private static void applyIdTableRestrictions( new ColumnReference( idTableReference, idTable.getSessionUidColumn().getColumnName(), - false, - null, + false, null, idTable.getSessionUidColumn().getJdbcMapping() ), ComparisonOperator.EQUAL, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java index ce76569ce..2f7f7bb37 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java @@ -84,14 +84,4 @@ public boolean isDropIdTables() { public void setDropIdTables(boolean dropIdTables) { this.dropIdTables = dropIdTables; } - - @Override - public CompletionStage getDropTableActionStage() { - return tableDroppedStage; - } - - @Override - public CompletionStage getCreateTableActionStage() { - return tableCreatedStage; - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java index 15c2bd293..66b001520 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java @@ -81,16 +81,6 @@ public CompletionStage reactiveExecuteDelete( ).reactiveExecute( context ) ); } - @Override - public CompletionStage getDropTableActionStage() { - return tableDroppedStage; - } - - @Override - public CompletionStage getCreateTableActionStage() { - return tableCreatedStage; - } - @Override public boolean isPrepared() { return prepared; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableStrategy.java index 37c3d23bc..095230723 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableStrategy.java @@ -43,10 +43,6 @@ static String sessionIdentifier(SharedSessionContractImplementor session) { TemporaryTable getTemporaryTable(); - CompletionStage getDropTableActionStage(); - - CompletionStage getCreateTableActionStage(); - SessionFactoryImplementor getSessionFactory(); default void prepare(MappingModelCreationProcess mappingModelCreationProcess, JdbcConnectionAccess connectionAccess, CompletableFuture tableCreatedStage) { @@ -65,22 +61,23 @@ default void prepare(MappingModelCreationProcess mappingModelCreationProcess, Jd if ( !createIdTables ) { tableCreatedStage.complete( null ); } - - LOG.debugf( "Creating global-temp ID table : %s", getTemporaryTable().getTableExpression() ); - - connectionStage() - .thenCompose( this::createTable ) - .whenComplete( (connection, throwable) -> releaseConnection( connection ) - .thenAccept( v -> { - if ( throwable == null ) { - setDropIdTables( configService ); - tableCreatedStage.complete( null ); - } - else { - tableCreatedStage.completeExceptionally( throwable ); - } - } ) - ); + else { + LOG.debugf( "Creating global-temp ID table : %s", getTemporaryTable().getTableExpression() ); + + connectionStage() + .thenCompose( this::createTable ) + .whenComplete( (connection, throwable) -> releaseConnection( connection ) + .thenAccept( v -> { + if ( throwable == null ) { + setDropIdTables( configService ); + tableCreatedStage.complete( null ); + } + else { + tableCreatedStage.completeExceptionally( throwable ); + } + } ) + ); + } } private CompletionStage releaseConnection(ReactiveConnection connection) { @@ -147,24 +144,25 @@ default void release( if ( !isDropIdTables() ) { tableDroppedStage.complete( null ); } - - setDropIdTables( false ); - - final TemporaryTable temporaryTable = getTemporaryTable(); - LOG.debugf( "Dropping global-tempk ID table : %s", temporaryTable.getTableExpression() ); - - connectionStage() - .thenCompose( this::dropTable ) - .whenComplete( (connection, throwable) -> releaseConnection( connection ) - .thenAccept( v -> { - if ( throwable == null ) { - tableDroppedStage.complete( null ); - } - else { - tableDroppedStage.completeExceptionally( throwable ); - } - } ) - ); + else { + setDropIdTables( false ); + + final TemporaryTable temporaryTable = getTemporaryTable(); + LOG.debugf( "Dropping global-temp ID table : %s", temporaryTable.getTableExpression() ); + + connectionStage() + .thenCompose( this::dropTable ) + .whenComplete( (connection, throwable) -> releaseConnection( connection ) + .thenAccept( v -> { + if ( throwable == null ) { + tableDroppedStage.complete( null ); + } + else { + tableDroppedStage.completeExceptionally( throwable ); + } + } ) + ); + } } private CompletionStage dropTable(ReactiveConnection connection) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java index 3d6681881..ae2298ddd 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java @@ -10,8 +10,8 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.internal.temptable.AfterUseAction; import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableInsertStrategy; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java index 983a21043..fe07871eb 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java @@ -10,8 +10,8 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.internal.temptable.AfterUseAction; import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableStrategy.java index 472dc0ce7..ba70601ad 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableStrategy.java @@ -68,22 +68,23 @@ default void prepare(MappingModelCreationProcess mappingModelCreationProcess, Jd if ( !createIdTables ) { tableCreatedStage.complete( null ); } - - LOG.debugf( "Creating persistent ID table : %s", getTemporaryTable().getTableExpression() ); - - connectionStage() - .thenCompose( this::createTable ) - .whenComplete( (connection, throwable) -> releaseConnection( connection ) - .thenAccept( v -> { - if ( throwable == null ) { - setDropIdTables( configService ); - tableCreatedStage.complete( null ); - } - else { - tableCreatedStage.completeExceptionally( throwable ); - } - } ) - ); + else { + LOG.debugf( "Creating persistent ID table : %s", getTemporaryTable().getTableExpression() ); + + connectionStage() + .thenCompose( this::createTable ) + .whenComplete( (connection, throwable) -> releaseConnection( connection ) + .thenAccept( v -> { + if ( throwable == null ) { + setDropIdTables( configService ); + tableCreatedStage.complete( null ); + } + else { + tableCreatedStage.completeExceptionally( throwable ); + } + } ) + ); + } } private CompletionStage releaseConnection(ReactiveConnection connection) { @@ -150,24 +151,25 @@ default void release( if ( !isDropIdTables() ) { tableDroppedStage.complete( null ); } - - setDropIdTables( false ); - - final TemporaryTable temporaryTable = getTemporaryTable(); - LOG.debugf( "Dropping persistent ID table : %s", temporaryTable.getTableExpression() ); - - connectionStage() - .thenCompose( this::dropTable ) - .whenComplete( (connection, throwable) -> releaseConnection( connection ) - .thenAccept( v -> { - if ( throwable == null ) { - tableDroppedStage.complete( null ); - } - else { - tableDroppedStage.completeExceptionally( throwable ); - } - } ) - ); + else { + setDropIdTables( false ); + + final TemporaryTable temporaryTable = getTemporaryTable(); + LOG.debugf( "Dropping persistent ID table : %s", temporaryTable.getTableExpression() ); + + connectionStage() + .thenCompose( this::dropTable ) + .whenComplete( (connection, throwable) -> releaseConnection( connection ) + .thenAccept( v -> { + if ( throwable == null ) { + tableDroppedStage.complete( null ); + } + else { + tableDroppedStage.completeExceptionally( throwable ); + } + } ) + ); + } } private CompletionStage dropTable(ReactiveConnection connection) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java index 61a866756..0e1097b63 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java @@ -27,7 +27,6 @@ import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.persister.entity.Joinable; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryParameterBindings; @@ -36,10 +35,10 @@ import org.hibernate.query.sqm.internal.SqmUtil; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.TableKeyExpressionCollector; -import org.hibernate.query.sqm.mutation.internal.temptable.AfterUseAction; import org.hibernate.query.sqm.mutation.internal.temptable.ColumnReferenceCheckingSqlAstWalker; import org.hibernate.query.sqm.mutation.internal.temptable.ExecuteWithoutIdTableHelper; import org.hibernate.query.sqm.mutation.internal.temptable.RestrictedDeleteExecutionDelegate; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.expression.SqmParameter; @@ -48,6 +47,7 @@ import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveSqmMutationStrategyHelper; import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; import org.hibernate.reactive.util.impl.CompletionStages; +import org.hibernate.reactive.util.impl.CompletionStages.Completable; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.tree.delete.DeleteStatement; @@ -65,11 +65,10 @@ import org.hibernate.sql.ast.tree.predicate.PredicateCollector; import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQueryDelete; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import static org.hibernate.query.sqm.mutation.internal.temptable.ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec; -import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** @@ -118,7 +117,7 @@ public ReactiveRestrictedDeleteExecutionDelegate( queryOptions, loadQueryInfluencers, queryParameterBindings, - sessionFactory + sessionFactory.getSqlTranslationEngine() ); } @@ -127,7 +126,7 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec final EntityPersister entityDescriptor = sessionFactory.getRuntimeMetamodels() .getMappingMetamodel() .getEntityDescriptor( sqmDelete.getTarget().getEntityName() ); - final String hierarchyRootTableName = ( (Joinable) entityDescriptor ).getTableName(); + final String hierarchyRootTableName = entityDescriptor.getTableName(); final TableGroup deletingTableGroup = converter.getMutatingTableGroup(); @@ -150,6 +149,7 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec deletingTableGroup, true, executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(), + false, null, converter ); @@ -175,7 +175,6 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec if ( needsIdTable ) { return executeWithIdTable( predicateCollector.getPredicate(), - deletingTableGroup, converter.getJdbcParamsBySqmParam(), converter.getSqmParameterMappingModelExpressibleResolutions(), executionContextAdapter @@ -203,7 +202,7 @@ private CompletionStage executeWithoutIdTable( assert entityDescriptor == entityDescriptor.getRootEntityDescriptor(); final EntityPersister rootEntityPersister = entityDescriptor.getEntityPersister(); - final String rootTableName = ( (Joinable) rootEntityPersister ).getTableName(); + final String rootTableName = rootEntityPersister.getTableName(); final NamedTableReference rootTableReference = (NamedTableReference) tableGroup.resolveTableReference( tableGroup.getNavigablePath(), rootTableName @@ -225,18 +224,16 @@ private CompletionStage executeWithoutIdTable( domainParameterXref, () -> restrictionSqmParameterResolutions ), - sessionFactory.getRuntimeMetamodels().getMappingMetamodel(), - navigablePath -> tableGroup, new SqmParameterMappingModelResolutionAccess() { - @Override - @SuppressWarnings("unchecked") + @Override @SuppressWarnings("unchecked") public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) paramTypeResolutions.get( parameter ); + return (MappingModelExpressible) paramTypeResolutions.get(parameter); } }, executionContext.getSession() ); + CompletionStage cleanUpCollectionTablesStage = ReactiveSqmMutationStrategyHelper.cleanUpCollectionTables( entityDescriptor, (tableReference, attributeMapping) -> { @@ -480,9 +477,9 @@ private static CompletionStage executeSqlDelete( final JdbcServices jdbcServices = factory.getJdbcServices(); - final JdbcOperationQueryDelete jdbcDelete = jdbcServices.getJdbcEnvironment() + final JdbcOperationQueryMutation jdbcDelete = jdbcServices.getJdbcEnvironment() .getSqlAstTranslatorFactory() - .buildDeleteTranslator( factory, sqlAst ) + .buildMutationTranslator( factory, sqlAst ) .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); return StandardReactiveJdbcMutationExecutor.INSTANCE @@ -500,7 +497,6 @@ private static CompletionStage executeSqlDelete( private CompletionStage executeWithIdTable( Predicate predicate, - TableGroup deletingTableGroup, Map, List>> restrictionSqmParameterResolutions, Map, MappingModelExpressible> paramTypeResolutions, ExecutionContext executionContext) { @@ -511,13 +507,10 @@ private CompletionStage executeWithIdTable( domainParameterXref, () -> restrictionSqmParameterResolutions ), - sessionFactory.getRuntimeMetamodels().getMappingMetamodel(), - navigablePath -> deletingTableGroup, new SqmParameterMappingModelResolutionAccess() { - @Override - @SuppressWarnings("unchecked") + @Override @SuppressWarnings("unchecked") public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) paramTypeResolutions.get( parameter ); + return (MappingModelExpressible) paramTypeResolutions.get(parameter); } }, executionContext.getSession() @@ -589,20 +582,20 @@ private CompletionStage executeUsingIdTable( } ); } - private CompletionStage visitConstraintOrderedTables(QuerySpec idTableIdentifierSubQuery, ExecutionContext executionContext) { - final CompletionStage[] resultStage = new CompletionStage[]{ completedFuture( -1 ) }; - entityDescriptor.visitConstraintOrderedTables( - (tableExpression, tableKeyColumnVisitationSupplier) -> { - resultStage[0] = resultStage[0].thenCompose( ignore -> deleteFromTableUsingIdTable( - tableExpression, - tableKeyColumnVisitationSupplier, - idTableIdentifierSubQuery, - executionContext - ) ); - } - ); - return resultStage[0] - .thenCompose( CompletionStages::voidFuture ); + private CompletionStage visitConstraintOrderedTables( + QuerySpec idTableIdentifierSubQuery, + ExecutionContext executionContext) { + final Completable completable = new Completable<>(); + entityDescriptor + .visitConstraintOrderedTables( (tableExpression, tableKeyColumnVisitationSupplier) -> deleteFromTableUsingIdTable( + tableExpression, + tableKeyColumnVisitationSupplier, + idTableIdentifierSubQuery, + executionContext + ) + .handle( completable::complete ) + ); + return completable.getStage().thenCompose( CompletionStages::voidFuture ); } private CompletionStage deleteFromTableUsingIdTable( diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java index c7af71107..bb9ceff9c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java @@ -14,8 +14,8 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.internal.temptable.AfterUseAction; import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedDeleteHandler; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java index b27340418..d697758ed 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java @@ -18,8 +18,8 @@ import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.temptable.AfterUseAction; import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedInsertHandler; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; @@ -92,7 +92,6 @@ protected ExecutionDelegate buildExecutionDelegate( JdbcParameter sessionUidParameter, DomainQueryExecutionContext executionContext) { return new ReactiveInsertExecutionDelegate( - sqmInsert, sqmConverter, entityTable, afterUseAction, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java index a247c8d7d..7379f962e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java @@ -18,8 +18,8 @@ import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.temptable.AfterUseAction; import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedUpdateHandler; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java index d027235a2..126c168f8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java @@ -20,7 +20,6 @@ import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.pool.ReactiveConnection; -import org.hibernate.reactive.pool.impl.Parameters; import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.reactive.util.impl.CompletionStages; @@ -68,11 +67,8 @@ public TemporaryTableCreationWork( @Override public CompletionStage reactiveExecute(ReactiveConnection connection) { - final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); - try { final String creationCommand = exporter.getSqlCreateCommand( temporaryTable ); - logStatement( creationCommand, jdbcServices ); return connection.executeUnprepared( creationCommand ) .handle( (integer, throwable) -> { @@ -116,11 +112,8 @@ public TemporaryTableDropWork( @Override public CompletionStage reactiveExecute(ReactiveConnection connection) { - final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); - try { final String dropCommand = exporter.getSqlDropCommand( temporaryTable ); - logStatement( dropCommand, jdbcServices ); return connection.update( dropCommand ) .handle( (integer, throwable) -> { @@ -144,10 +137,7 @@ public static CompletionStage cleanTemporaryTableRows( TemporaryTableExporter exporter, Function sessionUidAccess, SharedSessionContractImplementor session) { - // Workaround for https://hibernate.atlassian.net/browse/HHH-16486 - final String sql = Parameters.instance( temporaryTable.getDialect() ) - .process( exporter.getSqlTruncateCommand( temporaryTable, sessionUidAccess, session ) ); - + final String sql = exporter.getSqlTruncateCommand( temporaryTable, sessionUidAccess, session ); Object[] params = PreparedStatementAdaptor.bind( ps -> { if ( temporaryTable.getSessionUidColumn() != null ) { final String sessionUid = sessionUidAccess.apply( session ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java index f70e5c794..927105dfa 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java @@ -23,8 +23,8 @@ import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.temptable.AfterUseAction; import org.hibernate.query.sqm.mutation.internal.temptable.UpdateExecutionDelegate; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; @@ -44,8 +44,7 @@ import org.hibernate.sql.ast.tree.update.Assignment; import org.hibernate.sql.ast.tree.update.UpdateStatement; import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert; -import org.hibernate.sql.exec.spi.JdbcOperationQueryUpdate; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.results.internal.SqlSelectionImpl; import static org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec; @@ -200,8 +199,8 @@ private CompletionStage executeUpdate(QuerySpec idTableSubQuery, Execut new InSubQueryPredicate( keyExpression, idTableSubQuery, false ) ); - final JdbcOperationQueryUpdate jdbcUpdate = sqlAstTranslatorFactory - .buildUpdateTranslator( getSessionFactory(), sqlAst ) + final JdbcOperationQueryMutation jdbcUpdate = sqlAstTranslatorFactory + .buildMutationTranslator( getSessionFactory(), sqlAst ) .translate( getJdbcParameterBindings(), executionContext.getQueryOptions() ); return StandardReactiveJdbcMutationExecutor.INSTANCE @@ -274,8 +273,8 @@ private CompletionStage executeInsert( insertSqlAst.addTargetColumnReferences( targetColumnReferences.toArray( new ColumnReference[0] ) ); insertSqlAst.setSourceSelectStatement( insertSourceSelectQuerySpec ); - final JdbcOperationQueryInsert jdbcInsert = sqlAstTranslatorFactory - .buildInsertTranslator( getSessionFactory(), insertSqlAst ) + final JdbcOperationQueryMutation jdbcInsert = sqlAstTranslatorFactory + .buildMutationTranslator( getSessionFactory(), insertSqlAst ) .translate( getJdbcParameterBindings(), executionContext.getQueryOptions() ); return StandardReactiveJdbcMutationExecutor.INSTANCE 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 e67605c81..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,13 +5,11 @@ */ 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; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.query.criteria.JpaCriteriaInsertSelect; +import org.hibernate.query.criteria.JpaCriteriaInsert; import org.hibernate.reactive.common.AffectedEntities; import org.hibernate.reactive.common.ResultSetMapping; import org.hibernate.reactive.query.ReactiveMutationQuery; @@ -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,20 +53,27 @@ public interface ReactiveQueryProducer extends ReactiveConnectionSupplier { ReactiveQuery createReactiveQuery(String queryString); + ReactiveQuery createReactiveQuery(TypedQueryReference typedQueryReference); + ReactiveQuery createReactiveQuery(CriteriaQuery criteriaQuery); ReactiveQuery createReactiveQuery(String queryString, Class resultType); + ReactiveQueryImplementor createReactiveNamedQuery(String queryString); + ReactiveQueryImplementor createReactiveNamedQuery(String queryString, Class resultType); ReactiveNativeQuery createReactiveNativeQuery(String sqlString); ReactiveNativeQuery createReactiveNativeQuery(String sqlString, Class resultClass); + @Deprecated(forRemoval = true) ReactiveNativeQuery createReactiveNativeQuery(String sqlString, Class resultClass, String tableAlias); + @Deprecated(forRemoval = true) ReactiveNativeQuery createReactiveNativeQuery(String sqlString, String resultSetMappingName); + @Deprecated(forRemoval = true) ReactiveNativeQuery createReactiveNativeQuery(String sqlString, String resultSetMappingName, Class resultClass); ReactiveSelectionQuery createReactiveSelectionQuery(String hqlString, Class resultType); @@ -79,7 +86,7 @@ public interface ReactiveQueryProducer extends ReactiveConnectionSupplier { ReactiveMutationQuery createReactiveMutationQuery(CriteriaDelete deleteQuery); - ReactiveMutationQuery createReactiveMutationQuery(JpaCriteriaInsertSelect insertSelect); + ReactiveMutationQuery createReactiveMutationQuery(JpaCriteriaInsert insert); ReactiveMutationQuery createNativeReactiveMutationQuery(String sqlString); 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 d521f2e8c..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,26 +44,29 @@ public interface ReactiveSession extends ReactiveQueryProducer, ReactiveSharedSe ReactiveActionQueue getReactiveActionQueue(); + @Override SessionImplementor getSharedContract(); CompletionStage reactiveFetch(E entity, Attribute field); CompletionStage reactivePersist(Object entity); + CompletionStage reactivePersist(String entityName, Object object); + CompletionStage reactivePersist(Object object, PersistContext copiedAlready); CompletionStage reactivePersistOnFlush(Object entity, PersistContext copiedAlready); 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(); @@ -76,6 +79,8 @@ public interface ReactiveSession extends ReactiveQueryProducer, ReactiveSharedSe CompletionStage reactiveLock(Object entity, LockOptions lockMode); + CompletionStage reactiveLock(String entityName, Object entity, LockOptions lockMode); + CompletionStage reactiveGet(Class entityClass, Object id); CompletionStage reactiveFind(Class entityClass, Object id, LockOptions lockOptions, EntityGraph fetchGraph); @@ -84,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 bed4725c5..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 @@ -10,6 +10,8 @@ import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import jakarta.persistence.EntityGraph; + +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -26,11 +28,13 @@ @Incubating public interface ReactiveStatelessSession extends ReactiveQueryProducer, ReactiveSharedSessionContractImplementor { - CompletionStage reactiveGet(Class entityClass, Object id); + CompletionStage reactiveGet(Class entityClass, Object id); + + CompletionStage> reactiveGet(Class entityClass, Object... id); CompletionStage reactiveGet(String entityName, Object id); - CompletionStage reactiveGet(Class entityClass, Object id, LockMode lockMode, EntityGraph fetchGraph); + CompletionStage reactiveGet(Class entityClass, Object id, LockMode lockMode, EntityGraph fetchGraph); CompletionStage reactiveGet(String entityName, Object id, LockMode lockMode, EntityGraph fetchGraph); @@ -42,16 +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); @@ -71,4 +71,6 @@ public interface ReactiveStatelessSession extends ReactiveQueryProducer, Reactiv boolean isOpen(); void close(CompletableFuture closing); + + Object getIdentifier(Object entity); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionFactoryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionFactoryImpl.java index 85c921c7c..f8029e297 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionFactoryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionFactoryImpl.java @@ -9,7 +9,6 @@ import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.internal.SessionFactoryImpl; -import org.hibernate.metamodel.spi.RuntimeMetamodelsImplementor; import org.hibernate.query.spi.QueryEngine; import org.hibernate.reactive.boot.spi.ReactiveMetadataImplementor; import org.hibernate.reactive.mutiny.Mutiny; @@ -28,11 +27,6 @@ public ReactiveSessionFactoryImpl(MetadataImplementor bootMetamodel, SessionFact super( new ReactiveMetadataImplementor( bootMetamodel ), options, bootstrapContext ); } - @Override - public RuntimeMetamodelsImplementor getRuntimeMetamodels() { - return super.getRuntimeMetamodels(); - } - @Override public QueryEngine getQueryEngine() { return super.getQueryEngine(); 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 f3a24808d..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; @@ -28,7 +20,7 @@ import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.dialect.Dialect; -import org.hibernate.engine.internal.StatefulPersistenceContext; +import org.hibernate.engine.internal.ReactivePersistenceContextAdapter; import org.hibernate.engine.spi.EffectiveEntityGraph; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; @@ -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; @@ -55,7 +45,6 @@ import org.hibernate.event.spi.PersistEvent; import org.hibernate.event.spi.RefreshContext; import org.hibernate.event.spi.RefreshEvent; -import org.hibernate.event.spi.ResolveNaturalIdEvent; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.RootGraph; import org.hibernate.graph.spi.RootGraphImplementor; @@ -63,31 +52,41 @@ import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.internal.SessionImpl; 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; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import org.hibernate.query.IllegalMutationQueryException; -import org.hibernate.query.criteria.JpaCriteriaInsertSelect; +import org.hibernate.query.UnknownNamedQueryException; +import org.hibernate.query.criteria.JpaCriteriaInsert; import org.hibernate.query.hql.spi.SqmQueryImplementor; import org.hibernate.query.named.NamedResultSetMappingMemento; +import org.hibernate.query.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; import org.hibernate.query.sql.spi.NativeQueryImplementor; import org.hibernate.query.sqm.internal.SqmUtil; +import org.hibernate.query.sqm.spi.NamedSqmQueryMemento; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement; +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; import org.hibernate.reactive.common.InternalStateAssertions; import org.hibernate.reactive.common.ResultSetMapping; import org.hibernate.reactive.engine.ReactiveActionQueue; -import org.hibernate.reactive.engine.impl.ReactivePersistenceContextAdapter; import org.hibernate.reactive.event.ReactiveDeleteEventListener; import org.hibernate.reactive.event.ReactiveFlushEventListener; import org.hibernate.reactive.event.ReactiveLoadEventListener; @@ -95,9 +94,9 @@ import org.hibernate.reactive.event.ReactiveMergeEventListener; import org.hibernate.reactive.event.ReactivePersistEventListener; import org.hibernate.reactive.event.ReactiveRefreshEventListener; -import org.hibernate.reactive.event.ReactiveResolveNaturalIdEventListener; import org.hibernate.reactive.event.impl.DefaultReactiveAutoFlushEventListener; import org.hibernate.reactive.event.impl.DefaultReactiveInitializeCollectionEventListener; +import org.hibernate.reactive.loader.ast.spi.ReactiveNaturalIdLoader; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; @@ -118,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; @@ -129,16 +137,17 @@ import static org.hibernate.engine.spi.NaturalIdResolutions.INVALID_NATURAL_ID_REFERENCE; import static org.hibernate.event.spi.LoadEventListener.IMMEDIATE_LOAD; import static org.hibernate.internal.util.StringHelper.isEmpty; -import static org.hibernate.internal.util.StringHelper.isNotEmpty; import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; import static org.hibernate.reactive.common.InternalStateAssertions.assertUseOnEventLoop; import static org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister.forceInitialize; 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; /** @@ -186,8 +195,8 @@ private void threadCheck() { } @Override - protected StatefulPersistenceContext createPersistenceContext() { - return new ReactivePersistenceContextAdapter( this ); + protected PersistenceContext createPersistenceContext() { + return new ReactivePersistenceContextAdapter( super.createPersistenceContext() ); } @Override @@ -209,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(); @@ -243,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(); @@ -262,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() @@ -270,9 +312,8 @@ public CompletionStage reactiveFetch(T association, boolean unproxy) { if ( association == null ) { return nullFuture(); } - - if ( association instanceof HibernateProxy ) { - LazyInitializer initializer = ( (HibernateProxy) association ).getHibernateLazyInitializer(); + else if ( association instanceof HibernateProxy proxy ) { + final LazyInitializer initializer = proxy.getHibernateLazyInitializer(); if ( !initializer.isUninitialized() ) { return completedFuture( unproxy ? (T) initializer.getImplementation() : association ); } @@ -288,13 +329,12 @@ public CompletionStage reactiveFetch(T association, boolean unproxy) { } ); } } - else if ( association instanceof PersistentCollection ) { - final PersistentCollection persistentCollection = (PersistentCollection) association; - if ( persistentCollection.wasInitialized() ) { + else if (association instanceof PersistentCollection collection) { + if ( collection.wasInitialized() ) { return completedFuture( association ); } else { - return reactiveInitializeCollection( persistentCollection, false ) + return reactiveInitializeCollection( collection, false ) // don't reassociate the collection instance, because // its owner isn't associated with this session .thenApply( v -> association ); @@ -303,9 +343,8 @@ else if ( association instanceof PersistentCollection ) { else if ( isPersistentAttributeInterceptable( association ) ) { final PersistentAttributeInterceptable interceptable = asPersistentAttributeInterceptable( association ); final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); - if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { - EnhancementAsProxyLazinessInterceptor eapli = (EnhancementAsProxyLazinessInterceptor) interceptor; - return forceInitialize( association, null, eapli.getIdentifier(), eapli.getEntityName(), this ) + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor lazinessInterceptor ) { + return forceInitialize( association, null, lazinessInterceptor.getIdentifier(), lazinessInterceptor.getEntityName(), this ) .thenApply( i -> association ); } @@ -332,29 +371,55 @@ 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) ); } } } - return createCriteriaQuery( selectStatement, criteriaQuery.getResultType() ); + return createReactiveCriteriaQuery( selectStatement, criteriaQuery.getResultType() ); } catch (RuntimeException e) { - if ( getSessionFactory().getJpaMetamodel().getJpaCompliance().isJpaTransactionComplianceEnabled() ) { + if ( getSessionFactory().getSessionFactoryOptions().getJpaCompliance().isJpaTransactionComplianceEnabled() ) { markForRollbackOnly(); } throw getExceptionConverter().convert( e ); } } - private ReactiveQueryImplementor createCriteriaQuery(SqmStatement criteria, Class resultType) { + protected ReactiveQueryImplementor createReactiveCriteriaQuery(SqmStatement criteria, Class resultType) { final ReactiveQuerySqmImpl query = new ReactiveQuerySqmImpl<>( criteria, resultType, this ); applyQuerySettingsAndHints( query ); 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 ); @@ -367,7 +432,7 @@ public ReactiveQuery createReactiveQuery(String queryString, Class exp delayedAfterCompletion(); try { - final HqlInterpretation interpretation = interpretHql( queryString, expectedResultType ); + final HqlInterpretation interpretation = interpretHql( queryString, expectedResultType ); final ReactiveQuerySqmImpl query = new ReactiveQuerySqmImpl<>( queryString, interpretation, expectedResultType, this ); applyQuerySettingsAndHints( query ); @@ -415,23 +480,21 @@ public void prepareForQueryExecution(boolean requiresTxn) { @Override public ReactiveNativeQuery createReactiveNativeQuery(String sqlString, Class resultClass) { final ReactiveNativeQuery query = createReactiveNativeQuery( sqlString ); - return addResultType( resultClass, query ); + handleTupleResultType( resultClass, query ); + addEntityOrResultType( resultClass, query ); + return query; } - private ReactiveNativeQuery addResultType(Class resultClass, ReactiveNativeQuery query) { - if ( Tuple.class.equals( resultClass ) ) { - query.setTupleTransformer( new NativeQueryTupleTransformer() ); - } - else if ( getFactory().getMappingMetamodel().isEntityClass( resultClass ) ) { + private void addEntityOrResultType(Class resultClass, ReactiveNativeQuery query) { + if ( getFactory().getMappingMetamodel().isEntityClass( resultClass ) ) { query.addEntity( "alias1", resultClass.getName(), LockMode.READ ); } - else if ( resultClass != Object.class && resultClass != Object[].class ) { + else if ( resultClass != Object.class && resultClass != Object[].class && resultClass != Tuple.class ) { query.addResultTypeClass( resultClass ); } - return query; } - @Override + @Override @Deprecated(forRemoval = true) public ReactiveNativeQuery createReactiveNativeQuery(String sqlString, Class resultClass, String tableAlias) { final ReactiveNativeQuery query = createReactiveNativeQuery( sqlString ); if ( getFactory().getMappingMetamodel().isEntityClass( resultClass ) ) { @@ -443,16 +506,18 @@ public ReactiveNativeQuery createReactiveNativeQuery(String sqlString, Cl } } - @Override + @Override @Deprecated(forRemoval = true) public ReactiveNativeQuery createReactiveNativeQuery(String sqlString, String resultSetMappingName) { + if ( isEmpty( resultSetMappingName ) ) { + throw new IllegalArgumentException( "Result set mapping name was not specified" ); + } + checkOpen(); pulseTransactionCoordinator(); delayedAfterCompletion(); try { - return isNotEmpty( resultSetMappingName ) - ? new ReactiveNativeQueryImpl<>( sqlString, getResultSetMappingMemento( resultSetMappingName ), this ) - : new ReactiveNativeQueryImpl<>( sqlString, this ); + return new ReactiveNativeQueryImpl<>( sqlString, getResultSetMappingMemento( resultSetMappingName ), null, this ); //TODO: why no applyQuerySettingsAndHints( query ); ??? } catch (RuntimeException he) { @@ -460,12 +525,10 @@ public ReactiveNativeQuery createReactiveNativeQuery(String sqlString, St } } - @Override + @Override @Deprecated(forRemoval = true) public ReactiveNativeQuery createReactiveNativeQuery(String sqlString, String resultSetMappingName, Class resultClass) { final ReactiveNativeQuery query = createReactiveNativeQuery( sqlString, resultSetMappingName ); - if ( Tuple.class.equals( resultClass ) ) { - query.setTupleTransformer( new NativeQueryTupleTransformer() ); - } + handleTupleResultType( resultClass, query ); return query; } @@ -480,7 +543,7 @@ private ReactiveSelectionQuery interpretAndCreateSelectionQuery(String hq delayedAfterCompletion(); try { - final HqlInterpretation interpretation = interpretHql( hql, resultType ); + final HqlInterpretation interpretation = interpretHql( hql, resultType ); checkSelectionQuery( hql, interpretation ); return createSelectionQuery( hql, resultType, interpretation ); } @@ -490,7 +553,7 @@ private ReactiveSelectionQuery interpretAndCreateSelectionQuery(String hq } } - private ReactiveSelectionQuery createSelectionQuery(String hql, Class resultType, HqlInterpretation interpretation) { + private ReactiveSelectionQuery createSelectionQuery(String hql, Class resultType, HqlInterpretation interpretation) { final ReactiveSqmSelectionQueryImpl query = new ReactiveSqmSelectionQueryImpl<>( hql, interpretation, resultType, this ); if ( resultType != null ) { @@ -501,9 +564,78 @@ private ReactiveSelectionQuery createSelectionQuery(String hql, Class return query; } + @Override + public ReactiveQueryImplementor createReactiveNamedQuery(String name) { + checksBeforeQueryCreation(); + try { + return (ReactiveQueryImplementor) buildNamedQuery( + name, + this::createSqmQueryImplementor, + this::createNativeQueryImplementor + ); + } + catch (RuntimeException e) { + throw convertNamedQueryException( e ); + } + } + @Override public ReactiveQueryImplementor createReactiveNamedQuery(String name, Class resultType) { - return (ReactiveQueryImplementor) buildNamedQuery( name, resultType ); + checksBeforeQueryCreation(); + if ( resultType == null ) { + throw new IllegalArgumentException( "Result class is null" ); + } + try { + return buildNamedQuery( + name, + memento -> createReactiveSqmQueryImplementor( resultType, memento ), + memento -> createReactiveNativeQueryImplementor( resultType, memento ) + ); + } + catch (RuntimeException e) { + throw convertNamedQueryException( e ); + } + } + + private void checksBeforeQueryCreation() { + checkOpen(); + checkTransactionSynchStatus(); + } + + protected ReactiveNativeQueryImpl createReactiveNativeQueryImplementor(Class resultType, NamedNativeQueryMemento memento) { + final NativeQueryImplementor query = memento.toQuery(this, resultType ); + if ( isEmpty( query.getComment() ) ) { + query.setComment( "dynamic native SQL query" ); + } + applyQuerySettingsAndHints( query ); + return (ReactiveNativeQueryImpl) query; + } + + protected ReactiveQuerySqmImpl createReactiveSqmQueryImplementor(Class resultType, NamedSqmQueryMemento memento) { + final SqmQueryImplementor query = memento.toQuery( this, resultType ); + if ( isEmpty( query.getComment() ) ) { + query.setComment( "dynamic query" ); + } + applyQuerySettingsAndHints( query ); + if ( memento.getLockOptions() != null ) { + query.setLockOptions( memento.getLockOptions() ); + } + return (ReactiveQuerySqmImpl) query; + } + + private RuntimeException convertNamedQueryException(RuntimeException e) { + if ( e instanceof UnknownNamedQueryException ) { + // JPA expects this to mark the transaction for rollback only + getTransactionCoordinator().getTransactionDriverControl().markRollbackOnly(); + // it also expects an IllegalArgumentException, so wrap UnknownNamedQueryException + return new IllegalArgumentException( e.getMessage(), e ); + } + else if ( e instanceof IllegalArgumentException ) { + return e; + } + else { + return getExceptionConverter().convert( e ); + } } @Override @@ -524,7 +656,7 @@ public ReactiveMutationQuery createReactiveMutationQuery(String hqlString public ReactiveMutationQuery createReactiveMutationQuery(CriteriaUpdate updateQuery) { checkOpen(); try { - return createCriteriaQuery( (SqmUpdateStatement) updateQuery, null ); + return createReactiveCriteriaQuery( (SqmUpdateStatement) updateQuery, null ); } catch ( RuntimeException e ) { throw getExceptionConverter().convert( e ); @@ -535,7 +667,7 @@ public ReactiveMutationQuery createReactiveMutationQuery(CriteriaUpdate ReactiveMutationQuery createReactiveMutationQuery(CriteriaDelete deleteQuery) { checkOpen(); try { - return createCriteriaQuery( (SqmDeleteStatement) deleteQuery, null ); + return createReactiveCriteriaQuery( (SqmDeleteStatement) deleteQuery, null ); } catch ( RuntimeException e ) { throw getExceptionConverter().convert( e ); @@ -543,10 +675,10 @@ public ReactiveMutationQuery createReactiveMutationQuery(CriteriaDelete ReactiveMutationQuery createReactiveMutationQuery(JpaCriteriaInsertSelect insertSelect) { + public ReactiveMutationQuery createReactiveMutationQuery(JpaCriteriaInsert insert) { checkOpen(); try { - return createCriteriaQuery( (SqmInsertSelectStatement) insertSelect, null ); + return createReactiveCriteriaQuery( (SqmInsertStatement) insert, null ); } catch ( RuntimeException e ) { throw getExceptionConverter().convert( e ); @@ -588,7 +720,7 @@ public ReactiveNativeQuery createReactiveNativeQuery(String queryString, delayedAfterCompletion(); try { - final ReactiveNativeQueryImpl query = new ReactiveNativeQueryImpl<>( queryString, this ); + final ReactiveNativeQueryImpl query = new ReactiveNativeQueryImpl<>( queryString, null, this ); addAffectedEntities( affectedEntities, query ); if ( isEmpty( query.getComment() ) ) { query.setComment( "dynamic native SQL query" ); @@ -610,7 +742,15 @@ private void addAffectedEntities(AffectedEntities affectedEntities, NativeQueryI @Override public ReactiveNativeQuery createReactiveNativeQuery(String queryString, Class resultType, AffectedEntities affectedEntities) { final ReactiveNativeQuery query = createReactiveNativeQuery( queryString, affectedEntities ); - return addResultType( resultType, query ); + handleTupleResultType( resultType, query ); + addEntityOrResultType( resultType, query ); + return query; + } + + private static void handleTupleResultType(Class resultType, ReactiveNativeQuery query) { + if ( Tuple.class.equals(resultType) ) { + query.setTupleTransformer( NativeQueryTupleTransformer.INSTANCE ); + } } @Override @@ -618,12 +758,11 @@ public ReactiveNativeQueryImpl createReactiveNativeQuery(String queryStri checkOpen(); pulseTransactionCoordinator(); delayedAfterCompletion(); - + // Should we throw an exception? + NamedResultSetMappingMemento memento = resultSetMapping == null ? null : getResultSetMappingMemento( resultSetMapping.getName() ); try { // Same approach as AbstractSharedSessionContract#createNativeQuery(String, String) - final ReactiveNativeQueryImpl nativeQuery = resultSetMapping != null - ? new ReactiveNativeQueryImpl<>( queryString, getResultSetMappingMemento( resultSetMapping.getName() ), this ) - : new ReactiveNativeQueryImpl<>( queryString, this ); + final ReactiveNativeQueryImpl nativeQuery = new ReactiveNativeQueryImpl<>( queryString, memento, null, this ); applyQuerySettingsAndHints( nativeQuery ); return nativeQuery; } @@ -648,20 +787,13 @@ public ResultSetMapping getResultSetMapping(Class resultType, String m if ( mapping == null ) { throw new IllegalArgumentException( "result set mapping does not exist: " + mappingName ); } -// -// ResultSetMappingImpl resultSetMapping = new ResultSetMappingImpl( "impl" ); -// if ( resultType != null ) { -// Class mappedResultType = resultSetMapping.; -// if ( !resultType.equals( mappedResultType ) ) { -// throw new IllegalArgumentException( "incorrect result type for result set mapping: " + mappingName + " has type " + mappedResultType.getName() ); -// } -// } return new ResultSetMapping<>() { @Override public String getName() { return mappingName; } + @Override public Class getResultType() { return resultType; @@ -691,8 +823,7 @@ public CompletionStage reactiveInitializeCollection(PersistentCollection eventListenerGroupInitCollection = fastSessionServices.eventListenerGroup_INIT_COLLECTION; - return eventListenerGroupInitCollection + return getFactory().getEventListenerGroups().eventListenerGroup_INIT_COLLECTION .fireEventOnEachListener( event, (DefaultReactiveInitializeCollectionEventListener l) -> l::onReactiveInitializeCollection @@ -715,6 +846,12 @@ public CompletionStage reactivePersist(Object entity) { return firePersist( new PersistEvent( null, entity, this ) ); } + @Override + public CompletionStage reactivePersist(String entityName, Object entity) { + checkOpen(); + return firePersist( new PersistEvent( entityName, entity, this ) ); + } + @Override public CompletionStage reactivePersist(Object object, PersistContext copiedAlready) { checkOpenOrWaitingForAutoClose(); @@ -726,7 +863,7 @@ private CompletionStage firePersist(PersistEvent event) { checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - return fastSessionServices.eventListenerGroup_PERSIST + return getFactory().getEventListenerGroups().eventListenerGroup_PERSIST .fireEventOnEachListener( event, (ReactivePersistEventListener l) -> l::reactiveOnPersist ) .handle( (v, e) -> { checkNoUnresolvedActionsAfterOperation(); @@ -744,7 +881,7 @@ else if ( e instanceof RuntimeException ) { private CompletionStage firePersist(PersistContext copiedAlready, PersistEvent event) { pulseTransactionCoordinator(); - return fastSessionServices.eventListenerGroup_PERSIST + return getFactory().getEventListenerGroups().eventListenerGroup_PERSIST .fireEventOnEachListener( event, copiedAlready, (ReactivePersistEventListener l) -> l::reactiveOnPersist ) .handle( (v, e) -> { delayedAfterCompletion(); @@ -768,7 +905,7 @@ public CompletionStage reactivePersistOnFlush(Object entity, PersistContex private CompletionStage firePersistOnFlush(PersistContext copiedAlready, PersistEvent event) { pulseTransactionCoordinator(); - return fastSessionServices.eventListenerGroup_PERSIST + return getFactory().getEventListenerGroups().eventListenerGroup_PERSIST .fireEventOnEachListener( event, copiedAlready, (ReactivePersistEventListener l) -> l::reactiveOnPersist ) .whenComplete( (v, e) -> delayedAfterCompletion() ); } @@ -779,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, @@ -796,13 +923,13 @@ public CompletionStage reactiveRemove( boolean isCascadeDeleteEnabled, DeleteContext transientEntities) { checkOpenOrWaitingForAutoClose(); - final boolean removingOrphanBeforeUpates = persistenceContext().isRemovingOrphanBeforeUpates(); - if ( LOG.isTraceEnabled() && removingOrphanBeforeUpates ) { + final boolean removingOrphanBeforeUpdates = persistenceContext().isRemovingOrphanBeforeUpdates(); + if ( LOG.isTraceEnabled() && removingOrphanBeforeUpdates ) { logRemoveOrphanBeforeUpdates( "before continuing", entityName, entityName ); } return fireRemove( - new DeleteEvent( entityName, child, isCascadeDeleteEnabled, removingOrphanBeforeUpates, this ), + new DeleteEvent( entityName, child, isCascadeDeleteEnabled, removingOrphanBeforeUpdates, this ), transientEntities ); } @@ -824,10 +951,8 @@ private void logRemoveOrphanBeforeUpdates(String timing, String entityName, Obje private CompletionStage fireRemove(DeleteEvent event) { pulseTransactionCoordinator(); - return fastSessionServices.eventListenerGroup_DELETE.fireEventOnEachListener( - event, - (ReactiveDeleteEventListener l) -> l::reactiveOnDelete - ) + return getFactory().getEventListenerGroups().eventListenerGroup_DELETE + .fireEventOnEachListener( event, (ReactiveDeleteEventListener l) -> l::reactiveOnDelete ) .handle( (v, e) -> { delayedAfterCompletion(); @@ -848,9 +973,8 @@ else if ( e instanceof RuntimeException ) { private CompletionStage fireRemove(DeleteEvent event, DeleteContext transientEntities) { pulseTransactionCoordinator(); - return fastSessionServices.eventListenerGroup_DELETE.fireEventOnEachListener( event, transientEntities, - (ReactiveDeleteEventListener l) -> l::reactiveOnDelete - ) + return getFactory().getEventListenerGroups().eventListenerGroup_DELETE + .fireEventOnEachListener( event, transientEntities, (ReactiveDeleteEventListener l) -> l::reactiveOnDelete ) .handle( (v, e) -> { delayedAfterCompletion(); @@ -886,7 +1010,7 @@ private CompletionStage fireMerge(MergeEvent event) { checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - return fastSessionServices.eventListenerGroup_MERGE + return getFactory().getEventListenerGroups().eventListenerGroup_MERGE .fireEventOnEachListener( event, (ReactiveMergeEventListener l) -> l::reactiveOnMerge ) .handle( (v, e) -> { checkNoUnresolvedActionsAfterOperation(); @@ -908,7 +1032,7 @@ else if ( e instanceof RuntimeException ) { private CompletionStage fireMerge(MergeContext copiedAlready, MergeEvent event) { pulseTransactionCoordinator(); - return fastSessionServices.eventListenerGroup_MERGE + return getFactory().getEventListenerGroups().eventListenerGroup_MERGE .fireEventOnEachListener( event, copiedAlready,(ReactiveMergeEventListener l) -> l::reactiveOnMerge ) .handle( (v, e) -> { delayedAfterCompletion(); @@ -948,7 +1072,7 @@ public CompletionStage reactiveAutoFlushIfRequired(Set querySpa // } AutoFlushEvent event = new AutoFlushEvent( querySpaces, this ); - return fastSessionServices.eventListenerGroup_AUTO_FLUSH + return getFactory().getEventListenerGroups().eventListenerGroup_AUTO_FLUSH .fireEventOnEachListener( event, (DefaultReactiveAutoFlushEventListener l) -> l::reactiveOnAutoFlush ) .thenApply( v -> event.isFlushRequired() ); } @@ -963,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() @@ -981,7 +1105,7 @@ private CompletionStage doFlush() { throw LOG.flushDuringCascadeIsDangerous(); } - return fastSessionServices.eventListenerGroup_FLUSH + return getFactory().getEventListenerGroups().eventListenerGroup_FLUSH .fireEventOnEachListener( new FlushEvent( this ), (ReactiveFlushEventListener l) -> l::reactiveOnFlush ) .handle( (v, e) -> { delayedAfterCompletion(); @@ -1030,10 +1154,8 @@ CompletionStage fireRefresh(RefreshEvent event) { } pulseTransactionCoordinator(); - return fastSessionServices.eventListenerGroup_REFRESH.fireEventOnEachListener( - event, - (ReactiveRefreshEventListener l) -> l::reactiveOnRefresh - ) + return getFactory().getEventListenerGroups().eventListenerGroup_REFRESH + .fireEventOnEachListener( event, (ReactiveRefreshEventListener l) -> l::reactiveOnRefresh ) .handle( (v, e) -> { delayedAfterCompletion(); @@ -1053,12 +1175,8 @@ CompletionStage fireRefresh(RefreshEvent event) { private CompletionStage fireRefresh(RefreshContext refreshedAlready, RefreshEvent event) { pulseTransactionCoordinator(); - return fastSessionServices.eventListenerGroup_REFRESH - .fireEventOnEachListener( - event, - refreshedAlready, - (ReactiveRefreshEventListener l) -> l::reactiveOnRefresh - ) + return getFactory().getEventListenerGroups().eventListenerGroup_REFRESH + .fireEventOnEachListener( event, refreshedAlready, (ReactiveRefreshEventListener l) -> l::reactiveOnRefresh ) .handle( (v, e) -> { delayedAfterCompletion(); @@ -1075,13 +1193,17 @@ public CompletionStage reactiveLock(Object object, LockOptions lockOptions return fireLock( new LockEvent( object, lockOptions, this ) ); } + @Override + public CompletionStage reactiveLock(String entityName, Object object, LockOptions lockOptions) { + checkOpen(); + return fireLock( new LockEvent( entityName, object, lockOptions, this ) ); + } + private CompletionStage fireLock(LockEvent event) { pulseTransactionCoordinator(); - return fastSessionServices.eventListenerGroup_LOCK.fireEventOnEachListener( - event, - (ReactiveLockEventListener l) -> l::reactiveOnLock - ) + return getFactory().getEventListenerGroups().eventListenerGroup_LOCK + .fireEventOnEachListener( event, (ReactiveLockEventListener l) -> l::reactiveOnLock ) .handle( (v, e) -> { delayedAfterCompletion(); @@ -1093,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 @@ -1106,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 @@ -1169,35 +1314,37 @@ public CompletionStage> reactiveFind(Class entityClass, Object... @Override public CompletionStage reactiveFind(Class entityClass, Map ids) { - final EntityPersister persister = getFactory().getMappingMetamodel().getEntityDescriptor( entityClass ); - return new NaturalIdLoadAccessImpl( persister ).resolveNaturalId( ids ) + final ReactiveEntityPersister persister = entityPersister( entityClass ); + final Object normalizedIdValues = persister.getNaturalIdMapping().normalizeInput( ids ); + return new NaturalIdLoadAccessImpl( this, persister, requireEntityPersister( entityClass ) ) + .resolveNaturalId( normalizedIdValues ) .thenCompose( id -> reactiveFind( entityClass, id, null, null ) ); } - private CompletionStage fireReactiveLoad(LoadEvent event, LoadEventListener.LoadType loadType) { - checkOpenOrWaitingForAutoClose(); + private ReactiveEntityPersister entityPersister(Class entityClass) { + return (ReactiveEntityPersister) getFactory().getMappingMetamodel().getEntityDescriptor( entityClass ); + } + 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(); - return fastSessionServices.eventListenerGroup_LOAD + return getFactory().getEventListenerGroups().eventListenerGroup_LOAD .fireEventOnEachListener( event, loadType,(ReactiveLoadEventListener l) -> l::reactiveOnLoad ); } - private CompletionStage fireResolveNaturalId(ResolveNaturalIdEvent event) { - checkOpenOrWaitingForAutoClose(); - return fastSessionServices.eventListenerGroup_RESOLVE_NATURAL_ID.fireEventOnEachListener( - event, - (ReactiveResolveNaturalIdEventListener l) -> l::onReactiveResolveNaturalId - ) - .whenComplete( (c, e) -> delayedAfterCompletion() ); - } - @Override public void delayedAfterCompletion() { //disable for now, but figure out what to do here @@ -1212,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; - } + private class ReactiveIdentifierLoadAccessImpl extends IdentifierLoadAccessImpl> { - public ReactiveIdentifierLoadAccessImpl with(RootGraph graph, GraphSemantic semantic) { - rootGraph = (RootGraphImplementor) graph; - graphSemantic = semantic; - return this; - } - - 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 ) { @@ -1295,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 ) ); + // 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 load(Object id) { - return perform( () -> doLoad( id, LoadEventListener.GET ) ); - } - - // 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; @@ -1376,9 +1449,15 @@ private class ReactiveMultiIdentifierLoadAccessImpl implements MultiIdLoadOpt private boolean sessionCheckingEnabled; private boolean returnOfDeletedEntitiesEnabled; private boolean orderedReturnEnabled = true; + private boolean readOnly; public ReactiveMultiIdentifierLoadAccessImpl(EntityPersister entityPersister) { - this.entityPersister = entityPersister; + this.entityPersister = (ReactiveEntityPersister) entityPersister; + } + + @Override + public Boolean getReadOnly(SessionImplementor session) { + return session.getLoadQueryInfluencers().getReadOnly(); } public ReactiveMultiIdentifierLoadAccessImpl(Class entityClass) { @@ -1412,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; } @@ -1456,14 +1530,15 @@ public ReactiveMultiIdentifierLoadAccessImpl enableOrderedReturn(boolean enab return this; } - @SuppressWarnings("unchecked") 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) { @@ -1497,21 +1572,19 @@ 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 { - private final EntityPersister entityPersister; + private final LoadAccessContext context; + private final ReactiveEntityPersister entityPersister; + private final EntityMappingType entityDescriptor; private LockOptions lockOptions; private boolean synchronizationEnabled = true; - private NaturalIdLoadAccessImpl(EntityPersister entityPersister) { + private NaturalIdLoadAccessImpl(LoadAccessContext context, ReactiveEntityPersister entityPersister, EntityMappingType entityDescriptor) { + this.context = context; this.entityPersister = entityPersister; + this.entityDescriptor = entityDescriptor; if ( !entityPersister.hasNaturalIdentifier() ) { throw LOG.entityDidNotDefinedNaturalId( entityPersister.getEntityName() ); @@ -1523,17 +1596,35 @@ public NaturalIdLoadAccessImpl with(LockOptions lockOptions) { return this; } - protected void synchronizationEnabled(boolean synchronizationEnabled) { - this.synchronizationEnabled = synchronizationEnabled; - } - - protected final CompletionStage resolveNaturalId(Map naturalIdParameters) { + /** + * @see org.hibernate.loader.internal.BaseNaturalIdLoadAccessImpl#doGetReference(Object) + */ + protected final CompletionStage resolveNaturalId(Object normalizedNaturalIdValue) { performAnyNeededCrossReferenceSynchronizations(); - ResolveNaturalIdEvent event = - new ResolveNaturalIdEvent( naturalIdParameters, entityPersister, ReactiveSessionImpl.this ); - return fireResolveNaturalId( event ) - .thenApply( v -> event.getEntityId() == INVALID_NATURAL_ID_REFERENCE ? null : event.getEntityId() ); + context.checkOpenOrWaitingForAutoClose(); + context.pulseTransactionCoordinator(); + + final SessionImplementor session = getSession(); + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + final Object cachedResolution = persistenceContext.getNaturalIdResolutions() + .findCachedIdByNaturalId( normalizedNaturalIdValue, entityPersister() ); + if ( cachedResolution == INVALID_NATURAL_ID_REFERENCE ) { + // the entity is deleted, although not yet flushed - return null + return nullFuture(); + } + else if ( cachedResolution != null ) { + return completedFuture( cachedResolution ); + } + else { + LoaderLogging.LOADER_LOGGER.debugf( + "Selecting entity identifier by natural-id for `#getReference` handling - %s : %s", + entityPersister().getEntityName(), + normalizedNaturalIdValue + ); + return ( (ReactiveNaturalIdLoader) entityPersister().getNaturalIdLoader() ) + .resolveNaturalIdToId( normalizedNaturalIdValue, session ); + } } protected void performAnyNeededCrossReferenceSynchronizations() { @@ -1541,7 +1632,9 @@ protected void performAnyNeededCrossReferenceSynchronizations() { // synchronization (this process) was disabled return; } - if ( entityPersister.getEntityMetamodel().hasImmutableNaturalId() ) { + + final NaturalIdMapping naturalIdMapping = entityDescriptor.getNaturalIdMapping(); + if ( !naturalIdMapping.isMutable() ) { // only mutable natural-ids need this processing return; } @@ -1551,7 +1644,7 @@ protected void performAnyNeededCrossReferenceSynchronizations() { } final PersistenceContext persistenceContext = getPersistenceContextInternal(); -// final boolean debugEnabled = log.isDebugEnabled(); + final boolean loggerDebugEnabled = LoaderLogging.LOADER_LOGGER.isDebugEnabled(); for ( Object pk : persistenceContext.getNaturalIdResolutions() .getCachedPkResolutions( entityPersister ) ) { final EntityKey entityKey = generateEntityKey( pk, entityPersister ); @@ -1559,12 +1652,13 @@ protected void performAnyNeededCrossReferenceSynchronizations() { final EntityEntry entry = persistenceContext.getEntry( entity ); if ( entry == null ) { -// if ( debugEnabled ) { -// log.debug( -// "Cached natural-id/pk resolution linked to null EntityEntry in persistence context : " -// + MessageHelper.infoString( entityPersister, pk, getFactory() ) -// ); -// } + if ( loggerDebugEnabled ) { + LoaderLogging.LOADER_LOGGER.debugf( + "Cached natural-id/pk resolution linked to null EntityEntry in persistence context : %s#%s", + entityDescriptor.getEntityName(), + pk + ); + } continue; } @@ -1577,21 +1671,11 @@ protected void performAnyNeededCrossReferenceSynchronizations() { continue; } - persistenceContext.getNaturalIdResolutions() - .handleSynchronization( pk, entity, entityPersister ); - } - } - - protected final ReactiveIdentifierLoadAccessImpl getIdentifierLoadAccess() { - final ReactiveIdentifierLoadAccessImpl identifierLoadAccess = new ReactiveIdentifierLoadAccessImpl<>( - entityPersister ); - if ( this.lockOptions != null ) { - identifierLoadAccess.with( lockOptions ); + persistenceContext.getNaturalIdResolutions().handleSynchronization( pk, entity, entityPersister() ); } - return identifierLoadAccess; } - protected EntityPersister entityPersister() { + protected ReactiveEntityPersister entityPersister() { return entityPersister; } } @@ -1636,9 +1720,8 @@ public void setBatchSize(Integer batchSize) { @Override @SuppressWarnings("unchecked") public Class getEntityClass(T entity) { - if ( entity instanceof HibernateProxy ) { - return (Class) ( (HibernateProxy) entity ) - .getHibernateLazyInitializer() + if ( entity instanceof HibernateProxy proxy ) { + return (Class) proxy.getHibernateLazyInitializer() .getPersistentClass(); } else { @@ -1649,8 +1732,8 @@ public Class getEntityClass(T entity) { @Override public Object getEntityId(Object entity) { - if ( entity instanceof HibernateProxy ) { - return ( (HibernateProxy) entity ).getHibernateLazyInitializer() + if ( entity instanceof HibernateProxy proxy ) { + return proxy.getHibernateLazyInitializer() .getIdentifier(); } else { @@ -1677,7 +1760,7 @@ public void removeOrphanBeforeUpdates(String entityName, Object child) { public CompletionStage reactiveRemoveOrphanBeforeUpdates(String entityName, Object child) { // TODO: The removeOrphan concept is a temporary "hack" for HHH-6484. This should be removed once action/task // ordering is improved. - final StatefulPersistenceContext persistenceContext = (StatefulPersistenceContext) getPersistenceContextInternal(); + final PersistenceContext persistenceContext = getPersistenceContextInternal(); persistenceContext.beginRemoveOrphanBeforeUpdates(); return fireRemove( new DeleteEvent( entityName, child, false, true, this ) ) .thenAccept( v -> { @@ -1698,7 +1781,7 @@ private void logRemoveOrphanBeforeUpdates( String timing, String entityName, Object entity, - StatefulPersistenceContext persistenceContext) { + PersistenceContext persistenceContext) { if ( LOG.isTraceEnabled() ) { final EntityEntry entityEntry = persistenceContext.getEntry( entity ); LOG.tracef( 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 06faba07f..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,32 +5,28 @@ */ package org.hibernate.reactive.session.impl; -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.spi.EntityKey; +import org.hibernate.engine.internal.ReactivePersistenceContextAdapter; +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.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; import org.hibernate.graph.internal.RootGraphImpl; import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.id.IdentifierGenerationException; import org.hibernate.internal.SessionCreationOptions; import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.internal.StatelessSessionImpl; @@ -40,24 +36,25 @@ import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import org.hibernate.query.IllegalMutationQueryException; -import org.hibernate.query.criteria.JpaCriteriaInsertSelect; +import org.hibernate.query.UnknownNamedQueryException; +import org.hibernate.query.criteria.JpaCriteriaInsert; import org.hibernate.query.hql.spi.SqmQueryImplementor; import org.hibernate.query.named.NamedResultSetMappingMemento; +import org.hibernate.query.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.internal.NativeQueryImpl; import org.hibernate.query.sql.spi.NativeQueryImplementor; import org.hibernate.query.sqm.internal.SqmUtil; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement; +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.SqmSelectStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.common.AffectedEntities; import org.hibernate.reactive.common.ResultSetMapping; -import org.hibernate.reactive.engine.impl.ReactivePersistenceContextAdapter; import org.hibernate.reactive.id.ReactiveIdentifierGenerator; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.persister.collection.impl.ReactiveCollectionPersister; @@ -75,25 +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; @@ -104,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; /** @@ -117,17 +128,14 @@ 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 ReactiveStatelessSession batchingHelperSession; - + private final ReactiveStatelessSessionImpl batchingHelperSession; private final PersistenceContext persistenceContext; public ReactiveStatelessSessionImpl(SessionFactoryImpl factory, SessionCreationOptions options, ReactiveConnection connection) { super( factory, options ); reactiveConnection = connection; - persistenceContext = new ReactivePersistenceContextAdapter( this ); + persistenceContext = new ReactivePersistenceContextAdapter( super.getPersistenceContext() ); batchingHelperSession = new ReactiveStatelessSessionImpl( factory, options, reactiveConnection, persistenceContext ); influencers = new LoadQueryInfluencers( factory ); } @@ -142,10 +150,12 @@ private ReactiveStatelessSessionImpl( PersistenceContext persistenceContext) { super( factory, options ); this.persistenceContext = persistenceContext; - Integer batchSize = getConfiguredJdbcBatchSize(); - reactiveConnection = batchSize == null || batchSize < 2 - ? connection - : new BatchingConnection( connection, batchSize ); + // StatelessSession should not allow JDBC batching, because that would change + // its "immediate synchronous execution" model into something more like transactional + // write-behind and be confusing. For this reason, the default batch size is always set to 0. + // When a user calls the CRUD operations for batching, we set the batch size to the same number of + // objects to process, therefore, there is no write-behind behavior. + reactiveConnection = new BatchingConnection( connection, 0 ); batchingHelperSession = this; influencers = new LoadQueryInfluencers( factory ); } @@ -198,18 +208,40 @@ public void checkTransactionNeededForUpdateOperation(String exceptionMessage) { } @Override - public CompletionStage reactiveGet(Class entityClass, Object id) { + public CompletionStage reactiveGet(Class entityClass, Object id) { return reactiveGet( entityClass.getName(), id, LockMode.NONE, null ); } + @Override + public CompletionStage> reactiveGet(Class entityClass, Object... ids) { + checkOpen(); + for (Object id : ids) { + if ( id == null ) { + return failedFuture( new IllegalArgumentException( "Null id" ) ); + } + } + + Object[] sids = new Object[ids.length]; + System.arraycopy( ids, 0, sids, 0, ids.length ); + + return getEntityPersister( entityClass.getName() ) + .reactiveMultiLoad( sids, this, StatelessSessionImpl.MULTI_ID_LOAD_OPTIONS ) + .whenComplete( (list, e) -> { + if ( getPersistenceContext().isLoadFinished() ) { + getPersistenceContext().clear(); + } + } ) + .thenApply( list -> (List) list ); + } + @Override public CompletionStage reactiveGet(String entityName, Object id) { return reactiveGet( entityName, id, LockMode.NONE, null ); } @Override - public CompletionStage reactiveGet(Class entityClass, Object id, LockMode lockMode, EntityGraph fetchGraph) { - return reactiveGet( entityClass.getName(), id, LockMode.NONE, fetchGraph ); + public CompletionStage reactiveGet(Class entityClass, Object id, LockMode lockMode, EntityGraph fetchGraph) { + return reactiveGet( entityClass.getName(), id, lockMode, fetchGraph ); } @Override @@ -228,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() ) { @@ -254,31 +294,156 @@ public CompletionStage reactiveInsert(Object entity) { checkOpen(); final ReactiveEntityPersister persister = getEntityPersister( null, entity ); 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() ); + } + } ); + } + + private static class Loop { + private CompletionStage loop = voidFuture(); + + public void then(Supplier> step) { + loop = loop.thenCompose( v -> step.get() ); + } + + 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 ); + } + } final Generator generator = persister.getGenerator(); - if ( !generator.generatedOnExecution() ) { - return generateId( entity, generator ) - .thenCompose( generatedId -> { - final Object id = castToIdentifierType( generatedId, persister ); - if ( persister.isVersioned() ) { - if ( seedVersion( entity, state, persister, this ) ) { - persister.setValues( entity, state ); - } - } - return persister.insertReactive( id, state, entity, this ) - .thenAccept( ignore -> persister.setIdentifier( entity, id, this ) ); - } ); + if ( generator.generatedBeforeExecution( entity, this ) ) { + return generatedIdBeforeInsert( entity, persister, generator, state ); } - else { - return persister.insertReactive( state, entity, this ) - .thenAccept( id -> persister.setIdentifier( entity, id, this ) ); + if ( generator.generatedOnExecution( entity, this ) ) { + return generateIdOnInsert( entity, persister, generator, state ); + } + return applyAssignedIdentifierInsert( entity, persister, state ); + } + + 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()'" ) ); + } + 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 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 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 generateId(Object entity, Generator generator) { - return generator instanceof ReactiveIdentifierGenerator - ? ( (ReactiveIdentifierGenerator) generator ).generate( this, this ) - : completedFuture( ( (BeforeExecutionGenerator) generator ) - .generate( this, entity, null, INSERT ) ); + 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 @@ -287,14 +452,64 @@ 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 public CompletionStage reactiveUpdate(Object entity) { checkOpen(); - if ( entity instanceof HibernateProxy ) { - final LazyInitializer hibernateLazyInitializer = ( (HibernateProxy) entity ).getHibernateLazyInitializer(); + if ( entity instanceof HibernateProxy proxy ) { + final LazyInitializer hibernateLazyInitializer = proxy.getHibernateLazyInitializer(); return hibernateLazyInitializer.isUninitialized() ? failedFuture( LOG.uninitializedProxyUpdate( entity.getClass() ) ) : executeReactiveUpdate( hibernateLazyInitializer.getImplementation() ); @@ -312,38 +527,79 @@ 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 public CompletionStage reactiveRefresh(Object entity, LockMode lockMode) { - return reactiveRefresh( bestGuessEntityName( entity ), entity, LockMode.NONE ); + 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 ); @@ -354,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(); } ) @@ -391,48 +639,41 @@ 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(); } + 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 ) ); + } - return persister - .mergeReactive( id, state, null, false, null, oldVersion, entity, null, this ); + @Override + public CompletionStage reactiveUpsertAll(int batchSize, Object... entities) { + final Integer jdbcBatchSize = batchingHelperSession.getJdbcBatchSize(); + batchingHelperSession.setJdbcBatchSize( batchSize ); + final ReactiveConnection connection = batchingConnection( batchSize ); + return loop( entities, batchingHelperSession::reactiveUpsert ) + .thenCompose( v -> connection.executeBatch() ) + .whenComplete( (v, throwable) -> batchingHelperSession.setJdbcBatchSize( jdbcBatchSize ) ); } @Override @@ -443,9 +684,12 @@ public CompletionStage reactiveInsertAll(Object... entities) { @Override public CompletionStage reactiveInsertAll(int batchSize, Object... entities) { + final Integer jdbcBatchSize = batchingHelperSession.getJdbcBatchSize(); + batchingHelperSession.setJdbcBatchSize( batchSize ); final ReactiveConnection connection = batchingConnection( batchSize ); return loop( entities, batchingHelperSession::reactiveInsert ) - .thenCompose( v -> connection.executeBatch() ); + .thenCompose( v -> connection.executeBatch() ) + .whenComplete( (v, throwable) -> batchingHelperSession.setJdbcBatchSize( jdbcBatchSize ) ); } @Override @@ -456,9 +700,12 @@ public CompletionStage reactiveUpdateAll(Object... entities) { @Override public CompletionStage reactiveUpdateAll(int batchSize, Object... entities) { + final Integer jdbcBatchSize = batchingHelperSession.getJdbcBatchSize(); + batchingHelperSession.setJdbcBatchSize( batchSize ); final ReactiveConnection connection = batchingConnection( batchSize ); return loop( entities, batchingHelperSession::reactiveUpdate ) - .thenCompose( v -> connection.executeBatch() ); + .thenCompose( v -> connection.executeBatch() ) + .whenComplete( (v, throwable) -> batchingHelperSession.setJdbcBatchSize( jdbcBatchSize ) ); } @Override @@ -469,12 +716,13 @@ public CompletionStage reactiveDeleteAll(Object... entities) { @Override public CompletionStage reactiveDeleteAll(int batchSize, Object... entities) { + final Integer jdbcBatchSize = batchingHelperSession.getJdbcBatchSize(); + batchingHelperSession.setJdbcBatchSize( batchSize ); final ReactiveConnection connection = batchingConnection( batchSize ); - return loop( entities, batchingHelperSession::reactiveDelete ) - .thenCompose( v -> connection.executeBatch() ); + return loop( entities, batchingHelperSession::reactiveDelete ).thenCompose( v -> connection.executeBatch() ) + .whenComplete( (v, throwable) -> batchingHelperSession.setJdbcBatchSize( jdbcBatchSize ) ); } - @Override public CompletionStage reactiveRefreshAll(Object... entities) { return loop( entities, batchingHelperSession::reactiveRefresh ) @@ -483,9 +731,12 @@ public CompletionStage reactiveRefreshAll(Object... entities) { @Override public CompletionStage reactiveRefreshAll(int batchSize, Object... entities) { + final Integer jdbcBatchSize = batchingHelperSession.getJdbcBatchSize(); + batchingHelperSession.setJdbcBatchSize( batchSize ); final ReactiveConnection connection = batchingConnection( batchSize ); return loop( entities, batchingHelperSession::reactiveRefresh ) - .thenCompose( v -> connection.executeBatch() ); + .thenCompose( v -> connection.executeBatch() ) + .whenComplete( (v, throwable) -> batchingHelperSession.setJdbcBatchSize( jdbcBatchSize ) ); } private ReactiveConnection batchingConnection(int batchSize) { @@ -493,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 ) { @@ -592,69 +820,13 @@ 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 ) { - final PersistentCollection persistentCollection = (PersistentCollection) association; - if ( persistentCollection.wasInitialized() ) { - return completedFuture( association ); - } - else { - final ReactiveCollectionPersister collectionDescriptor = - (ReactiveCollectionPersister) getFactory().getMappingMetamodel() - .getCollectionDescriptor( persistentCollection.getRole() ); - - final Object key = persistentCollection.getKey(); - persistenceContext.addUninitializedCollection( collectionDescriptor, persistentCollection, key ); - persistentCollection.setCurrentSession( this ); - return collectionDescriptor.reactiveInitialize( key, this ) - .whenComplete( (v, e) -> { - persistentCollection.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 ) { - final EnhancementAsProxyLazinessInterceptor proxyInterceptor = - (EnhancementAsProxyLazinessInterceptor) interceptor; + if ( asPersistentAttributeInterceptable( association ).$$_hibernate_getInterceptor() + instanceof EnhancementAsProxyLazinessInterceptor proxyInterceptor ) { proxyInterceptor.setSession( this ); return forceInitialize( association, null, proxyInterceptor.getIdentifier(), proxyInterceptor.getEntityName(), this ) .whenComplete( (i,e) -> { @@ -664,15 +836,76 @@ else if ( isPersistentAttributeInterceptable( association ) ) { } } ) .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 @@ -700,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 ); @@ -720,17 +976,17 @@ public ReactiveQuery createReactiveQuery(CriteriaQuery criteriaQuery) } } - return createCriteriaQuery( selectStatement, criteriaQuery.getResultType() ); + return createReactiveCriteriaQuery( selectStatement, criteriaQuery.getResultType() ); } catch (RuntimeException e) { - if ( getSessionFactory().getJpaMetamodel().getJpaCompliance().isJpaTransactionComplianceEnabled() ) { + if ( getSessionFactory().getSessionFactoryOptions().getJpaCompliance().isJpaTransactionComplianceEnabled() ) { markForRollbackOnly(); } throw getExceptionConverter().convert( e ); } } - private ReactiveQuery createCriteriaQuery(SqmStatement criteria, Class resultType) { + private ReactiveQuery createReactiveCriteriaQuery(SqmStatement criteria, Class resultType) { final ReactiveQuerySqmImpl query = new ReactiveQuerySqmImpl<>( criteria, resultType, this ); applyQuerySettingsAndHints( query ); return query; @@ -756,7 +1012,7 @@ public ReactiveSqmQueryImplementor createReactiveQuery(String queryString delayedAfterCompletion(); try { - final HqlInterpretation interpretation = interpretHql( queryString, expectedResultType ); + final HqlInterpretation interpretation = interpretHql( queryString, expectedResultType ); final ReactiveQuerySqmImpl query = new ReactiveQuerySqmImpl<>( queryString, interpretation, expectedResultType, this ); applyQuerySettingsAndHints( query ); @@ -777,8 +1033,7 @@ public ReactiveNativeQueryImplementor createReactiveNativeQuery(String sq delayedAfterCompletion(); try { - ReactiveNativeQueryImpl query = new ReactiveNativeQueryImpl<>( sqlString, this); - + final ReactiveNativeQueryImpl query = new ReactiveNativeQueryImpl<>( sqlString, null, this); if ( isEmpty( query.getComment() ) ) { query.setComment( "dynamic native SQL query" ); } @@ -792,34 +1047,37 @@ public ReactiveNativeQueryImplementor createReactiveNativeQuery(String sq @Override public ReactiveNativeQuery createReactiveNativeQuery(String sqlString, Class resultClass) { - ReactiveNativeQuery query = createReactiveNativeQuery( sqlString ); - if ( Tuple.class.equals( resultClass ) ) { - query.setTupleTransformer( new NativeQueryTupleTransformer() ); - } - else if ( getFactory().getMappingMetamodel().isEntityClass( resultClass ) ) { + final ReactiveNativeQuery query = createReactiveNativeQuery( sqlString ); + handleTupleResultType( resultClass, query ); + addEntityOrResultType( resultClass, query ); + return query; + } + + private void addEntityOrResultType(Class resultClass, ReactiveNativeQuery query) { + if ( getFactory().getMappingMetamodel().isEntityClass( resultClass ) ) { query.addEntity( "alias1", resultClass.getName(), LockMode.READ ); } - else { - ( (NativeQueryImpl) query ).addScalar( 1, resultClass ); + else if ( resultClass != Object.class && resultClass != Object[].class && resultClass != Tuple.class ) { + query.addResultTypeClass( resultClass ); } - return query; } - @Override + @Override @Deprecated(forRemoval = true) public ReactiveNativeQuery createReactiveNativeQuery( String sqlString, Class resultClass, String tableAlias) { final ReactiveNativeQuery query = createReactiveNativeQuery( sqlString ); - if ( getFactory().getMappingMetamodel().isEntityClass(resultClass) ) { + if ( getFactory().getMappingMetamodel().isEntityClass( resultClass ) ) { query.addEntity( tableAlias, resultClass.getName(), LockMode.READ ); return query; } - - throw new UnknownEntityTypeException( "unable to locate persister: " + resultClass.getName() ); + else { + throw new UnknownEntityTypeException( "unable to locate persister: " + resultClass.getName() ); + } } - @Override + @Override @Deprecated(forRemoval = true) public ReactiveNativeQuery createReactiveNativeQuery(String sqlString, String resultSetMappingName) { checkOpen(); pulseTransactionCoordinator(); @@ -834,7 +1092,7 @@ public ReactiveNativeQuery createReactiveNativeQuery(String sqlString, St if ( resultSetMappingMemento == null ) { throw new HibernateException( "Could not resolve specified result-set mapping name : " + resultSetMappingName ); } - return new ReactiveNativeQueryImpl<>( sqlString, resultSetMappingMemento, this ); + return new ReactiveNativeQueryImpl<>( sqlString, resultSetMappingMemento, null, this ); } else { return new ReactiveNativeQueryImpl<>( sqlString, this ); @@ -846,15 +1104,13 @@ public ReactiveNativeQuery createReactiveNativeQuery(String sqlString, St } } - @Override + @Override @Deprecated(forRemoval = true) public ReactiveNativeQuery createReactiveNativeQuery( String sqlString, String resultSetMappingName, Class resultClass) { final ReactiveNativeQuery query = createReactiveNativeQuery( sqlString, resultSetMappingName ); - if ( Tuple.class.equals( resultClass ) ) { - query.setTupleTransformer( new NativeQueryTupleTransformer() ); - } + handleTupleResultType( resultClass, query ); return query; } @@ -869,7 +1125,7 @@ private ReactiveSelectionQuery interpretAndCreateSelectionQuery(String hq delayedAfterCompletion(); try { - final HqlInterpretation interpretation = interpretHql( hql, resultType ); + final HqlInterpretation interpretation = interpretHql( hql, resultType ); checkSelectionQuery( hql, interpretation ); return createSelectionQuery( hql, resultType, interpretation ); } @@ -879,7 +1135,7 @@ private ReactiveSelectionQuery interpretAndCreateSelectionQuery(String hq } } - private ReactiveSelectionQuery createSelectionQuery(String hql, Class resultType, HqlInterpretation interpretation) { + private ReactiveSelectionQuery createSelectionQuery(String hql, Class resultType, HqlInterpretation interpretation) { final ReactiveSqmSelectionQueryImpl query = new ReactiveSqmSelectionQueryImpl<>( hql, interpretation, resultType, this ); if ( resultType != null ) { @@ -908,7 +1164,7 @@ public ReactiveMutationQuery createReactiveMutationQuery(String hqlString public ReactiveMutationQuery createReactiveMutationQuery(CriteriaUpdate updateQuery) { checkOpen(); try { - return createCriteriaQuery( (SqmUpdateStatement) updateQuery, null ); + return createReactiveCriteriaQuery( (SqmUpdateStatement) updateQuery, null ); } catch ( RuntimeException e ) { throw getExceptionConverter().convert( e ); @@ -919,7 +1175,7 @@ public ReactiveMutationQuery createReactiveMutationQuery(CriteriaUpdate ReactiveMutationQuery createReactiveMutationQuery(CriteriaDelete deleteQuery) { checkOpen(); try { - return createCriteriaQuery( (SqmDeleteStatement) deleteQuery, null ); + return createReactiveCriteriaQuery( (SqmDeleteStatement) deleteQuery, null ); } catch ( RuntimeException e ) { throw getExceptionConverter().convert( e ); @@ -927,10 +1183,10 @@ public ReactiveMutationQuery createReactiveMutationQuery(CriteriaDelete ReactiveMutationQuery createReactiveMutationQuery(JpaCriteriaInsertSelect insertSelect) { + public ReactiveMutationQuery createReactiveMutationQuery(JpaCriteriaInsert insert) { checkOpen(); try { - return createCriteriaQuery( (SqmInsertSelectStatement) insertSelect, null ); + return createReactiveCriteriaQuery( (SqmInsertStatement) insert, null ); } catch ( RuntimeException e ) { throw getExceptionConverter().convert( e ); @@ -951,11 +1207,64 @@ public ReactiveMutationQuery createNamedReactiveMutationQuery(String quer ); } + @Override + public ReactiveQueryImplementor createReactiveNamedQuery(String queryName) { + checksBeforeQueryCreation(); + try { + return (ReactiveQueryImplementor) buildNamedQuery( + queryName, + this::createSqmQueryImplementor, + this::createNativeQueryImplementor + ); + } + catch (RuntimeException e) { + throw convertNamedQueryException( e ); + } + } + + @Override + public ReactiveQueryImplementor createReactiveNamedQuery(String queryName, Class resultType) { + checksBeforeQueryCreation(); + if ( resultType == null ) { + throw new IllegalArgumentException( "Result class is null" ); + } + try { + return (ReactiveQueryImplementor) buildNamedQuery( + queryName, + memento -> createSqmQueryImplementor( resultType, memento ), + memento -> createNativeQueryImplementor( resultType, memento ) + ); + } + catch (RuntimeException e) { + throw convertNamedQueryException( e ); + } + } + + private RuntimeException convertNamedQueryException(RuntimeException e) { + if ( e instanceof UnknownNamedQueryException ) { + // JPA expects this to mark the transaction for rollback only + getTransactionCoordinator().getTransactionDriverControl().markRollbackOnly(); + // it also expects an IllegalArgumentException, so wrap UnknownNamedQueryException + return new IllegalArgumentException( e.getMessage(), e ); + } + else if ( e instanceof IllegalArgumentException ) { + return e; + } + else { + return getExceptionConverter().convert( e ); + } + } + @Override public ReactiveSelectionQuery createNamedReactiveSelectionQuery(String queryName, Class expectedResultType) { return (ReactiveSelectionQuery) createNamedSelectionQuery( queryName , expectedResultType ); } + private void checksBeforeQueryCreation() { + checkOpen(); + checkTransactionSynchStatus(); + } + @Override public ReactiveMutationQuery createNativeReactiveMutationQuery(String sqlString) { final ReactiveNativeQueryImplementor query = createReactiveNativeQuery( sqlString ); @@ -965,11 +1274,6 @@ public ReactiveMutationQuery createNativeReactiveMutationQuery(String sql return query; } - @Override - public ReactiveQueryImplementor createReactiveNamedQuery(String queryName, Class resultType) { - return (ReactiveQueryImplementor) buildNamedQuery( queryName, resultType ); - } - @Override public ReactiveNativeQuery createReactiveNativeQuery(String queryString, AffectedEntities affectedEntities) { checkOpen(); @@ -996,21 +1300,15 @@ public ReactiveNativeQuery createReactiveNativeQuery( Class resultType, AffectedEntities affectedEntities) { final ReactiveNativeQuery query = createReactiveNativeQuery( queryString, affectedEntities ); - return addResultType( resultType, query ); + handleTupleResultType( resultType, query ); + addEntityOrResultType( resultType, query ); + return query; } - //TODO: copy/paste from ORM, change visibility - private ReactiveNativeQuery addResultType(Class resultClass, ReactiveNativeQuery query) { - if ( Tuple.class.equals( resultClass ) ) { - query.setTupleTransformer( new NativeQueryTupleTransformer() ); - } - else if ( getFactory().getMappingMetamodel().isEntityClass( resultClass ) ) { - query.addEntity( "alias1", resultClass.getName(), LockMode.READ ); - } - else if ( resultClass != Object.class && resultClass != Object[].class ) { - query.addScalar( 1, resultClass ); + private static void handleTupleResultType(Class resultType, ReactiveNativeQuery query) { + if ( Tuple.class.equals(resultType) ) { + query.setTupleTransformer( NativeQueryTupleTransformer.INSTANCE ); } - return query; } @Override @@ -1019,11 +1317,13 @@ public ReactiveNativeQueryImpl createReactiveNativeQuery(String queryStri pulseTransactionCoordinator(); delayedAfterCompletion(); + if ( resultSetMapping == null ) { + throw new IllegalArgumentException( "Result set mapping was not specified" ); + } + try { - // Same approach as AbstractSharedSessionContract#createNativeQuery(String, String) - final ReactiveNativeQueryImpl nativeQuery = resultSetMapping != null - ? new ReactiveNativeQueryImpl<>( queryString, getResultSetMappingMemento( resultSetMapping.getName() ), this ) - : new ReactiveNativeQueryImpl<>( queryString, this ); + final NamedResultSetMappingMemento memento = getResultSetMappingMemento( resultSetMapping.getName() ); + final ReactiveNativeQueryImpl nativeQuery = new ReactiveNativeQueryImpl<>( queryString, memento, null, this ); applyQuerySettingsAndHints( nativeQuery ); return nativeQuery; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/ReactiveStandardMutationExecutorService.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/ReactiveStandardMutationExecutorService.java index c02fc0551..46fffd02b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/ReactiveStandardMutationExecutorService.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/ReactiveStandardMutationExecutorService.java @@ -72,9 +72,8 @@ public MutationExecutor createExecutor( } private static GeneratedValuesMutationDelegate generatedValuesDelegate(MutationOperationGroup operationGroup) { - GeneratedValuesMutationDelegate generatedValuesMutationDelegate = operationGroup.asEntityMutationOperationGroup() != null + return operationGroup.asEntityMutationOperationGroup() != null ? operationGroup.asEntityMutationOperationGroup().getMutationDelegate() : null; - return generatedValuesMutationDelegate; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveJdbcMutationExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveJdbcMutationExecutor.java index ea272765a..9182d4939 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveJdbcMutationExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveJdbcMutationExecutor.java @@ -12,8 +12,6 @@ import java.util.function.BiConsumer; import java.util.function.Function; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.DialectDelegateWrapper; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.spi.QueryOptions; @@ -22,7 +20,6 @@ import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.pool.ReactiveConnection; -import org.hibernate.reactive.pool.impl.Parameters; import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.reactive.sql.exec.spi.ReactiveJdbcMutationExecutor; import org.hibernate.resource.jdbc.spi.LogicalConnectionImplementor; @@ -117,7 +114,7 @@ private static String finalSql( ExecutionContext executionContext, JdbcServices jdbcServices, QueryOptions queryOptions) { - String sql = queryOptions == null + return queryOptions == null ? jdbcMutation.getSqlString() : jdbcServices.getDialect() .addSqlHintOrComment( @@ -128,7 +125,5 @@ private static String finalSql( .getSessionFactoryOptions() .isCommentsEnabled() ); - final Dialect dialect = DialectDelegateWrapper.extractRealDialect( executionContext.getSession().getJdbcServices().getDialect() ); - return Parameters.instance( dialect ).process( sql ); } } 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 bcf33c942..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 @@ -12,14 +12,16 @@ import java.util.concurrent.TimeUnit; import org.hibernate.CacheMode; +import org.hibernate.SharedSessionContract; import org.hibernate.cache.spi.QueryKey; import org.hibernate.cache.spi.QueryResultsCache; +import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.query.TupleTransformer; -import org.hibernate.reactive.engine.impl.ReactivePersistenceContextAdapter; +import org.hibernate.engine.internal.ReactivePersistenceContextAdapter; +import org.hibernate.query.spi.QueryOptions; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.exec.spi.ReactiveSelectExecutor; import org.hibernate.reactive.sql.exec.spi.ReactiveValuesResultSet; @@ -31,7 +33,6 @@ import org.hibernate.reactive.sql.results.spi.ReactiveRowReader; import org.hibernate.reactive.sql.results.spi.ReactiveValuesMappingProducer; import org.hibernate.sql.exec.SqlExecLogger; -import org.hibernate.sql.exec.internal.JdbcExecHelper; import org.hibernate.sql.exec.internal.StandardStatementCreator; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; @@ -42,6 +43,7 @@ import org.hibernate.sql.results.internal.RowTransformerTupleTransformerAdapter; import org.hibernate.sql.results.jdbc.internal.CachedJdbcValuesMetadata; import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; import org.hibernate.sql.results.spi.RowTransformer; @@ -50,6 +52,9 @@ import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.spi.TypeConfiguration; +import static org.hibernate.internal.util.NullnessHelper.coalesceSuppliedValues; +import static org.hibernate.internal.util.collections.ArrayHelper.indexOf; + /** * @see org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl */ @@ -145,8 +150,8 @@ public CompletionStage executeQuery( ReactiveResultsConsumer resultsConsumer) { final PersistenceContext persistenceContext = executionContext.getSession().getPersistenceContext(); - boolean defaultReadOnlyOrig = persistenceContext.isDefaultReadOnly(); - Boolean readOnly = executionContext.getQueryOptions().isReadOnly(); + final boolean defaultReadOnlyOrig = persistenceContext.isDefaultReadOnly(); + final Boolean readOnly = executionContext.getQueryOptions().isReadOnly(); if ( readOnly != null ) { // The read-only/modifiable mode for the query was explicitly set. // Temporarily set the default read-only/modifiable setting to the query's setting. @@ -176,7 +181,8 @@ private CompletionStage doExecuteQuery( JdbcSelectExecutor.StatementCreator statementCreator, ReactiveResultsConsumer resultsConsumer) { - final ReactiveDeferredResultSetAccess deferredResultSetAccess = new ReactiveDeferredResultSetAccess( jdbcSelect, jdbcParameterBindings, executionContext, statementCreator, resultCountEstimate ); + final ReactiveDeferredResultSetAccess deferredResultSetAccess = + new ReactiveDeferredResultSetAccess( jdbcSelect, jdbcParameterBindings, executionContext, statementCreator, resultCountEstimate ); return resolveJdbcValuesSource( executionContext.getQueryIdentifier( deferredResultSetAccess.getFinalSql() ), @@ -213,10 +219,8 @@ public boolean shouldReturnProxies() { } }; - final JdbcValuesSourceProcessingStateStandardImpl valuesProcessingState = new JdbcValuesSourceProcessingStateStandardImpl( - executionContext, - processingOptions - ); + final JdbcValuesSourceProcessingStateStandardImpl valuesProcessingState = + new JdbcValuesSourceProcessingStateStandardImpl( executionContext, processingOptions ); final ReactiveRowReader rowReader = ReactiveResultsHelper.createRowReader( executionContext.getSession().getSessionFactory(), @@ -254,26 +258,25 @@ private static RowTransformer rowTransformer( ExecutionContext executionContext, RowTransformer transformer, ReactiveValuesResultSet jdbcValues) { - RowTransformer rowTransformer = transformer; - if ( rowTransformer == null ) { - @SuppressWarnings("unchecked") final TupleTransformer tupleTransformer = (TupleTransformer) executionContext - .getQueryOptions() - .getTupleTransformer(); - + if ( transformer == null ) { + @SuppressWarnings("unchecked") + final TupleTransformer tupleTransformer = + (TupleTransformer) executionContext.getQueryOptions().getTupleTransformer(); if ( tupleTransformer == null ) { - rowTransformer = RowTransformerStandardImpl.instance(); + return RowTransformerStandardImpl.instance(); } else { - final List> domainResults = jdbcValues.getValuesMapping() - .getDomainResults(); + final List> domainResults = jdbcValues.getValuesMapping().getDomainResults(); final String[] aliases = new String[domainResults.size()]; for ( int i = 0; i < domainResults.size(); i++ ) { aliases[i] = domainResults.get( i ).getResultVariable(); } - rowTransformer = new RowTransformerTupleTransformerAdapter<>( aliases, tupleTransformer ); + return new RowTransformerTupleTransformerAdapter<>( aliases, tupleTransformer ); } } - return rowTransformer; + else { + return transformer; + } } public CompletionStage resolveJdbcValuesSource( @@ -286,13 +289,14 @@ public CompletionStage resolveJdbcValuesSource( final SessionFactoryImplementor factory = session.getFactory(); final boolean queryCacheEnabled = factory.getSessionFactoryOptions().isQueryCacheEnabled(); - final List cachedResults; - final CacheMode cacheMode = JdbcExecHelper.resolveCacheMode( executionContext ); + final CacheMode cacheMode = resolveCacheMode( executionContext ); + final QueryOptions queryOptions = executionContext.getQueryOptions(); final boolean cacheable = queryCacheEnabled && canBeCached - && executionContext.getQueryOptions().isResultCachingEnabled() == Boolean.TRUE; - final QueryKey queryResultsCacheKey; + && queryOptions.isResultCachingEnabled() == Boolean.TRUE; + final QueryKey queryResultsCacheKey; + final List cachedResults; if ( cacheable && cacheMode.isGetEnabled() ) { SqlExecLogger.SQL_EXEC_LOGGER.debugf( "Reading Query result cache data per CacheMode#isGetEnabled [%s]", cacheMode.name() ); final Set querySpaces = jdbcSelect.getAffectedTableNames(); @@ -304,10 +308,10 @@ public CompletionStage resolveJdbcValuesSource( } final QueryResultsCache queryCache = factory.getCache() - .getQueryResultsCache( executionContext.getQueryOptions().getResultCacheRegionName() ); + .getQueryResultsCache( queryOptions.getResultCacheRegionName() ); queryResultsCacheKey = QueryKey - .from( jdbcSelect.getSqlString(), executionContext.getQueryOptions().getLimit(), executionContext.getQueryParameterBindings(), session ); + .from( jdbcSelect.getSqlString(), queryOptions.getLimit(), executionContext.getQueryParameterBindings(), session ); cachedResults = queryCache.get( // todo (6.0) : QueryCache#get takes the `queryResultsCacheKey` see tat discussion above @@ -342,7 +346,7 @@ public CompletionStage resolveJdbcValuesSource( if ( cacheable && cacheMode.isPutEnabled() ) { queryResultsCacheKey = QueryKey.from( jdbcSelect.getSqlString(), - executionContext.getQueryOptions().getLimit(), + queryOptions.getLimit(), executionContext.getQueryParameterBindings(), session ); @@ -352,16 +356,18 @@ public CompletionStage resolveJdbcValuesSource( } } - ReactiveValuesMappingProducer mappingProducer = (ReactiveValuesMappingProducer) jdbcSelect.getJdbcValuesMappingProducer(); + final LoadQueryInfluencers loadQueryInfluencers = session.getLoadQueryInfluencers(); + + final ReactiveValuesMappingProducer mappingProducer = + (ReactiveValuesMappingProducer) jdbcSelect.getJdbcValuesMappingProducer(); if ( cachedResults == null ) { if ( queryResultsCacheKey == null ) { - return mappingProducer - .reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ) + return mappingProducer.reactiveResolve( resultSetAccess, loadQueryInfluencers, factory ) .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( resultSetAccess, null, queryIdentifier, - executionContext.getQueryOptions(), + queryOptions, resultSetAccess.usesFollowOnLocking(), jdbcValuesMapping, null, @@ -371,13 +377,12 @@ public CompletionStage resolveJdbcValuesSource( else { // If we need to put the values into the cache, we need to be able to capture the JdbcValuesMetadata final CapturingJdbcValuesMetadata capturingMetadata = new CapturingJdbcValuesMetadata( resultSetAccess ); - return mappingProducer - .reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ) + return mappingProducer.reactiveResolve( resultSetAccess, loadQueryInfluencers, factory ) .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( resultSetAccess, queryResultsCacheKey, queryIdentifier, - executionContext.getQueryOptions(), + queryOptions, resultSetAccess.usesFollowOnLocking(), jdbcValuesMapping, capturingMetadata.resolveMetadataForCache(), @@ -389,34 +394,35 @@ public CompletionStage resolveJdbcValuesSource( // TODO: Implements JdbcValuesCacheHit for reactive, see JdbcSelectExecutorStandardImpl#resolveJdbcValuesSource // If we need to put the values into the cache, we need to be able to capture the JdbcValuesMetadata final CapturingJdbcValuesMetadata capturingMetadata = new CapturingJdbcValuesMetadata( resultSetAccess ); - if ( cachedResults.isEmpty() || !( cachedResults.get( 0 ) instanceof JdbcValuesMetadata ) ) { - return mappingProducer.reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ) - .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( - resultSetAccess, - queryResultsCacheKey, - queryIdentifier, - executionContext.getQueryOptions(), - resultSetAccess.usesFollowOnLocking(), - jdbcValuesMapping, - capturingMetadata.resolveMetadataForCache(), - executionContext - ) ); - } - else { - return mappingProducer - .reactiveResolve( (JdbcValuesMetadata) cachedResults.get( 0 ), session.getLoadQueryInfluencers(), factory ) - .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( - resultSetAccess, - queryResultsCacheKey, - queryIdentifier, - executionContext.getQueryOptions(), - resultSetAccess.usesFollowOnLocking(), - jdbcValuesMapping, - capturingMetadata.resolveMetadataForCache(), - executionContext - ) ); - } - } + final CompletionStage stage = + !cachedResults.isEmpty() + && cachedResults.get(0) instanceof JdbcValuesMetadata jdbcValuesMetadata + ? mappingProducer.reactiveResolve( jdbcValuesMetadata, loadQueryInfluencers, factory ) + : mappingProducer.reactiveResolve( resultSetAccess, loadQueryInfluencers, factory ); + return stage.thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( + resultSetAccess, + queryResultsCacheKey, + queryIdentifier, + queryOptions, + resultSetAccess.usesFollowOnLocking(), + jdbcValuesMapping, + capturingMetadata.resolveMetadataForCache(), + executionContext + ) ); + } + } + + /** + * 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 + ); } /** @@ -455,7 +461,7 @@ public int resolveColumnPosition(String columnName) { position = resultSetAccess.resolveColumnPosition( columnName ); columnNames[position - 1] = columnName; } - else if ( ( position = ArrayHelper.indexOf( columnNames, columnName ) + 1 ) == 0 ) { + else if ( ( position = indexOf( columnNames, columnName ) + 1 ) == 0 ) { position = resultSetAccess.resolveColumnPosition( columnName ); columnNames[position - 1] = columnName; } @@ -543,9 +549,7 @@ public void end(JdbcOperationQuerySelect jdbcSelect, T result) { } private int getResultSize(T result) { - return result instanceof Collection - ? ( (Collection) result ).size() - : -1; + return result instanceof Collection collection ? collection.size() : -1; } } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/model/ReactiveDeleteOrUpsertOperation.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/model/ReactiveDeleteOrUpsertOperation.java index d837af865..10d3cd3bc 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/model/ReactiveDeleteOrUpsertOperation.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/model/ReactiveDeleteOrUpsertOperation.java @@ -38,8 +38,6 @@ public class ReactiveDeleteOrUpsertOperation extends DeleteOrUpsertOperation implements ReactiveSelfExecutingUpdateOperation { private static final Log LOG = make( Log.class, lookup() ); - private final OptionalTableUpdate upsert; - private final UpsertOperation upsertOperation; public ReactiveDeleteOrUpsertOperation( EntityMutationTarget mutationTarget, @@ -47,8 +45,10 @@ public ReactiveDeleteOrUpsertOperation( UpsertOperation upsertOperation, OptionalTableUpdate optionalTableUpdate) { super( mutationTarget, tableMapping, upsertOperation, optionalTableUpdate ); - this.upsert = optionalTableUpdate; - this.upsertOperation = upsertOperation; + } + + public ReactiveDeleteOrUpsertOperation(DeleteOrUpsertOperation original) { + super( original ); } @Override @@ -92,10 +92,7 @@ private CompletionStage performReactiveUpsert( final String tableName = getTableDetails().getTableName(); MODEL_MUTATION_LOGGER.tracef( "#performReactiveUpsert(%s)", tableName ); - final PreparedStatementGroupSingleTable statementGroup = new PreparedStatementGroupSingleTable( - upsertOperation, - session - ); + final PreparedStatementGroupSingleTable statementGroup = new PreparedStatementGroupSingleTable( getUpsertOperation(), session ); final PreparedStatementDetails statementDetails = statementGroup.resolvePreparedStatementDetails( tableName ); session.getJdbcServices().getSqlStatementLogger().logStatement( statementDetails.getSqlString() ); @@ -123,10 +120,10 @@ private CompletionStage performReactiveDelete( MODEL_MUTATION_LOGGER.tracef( "#performReactiveDelete(%s)", tableName ); final TableDeleteStandard upsertDeleteAst = new TableDeleteStandard( - upsert.getMutatingTable(), + getOptionalTableUpdate().getMutatingTable(), getMutationTarget(), "upsert delete", - upsert.getKeyBindings(), + getOptionalTableUpdate().getKeyBindings(), emptyList(), emptyList() ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/model/ReactiveOptionalTableUpdateOperation.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/model/ReactiveOptionalTableUpdateOperation.java index 6fea783a3..366d09d48 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/model/ReactiveOptionalTableUpdateOperation.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/model/ReactiveOptionalTableUpdateOperation.java @@ -6,16 +6,13 @@ package org.hibernate.reactive.sql.model; import java.sql.SQLException; -import java.util.Collections; import java.util.concurrent.CompletionStage; import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; -import org.hibernate.engine.jdbc.mutation.internal.MutationQueryOptions; import org.hibernate.engine.jdbc.mutation.internal.PreparedStatementGroupSingleTable; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.persister.entity.mutation.UpdateValuesAnalysis; import org.hibernate.reactive.adaptor.impl.PrepareStatementDetailsAdaptor; import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor; @@ -23,22 +20,10 @@ import org.hibernate.reactive.pool.ReactiveConnection; import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.reactive.util.impl.CompletionStages; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.model.MutationTarget; import org.hibernate.sql.model.TableMapping; import org.hibernate.sql.model.ValuesAnalysis; -import org.hibernate.sql.model.ast.MutatingTableReference; -import org.hibernate.sql.model.ast.TableDelete; -import org.hibernate.sql.model.ast.TableInsert; -import org.hibernate.sql.model.ast.TableUpdate; import org.hibernate.sql.model.internal.OptionalTableUpdate; -import org.hibernate.sql.model.internal.TableDeleteCustomSql; -import org.hibernate.sql.model.internal.TableDeleteStandard; -import org.hibernate.sql.model.internal.TableInsertCustomSql; -import org.hibernate.sql.model.internal.TableInsertStandard; -import org.hibernate.sql.model.internal.TableUpdateCustomSql; -import org.hibernate.sql.model.internal.TableUpdateStandard; import org.hibernate.sql.model.jdbc.JdbcDeleteMutation; import org.hibernate.sql.model.jdbc.JdbcInsertMutation; import org.hibernate.sql.model.jdbc.JdbcMutationOperation; @@ -196,43 +181,6 @@ private CompletionStage performReactiveUpdate( } ); } - // FIXME: Adding this to ORM will save us some duplicated code (similar to createJdbcInsert and createJdbcDelete) - private JdbcMutationOperation createJdbcUpdate(SharedSessionContractImplementor session) { - MutationTarget mutationTarget = super.getMutationTarget(); - TableUpdate tableUpdate; - if ( getTableDetails().getUpdateDetails() != null - && getTableDetails().getUpdateDetails().getCustomSql() != null ) { - tableUpdate = new TableUpdateCustomSql( - new MutatingTableReference( getTableDetails() ), - mutationTarget, - "upsert update for " + mutationTarget.getRolePath(), - upsert.getValueBindings(), - upsert.getKeyBindings(), - upsert.getOptimisticLockBindings(), - upsert.getParameters() - ); - } - else { - tableUpdate = new TableUpdateStandard( - new MutatingTableReference( getTableDetails() ), - mutationTarget, - "upsert update for " + mutationTarget.getRolePath(), - upsert.getValueBindings(), - upsert.getKeyBindings(), - upsert.getOptimisticLockBindings(), - upsert.getParameters() - ); - } - - final SqlAstTranslator translator = session - .getJdbcServices() - .getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildModelMutationTranslator( tableUpdate, session.getFactory() ); - - return translator.translate( null, MutationQueryOptions.INSTANCE ); - } - private CompletionStage performReactiveInsert( JdbcValueBindings jdbcValueBindings, SharedSessionContractImplementor session) { @@ -249,85 +197,4 @@ private CompletionStage performReactiveInsert( ReactiveConnection reactiveConnection = ( (ReactiveConnectionSupplier) session ).getReactiveConnection(); return reactiveConnection.update( statementDetails.getSqlString(), params ).thenCompose(CompletionStages::voidFuture); } - - /** - * @see org.hibernate.sql.model.jdbc.OptionalTableUpdateOperation#createJdbcInsert(SharedSessionContractImplementor) - */ - // FIXME: change visibility to protected in ORM and remove this method - private JdbcInsertMutation createJdbcInsert(SharedSessionContractImplementor session) { - final TableInsert tableInsert; - if ( getTableDetails().getInsertDetails() != null - && getTableDetails().getInsertDetails().getCustomSql() != null ) { - tableInsert = new TableInsertCustomSql( - new MutatingTableReference( getTableDetails() ), - getMutationTarget(), - CollectionHelper.combine( upsert.getValueBindings(), upsert.getKeyBindings() ), - upsert.getParameters() - ); - } - else { - tableInsert = new TableInsertStandard( - new MutatingTableReference( getTableDetails() ), - getMutationTarget(), - CollectionHelper.combine( upsert.getValueBindings(), upsert.getKeyBindings() ), - Collections.emptyList(), - upsert.getParameters() - ); - } - - final SessionFactoryImplementor factory = session.getSessionFactory(); - final SqlAstTranslatorFactory sqlAstTranslatorFactory = factory - .getJdbcServices() - .getJdbcEnvironment() - .getSqlAstTranslatorFactory(); - - final SqlAstTranslator translator = sqlAstTranslatorFactory.buildModelMutationTranslator( - tableInsert, - factory - ); - - return translator.translate( null, MutationQueryOptions.INSTANCE ); - } - - /** - * @see org.hibernate.sql.model.jdbc.OptionalTableUpdateOperation#createJdbcDelete(SharedSessionContractImplementor) - */ - // FIXME: change visibility to protected in ORM and remove this method - private JdbcDeleteMutation createJdbcDelete(SharedSessionContractImplementor session) { - final TableDelete tableDelete; - if ( getTableDetails().getDeleteDetails() != null - && getTableDetails().getDeleteDetails().getCustomSql() != null ) { - tableDelete = new TableDeleteCustomSql( - new MutatingTableReference( getTableDetails() ), - getMutationTarget(), - "upsert delete for " + upsert.getMutationTarget().getRolePath(), - upsert.getKeyBindings(), - upsert.getOptimisticLockBindings(), - upsert.getParameters() - ); - } - else { - tableDelete = new TableDeleteStandard( - new MutatingTableReference( getTableDetails() ), - getMutationTarget(), - "upsert delete for " + getMutationTarget().getRolePath(), - upsert.getKeyBindings(), - upsert.getOptimisticLockBindings(), - upsert.getParameters() - ); - } - - final SessionFactoryImplementor factory = session.getSessionFactory(); - final SqlAstTranslatorFactory sqlAstTranslatorFactory = factory - .getJdbcServices() - .getJdbcEnvironment() - .getSqlAstTranslatorFactory(); - - final SqlAstTranslator translator = sqlAstTranslatorFactory.buildModelMutationTranslator( - tableDelete, - factory - ); - - return translator.translate( null, MutationQueryOptions.INSTANCE ); - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/ReactiveResultSetMapping.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/ReactiveResultSetMapping.java index ea440fb80..a4b45101f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/ReactiveResultSetMapping.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/ReactiveResultSetMapping.java @@ -16,9 +16,9 @@ import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.named.NamedResultSetMappingMemento; +import org.hibernate.query.results.LegacyFetchBuilder; import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.ResultSetMapping; -import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.sql.results.internal.ReactiveResultSetAccess; @@ -88,7 +88,7 @@ public void visitResultBuilders(BiConsumer resultBuilder } @Override - public void visitLegacyFetchBuilders(Consumer resultBuilderConsumer) { + public void visitLegacyFetchBuilders(Consumer resultBuilderConsumer) { delegate.visitLegacyFetchBuilders( resultBuilderConsumer ); } @@ -98,7 +98,7 @@ public void addResultBuilder(ResultBuilder resultBuilder) { } @Override - public void addLegacyFetchBuilder(DynamicFetchBuilderLegacy fetchBuilder) { + public void addLegacyFetchBuilder(LegacyFetchBuilder fetchBuilder) { delegate.addLegacyFetchBuilder( fetchBuilder ); } 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/ReactiveEmbeddableForeignKeyResultImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java index a708481cf..1e3433320 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java @@ -10,7 +10,6 @@ import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableForeignKeyResultImpl; -import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableInitializerImpl; public class ReactiveEmbeddableForeignKeyResultImpl extends EmbeddableForeignKeyResultImpl { @@ -22,6 +21,6 @@ public ReactiveEmbeddableForeignKeyResultImpl(EmbeddableForeignKeyResultImpl public EmbeddableInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { return getReferencedModePart() instanceof NonAggregatedIdentifierMapping ? new ReactiveNonAggregatedIdentifierMappingInitializer( this, null, creationState, true ) - : new EmbeddableInitializerImpl( this, null, null, creationState, true ); + : new ReactiveEmbeddableInitializerImpl( this, null, null, creationState, true ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java index 69ae34a73..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,20 @@ public ReactiveEmbeddableInitializerData( super( initializer, rowProcessingState ); } + public Object[] getRowState(){ + return rowState; + } + + @Override + public void setState(State state) { + super.setState( state ); + if ( State.UNINITIALIZED == state ) { + // reset instance to null as otherwise EmbeddableInitializerImpl#prepareCompositeInstance + // will never create a new instance after the "first row with a non-null instance" gets processed + setInstance( null ); + } + } + public EmbeddableMappingType.ConcreteEmbeddableType getConcreteEmbeddableType() { return super.concreteEmbeddableType; } @@ -54,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 ); @@ -71,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/embeddable/internal/ReactiveNonAggregatedIdentifierMappingInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingInitializer.java index 0096e8d0f..51f7d59b7 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingInitializer.java @@ -60,9 +60,8 @@ public CompletionStage reactiveResolveKey(NonAggregatedIdentifierMappingIn } else { final RowProcessingState rowProcessingState = data.getRowProcessingState(); - final boolean[] dataIsMissing = {false}; return loop( getInitializers(), initializer -> { - if ( dataIsMissing[0] ) { + if ( initializer == null ) { return voidFuture(); } final InitializerData subData = ( (ReactiveInitializer) initializer ) @@ -72,7 +71,6 @@ public CompletionStage reactiveResolveKey(NonAggregatedIdentifierMappingIn .thenAccept( v -> { if ( subData.getState() == State.MISSING ) { data.setState( State.MISSING ); - dataIsMissing[0] = true; } } ); } ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityDelayedFetchInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityDelayedFetchInitializer.java index 1a1660725..c40ee20d3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityDelayedFetchInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityDelayedFetchInitializer.java @@ -131,7 +131,7 @@ public CompletionStage reactiveResolveInstance(EntityDelayedFetchInitializ final String uniqueKeyPropertyName = referencedModelPart.getReferencedPropertyName(); final Type uniqueKeyPropertyType = uniqueKeyPropertyName == null ? concreteDescriptor.getIdentifierType() - : session.getFactory().getReferencedPropertyType( concreteDescriptor.getEntityName(), uniqueKeyPropertyName ); + : session.getFactory().getRuntimeMetamodels().getReferencedPropertyType( concreteDescriptor.getEntityName(), uniqueKeyPropertyName ); final EntityUniqueKey euk = new EntityUniqueKey( concreteDescriptor.getEntityName(), uniqueKeyPropertyName, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchSelectImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchSelectImpl.java index 0cae0a79d..ce38df6e1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchSelectImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchSelectImpl.java @@ -32,7 +32,7 @@ public EntityInitializer createInitializer(InitializerParent parent, Assem } @Override - protected EntityAssembler buildEntityAssembler(EntityInitializer entityInitializer) { + protected EntityAssembler buildEntityAssembler(EntityInitializer entityInitializer) { return new ReactiveEntityAssembler( getFetchedMapping().getJavaType(), entityInitializer ); } } 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 a00287eab..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 @@ -20,13 +20,13 @@ import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; -import org.hibernate.loader.ast.internal.CacheEntityLoaderHelper; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; 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; @@ -45,6 +45,7 @@ import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; +import static org.hibernate.loader.internal.CacheLoadHelper.loadFromSecondLevelCache; import static org.hibernate.metamodel.mapping.ForeignKeyDescriptor.Nature.TARGET; import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; @@ -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, @@ -576,7 +590,7 @@ private boolean isProxyInstance(Object proxy) { // FIXME: I could change the scope of this method in ORM private Object resolveInstanceFromCache(ReactiveEntityInitializerData data) { - return CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( + return loadFromSecondLevelCache( data.getRowProcessingState().getSession().asEventSource(), null, data.getLockMode(), diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java index b27c0cc93..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; @@ -56,6 +56,16 @@ public Object getEntityIdentifier() { public void setEntityIdentifier(Object entityIdentifier) { super.entityIdentifier = entityIdentifier; } + + @Override + public void setState(State state) { + super.setState( state ); + if ( State.UNINITIALIZED == state ) { + // reset instance to null as otherwise EmbeddableInitializerImpl#prepareCompositeInstance + // will never create a new instance after the "first row with a non-null instance" gets processed + setInstance( null ); + } + } } private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); @@ -144,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/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializerBuilder.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializerBuilder.java index c4cb87838..a3b7d46f1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializerBuilder.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializerBuilder.java @@ -100,7 +100,7 @@ public static EntityInitializer createInitializer( throw new IllegalStateException( "Should be unreachable" ); } - // FIXME: Use the one in ORM + // FIXME: Use the one in ORM: EntitySelectFetchInitializerBuilder#determineBatchMode public static BatchMode determineBatchMode( EntityPersister entityPersister, InitializerParent parent, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java index da94cc329..3f88bcffb 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java @@ -13,17 +13,17 @@ import java.util.concurrent.CompletionStage; import org.hibernate.HibernateException; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.DialectDelegateWrapper; +import org.hibernate.LockOptions; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.engine.spi.SessionEventListenerManager; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor; +import org.hibernate.reactive.engine.impl.ReactiveCallbackImpl; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.pool.ReactiveConnection; -import org.hibernate.reactive.pool.impl.Parameters; import org.hibernate.reactive.session.ReactiveConnectionSupplier; +import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.resource.jdbc.spi.JdbcSessionContext; import org.hibernate.resource.jdbc.spi.LogicalConnectionImplementor; @@ -63,6 +63,22 @@ public ReactiveDeferredResultSetAccess( this.sqlStatementLogger = executionContext.getSession().getJdbcServices().getSqlStatementLogger(); } + /** + * Reactive version of {@link org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess#registerAfterLoadAction(ExecutionContext, LockOptions)} + * calling {@link ReactiveSession#reactiveLock(String, Object, LockOptions)} + */ + @Override + protected void registerAfterLoadAction(ExecutionContext executionContext, LockOptions lockOptionsToUse) { + ( (ReactiveCallbackImpl) executionContext.getCallback() ).registerReactiveAfterLoadAction( + (entity, persister, session) -> + ( (ReactiveSession) session ).reactiveLock( + persister.getEntityName(), + entity, + lockOptionsToUse + ) + ); + } + @Override public ResultSet getResultSet() { if ( resultSet == null ) { @@ -100,12 +116,18 @@ private Integer saveColumnCount(Integer columnCount) { } @Override - public BasicType resolveType(int position, JavaType explicitJavaType, SessionFactoryImplementor sessionFactory) { + public BasicType resolveType( + int position, + JavaType explicitJavaType, + SessionFactoryImplementor sessionFactory) { return super.resolveType( position, explicitJavaType, sessionFactory ); } @Override - public BasicType resolveType(int position, JavaType explicitJavaType, TypeConfiguration typeConfiguration) { + public BasicType resolveType( + int position, + JavaType explicitJavaType, + TypeConfiguration typeConfiguration) { return super.resolveType( position, explicitJavaType, typeConfiguration ); } @@ -145,14 +167,11 @@ private JdbcSessionContext context() { } private CompletionStage executeQuery() { - final LogicalConnectionImplementor logicalConnection = getPersistenceContext().getJdbcCoordinator().getLogicalConnection(); + final LogicalConnectionImplementor logicalConnection = getPersistenceContext() + .getJdbcCoordinator().getLogicalConnection(); return completedFuture( logicalConnection ) .thenCompose( lg -> { LOG.tracef( "Executing query to retrieve ResultSet : %s", getFinalSql() ); - - Dialect dialect = DialectDelegateWrapper.extractRealDialect( executionContext.getSession().getJdbcServices().getDialect() ); - // I'm not sure calling Parameters here is necessary, the query should already have the right parameters - final String sql = Parameters.instance( dialect ).process( getFinalSql() ); Object[] parameters = PreparedStatementAdaptor.bind( super::bindParameters ); final SessionEventListenerManager eventListenerManager = executionContext @@ -162,7 +181,7 @@ private CompletionStage executeQuery() { eventListenerManager.jdbcExecuteStatementStart(); return connection() - .selectJdbc( sql, parameters ) + .selectJdbc( getFinalSql(), parameters ) .thenCompose( this::validateResultSet ) .whenComplete( (resultSet, throwable) -> { // FIXME: I don't know if this event makes sense for Vert.x @@ -172,10 +191,11 @@ private CompletionStage executeQuery() { .thenCompose( this::reactiveSkipRows ) .handle( CompletionStages::handle ) .thenCompose( handler -> handler.hasFailed() - ? convertException( resultSet, handler.getThrowable() ) - : handler.getResultAsCompletionStage() + ? convertException( resultSet, handler.getThrowable() ) + : handler.getResultAsCompletionStage() ); } ) + // same as a finally block .whenComplete( (o, throwable) -> logicalConnection.afterStatement() ); } @@ -225,6 +245,10 @@ private CompletionStage convertException(T object, Throwable throwable) { if ( cause instanceof HibernateException ) { return failedFuture( cause ); } + // SQL server throws an exception as soon as we run the query + if ( cause instanceof UnsupportedOperationException && cause.getMessage().contains( "Unable to decode typeInfo for XML" ) ) { + return failedFuture( LOG.unsupportedXmlType() ); + } return failedFuture( new HibernateException( cause ) ); } return completedFuture( object ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityDelayedFetchImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityDelayedFetchImpl.java index 7adeab775..45312ed3f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityDelayedFetchImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityDelayedFetchImpl.java @@ -43,7 +43,7 @@ public EntityInitializer createInitializer(InitializerParent parent, Assem } @Override - protected EntityAssembler buildEntityAssembler(EntityInitializer entityInitializer) { + protected EntityAssembler buildEntityAssembler(EntityInitializer entityInitializer) { return new ReactiveEntityAssembler( getFetchedMapping().getJavaType(), entityInitializer ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java index 312f510f5..49c7deda4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java @@ -87,7 +87,7 @@ ReactiveInitializersList build(final Map> initiali } private Initializer[] toArray(final ArrayList> initializers) { - return initializers.toArray( new Initializer[initializers.size()] ); + return initializers.toArray( new Initializer[0] ); } } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java index 7c23bc739..e7283647f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java @@ -13,6 +13,7 @@ import java.util.function.Supplier; import org.hibernate.HibernateException; +import org.hibernate.engine.internal.ReactivePersistenceContextAdapter; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.ResultListTransformer; @@ -103,7 +104,7 @@ public CompletionStage> consume( } return falseFuture(); } ) ) - .thenApply( v -> finishUp( rowReader, rowProcessingState, jdbcValuesSourceProcessingState, results, readRows, queryOptions ) ) + .thenCompose( v -> finishUp( rowReader, rowProcessingState, jdbcValuesSourceProcessingState, results, readRows, queryOptions ) ) .handle( CompletionStages::handle ) .thenCompose( handler -> { end( jdbcValues, session, jdbcValuesSourceProcessingState, persistenceContext, handler.getThrowable() ); @@ -111,18 +112,36 @@ public CompletionStage> consume( } ); } - private List finishUp( + private CompletionStage> finishUp( ReactiveRowReader rowReader, ReactiveRowProcessingState rowProcessingState, JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState, Results results, int[] readRows, QueryOptions queryOptions) { rowReader.finishUp( rowProcessingState ); - jdbcValuesSourceProcessingState.finishUp( readRows[0] > 1 ); + return finishUp( readRows[0] > 1, rowProcessingState.getSession(), jdbcValuesSourceProcessingState ) + .thenApply( v -> { + final ResultListTransformer resultListTransformer = (ResultListTransformer) queryOptions.getResultListTransformer(); + return resultListTransformer != null + ? resultListTransformer.transformList( results.getResults() ) + : results.getResults(); + } ); + } - final ResultListTransformer resultListTransformer = (ResultListTransformer) queryOptions.getResultListTransformer(); - return resultListTransformer != null - ? resultListTransformer.transformList( results.getResults() ) - : results.getResults(); + /** + * Reactive equivalent of {@link org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl#finishUp(boolean)} + */ + private static CompletionStage finishUp( + boolean registerSubselects, + SharedSessionContractImplementor session, + JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState) { + jdbcValuesSourceProcessingState.finishLoadingCollections(); + return ( (ReactivePersistenceContextAdapter) session.getPersistenceContextInternal() ) + .reactivePostLoad( + jdbcValuesSourceProcessingState, + registerSubselects + ? jdbcValuesSourceProcessingState.getExecutionContext()::registerLoadingEntityHolder + : null + ); } /** @@ -284,8 +303,7 @@ public boolean addUnique(R result) { } public boolean add(R result) { - results.add( result ); - return true; + return results.add( result ); } public List getResults() { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java index 814db1e4b..df0774afe 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java @@ -8,12 +8,17 @@ import java.util.concurrent.CompletionStage; import org.hibernate.Incubating; +import org.hibernate.engine.internal.ReactivePersistenceContextAdapter; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.exec.spi.ReactiveValuesResultSet; +import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; +/** + * @see org.hibernate.sql.results.spi.SingleResultConsumer + */ @Incubating public class ReactiveSingleResultConsumer implements ReactiveResultsConsumer { @@ -29,15 +34,28 @@ public CompletionStage consume( return rowProcessingState.next() .thenCompose( hasNext -> rowReader .reactiveReadRow( rowProcessingState, processingOptions ) - .thenApply( result -> { + .thenCompose( result -> { rowProcessingState.finishRowProcessing( true ); rowReader.finishUp( rowProcessingState ); - jdbcValuesSourceProcessingState.finishUp( false ); - return result; + return finishUp( session, jdbcValuesSourceProcessingState, result ); } ) ); } + /** + * Reactive version of {@link JdbcValuesSourceProcessingStateStandardImpl#finishUp(boolean)} + */ + private static CompletionStage finishUp( + SharedSessionContractImplementor session, + JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState, + T result) { + jdbcValuesSourceProcessingState.finishLoadingCollections(); + final ExecutionContext executionContext = jdbcValuesSourceProcessingState.getExecutionContext(); + return ( (ReactivePersistenceContextAdapter) session.getPersistenceContextInternal() ) + .reactivePostLoad( jdbcValuesSourceProcessingState, executionContext::registerLoadingEntityHolder ) + .thenApply( v -> result ); + } + @Override public boolean canResultsBeCached() { return false; 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 47da9f6cf..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; @@ -19,14 +20,14 @@ import org.hibernate.LockMode; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.collection.spi.AbstractPersistentCollection; -import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; 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; import org.hibernate.reactive.common.AffectedEntities; import org.hibernate.reactive.common.Identifier; import org.hibernate.reactive.common.ResultSetMapping; @@ -359,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. @@ -514,683 +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}. + * Create an instance of {@link MutationQuery} for the given update tree. * - * @param entityClass The entity type - * @param id an identifier - * @param lockModeType the requested {@link LockModeType} + * @param updateQuery the update criteria query * - * @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 org.hibernate.query.QueryProducer#createMutationQuery(CriteriaUpdate) */ - 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); + MutationQuery createMutationQuery(CriteriaUpdate updateQuery); - /** - * 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 delete tree. * - * @param entityGraph an {@link EntityGraph} specifying the entity - * and associations to be fetched - * @param id an identifier + * @param deleteQuery the delete criteria query * - * @see #find(Class,Object) + * @return The {@link MutationQuery} instance for manipulation and execution + * + * @see org.hibernate.query.QueryProducer#createMutationQuery(CriteriaDelete) */ - CompletionStage find(EntityGraph entityGraph, Object id); + MutationQuery createMutationQuery(CriteriaDelete deleteQuery); /** - * Asynchronously return the persistent instances of the given entity - * class with the given 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 ids the identifiers + * @param insert the insert select 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(JpaCriteriaInsert) */ - CompletionStage> find(Class entityClass, Object... ids); + MutationQuery createMutationQuery(JpaCriteriaInsert insert); /** - * 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 typed {@link Query} instance for the given typed query reference. * - * @param entityClass The entity type - * @param naturalId the natural identifier + * @param typedQueryReference the type query reference * - * @return a persistent instance or null via a {@code CompletionStage} + * @return The {@link Query} instance for execution + * + * @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) */ - @Incubating - CompletionStage find(Class entityClass, Identifier naturalId); + Query createQuery(TypedQueryReference typedQueryReference); /** - * 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 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 Query} 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}. - * - *

    -		 * {@code session.persist(newBook).thenAccept(v -> session.flush());}
    -		 * 
    + * Create an instance of {@link MutationQuery} for the given criteria + * update. * - * @param entity a transient instance of a persistent class + * @param criteriaUpdate The {@link CriteriaUpdate} * - * @see jakarta.persistence.EntityManager#persist(Object) + * @return The {@link MutationQuery} instance for manipulation and execution */ - CompletionStage persist(Object entity); + 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 */ - CompletionStage persist(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}. - * - *

    -		 * {@code session.delete(book).thenAccept(v -> session.flush());}
    -		 * 
    + * Create an instance of {@link Query} for the named query. * - * @param entity the managed persistent instance to be removed + * @param queryName The name of the query * - * @throws IllegalArgumentException if the given instance is not managed + * @return The {@link Query} instance for manipulation and execution * - * @see jakarta.persistence.EntityManager#remove(Object) + * @see jakarta.persistence.EntityManager#createQuery(String) */ - CompletionStage 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) */ - CompletionStage remove(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 - * mapped with {@link jakarta.persistence.CascadeType#MERGE}. + * Create an instance of {@link Query} for the given SQL query string, + * using the given {@code resultType} to interpret the results. * - * @param entity a detached instance with state to be copied + *

      + *
    • 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.
    • + *
    * - * @return an updated persistent instance + * @param queryString The SQL query + * @param resultType the Java type returned in each row of query results * - * @see jakarta.persistence.EntityManager#merge(Object) - */ - CompletionStage merge(T entity); - - /** - * Merge multiple entity instances at once. + * @return The {@link SelectionQuery} instance for manipulation and execution * - * @see #merge(Object) + * @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: + * Create an instance of {@link SelectionQuery} for the given criteria + * query. * - *
      - *
    • 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}. - *
    + * @param criteriaQuery The {@link CriteriaQuery} * - * 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 lockModeType the lock level + * @see jakarta.persistence.EntityManager#createQuery(String) + */ + SelectionQuery createQuery(CriteriaQuery criteriaQuery); + + /** + * 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}. * - * @throws IllegalArgumentException if the given instance is not managed + * @since 3 */ - default CompletionStage lock(Object entity, LockModeType lockModeType) { - return lock( entity, convertToLockMode(lockModeType) ); - } + CriteriaBuilder getCriteriaBuilder(); + } -// /** -// * 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); + /** + * 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 { /** - * 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. + * 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.flush().thenAccept(v -> print("done saving changes"));}
    +		 * {@code session.find(Book.class, id).thenAccept(book -> print(book.getTitle()));}
     		 * 
    * - * @see jakarta.persistence.EntityManager#flush() + * @param entityClass The entity type + * @param id an identifier + * + * @return a persistent instance or null via a {@code CompletionStage} + * + * @see jakarta.persistence.EntityManager#find(Class, Object) */ - CompletionStage flush(); + CompletionStage find(Class entityClass, Object id); /** - * 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: - *

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

    + * Asynchronously return the persistent instance of the given entity + * class with the given identifier, requesting the given {@link LockMode}. * - * @param association a lazy-loaded association, or a proxy + * @param entityClass The entity type + * @param id an identifier + * @param lockMode the requested {@link LockMode} * - * @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 #find(Class,Object) + * @see #lock(Object, LockMode) this discussion of lock modes */ - CompletionStage fetch(T association); + CompletionStage find(Class entityClass, Object id, LockMode lockMode); /** - * 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 LockModeType}. * - *
    -		 * {@code session.fetch(book, Book_.isbn).thenAccept(isbn -> print(isbn))}
    -		 * 
    + * @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} + * + * @see #find(Class,Object) + * @see #lock(Object, LockMode) this discussion of lock modes */ - CompletionStage fetch(E entity, Attribute field); + 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. + * + * @param entityGraph an {@link EntityGraph} specifying the entity + * and associations to be fetched + * @param id an identifier + * + * @see #find(Class,Object) + */ + CompletionStage find(EntityGraph entityGraph, Object id); /** - * Asynchronously fetch an association that's configured for lazy loading, - * and unwrap the underlying entity implementation from any proxy. + * Asynchronously return the persistent instances of the given entity + * class with the given identifiers, or null if there is no such + * persistent instance. * - *
    -		 * {@code session.unproxy(author.getBook()).thenAccept(book -> print(book.getTitle()));}
    -		 * 
    + * @param entityClass The entity type + * @param ids the identifiers * - * @param association a lazy-loaded association + * @return a list of persistent instances and nulls via a {@code CompletionStage} + */ + CompletionStage> find(Class entityClass, Object... ids); + + /** + * Asynchronously return the persistent instance of the given entity + * class with the given natural identifiers, or null if there is no + * such persistent instance. * - * @return the fetched association, via a {@code CompletionStage} + * @param entityClass The entity type + * @param naturalId the natural identifier * - * @see org.hibernate.Hibernate#unproxy(Object) + * @return a persistent instance or null via a {@code CompletionStage} */ - CompletionStage unproxy(T association); + @Incubating + CompletionStage find(Class entityClass, Identifier naturalId); /** - * Determine the current lock mode of the given entity. + * 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 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) */ - LockMode getLockMode(Object entity); + T getReference(Class entityClass, Object id); /** - * Determine if the given instance belongs to this persistence context. + * 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 */ - boolean contains(Object entity); + T getReference(T entity); /** - * Create an instance of {@link SelectionQuery} for the given HQL/JPQL - * query string. + * 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 queryString The HQL/JPQL query + *

    +		 * {@code session.persist(newBook).thenAccept(v -> session.flush());}
    +		 * 
    * - * @return The {@link SelectionQuery} instance for manipulation and execution + * @param entity a transient instance of a persistent class * - * @see jakarta.persistence.EntityManager#createQuery(String, Class) + * @see jakarta.persistence.EntityManager#persist(Object) */ - SelectionQuery createSelectionQuery(String queryString, Class resultType); + CompletionStage persist(Object entity); /** - * 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 + * 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. * - * @return The {@link MutationQuery} instance for manipulation and execution + * @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. * - * @see jakarta.persistence.EntityManager#createQuery(String) + * @see #persist(Object) */ - MutationQuery createMutationQuery(String queryString); + CompletionStage persist(Object... entities); /** - * 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. + * 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}. * - * @param queryString The HQL/JPQL query, update or delete statement + *

    +		 * {@code session.delete(book).thenAccept(v -> session.flush());}
    +		 * 
    * - * @return The {@link Query} instance for manipulation and execution + * @param entity the managed persistent instance to be removed * - * @deprecated See explanation in - * {@link org.hibernate.query.QueryProducer#createSelectionQuery(String)} + * @throws IllegalArgumentException if the given instance is not managed * - * @see jakarta.persistence.EntityManager#createQuery(String) + * @see jakarta.persistence.EntityManager#remove(Object) */ - @Deprecated - Query createQuery(String queryString); + CompletionStage remove(Object entity); /** - * Create an instance of {@link SelectionQuery} for the given HQL/JPQL - * query string and query result type. + * Remove multiple entity instances at once. * - * @param queryString The HQL/JPQL query - * @param resultType the Java type returned in each row of query results + * @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}. * - * @return The {@link Query} instance for manipulation and execution + * @param entity a detached instance with state to be copied * - * @see jakarta.persistence.EntityManager#createQuery(String, Class) + * @return an updated persistent instance + * + * @see jakarta.persistence.EntityManager#merge(Object) */ - SelectionQuery createQuery(String queryString, Class resultType); + CompletionStage merge(T entity); /** - * Create an instance of {@link MutationQuery} for the given criteria - * update. + * Merge multiple entity instances at once. + * + * @see #merge(Object) + */ + CompletionStage merge(Object... entities); + + /** + * 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: + * + *

      + *
    • where a database trigger alters the object state after insert or + * update, or + *
    • after executing direct native SQL in the same session. + *
    * - * @param criteriaUpdate The {@link CriteriaUpdate} + * @param entity a managed persistent instance * - * @return The {@link MutationQuery} instance for manipulation and execution + * @throws IllegalArgumentException if the given instance is not managed + * + * @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); /** - * 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 the current lock mode of the given entity. */ - SelectionQuery createQuery(CriteriaQuery criteriaQuery); + LockMode getLockMode(Object entity); + + /** + * Determine if the given instance belongs to this persistence context. + */ + boolean contains(Object entity); /** * Set the {@link FlushMode flush mode} for this session. @@ -1244,44 +1281,23 @@ default Session setFlushMode(FlushModeType flushModeType) { /** * Completely clear the session. Detach all persistent instances and cancel - * all pending insertions, updates and deletions. - * - * @see jakarta.persistence.EntityManager#clear() - */ - Session clear(); - - /** - * Enable a particular fetch profile on this session, or do nothing if - * requested fetch profile is already enabled. - * - * @param name The name of the fetch profile to be enabled. - * @throws org.hibernate.UnknownProfileException Indicates that the given name does not - * match any known profile names - * - * @see org.hibernate.engine.profile.FetchProfile for discussion of this feature - */ - 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} + * all pending insertions, updates and deletions. + * + * @see jakarta.persistence.EntityManager#clear() */ - EntityGraph createEntityGraph(Class rootType); + Session clear(); /** - * Create a new mutable copy of a named {@link EntityGraph} + * Enable a particular fetch profile on this session, or do nothing if + * requested fetch profile is already enabled. + * + * @param name The name of the fetch profile to be enabled. + * @throws org.hibernate.UnknownProfileException Indicates that the given name does not + * match any known profile names + * + * @see org.hibernate.engine.profile.FetchProfile for discussion of this feature */ - EntityGraph createEntityGraph(Class rootType, String graphName); + Session enableFetchProfile(String name); /** * Disable a particular fetch profile on this session, or do nothing if @@ -1539,7 +1555,7 @@ default Session setCacheRetrieveMode(CacheRetrieveMode cacheRetrieveMode) { * * @see org.hibernate.StatelessSession */ - interface StatelessSession extends Closeable { + interface StatelessSession extends QueryProducer, Closeable { /** * Retrieve a row. @@ -1553,6 +1569,18 @@ interface StatelessSession extends Closeable { */ CompletionStage get(Class entityClass, Object id); + /** + * Retrieve multiple rows. + * + * @param entityClass The class of the entity to retrieve + * @param ids The ids of the entities to retrieve + * + * @return a list of detached entity instances, via a {@code Uni} + * + * @see org.hibernate.StatelessSession#getMultiple(Class, List) + */ + CompletionStage> get(Class entityClass, Object... ids); + /** * Retrieve a row, obtaining the specified lock mode. * @@ -1592,145 +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 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. * @@ -1741,7 +1630,8 @@ default CompletionStage get(Class entityClass, Object id, LockModeType CompletionStage insert(Object entity); /** - * Insert multiple rows. + * Insert multiple rows, using the number of the + * given entities as the batch size. * * @param entities new transient instances * @@ -1759,6 +1649,16 @@ default CompletionStage get(Class entityClass, Object id, LockModeType */ CompletionStage insert(int batchSize, Object... entities); + /** + * Insert multiple rows, using the size of the + * given list as the batch size. + * + * @param entities new transient instances + * + * @see org.hibernate.StatelessSession#insert(Object) + */ + CompletionStage insertMultiple(List entities); + /** * Delete a row. * @@ -1769,7 +1669,8 @@ default CompletionStage get(Class entityClass, Object id, LockModeType CompletionStage delete(Object entity); /** - * Delete multiple rows. + * Delete multiple rows, using the number of the + * given entities as the batch size. * * @param entities detached entity instances * @@ -1787,6 +1688,16 @@ default CompletionStage get(Class entityClass, Object id, LockModeType */ CompletionStage delete(int batchSize, Object... entities); + /** + * Delete multiple rows, using the size of the + * given list as the batch size. + * + * @param entities detached entity instances + * + * @see org.hibernate.StatelessSession#delete(Object) + */ + CompletionStage deleteMultiple(List entities); + /** * Update a row. * @@ -1797,7 +1708,8 @@ default CompletionStage get(Class entityClass, Object id, LockModeType CompletionStage update(Object entity); /** - * Update multiple rows. + * Update multiple rows, using the number of the + * given entities as the batch size. * * @param entities a detached entity instance * @@ -1815,6 +1727,16 @@ default CompletionStage get(Class entityClass, Object id, LockModeType */ CompletionStage update(int batchSize, Object... entities); + /** + * Update multiple rows, using the size of the + * given list as the batch size. + * + * @param entities a detached entity instance + * + * @see org.hibernate.StatelessSession#update(Object) + */ + CompletionStage updateMultiple(List entities); + /** * Refresh the entity instance state from the database. * @@ -1825,7 +1747,8 @@ default CompletionStage get(Class entityClass, Object id, LockModeType CompletionStage refresh(Object entity); /** - * Refresh the entity instance state from the database. + * Refresh the entity instance state from the database, using the number of the + * given entities as the batch size. * * @param entities The entities to be refreshed. * @@ -1843,6 +1766,16 @@ default CompletionStage get(Class entityClass, Object id, LockModeType */ CompletionStage refresh(int batchSize, Object... entities); + /** + * Refresh the entity instance state from the database, + * using the size of the given list as the batch size. + * + * @param entities The entities to be refreshed. + * + * @see org.hibernate.StatelessSession#refresh(Object) + */ + CompletionStage refreshMultiple(List entities); + /** * Refresh the entity instance state from the database. * @@ -1874,13 +1807,40 @@ default CompletionStage refresh(Object entity, LockModeType lockModeType) CompletionStage upsert(Object entity); /** + * Use a SQL {@code merge into} statement to perform + * an upsert on multiple rows using the size of the given array + * as batch size. * - * @param entityName The entityName for the entity to be merged - * @param entity a detached entity instance + * @param entities the entities to upsert + * + * @see org.hibernate.StatelessSession#upsert(Object) + */ + @Incubating + CompletionStage upsertAll(Object... entities); + + /** + * Use a SQL {@code merge into} statement to perform + * an upsert on multiple rows using the specified batch size. + * + * @param batchSize the batch size + * @param entities the list of entities to upsert + * + * @see org.hibernate.StatelessSession#upsert(Object) + */ + @Incubating + CompletionStage upsertAll(int batchSize, Object... entities); + + /** + * Use a SQL {@code merge into} statement to perform + * an upsert on multiple rows using the size of the given list + * as batch size. + * + * @param entities the entities to upsert * - * @see org.hibernate.StatelessSession#upsert(String, Object) + * @see org.hibernate.StatelessSession#upsert(Object) */ - CompletionStage upsert(String entityName, Object entity); + @Incubating + CompletionStage upsertMultiple(List entities); /** * Asynchronously fetch an association that's configured for lazy loading. @@ -1901,25 +1861,15 @@ default CompletionStage refresh(Object entity, LockModeType lockModeType) CompletionStage fetch(T association); /** - * 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} + * Return the identifier value of the given entity, which may be detached. + * + * @param entity a persistent instance associated with this session + * + * @return the identifier + * + * @since 3.0 */ - EntityGraph createEntityGraph(Class rootType, String graphName); + Object getIdentifier(Object entity); /** * Performs the given work within the scope of a database transaction, @@ -1969,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(); + } } /** @@ -2021,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. *

    @@ -2035,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. @@ -2074,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 @@ -2093,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. *

    * @@ -2113,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}. *

    * @@ -2137,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. *

    * @@ -2162,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 @@ -2186,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 @@ -2197,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}. @@ -2210,8 +2172,8 @@ default CompletionStage withStatelessTransaction(Function * *

  • If there is already a stateless session associated with the @@ -2221,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}. @@ -2232,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 @@ -2255,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 @@ -2273,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 @@ -2295,7 +2261,7 @@ default CompletionStage withStatelessTransaction(Function CompletionStage withStatelessTransaction(Function CompletionStage fetch(T association) { } final SharedSessionContractImplementor session; - if ( association instanceof HibernateProxy) { - session = ( (HibernateProxy) association ).getHibernateLazyInitializer().getSession(); + if ( association instanceof HibernateProxy proxy ) { + session = proxy.getHibernateLazyInitializer().getSession(); } - else if ( association instanceof PersistentCollection) { - session = ( (AbstractPersistentCollection) association ).getSession(); + else if ( association instanceof AbstractPersistentCollection collection ) { + session = collection.getSession(); } else if ( isPersistentAttributeInterceptable( association ) ) { final PersistentAttributeInterceptable interceptable = asPersistentAttributeInterceptable( association ); final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor(); - if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor) { - session = ( (EnhancementAsProxyLazinessInterceptor) interceptor ).getLinkedSession(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor lazinessInterceptor) { + session = lazinessInterceptor.getLinkedSession(); } else { return CompletionStages.completedFuture( association ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageQueryImpl.java index fb022c499..df0c04c5a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageQueryImpl.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.ReactiveQuery; import org.hibernate.reactive.stage.Stage; @@ -65,18 +64,6 @@ public Query 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 6a1be5d08..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 @@ -5,7 +5,6 @@ */ package org.hibernate.reactive.stage.impl; -import java.lang.invoke.MethodHandles; import java.util.List; import java.util.concurrent.CompletionStage; @@ -14,10 +13,7 @@ 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.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.ReactiveSelectionQuery; import org.hibernate.reactive.stage.Stage.SelectionQuery; @@ -29,7 +25,6 @@ import jakarta.persistence.Parameter; public class StageSelectionQueryImpl implements SelectionQuery { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); private final ReactiveSelectionQuery delegate; public StageSelectionQueryImpl(ReactiveSelectionQuery delegate) { @@ -198,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 b03df1fc9..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 @@ -5,11 +5,11 @@ */ package org.hibernate.reactive.stage.impl; -import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.metamodel.Metamodel; import org.hibernate.Cache; import org.hibernate.internal.SessionCreationOptions; import org.hibernate.internal.SessionFactoryImpl; +import org.hibernate.query.criteria.HibernateCriteriaBuilder; import org.hibernate.reactive.common.spi.Implementor; import org.hibernate.reactive.context.Context; import org.hibernate.reactive.context.impl.BaseKey; @@ -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" ); @@ -274,7 +284,7 @@ public boolean isOpen() { } @Override - public CriteriaBuilder getCriteriaBuilder() { + public HibernateCriteriaBuilder getCriteriaBuilder() { return delegate.getCriteriaBuilder(); } 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 bc66b714e..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,16 +5,7 @@ */ package org.hibernate.reactive.stage.impl; -import jakarta.persistence.CacheRetrieveMode; -import jakarta.persistence.CacheStoreMode; -import jakarta.persistence.EntityGraph; -import jakarta.persistence.FlushModeType; -import jakarta.persistence.LockModeType; -import jakarta.persistence.PersistenceException; -import jakarta.persistence.criteria.CriteriaDelete; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.CriteriaUpdate; -import jakarta.persistence.metamodel.Attribute; + import org.hibernate.CacheMode; import org.hibernate.Filter; import org.hibernate.FlushMode; @@ -22,14 +13,14 @@ import org.hibernate.LockOptions; import org.hibernate.graph.RootGraph; import org.hibernate.jpa.internal.util.LockModeTypeHelper; +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.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; @@ -38,13 +29,25 @@ import org.hibernate.reactive.stage.Stage.Query; import org.hibernate.reactive.stage.Stage.SelectionQuery; -import java.lang.invoke.MethodHandles; +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.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; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** * Implements the {@link Stage.Session} API. This delegating class is @@ -53,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) { @@ -117,6 +118,21 @@ public MutationQuery createMutationQuery(String queryString) { return new StageMutationQueryImpl<>( delegate.createReactiveMutationQuery( queryString ) ); } + @Override + public MutationQuery createMutationQuery(CriteriaUpdate updateQuery) { + return new StageMutationQueryImpl<>( delegate.createReactiveMutationQuery( updateQuery ) ); + } + + @Override + public MutationQuery createMutationQuery(CriteriaDelete deleteQuery) { + return new StageMutationQueryImpl<>( delegate.createReactiveMutationQuery( deleteQuery ) ); + } + + @Override + public MutationQuery createMutationQuery(JpaCriteriaInsert insert) { + return new StageMutationQueryImpl<>( delegate.createReactiveMutationQuery( insert ) ); + } + @Override public CompletionStage find(Class entityClass, Object primaryKey) { return delegate.reactiveFind( entityClass, primaryKey, null, null ); @@ -159,6 +175,11 @@ public CompletionStage persist(Object entity) { return delegate.reactivePersist( entity ); } + @Override + public CompletionStage persist(String entityName, Object entity) { + return delegate.reactivePersist( entityName, entity ); + } + @Override public CompletionStage persist(Object... entity) { return applyToAll( delegate::reactivePersist, entity ); @@ -226,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; } @@ -349,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 @@ -413,7 +418,7 @@ CompletionStage execute(Function> work) * roll back) and an error thrown by the work. */ CompletionStage executeInTransaction(Function> work) { - return work.apply( this ) + return voidFuture().thenCompose( v -> work.apply( this ) ) // only flush() if the work completed with no exception .thenCompose( result -> flush().thenApply( v -> result ) ) // have to capture the error here and pass it along, @@ -487,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 ); @@ -507,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 ) ); @@ -534,7 +550,7 @@ public MutationQuery createQuery(CriteriaDelete criteriaDelete) { @Override public Query createNamedQuery(String queryName) { - return new StageQueryImpl<>( delegate.createReactiveNamedQuery( queryName, null ) ); + return new StageQueryImpl<>( delegate.createReactiveNamedQuery( queryName ) ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java index 1bacfa354..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 @@ -5,25 +5,32 @@ */ package org.hibernate.reactive.stage.impl; -import jakarta.persistence.EntityGraph; -import jakarta.persistence.criteria.CriteriaDelete; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.CriteriaUpdate; import org.hibernate.LockMode; import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.query.criteria.JpaCriteriaInsert; +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; import org.hibernate.reactive.stage.Stage.Query; 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; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Function; import static org.hibernate.reactive.util.impl.CompletionStages.returnOrRethrow; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** * Implements the {@link Stage.StatelessSession} API. This delegating @@ -47,6 +54,11 @@ public CompletionStage get(Class entityClass, Object id) { return delegate.reactiveGet( entityClass, id ); } + @Override + public CompletionStage> get(Class entityClass, Object... ids) { + return delegate.reactiveGet( entityClass, ids ); + } + @Override public CompletionStage get(Class entityClass, Object id, LockMode lockMode) { return delegate.reactiveGet( entityClass, id, lockMode, null ); @@ -59,7 +71,7 @@ public CompletionStage insert(Object entity) { @Override public CompletionStage insert(Object... entities) { - return delegate.reactiveInsertAll( entities ); + return delegate.reactiveInsertAll( entities.length, entities ); } @Override @@ -67,6 +79,11 @@ public CompletionStage insert(int batchSize, Object... entities) { return delegate.reactiveInsertAll( batchSize, entities ); } + @Override + public CompletionStage insertMultiple(List entities) { + return delegate.reactiveInsertAll( entities.size(), entities.toArray() ); + } + @Override public CompletionStage delete(Object entity) { return delegate.reactiveDelete( entity ); @@ -74,7 +91,7 @@ public CompletionStage delete(Object entity) { @Override public CompletionStage delete(Object... entities) { - return delegate.reactiveDeleteAll( entities ); + return delegate.reactiveDeleteAll( entities.length, entities ); } @Override @@ -82,6 +99,11 @@ public CompletionStage delete(int batchSize, Object... entities) { return delegate.reactiveDeleteAll( batchSize, entities ); } + @Override + public CompletionStage deleteMultiple(List entities) { + return delegate.reactiveDeleteAll( entities.size(), entities.toArray() ); + } + @Override public CompletionStage update(Object entity) { return delegate.reactiveUpdate( entity ); @@ -89,7 +111,7 @@ public CompletionStage update(Object entity) { @Override public CompletionStage update(Object... entities) { - return delegate.reactiveUpdateAll( entities ); + return delegate.reactiveUpdateAll( entities.length, entities ); } @Override @@ -97,6 +119,11 @@ public CompletionStage update(int batchSize, Object... entities) { return delegate.reactiveUpdateAll( batchSize, entities ); } + @Override + public CompletionStage updateMultiple(List entities) { + return delegate.reactiveUpdateAll( entities.size(), entities.toArray() ); + } + @Override public CompletionStage refresh(Object entity) { return delegate.reactiveRefresh( entity ); @@ -104,7 +131,7 @@ public CompletionStage refresh(Object entity) { @Override public CompletionStage refresh(Object... entities) { - return delegate.reactiveRefreshAll( entities ); + return delegate.reactiveRefreshAll( entities.length, entities ); } @Override @@ -112,6 +139,11 @@ public CompletionStage refresh(int batchSize, Object... entities) { return delegate.reactiveRefreshAll( batchSize, entities ); } + @Override + public CompletionStage refreshMultiple(List entities) { + return delegate.reactiveRefreshAll( entities.size(), entities.toArray() ); + } + @Override public CompletionStage refresh(Object entity, LockMode lockMode) { return delegate.reactiveRefresh( entity, lockMode ); @@ -123,8 +155,18 @@ public CompletionStage upsert(Object entity) { } @Override - public CompletionStage upsert(String entityName, Object entity) { - return delegate.reactiveUpsert( entityName, entity ); + public CompletionStage upsertAll(Object... entities) { + return delegate.reactiveUpsertAll( entities.length, entities ); + } + + @Override + public CompletionStage upsertAll(int batchSize, Object... entities) { + return delegate.reactiveUpsertAll( batchSize, entities ); + } + + @Override + public CompletionStage upsertMultiple(List entities) { + return delegate.reactiveUpsertAll( entities.size(), entities.toArray() ); } @Override @@ -132,6 +174,11 @@ public CompletionStage fetch(T association) { return delegate.reactiveFetch( association, false ); } + @Override + public Object getIdentifier(Object entity) { + return delegate.getIdentifier(entity); + } + @Override public CompletionStage withTransaction(Function> work) { return currentTransaction == null @@ -156,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 @@ -180,7 +232,8 @@ CompletionStage execute(Function> work) * and an error thrown by the work. */ CompletionStage executeInTransaction(Function> work) { - return work.apply( this ) + return voidFuture() + .thenCompose( v -> work.apply( this ) ) // have to capture the error here and pass it along, // since we can't just return a CompletionStage that // rolls back the transaction from the handle() function @@ -240,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 ) ); @@ -250,6 +309,21 @@ public MutationQuery createMutationQuery(String queryString) { return new StageMutationQueryImpl<>( delegate.createReactiveMutationQuery( queryString ) ); } + @Override + public MutationQuery createMutationQuery(CriteriaUpdate updateQuery) { + return new StageMutationQueryImpl<>( delegate.createReactiveMutationQuery( updateQuery ) ); + } + + @Override + public MutationQuery createMutationQuery(CriteriaDelete deleteQuery) { + return new StageMutationQueryImpl<>( delegate.createReactiveMutationQuery( deleteQuery ) ); + } + + @Override + public MutationQuery createMutationQuery(JpaCriteriaInsert insert) { + return new StageMutationQueryImpl<>( delegate.createReactiveMutationQuery( insert ) ); + } + @Override public SelectionQuery createQuery(String queryString, Class resultType) { return new StageSelectionQueryImpl<>( delegate.createReactiveQuery( queryString, resultType ) ); @@ -260,9 +334,14 @@ 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, null ) ); + return new StageQueryImpl<>( delegate.createReactiveNamedQuery( queryName ) ); } @Override @@ -275,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/tuple/entity/ReactiveEntityMetamodel.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java new file mode 100644 index 000000000..2e20e7781 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java @@ -0,0 +1,124 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.tuple.entity; + +import java.util.function.Function; + + +import org.hibernate.generator.Generator; +import org.hibernate.generator.GeneratorCreationContext; +import org.hibernate.id.Configurable; +import org.hibernate.id.IdentifierGenerator; +import org.hibernate.id.SelectGenerator; +import org.hibernate.id.enhanced.DatabaseStructure; +import org.hibernate.id.enhanced.SequenceStructure; +import org.hibernate.id.enhanced.SequenceStyleGenerator; +import org.hibernate.id.enhanced.TableGenerator; +import org.hibernate.id.enhanced.TableStructure; +import org.hibernate.mapping.GeneratorCreator; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.SimpleValue; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.reactive.id.ReactiveIdentifierGenerator; +import org.hibernate.reactive.id.impl.EmulatedSequenceReactiveIdentifierGenerator; +import org.hibernate.reactive.id.impl.ReactiveGeneratorWrapper; +import org.hibernate.reactive.id.impl.ReactiveSequenceIdentifierGenerator; +import org.hibernate.reactive.id.impl.TableReactiveIdentifierGenerator; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.tuple.entity.EntityMetamodel; + +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; + +public class ReactiveEntityMetamodel extends EntityMetamodel { + + private static final Log LOG = make( Log.class, lookup() ); + + public ReactiveEntityMetamodel( + PersistentClass persistentClass, + EntityPersister persister, + RuntimeModelCreationContext creationContext) { + this( + persistentClass, + persister, + creationContext, + s -> buildIdGenerator( s, persistentClass, creationContext ) + ); + } + + public ReactiveEntityMetamodel( + PersistentClass persistentClass, + EntityPersister persister, + RuntimeModelCreationContext creationContext, + Function generatorSupplier) { + super( persistentClass, persister, creationContext, generatorSupplier ); + } + + private static Generator buildIdGenerator( + String rootName, + PersistentClass persistentClass, + RuntimeModelCreationContext creationContext) { + final Generator existing = creationContext.getGenerators().get( rootName ); + if ( existing != null ) { + return existing; + } + else { + SimpleValue identifier = (SimpleValue) persistentClass.getIdentifier(); + GeneratorCreator customIdGeneratorCreator = identifier.getCustomIdGeneratorCreator(); + identifier.setCustomIdGeneratorCreator( context -> { + Generator generator = customIdGeneratorCreator.createGenerator( context ); + return augmentWithReactiveGenerator( generator, context, creationContext ); + } ); + final Generator idgenerator = identifier + // returns the cached Generator if it was already created + .createGenerator( + creationContext.getDialect(), + persistentClass.getRootClass(), + persistentClass.getIdentifierProperty(), + creationContext.getGeneratorSettings() + ); + creationContext.getGenerators().put( rootName, idgenerator ); + return idgenerator; + } + } + + public static Generator augmentWithReactiveGenerator( + Generator generator, + GeneratorCreationContext creationContext, + RuntimeModelCreationContext runtimeModelCreationContext) { + if ( generator instanceof SequenceStyleGenerator ) { + final DatabaseStructure structure = ( (SequenceStyleGenerator) generator ).getDatabaseStructure(); + if ( structure instanceof TableStructure ) { + return initialize( (IdentifierGenerator) generator, new EmulatedSequenceReactiveIdentifierGenerator( (TableStructure) structure, runtimeModelCreationContext ), creationContext ); + } + if ( structure instanceof SequenceStructure ) { + return initialize( (IdentifierGenerator) generator, new ReactiveSequenceIdentifierGenerator( structure, runtimeModelCreationContext ), creationContext ); + } + throw LOG.unknownStructureType(); + } + if ( generator instanceof TableGenerator ) { + return initialize( + (IdentifierGenerator) generator, + new TableReactiveIdentifierGenerator( (TableGenerator) generator, runtimeModelCreationContext ), + creationContext + ); + } + if ( generator instanceof SelectGenerator ) { + throw LOG.selectGeneratorIsNotSupportedInHibernateReactive(); + } + //nothing to do + return generator; + } + + private static Generator initialize( + IdentifierGenerator idGenerator, + ReactiveIdentifierGenerator reactiveIdGenerator, + GeneratorCreationContext creationContext) { + ( (Configurable) reactiveIdGenerator ).initialize( creationContext.getSqlStringGenerationContext() ); + return new ReactiveGeneratorWrapper( reactiveIdGenerator, idGenerator ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcType.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcType.java index f9a295007..99d7efaf7 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcType.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcType.java @@ -45,7 +45,7 @@ * * @see org.hibernate.type.descriptor.jdbc.ArrayJdbcType */ -public class ReactiveArrayJdbcType implements JdbcType { +public class ReactiveArrayJdbcType implements JdbcType { private final JdbcType elementJdbcType; @@ -98,7 +98,7 @@ public ValueBinder getBinder(final JavaType javaTypeDescriptor) { protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { - ArrayAdaptor arrayObject = getArrayObject( value, options ); + ArrayAdaptor arrayObject = getArrayObject( value, options, getJdbcType(), getJavaType() ); st.setArray( index, arrayObject ); } @@ -107,10 +107,10 @@ protected void doBind(CallableStatement st, X value, String name, WrapperOptions throw new UnsupportedOperationException(); } - private ArrayAdaptor getArrayObject(X value, WrapperOptions options) { + private static ArrayAdaptor getArrayObject(X value, WrapperOptions options, JdbcType jdbcType, JavaType javaType) { final TypeConfiguration typeConfiguration = options.getSessionFactory().getTypeConfiguration(); - ReactiveArrayJdbcType jdbcType = (ReactiveArrayJdbcType) getJdbcType(); - final JdbcType elementJdbcType = jdbcType.getElementJdbcType(); + ReactiveArrayJdbcType arrayJdbcType = (ReactiveArrayJdbcType) jdbcType; + final JdbcType elementJdbcType = arrayJdbcType.getElementJdbcType(); final JdbcType underlyingJdbcType = typeConfiguration.getJdbcTypeRegistry() .getDescriptor( elementJdbcType.getDefaultSqlTypeCode() ); final Class elementJdbcJavaTypeClass = elementJdbcJavaTypeClass( @@ -122,11 +122,11 @@ private ArrayAdaptor getArrayObject(X value, WrapperOptions options) { //noinspection unchecked final Class arrayClass = (Class) Array.newInstance( elementJdbcJavaTypeClass, 0 ) .getClass(); - final Object[] objects = getJavaType().unwrap( value, arrayClass, options ); + final Object[] objects = javaType.unwrap( value, arrayClass, options ); return new ArrayAdaptor( elementJdbcType, objects ); } - private Class elementJdbcJavaTypeClass( + private static Class elementJdbcJavaTypeClass( WrapperOptions options, JdbcType elementJdbcType, JdbcType underlyingJdbcType, @@ -144,7 +144,7 @@ private Class elementJdbcJavaTypeClass( return convertTypes( elementJdbcJavaTypeClass ); } - private Class convertTypes(Class elementJdbcJavaTypeClass) { + private static Class convertTypes(Class elementJdbcJavaTypeClass) { if ( Timestamp.class.equals( elementJdbcJavaTypeClass ) ) { return LocalDateTime.class; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcTypeConstructor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcTypeConstructor.java index f0a26dccc..4c749d7e3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcTypeConstructor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcTypeConstructor.java @@ -15,8 +15,6 @@ import org.hibernate.type.descriptor.jdbc.JdbcTypeConstructor; import org.hibernate.type.spi.TypeConfiguration; -import static org.hibernate.dialect.DialectDelegateWrapper.extractRealDialect; - /** * Factory for {@link ReactiveArrayJdbcType}. */ @@ -29,8 +27,7 @@ public JdbcType resolveType( Dialect dialect, BasicType elementType, ColumnTypeInformation columnTypeInformation) { - Dialect realDialect = extractRealDialect( dialect ); - if ( realDialect instanceof OracleDialect ) { + if ( dialect instanceof OracleDialect ) { String typeName = columnTypeInformation == null ? null : columnTypeInformation.getTypeName(); if ( typeName == null || typeName.isBlank() ) { typeName = ReactiveOracleArrayJdbcType.getTypeName( elementType, dialect ); @@ -46,8 +43,7 @@ public JdbcType resolveType( Dialect dialect, JdbcType elementType, ColumnTypeInformation columnTypeInformation) { - Dialect realDialect = extractRealDialect( dialect ); - if ( realDialect instanceof OracleDialect ) { + if ( dialect instanceof OracleDialect ) { // a bit wrong, since columnTypeInformation.getTypeName() is typically null! return new ReactiveOracleArrayJdbcType( elementType, 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 new file mode 100644 index 000000000..81af936ba --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcType.java @@ -0,0 +1,133 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.type.descriptor.jdbc; + +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicPluralJavaType; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import org.hibernate.type.descriptor.jdbc.BasicBinder; +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 + */ +public class ReactiveJsonArrayJdbcType extends ReactiveArrayJdbcType { + + public ReactiveJsonArrayJdbcType(JdbcType elementJdbcType) { + super( elementJdbcType ); + } + + @Override + public int getJdbcTypeCode() { + return SqlTypes.VARCHAR; + } + + @Override + public int getDdlTypeCode() { + return SqlTypes.JSON; + } + + @Override + public int getDefaultSqlTypeCode() { + return SqlTypes.JSON_ARRAY; + } + + @Override + public String toString() { + return "JsonArrayJdbcType"; + } + + @Override + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaType javaType) { + // No literal support for now + return null; + } + + protected X fromString(String string, JavaType javaType, WrapperOptions options) throws SQLException { + if ( string == null ) { + return null; + } + + return JsonHelper.arrayFromString( javaType, getElementJdbcType(), string, options ); + } + + protected String toString(X value, JavaType javaType, WrapperOptions options) { + final JdbcType elementJdbcType = getElementJdbcType(); + final Object[] domainObjects = javaType.unwrap( value, Object[].class, options ); + if ( elementJdbcType instanceof JsonJdbcType jsonElementJdbcType ) { + final EmbeddableMappingType embeddableMappingType = jsonElementJdbcType.getEmbeddableMappingType(); + return JsonHelper.arrayToString( embeddableMappingType, domainObjects, options ); + } + else { + assert !( elementJdbcType instanceof AggregateJdbcType ); + final JavaType elementJavaType = ( (BasicPluralJavaType) javaType ).getElementJavaType(); + return JsonHelper.arrayToString( elementJavaType, elementJdbcType, domainObjects, options ); + } + } + + @Override + public ValueBinder getBinder(JavaType javaType) { + return new BasicBinder<>( javaType, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + final String json = ( (ReactiveJsonArrayJdbcType) getJdbcType() ).toString( value, getJavaType(), options ); + st.setString( index, json ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + final String json = ( (ReactiveJsonArrayJdbcType) getJdbcType() ).toString( value, getJavaType(), options ); + st.setString( name, json ); + } + }; + } + + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new BasicExtractor<>( javaType, this ) { + @Override + protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + return getObject( rs.getObject( paramIndex ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return getObject( statement.getObject( index ), options ); + } + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) + throws SQLException { + return getObject( statement.getObject( name ), options ); + } + + private X getObject(Object array, WrapperOptions options) throws SQLException { + if ( array == null ) { + return null; + } + + return ( (ReactiveJsonArrayJdbcType) getJdbcType() ) + .fromString( ( (JsonArray) array ).encode(), getJavaType(), options ); + } + }; + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcTypeConstructor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcTypeConstructor.java new file mode 100644 index 000000000..8971a2ae4 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcTypeConstructor.java @@ -0,0 +1,43 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.type.descriptor.jdbc; + +import org.hibernate.dialect.Dialect; +import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation; +import org.hibernate.type.BasicType; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcTypeConstructor; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * @see org.hibernate.type.descriptor.jdbc.JsonArrayJdbcType + * @see ReactiveJsonArrayJdbcType + */ +public class ReactiveJsonArrayJdbcTypeConstructor implements JdbcTypeConstructor { + public static final ReactiveJsonArrayJdbcTypeConstructor INSTANCE = new ReactiveJsonArrayJdbcTypeConstructor(); + + public JdbcType resolveType( + TypeConfiguration typeConfiguration, + Dialect dialect, + BasicType elementType, + ColumnTypeInformation columnTypeInformation) { + return resolveType( typeConfiguration, dialect, elementType.getJdbcType(), columnTypeInformation ); + } + + public JdbcType resolveType( + TypeConfiguration typeConfiguration, + Dialect dialect, + JdbcType elementType, + ColumnTypeInformation columnTypeInformation) { + return new ReactiveJsonArrayJdbcType( elementType ); + } + + @Override + public int getDefaultSqlTypeCode() { + return SqlTypes.JSON_ARRAY; + } +} 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 new file mode 100644 index 000000000..7f30d8d29 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlArrayJdbcType.java @@ -0,0 +1,63 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.type.descriptor.jdbc; + +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.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; + +/** + * @see org.hibernate.type.descriptor.jdbc.XmlArrayJdbcType + */ +public class ReactiveXmlArrayJdbcType extends XmlArrayJdbcType { + + public static final ReactiveXmlArrayJdbcType INSTANCE = new ReactiveXmlArrayJdbcType( null ); + + private static final Log LOG = make( Log.class, lookup() ); + + public ReactiveXmlArrayJdbcType(JdbcType elementJdbcType) { + super( elementJdbcType ); + } + + @Override + protected X fromString(String string, JavaType javaType, WrapperOptions options) throws SQLException { + if ( string == null ) { + return null; + } + if ( javaType.getJavaType() == SQLXML.class ) { + throw LOG.unsupportedXmlType(); + } + return XmlHelper.arrayFromString( javaType, this, string, options ); + } + + @Override + public ValueBinder getBinder(JavaType javaType) { + return new BasicBinder( javaType, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) { + throw LOG.unsupportedXmlType(); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) { + throw LOG.unsupportedXmlType(); + } + }; + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlArrayJdbcTypeConstructor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlArrayJdbcTypeConstructor.java new file mode 100644 index 000000000..1fa8999cb --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlArrayJdbcTypeConstructor.java @@ -0,0 +1,39 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.type.descriptor.jdbc; + +import org.hibernate.dialect.Dialect; +import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation; +import org.hibernate.type.BasicType; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.XmlArrayJdbcTypeConstructor; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * @see XmlArrayJdbcTypeConstructor + * @see ReactiveArrayJdbcType + */ +public class ReactiveXmlArrayJdbcTypeConstructor extends XmlArrayJdbcTypeConstructor { + public static final ReactiveXmlArrayJdbcTypeConstructor INSTANCE = new ReactiveXmlArrayJdbcTypeConstructor(); + + @Override + public JdbcType resolveType( + TypeConfiguration typeConfiguration, + Dialect dialect, + BasicType elementType, + ColumnTypeInformation columnTypeInformation) { + return resolveType( typeConfiguration, dialect, elementType.getJdbcType(), columnTypeInformation ); + } + + @Override + public JdbcType resolveType( + TypeConfiguration typeConfiguration, + Dialect dialect, + JdbcType elementType, + ColumnTypeInformation columnTypeInformation) { + return new ReactiveXmlArrayJdbcType( elementType ); + } +} 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 new file mode 100644 index 000000000..a81840263 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlJdbcType.java @@ -0,0 +1,69 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.type.descriptor.jdbc; + +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; + +/** + * @see XmlJdbcType + */ +public class ReactiveXmlJdbcType extends XmlJdbcType { + + private static final Log LOG = make( Log.class, lookup() ); + + public static final ReactiveXmlJdbcType INSTANCE = new ReactiveXmlJdbcType( null ); + + protected ReactiveXmlJdbcType(EmbeddableMappingType embeddableMappingType) { + super( embeddableMappingType ); + } + + @Override + protected X fromString(String string, JavaType javaType, WrapperOptions options) throws SQLException { + if ( getEmbeddableMappingType() != null ) { + return XmlHelper.fromString( + getEmbeddableMappingType(), + string, + javaType.getJavaTypeClass() != Object[].class, + options + ); + } + if ( javaType.getJavaType() == SQLXML.class ) { + throw LOG.unsupportedXmlType(); + } + return options.getSessionFactory().getSessionFactoryOptions().getXmlFormatMapper() + .fromString( string, javaType, options ); + } + + @Override + public ValueBinder getBinder(JavaType javaType) { + return new BasicBinder<>( javaType, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) { + throw LOG.unsupportedXmlType(); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) { + throw LOG.unsupportedXmlType(); + } + }; + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/async/impl/AsyncCloseable.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/async/impl/AsyncCloseable.java index 806c38a96..d32b32146 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/async/impl/AsyncCloseable.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/async/impl/AsyncCloseable.java @@ -17,7 +17,7 @@ *

    * Examples of such resources are manually managed memory, open file handles, socket descriptors * etc. While similar to {@link AutoCloseable}, this interface should be used when the resource - * release operation may possibly be async. For example, if an object is thread-safe and has many + * release operation may be async. For example, if an object is thread-safe and has many * consumers, an implementation may require all current ongoing operations to complete before * resources are relinquished. * diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/async/impl/AsyncIterator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/async/impl/AsyncIterator.java index 2680f56f8..0786e311e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/async/impl/AsyncIterator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/async/impl/AsyncIterator.java @@ -51,7 +51,7 @@ * below. The difference is that the parallelization in that case is from producing values in * parallel, not consuming values in parallel. * - *

    To implement an AsyncIterator you must only implement the {@link #nextStage()} method- + *

    To implement an AsyncIterator you must only implement the {@link #nextStage()} method - * however, it is recommended that users avoid actually using nextStage to consume the results of * iteration. It is less expressive and it can also be error prone; it is easy to cause a stack * overflow by incorrectly recursing on calls to nextStage. You should prefer to use the other diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/async/impl/AsyncTrampoline.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/async/impl/AsyncTrampoline.java index cc2ef5702..b8feee8c6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/async/impl/AsyncTrampoline.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/async/impl/AsyncTrampoline.java @@ -174,7 +174,7 @@ T poll() { * *

    * Effectively produces {@code fn(seed).thenCompose(fn).thenCompose(fn)... .thenCompose(fn)} until - * an value fails the predicate. Note that predicate will be applied on seed (like a while loop, + * a value fails the predicate. Note that predicate will be applied on seed (like a while loop, * the initial value is tested). If the predicate or fn throw an exception, * or the {@link CompletionStage} returned by fn completes exceptionally, iteration will stop and * an exceptional stage will be returned. 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 ca7df6d6e..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 ); @@ -226,6 +246,59 @@ public static CompletionStageHandler handle(R res return new CompletionStageHandler<>( result, throwable ); } + /** + * Makes the code simpler when dealing with ORM visitor pattern. + *

    + * For example: + *

    {@code
    +	 * 	private CompletionStage visitConstraintOrderedTables(QuerySpec idTableIdentifierSubQuery, ExecutionContext executionContext) {
    +	 * 		final Completable completable = new Completable<>();
    +	 * 		entityDescriptor
    +	 * 				.visitConstraintOrderedTables( (tableExpression, tableKeyColumnVisitationSupplier) -> deleteFromTableUsingIdTable(
    +	 * 								tableExpression,
    +	 * 								tableKeyColumnVisitationSupplier,
    +	 * 								idTableIdentifierSubQuery,
    +	 * 								executionContext
    +	 * 						)
    +	 * 						.handle( completable::complete )
    +	 * 				);
    +	 * 		return completable.getStage().thenCompose( CompletionStages::voidFuture );
    +	 *  }
    +	 * }
    + *

    + */ + public static class Completable { + + private final CompletableFuture stage; + + public Completable() { + this.stage = new CompletableFuture<>(); + } + + /** + * It will complete the underlying {@link CompletionStage} based on the parameters. + * + * @return {@code null} + * @see #getStage() + */ + public Object complete(T result, Throwable throwable) { + if ( throwable != null ) { + stage.completeExceptionally( throwable ); + } + else { + stage.complete( result ); + } + return null; + } + + /** + * @return a {@link CompletionStage} that will complete (successfully or exceptionally) after {@link #complete(Object, Throwable)} gets called + */ + public CompletionStage getStage() { + return stage; + } + } + public static class CompletionStageHandler { private final R result; @@ -248,7 +321,7 @@ public R getResult() throws T { if ( throwable == null ) { return result; } - throw (T) throwable; + throw throwable; } public CompletionStage getResultAsCompletionStage() { @@ -414,7 +487,7 @@ public static CompletionStage loop(int start, int end, IntPredicate filter } public static CompletionStage whileLoop(Supplier> loopSupplier) { - return asyncWhile( loopSupplier::get ); + return asyncWhile( loopSupplier ); } public static CompletionStage whileLoop(Supplier whileCondition, Supplier> loopSupplier) { 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 6cd705f2b..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; @@ -18,6 +17,8 @@ import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableStrategy; +import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableStrategy; import org.hibernate.reactive.containers.DatabaseConfiguration; import org.hibernate.reactive.containers.DatabaseConfiguration.DBType; import org.hibernate.reactive.mutiny.Mutiny; @@ -27,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; @@ -39,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; @@ -73,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() ); @@ -90,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. */ @@ -100,11 +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(); @@ -205,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/BatchQueryOnConnectionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchQueryOnConnectionTest.java index 675cbc4c3..0c6c03d85 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchQueryOnConnectionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchQueryOnConnectionTest.java @@ -13,9 +13,6 @@ import java.util.concurrent.CompletionStage; import org.hibernate.reactive.pool.ReactiveConnection; -import org.hibernate.reactive.pool.impl.OracleParameters; -import org.hibernate.reactive.pool.impl.PostgresParameters; -import org.hibernate.reactive.pool.impl.SQLServerParameters; import org.junit.jupiter.api.Test; @@ -41,7 +38,7 @@ protected Set> annotatedEntities() { @Test public void testBatchInsertSizeEqMultiple(VertxTestContext context) { - test( context, doBatchInserts( context, 50, BATCH_SIZE ) + test( context, doBatchInserts( 50, BATCH_SIZE ) .thenAccept( paramsBatches -> { assertEquals( 3, paramsBatches.size() ); assertEquals( 20, paramsBatches.get( 0 ).size() ); @@ -53,7 +50,7 @@ public void testBatchInsertSizeEqMultiple(VertxTestContext context) { @Test public void testBatchInsertUpdateSizeLtMultiple(VertxTestContext context) { - test( context, doBatchInserts( context, 50, BATCH_SIZE - 1 ) + test( context, doBatchInserts( 50, BATCH_SIZE - 1 ) .thenAccept( paramsBatches -> { assertEquals( 3, paramsBatches.size() ); assertEquals( 19, paramsBatches.get( 0 ).size() ); @@ -65,7 +62,7 @@ public void testBatchInsertUpdateSizeLtMultiple(VertxTestContext context) { @Test public void testBatchInsertUpdateSizeGtMultiple(VertxTestContext context) { - test( context, doBatchInserts( context, 50, BATCH_SIZE + 1 ) + test( context, doBatchInserts( 50, BATCH_SIZE + 1 ) .thenAccept( paramsBatches -> { assertEquals( 5, paramsBatches.size() ); assertEquals( 20, paramsBatches.get( 0 ).size() ); @@ -77,8 +74,8 @@ public void testBatchInsertUpdateSizeGtMultiple(VertxTestContext context) { ); } - public CompletionStage>> doBatchInserts(VertxTestContext context, int nEntities, int nEntitiesMultiple) { - final String insertSql = process( "insert into DataPoint (description, x, y, id) values (?, ?, ?, ?)" ); + public CompletionStage>> doBatchInserts(int nEntities, int nEntitiesMultiple) { + final String insertSql = createInsertSql(); List> paramsBatches = new ArrayList<>(); List paramsBatch = new ArrayList<>( BATCH_SIZE ); @@ -99,7 +96,7 @@ public CompletionStage>> doBatchInserts(VertxTestContext con } } - if ( paramsBatch.size() > 0 ) { + if ( !paramsBatch.isEmpty() ) { paramsBatches.add( paramsBatch ); } @@ -124,17 +121,17 @@ public CompletionStage>> doBatchInserts(VertxTestContext con .thenApply( v -> paramsBatches ); } - private String process(String sql) { + private String createInsertSql() { switch ( dbType() ) { case POSTGRESQL: case COCKROACHDB: - return PostgresParameters.INSTANCE.process( sql ); + return "insert into DataPoint (description, x, y, id) values ($1, $2, $3, $4)"; case SQLSERVER: - return SQLServerParameters.INSTANCE.process( sql ); + return "insert into DataPoint (description, x, y, id) values (@P1, @P2, @P3, @P4)"; case ORACLE: - return OracleParameters.INSTANCE.process( sql ); + return "insert into DataPoint (description, x, y, id) values (:1, :2, :3, :4)"; default: - return sql; + return "insert into DataPoint (description, x, y, id) values (?,?,?,?)"; } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchingConnectionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchingConnectionTest.java index 1cdc423f0..75d90375e 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchingConnectionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchingConnectionTest.java @@ -41,7 +41,7 @@ protected Configuration constructConfiguration() { configuration.setProperty( AvailableSettings.STATEMENT_BATCH_SIZE, "5"); // Construct a tracker that collects query statements via the SqlStatementLogger framework. - // Pass in configuration properties to hand-off any actual logging properties + // Pass in configuration properties to hand off any actual logging properties sqlTracker = new SqlStatementTracker( BatchingConnectionTest::filter, configuration.getProperties() ); return configuration; } @@ -145,6 +145,75 @@ public void testBatching(VertxTestContext context) { ); } + @Test + public void testBatchingWithStateless(VertxTestContext context) { + final GuineaPig[] pigs = { + new GuineaPig( 11, "One" ), + new GuineaPig( 22, "Two" ), + new GuineaPig( 33, "Three" ), + new GuineaPig( 44, "Four" ), + new GuineaPig( 55, "Five" ), + new GuineaPig( 66, "Six" ), + }; + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( 10, pigs ) ) + .invoke( () -> { + // We expect only one insert query + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) + .matches("insert into pig \\(name,version,id\\) values (.*)" ); + sqlTracker.clear(); + } ) + ); + } + + @Test + public void testMutinyInsertAllWithStateless(VertxTestContext context) { + final GuineaPig[] pigs = { + new GuineaPig( 11, "One" ), + new GuineaPig( 22, "Two" ), + new GuineaPig( 33, "Three" ), + new GuineaPig( 44, "Four" ), + new GuineaPig( 55, "Five" ), + new GuineaPig( 66, "Six" ), + }; + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( pigs ) ) + .invoke( () -> { + // We expect only 1 insert query, despite hibernate.jdbc.batch_size is set to 5, insertAll by default use the pigs.length as batch size + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) + .matches("insert into pig \\(name,version,id\\) values (.*)" ); + sqlTracker.clear(); + } ) + ); + } + + @Test + public void testStageInsertWithStateless(VertxTestContext context) { + final GuineaPig[] pigs = { + new GuineaPig( 11, "One" ), + new GuineaPig( 22, "Two" ), + new GuineaPig( 33, "Three" ), + new GuineaPig( 44, "Four" ), + new GuineaPig( 55, "Five" ), + new GuineaPig( 66, "Six" ), + }; + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( pigs ) ) + .thenAccept( v -> { + // We expect only 1 insert query, despite hibernate.jdbc.batch_size is set to 5, insertAll by default use the pigs.length as batch size + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) + .matches("insert into pig \\(name,version,id\\) values (.*)" ); + sqlTracker.clear(); + } ) + ); + } + @Test public void testBatchingConnection(VertxTestContext context) { test( context, openSession() diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CriteriaMutationQueryTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CriteriaMutationQueryTest.java new file mode 100644 index 000000000..356a93bd3 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CriteriaMutationQueryTest.java @@ -0,0 +1,326 @@ +/* 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.query.criteria.HibernateCriteriaBuilder; +import org.hibernate.query.criteria.JpaCriteriaInsertSelect; +import org.hibernate.query.criteria.JpaCriteriaInsertValues; +import org.hibernate.query.criteria.JpaCriteriaQuery; +import org.hibernate.query.criteria.JpaRoot; +import org.hibernate.reactive.mutiny.Mutiny; +import org.hibernate.reactive.stage.Stage; + +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.Tuple; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaDelete; +import jakarta.persistence.criteria.CriteriaUpdate; +import jakarta.persistence.criteria.Root; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; + +@Timeout(value = 10, timeUnit = MINUTES) +public class CriteriaMutationQueryTest extends BaseReactiveTest { + private static final Integer SPELT_ID = 1; + private static final String SPELT_NAME = "Spelt"; + private static final String SPELT_TYPE = "Wheat flour"; + Flour spelt = new Flour( SPELT_ID, SPELT_NAME, "An ancient grain, is a hexaploid species of wheat.", SPELT_TYPE ); + 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" ); + + @Override + protected Collection> annotatedEntities() { + return List.of( Flour.class ); + } + + @BeforeEach + public void populateDb(VertxTestContext context) { + test( context, getSessionFactory().withTransaction( s -> s.persist( spelt, rye, almond ) ) ); + } + + @Test + public void testStageUpdateCriteriaQuery(VertxTestContext context) { + String updatedDescription = "Most rye breads use a mix of rye and wheat flours"; + test( context, getSessionFactory() + .withTransaction( s -> s + .createMutationQuery( criteriaUpdate( getCriteriaBuilder( s ), updatedDescription, rye ) ) + .executeUpdate() + ) + .thenAccept( resultCount -> assertThat( resultCount ).isEqualTo( 1 ) ) + .thenCompose( v -> getSessionFactory() + .withTransaction( s -> s.find( Flour.class, rye.getId() ) ) ) + .thenAccept( result -> assertThat( result.getDescription() ).isEqualTo( updatedDescription ) ) + ); + } + + @Test + public void testMutinyUpdateCriteriaQuery(VertxTestContext context) { + String updatedDescription = "made from ground almonds."; + test( context, getMutinySessionFactory() + .withTransaction( s -> s + .createMutationQuery( criteriaUpdate( getCriteriaBuilder( s ), updatedDescription, almond ) ) + .executeUpdate() + ) + .invoke( resultCount -> assertThat( resultCount ).isEqualTo( 1 ) ) + .chain( v -> getMutinySessionFactory() + .withTransaction( s -> s.find( Flour.class, almond.getId() ) ) ) + .invoke( result -> assertThat( result.getDescription() ).isEqualTo( updatedDescription ) ) + ); + } + + @Test + public void testStageDeleteCriteriaQuery(VertxTestContext context) { + test( context, getSessionFactory() + .withTransaction( s -> s + .createMutationQuery( criteriaUpdate( getCriteriaBuilder( s ) ) ) + .executeUpdate() + ) + .thenAccept( resultCount -> assertThat( resultCount ).isEqualTo( 1 ) ) + .thenCompose( v -> getSessionFactory() + .withTransaction( s -> s.find( Flour.class, spelt.getId() ) ) ) + .thenAccept( result -> assertThat( result ).isNull() ) + ); + } + + @Test + public void testMutinyDeleteCriteriaQuery(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withTransaction( s -> { + return s.createMutationQuery( criteriaUpdate( getCriteriaBuilder( s ) ) ).executeUpdate(); + } ) + .invoke( resultCount -> assertThat( resultCount ).isEqualTo( 1 ) ) + .chain( v -> getMutinySessionFactory() + .withTransaction( s -> s.find( Flour.class, spelt.getId() ) ) ) + .invoke( result -> assertThat( result ).isNull() ) + ); + } + + @Test + public void testStageInsertCriteriaQuery(VertxTestContext context) { + final int id = 4; + final String flourName = "Rye"; + final String flourDescription = "Used to bake the traditional sourdough breads of Germany."; + final String flourType = "Wheat flour"; + test( context, getSessionFactory() + .withTransaction( s -> s + .createMutationQuery( insertCriteria( getCriteriaBuilder( s ), id, flourName, flourDescription, flourType ) ) + .executeUpdate() + ) + .thenAccept( resultCount -> assertThat( resultCount ).isEqualTo( 1 ) ) + .thenCompose( v -> getSessionFactory() + .withTransaction( s -> s.find( Flour.class, id ) ) ) + .thenAccept( result -> { + assertThat( result ).isNotNull(); + assertThat( result.name ).isEqualTo( flourName ); + assertThat( result.description ).isEqualTo( flourDescription ); + assertThat( result.type ).isEqualTo( flourType ); + } ) + ); + } + + @Test + public void testMutinyInsertCriteriaQuery(VertxTestContext context) { + final int id = 4; + final String flourName = "Almond"; + final String flourDescription = "made from ground almonds."; + final String flourType = "Gluten free"; + test( context, getMutinySessionFactory() + .withTransaction( s -> s + .createMutationQuery( insertCriteria( getCriteriaBuilder( s ), id, flourName, flourDescription, flourType ) ) + .executeUpdate() + ) + .invoke( resultCount -> assertThat( resultCount ).isEqualTo( 1 ) ) + .chain( v -> getMutinySessionFactory() + .withTransaction( s -> s.find( Flour.class, id ) ) ) + .invoke( result -> { + assertThat( result ).isNotNull(); + assertThat( result.name ).isEqualTo( flourName ); + assertThat( result.description ).isEqualTo( flourDescription ); + assertThat( result.type ).isEqualTo( flourType ); + } ) + ); + } + + @Test + public void testStageInsertSelectCriteriaQuery(VertxTestContext context) { + final int idOfTheNewFlour = 4; + test( context, getSessionFactory() + .withTransaction( s -> s + .createMutationQuery( insertSelectCriteria( getCriteriaBuilder( s ), idOfTheNewFlour ) ) + .executeUpdate() + ) + .thenAccept( resultCount -> assertThat( resultCount ).isEqualTo( 1 ) ) + .thenCompose( v -> getSessionFactory() + .withTransaction( s -> s.find( Flour.class, idOfTheNewFlour ) ) ) + .thenAccept( result -> { + assertThat( result ).isNotNull(); + assertThat( result.name ).isEqualTo( SPELT_NAME ); + assertThat( result.description ).isNull(); + assertThat( result.type ).isEqualTo( SPELT_TYPE ); + } ) + ); + } + + @Test + public void testMutinyInsertSelectCriteriaQuery(VertxTestContext context) { + final int idOfTheNewFlour = 4; + test( context, getMutinySessionFactory() + .withTransaction( s -> s + .createMutationQuery( insertSelectCriteria( getCriteriaBuilder( s ), idOfTheNewFlour ) ) + .executeUpdate() + ) + .invoke( resultCount -> assertThat( resultCount ).isEqualTo( 1 ) ) + .chain( v -> getMutinySessionFactory() + .withTransaction( s -> s.find( Flour.class, idOfTheNewFlour ) ) ) + .invoke( result -> { + assertThat( result ).isNotNull(); + assertThat( result.name ).isEqualTo( SPELT_NAME ); + assertThat( result.description ).isNull(); + assertThat( result.type ).isEqualTo( SPELT_TYPE ); + } ) + ); + } + + private CriteriaUpdate criteriaUpdate(CriteriaBuilder cb, String updatedDescription, Flour rye) { + CriteriaUpdate criteriaUpdate = cb.createCriteriaUpdate( Flour.class ); + Root from = criteriaUpdate.from( Flour.class ); + criteriaUpdate.set( from.get( "description" ), updatedDescription ); + criteriaUpdate.where( cb.equal( from.get( "id" ), rye.getId() ) ); + return criteriaUpdate; + } + + private CriteriaDelete criteriaUpdate(CriteriaBuilder criteriaBuilder) { + CriteriaDelete criteriaDelete = criteriaBuilder.createCriteriaDelete( Flour.class ); + Root from = criteriaDelete.from( Flour.class ); + criteriaDelete.where( criteriaBuilder.equal( from.get( "id" ), spelt.getId() ) ); + return criteriaDelete; + } + + private static JpaCriteriaInsertValues insertCriteria(HibernateCriteriaBuilder cb, int id, String name, String description, String type) { + JpaCriteriaInsertValues insert = cb.createCriteriaInsertValues( Flour.class ); + JpaRoot flour = insert.getTarget(); + insert.setInsertionTargetPaths( flour.get( "id" ), flour.get( "name" ), flour.get( "description" ), flour.get( "type" ) ); + insert.values( cb.values( cb.value( id ), cb.value( name ), cb.value( description ), cb.value( type ) ) ); + return insert; + } + + private static JpaCriteriaInsertSelect insertSelectCriteria( + HibernateCriteriaBuilder cb, + int idOfTheNewFlour) { + /* + The query executes and insert of Flour with id equals to 2 a name and type + selected from the existing spelt flour saved in the db + */ + JpaCriteriaInsertSelect insertSelect = cb.createCriteriaInsertSelect( Flour.class ); + // columns to insert + JpaRoot flour = insertSelect.getTarget(); + insertSelect.setInsertionTargetPaths( flour.get( "id" ), flour.get( "name" ), flour.get( "type" ) ); + // select query + JpaCriteriaQuery select = cb.createQuery( Tuple.class ); + JpaRoot root = select.from( Flour.class ); + select.multiselect( cb.literal( idOfTheNewFlour ), root.get( "name" ), root.get( "type" ) ); + select.where( cb.equal( root.get( "id" ), SPELT_ID ) ); + + insertSelect.select( select ); + return insertSelect; + } + + private static HibernateCriteriaBuilder getCriteriaBuilder(Stage.Session session) { + return session.getFactory().getCriteriaBuilder(); + } + + private static HibernateCriteriaBuilder getCriteriaBuilder(Mutiny.Session session) { + return session.getFactory().getCriteriaBuilder(); + } + + @Entity(name = "Flour") + @Table(name = "Flour") + public static class Flour { + @Id + private Integer id; + private String name; + private String description; + private String type; + + public Flour() { + } + + public Flour(Integer id, String name, String description, String type) { + this.id = id; + this.name = name; + this.description = description; + this.type = type; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Flour flour = (Flour) o; + return Objects.equals( name, flour.name ) && + Objects.equals( description, flour.description ) && + Objects.equals( type, flour.type ); + } + + @Override + public int hashCode() { + return Objects.hash( name, description, type ); + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CustomGeneratorTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CustomGeneratorTest.java index 44aea3d2e..cb2f3ae0b 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CustomGeneratorTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CustomGeneratorTest.java @@ -6,18 +6,20 @@ package org.hibernate.reactive; import java.util.Collection; +import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.Properties; import java.util.concurrent.CompletionStage; +import org.hibernate.MappingException; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Parameter; +import org.hibernate.generator.EventType; +import org.hibernate.generator.GeneratorCreationContext; import org.hibernate.id.Configurable; import org.hibernate.reactive.id.ReactiveIdentifierGenerator; import org.hibernate.reactive.session.ReactiveConnectionSupplier; -import org.hibernate.service.ServiceRegistry; -import org.hibernate.type.Type; import org.junit.jupiter.api.Test; @@ -44,36 +46,31 @@ protected Collection> annotatedEntities() { @Test public void testSequenceGenerator(VertxTestContext context) { - CustomId b = new CustomId(); b.string = "Hello World"; - test( - context, - openSession() - .thenCompose( s -> s.persist( b ).thenCompose( v -> s.flush() ) ) - .thenCompose( v -> openSession() ) - .thenCompose( s2 -> s2 - .find( CustomId.class, b.getId() ) - .thenAccept( bb -> { - assertNotNull( bb ); - assertEquals( bb.id, 1100 ); - assertEquals( bb.string, b.string ); - assertEquals( bb.version, 0 ); - - bb.string = "Goodbye"; - } ) - .thenCompose( vv -> s2.flush() ) - .thenCompose( vv -> s2.find( CustomId.class, b.getId() ) ) - .thenAccept( bt -> { - assertEquals( bt.version, 1 ); - } ) ) - .thenCompose( v -> openSession() ) - .thenCompose( s3 -> s3.find( CustomId.class, b.getId() ) ) + test( context, openSession() + .thenCompose( s -> s.persist( b ).thenCompose( v -> s.flush() ) ) + .thenCompose( v -> openSession() ) + .thenCompose( s2 -> s2 + .find( CustomId.class, b.getId() ) .thenAccept( bb -> { - assertEquals( bb.version, 1 ); - assertEquals( bb.string, "Goodbye" ); + assertNotNull( bb ); + assertEquals( 1100, bb.id ); + assertEquals( bb.string, b.string ); + assertEquals( 0, bb.version ); + + bb.string = "Goodbye"; } ) + .thenCompose( vv -> s2.flush() ) + .thenCompose( vv -> s2.find( CustomId.class, b.getId() ) ) + .thenAccept( bt -> assertEquals( 1, bt.version ) ) ) + .thenCompose( v -> openSession() ) + .thenCompose( s3 -> s3.find( CustomId.class, b.getId() ) ) + .thenAccept( bb -> { + assertEquals( 1, bb.version ); + assertEquals( "Goodbye", bb.string ); + } ) ); } @@ -87,15 +84,25 @@ public CompletionStage generate(ReactiveConnectionSupplier session, Obj } @Override - public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) { - current = Integer.parseInt( params.getProperty( "offset", "0" ) ); + public void configure(GeneratorCreationContext creationContext, Properties parameters) throws MappingException { + current = Integer.parseInt( parameters.getProperty( "offset", "0" ) ); + } + + @Override + public boolean generatedOnExecution() { + return false; + } + + @Override + public EnumSet getEventTypes() { + return EnumSet.of( EventType.INSERT ); } } @Entity @GenericGenerator( name = "thousands", - strategy = "org.hibernate.reactive.CustomGeneratorTest$Thousands", + type = Thousands.class, parameters = @Parameter(name = "offset", value = "100") ) public static class CustomId { diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerElementCollectionForBasicTypeSetTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerElementCollectionForBasicTypeSetTest.java index ba183b406..176e30c37 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerElementCollectionForBasicTypeSetTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerElementCollectionForBasicTypeSetTest.java @@ -442,7 +442,7 @@ private static void assertPhones(VertxTestContext context, Person person, String assertNotNull( person ); String[] sortedExpected = Arrays.stream( expectedPhones ).sorted() .sorted( String.CASE_INSENSITIVE_ORDER ) - .collect( Collectors.toList() ) + .toList() .toArray( new String[expectedPhones.length] ); List sortedActual = person.getPhones().stream() .sorted( String.CASE_INSENSITIVE_ORDER ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyEagerTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyEagerTest.java new file mode 100644 index 000000000..0ceeaef18 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyEagerTest.java @@ -0,0 +1,228 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +public class EmbeddedIdWithManyEagerTest extends BaseReactiveTest { + + Fruit cherry; + Fruit apple; + Fruit banana; + + Flower sunflower; + Flower chrysanthemum; + Flower rose; + + @Override + protected Collection> annotatedEntities() { + return List.of( Flower.class, Fruit.class ); + } + + @BeforeEach + public void populateDb(VertxTestContext context) { + Seed seed1 = new Seed( 1 ); + rose = new Flower( seed1, "Rose" ); + + cherry = new Fruit( seed1, "Cherry" ); + cherry.addFriend( rose ); + + Seed seed2 = new Seed( 2 ); + sunflower = new Flower( seed2, "Sunflower" ); + + apple = new Fruit( seed2, "Apple" ); + apple.addFriend( sunflower ); + + Seed seed3 = new Seed( 3 ); + chrysanthemum = new Flower( seed3, "Chrysanthemum" ); + + banana = new Fruit( seed3, "Banana" ); + banana.addFriend( chrysanthemum ); + + test( + context, + getMutinySessionFactory().withTransaction( s -> s + .persistAll( cherry, rose, sunflower, apple, chrysanthemum, banana ) + ) + ); + } + + @Test + public void testFindWithEmbeddedId(VertxTestContext context) { + test( + context, getMutinySessionFactory().withTransaction( s -> s + .find( Flower.class, chrysanthemum.getSeed() ) + .invoke( flower -> assertThat( flower.getName() ).isEqualTo( chrysanthemum.getName() ) ) + ) + ); + } + + @Test + public void testSelectQueryWithEmbeddedId(VertxTestContext context) { + test( + context, getMutinySessionFactory().withTransaction( s -> s + .createSelectionQuery( "from Flower", Flower.class ) + .getResultList() + .invoke( list -> assertThat( list.stream().map( Flower::getName ) ) + .containsExactlyInAnyOrder( + sunflower.getName(), + chrysanthemum.getName(), + rose.getName() + ) + ) + ) + ); + } + + @Test + public void testSelectQueryWithEmbeddedIdAndEagerRelation(VertxTestContext context) { + test( + context, getMutinySessionFactory().withTransaction( s -> s + .createSelectionQuery( "from Flower f ", Flower.class ) + .getResultList() + .invoke( list -> assertThat( list.stream().map( Flower::getFriend ).map( Plant::getName ) ) + .containsExactlyInAnyOrder( + cherry.getName(), + apple.getName(), + banana.getName() + ) + ) + ) + ); + } + + @Embeddable + public static class Seed { + + @Column(nullable = false, updatable = false) + private Integer id; + + public Seed() { + } + + public Seed(Integer id) { + this.id = id; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + } + + @MappedSuperclass + public static abstract class Plant { + + @EmbeddedId + private Seed seed; + + @Column(length = 40, unique = true) + private String name; + + protected Plant() { + } + + protected Plant(Seed seed, String name) { + this.seed = seed; + this.name = name; + } + + public Seed getSeed() { + return seed; + } + + public void setSeed(Seed seed) { + this.seed = seed; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Fruit") + @Table(name = "known_fruits") + public static class Fruit extends Plant { + + @OneToMany(mappedBy = "friend", fetch = FetchType.LAZY) + private List friends = new ArrayList<>(); + + public Fruit() { + } + + public Fruit(Seed seed, String name) { + super( seed, name ); + } + + public void addFriend(Flower flower) { + this.friends.add( flower ); + flower.friend = this; + } + + public List getFriends() { + return friends; + } + + @Override + public String toString() { + return "Fruit{" + getSeed().getId() + "," + getName() + '}'; + } + + } + + @Entity(name = "Flower") + @Table(name = "known_flowers") + public static class Flower extends Plant { + + @ManyToOne(fetch = FetchType.EAGER, optional = false) + @JoinColumn(name = "friend", referencedColumnName = "id", nullable = false) + private Fruit friend; + + public Flower() { + } + + public Flower(Seed seed, String name) { + super( seed, name ); + } + + public Fruit getFriend() { + return friend; + } + + @Override + public String toString() { + return "Flower{" + getSeed().getId() + "," + getName() + '}'; + } + + } + +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyTest.java new file mode 100644 index 000000000..eed1e5846 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyTest.java @@ -0,0 +1,207 @@ +/* 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.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EmbeddedIdWithManyTest extends BaseReactiveTest { + + Fruit cherry; + Fruit apple; + Fruit banana; + + Flower sunflower; + Flower chrysanthemum; + Flower rose; + + @Override + protected Collection> annotatedEntities() { + return List.of( Flower.class, Fruit.class ); + } + + @BeforeEach + public void populateDb(VertxTestContext context) { + Seed seed1 = new Seed( 1 ); + rose = new Flower( seed1, "Rose" ); + + cherry = new Fruit( seed1, "Cherry" ); + cherry.addFriend( rose ); + + Seed seed2 = new Seed( 2 ); + sunflower = new Flower( seed2, "Sunflower" ); + + apple = new Fruit( seed2, "Apple" ); + apple.addFriend( sunflower ); + + Seed seed3 = new Seed( 3 ); + chrysanthemum = new Flower( seed3, "Chrysanthemum" ); + + banana = new Fruit( seed3, "Banana" ); + banana.addFriend( chrysanthemum ); + + test( + context, + getMutinySessionFactory().withTransaction( s -> s + .persistAll( cherry, rose, sunflower, apple, chrysanthemum, banana ) + ) + ); + } + + @Test + public void testFindWithEmbeddedId(VertxTestContext context) { + test( + context, getMutinySessionFactory().withTransaction( s -> s + .find( Flower.class, chrysanthemum.getSeed() ) + .invoke( flower -> assertThat( flower.getName() ).isEqualTo( chrysanthemum.getName() ) ) + ) + ); + } + + @Test + public void testSelectQueryWithEmbeddedId(VertxTestContext context) { + test( + context, getMutinySessionFactory().withTransaction( s -> s + .createSelectionQuery( "from Flower", Flower.class ) + .getResultList() + .invoke( list -> assertThat( list.stream().map( Flower::getName ) ) + .containsExactlyInAnyOrder( + sunflower.getName(), + chrysanthemum.getName(), + rose.getName() + ) + ) + ) + ); + } + + @Embeddable + public static class Seed { + + @Column(nullable = false, updatable = false) + private Integer id; + + public Seed() { + } + + public Seed(Integer id) { + this.id = id; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + } + + @MappedSuperclass + public static abstract class Plant { + + @EmbeddedId + private Seed seed; + + @Column(length = 40, unique = true) + private String name; + + protected Plant() { + } + + protected Plant(Seed seed, String name) { + this.seed = seed; + this.name = name; + } + + public Seed getSeed() { + return seed; + } + + public void setSeed(Seed seed) { + this.seed = seed; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Fruit") + @Table(name = "known_fruits") + public static class Fruit extends Plant { + + @OneToMany(mappedBy = "friend", fetch = FetchType.LAZY) + private List friends = new ArrayList<>(); + + public Fruit() { + } + + public Fruit(Seed seed, String name) { + super( seed, name ); + } + + public void addFriend(Flower flower) { + this.friends.add( flower ); + flower.friend = this; + } + + public List getFriends() { + return friends; + } + + @Override + public String toString() { + return "Fruit{" + getSeed().getId() + "," + getName() + '}'; + } + + } + + @Entity(name = "Flower") + @Table(name = "known_flowers") + public static class Flower extends Plant { + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "friend", referencedColumnName = "id", nullable = false) + private Fruit friend; + + public Flower() { + } + + public Flower(Seed seed, String name) { + super( seed, name ); + } + + @Override + public String toString() { + return "Flower{" + getSeed().getId() + "," + getName() + '}'; + } + + } + +} 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/EmptyCompositeCollectionKeyTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmptyCompositeCollectionKeyTest.java deleted file mode 100644 index c879235da..000000000 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmptyCompositeCollectionKeyTest.java +++ /dev/null @@ -1,201 +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.HashSet; -import java.util.List; -import java.util.Set; - -import org.hibernate.Hibernate; -import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.reactive.annotations.DisabledFor; - -import org.junit.jupiter.api.Test; - -import io.vertx.junit5.Timeout; -import io.vertx.junit5.VertxTestContext; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.Embeddable; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; - -import static java.util.concurrent.TimeUnit.MINUTES; -import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@Timeout(value = 10, timeUnit = MINUTES) -@DisabledFor( value = DB2, reason = "IllegalStateException: Needed to have 6 in buffer but only had 0" ) -public class EmptyCompositeCollectionKeyTest extends BaseReactiveTest { - - @Override - protected Collection> annotatedEntities() { - return List.of( Family.class ); - } - - @Override - protected Configuration constructConfiguration() { - Configuration configuration = super.constructConfiguration(); - configuration.getProperties().put( Environment.CREATE_EMPTY_COMPOSITES_ENABLED, "true" ); - return configuration; - } - - @Test - public void testGetEntityWithEmptyChildrenCollection(VertxTestContext context) { - /* CASE 1: Family has Parent + child with null names + NULL relatives */ - Family family = new Family( 1, new Parent( new Child( null, null ) ) ); - - test( context, openSession() - .thenCompose( session -> session - .persist( family ) - .thenCompose( v -> session.flush() ) ) - .thenCompose( v -> openSession() ) - .thenCompose( session -> session.find( Family.class, family.id ) ) - .thenAccept( foundFamily -> { - assertTrue( Hibernate.isInitialized( foundFamily.parent.children ) ); - assertNull( foundFamily.parent.nickname ); - assertTrue( foundFamily.parent.children.isEmpty() ); - assertNull( foundFamily.parent.child.name ); - assertNull( foundFamily.parent.child.petname ); - assertTrue( Hibernate.isInitialized( foundFamily.relatives ) ); - assertTrue( foundFamily.relatives.isEmpty() ); - } ) - ); - } - - @Test - public void testGetEntityWithParentNullChild(VertxTestContext context) { - /* CASE 2: Family has Parent + child with null names + NULL relatives */ - Family family = new Family( 2, new Parent() ); - - test( context, openSession() - .thenCompose( session -> session - .persist( family ) - .thenCompose( v -> session.flush() ) ) - .thenCompose( v -> openSession() ) - .thenCompose( session -> session.find( Family.class, family.id ) ) - .thenAccept( foundFamily -> { - assertTrue( Hibernate.isInitialized( foundFamily.parent.children ) ); - assertNull( foundFamily.parent.nickname ); - assertTrue( foundFamily.parent.children.isEmpty() ); - assertNotNull( foundFamily.parent.child ); - assertNull( foundFamily.parent.child.petname ); - assertTrue( foundFamily.relatives.isEmpty() ); - } ) - ); - } - - @Test - public void testGetEntityWithNullParentNullChild(VertxTestContext context) { - /* CASE 3: Parent and children are all null */ - Family family = new Family( 3, null ); - - test( context, openSession() - .thenCompose( session -> session - .persist( family ) - .thenCompose( v -> session.flush() ) ) - .thenCompose( v -> openSession() ) - .thenCompose( session -> session.find( Family.class, family.id ) ) - .thenAccept( foundFamily -> { - assertTrue( Hibernate.isInitialized( foundFamily.parent.children ) ); - assertNull( foundFamily.parent.nickname ); - assertTrue( foundFamily.parent.children.isEmpty() ); - assertNotNull( foundFamily.parent.child ); - assertNull( foundFamily.parent.child.name ); - assertTrue( foundFamily.relatives.isEmpty() ); - } ) - ); - } - - - @Test - public void testGetEntityWithNullParentNullChildAndRelatives(VertxTestContext context) { - /* CASE 4: Parent and children are all null and relatives set exists */ - Set relatives = new HashSet<>(); - relatives.add( new Child() ); - Family family = new Family( 4, null, relatives ); - - test( context, openSession() - .thenCompose( session -> session - .persist( family ) - .thenCompose( v -> session.flush() ) ) - .thenCompose( v -> openSession() ) - .thenCompose( session -> session.find( Family.class, family.id ) ) - .thenAccept( foundFamily -> { - assertTrue( Hibernate.isInitialized( foundFamily.parent.children ) ); - assertNull( foundFamily.parent.nickname ); - assertTrue( foundFamily.parent.children.isEmpty() ); - assertNotNull( foundFamily.parent.child ); - assertNull( foundFamily.parent.child.petname ); - assertFalse( foundFamily.relatives.isEmpty() ); - assertNull( foundFamily.relatives.iterator().next().name ); - } ) - ); - } - - @Entity(name = "Family") - public static class Family { - @Id - private Integer id; - - private Parent parent; - - @ElementCollection(fetch = FetchType.EAGER) - private Set relatives; - - public Family() { - super(); - } - - public Family(Integer id, Parent parent) { - this.id = id; - this.parent = parent; - } - - public Family(Integer id, Parent parent, Set relatives) { - this.id = id; - this.parent = parent; - this.relatives = relatives; - } - } - - @Embeddable - public static class Parent { - private Child child; - private String nickname; - - @ElementCollection(fetch = FetchType.EAGER) - List children; - - public Parent() { - super(); - } - - public Parent(Child child) { - this.child = child; - } - } - - @Embeddable - public static class Child { - private String name; - private String petname; - - public Child() { - super(); - } - - public Child(String name, String petname) { - this.name = name; - this.petname = petname; - } - } -} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FindByIdWithLockTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FindByIdWithLockTest.java new file mode 100644 index 000000000..e78c549ca --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FindByIdWithLockTest.java @@ -0,0 +1,133 @@ +/* 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 java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.LockModeType; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; + +import static org.assertj.core.api.Assertions.assertThat; + +@Timeout(value = 10, timeUnit = TimeUnit.MINUTES) +public class FindByIdWithLockTest extends BaseReactiveTest { + private static final Long CHILD_ID = 1L; + + @Override + protected Collection> annotatedEntities() { + return List.of( Parent.class, Child.class ); + } + + @Test + public void testFindChild(VertxTestContext context) { + Parent parent = new Parent( 1L, "Lio" ); + Child child = new Child( CHILD_ID, "And" ); + test( + context, getMutinySessionFactory() + .withTransaction( session -> session.persistAll( parent, child ) ) + .chain( () -> getMutinySessionFactory() + .withTransaction( session -> session + .find( Child.class, CHILD_ID, LockModeType.PESSIMISTIC_WRITE ) + .invoke( c -> { + assertThat( c ).isNotNull(); + assertThat( c.getId() ).isEqualTo( CHILD_ID ); + } + ) ) ) + ); + } + + @Entity(name = "Parent") + public static class Parent { + + @Id + private Long id; + + private String name; + + @OneToMany(fetch = FetchType.EAGER) + public List children; + + public Parent() { + } + + public Parent(Long id, String name) { + this.id = id; + this.name = name; + } + + public void add(Child child) { + if ( children == null ) { + children = new ArrayList<>(); + } + children.add( child ); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public List getChildren() { + return children; + } + } + + + @Entity(name = "Child") + public static class Child { + + @Id + private Long id; + + public String name; + + @ManyToOne + public Parent parent; + + public Child() { + } + + public Child(Long id, String name) { + this.id = id; + this.name = name; + } + + public Child(Long id, String name, Parent parent) { + this.id = id; + this.name = name; + this.parent = parent; + parent.add( this ); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Parent getParent() { + return parent; + } + } + + +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/GeneratedPropertyJoinedTableTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/GeneratedPropertyJoinedTableTest.java index 018936ad5..879574b18 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/GeneratedPropertyJoinedTableTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/GeneratedPropertyJoinedTableTest.java @@ -11,9 +11,9 @@ import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.Generated; -import org.hibernate.annotations.GenerationTime; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; +import org.hibernate.generator.EventType; import org.hibernate.reactive.annotations.DisabledFor; import org.junit.jupiter.api.Test; @@ -152,7 +152,7 @@ static class GeneratedRegularParent { public String lastname; - @Generated(GenerationTime.ALWAYS) + @Generated( event = {EventType.INSERT, EventType.UPDATE} ) @Column(columnDefinition = "varchar(600) generated always as (firstname || ' ' || lastname) stored") public String fullName; @@ -171,7 +171,7 @@ public GeneratedRegularParent(String firstname, String lastname) { @Entity(name = "GeneratedRegular") static class GeneratedRegular extends GeneratedRegularParent { @Temporal(value = TemporalType.TIMESTAMP) - @Generated(GenerationTime.INSERT) + @Generated( event = {EventType.INSERT} ) @Column(columnDefinition = "timestamp") @ColumnDefault("current_timestamp") public Date createdAt; @@ -179,7 +179,6 @@ static class GeneratedRegular extends GeneratedRegularParent { @CurrentUser.LoggedUserMutinyAlways public String updatedBy; - @Generated(GenerationTime.NEVER) public String never; public GeneratedRegular() { @@ -201,7 +200,7 @@ static class GeneratedWithIdentityParent { public String lastname; - @Generated(GenerationTime.ALWAYS) + @Generated( event = {EventType.INSERT, EventType.UPDATE} ) @Column(columnDefinition = "varchar(600) generated always as (firstname || ' ' || lastname) stored") public String fullName; @@ -220,7 +219,7 @@ public GeneratedWithIdentityParent(String firstname, String lastname) { @Entity(name = "GeneratedWithIdentity") static class GeneratedWithIdentity extends GeneratedWithIdentityParent { @Temporal(value = TemporalType.TIMESTAMP) - @Generated(GenerationTime.INSERT) + @Generated( event = {EventType.INSERT} ) @Column(columnDefinition = "timestamp") @ColumnDefault("current_timestamp") public Date createdAt; @@ -228,7 +227,6 @@ static class GeneratedWithIdentity extends GeneratedWithIdentityParent { @CurrentUser.LoggedUserStageAlways public String updatedBy; - @Generated(GenerationTime.NEVER) public String never; public GeneratedWithIdentity() { diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/GeneratedPropertySingleTableTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/GeneratedPropertySingleTableTest.java index 60b23a563..deac05216 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/GeneratedPropertySingleTableTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/GeneratedPropertySingleTableTest.java @@ -11,9 +11,9 @@ import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.Generated; -import org.hibernate.annotations.GenerationTime; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; +import org.hibernate.generator.EventType; import org.hibernate.reactive.annotations.DisabledFor; import org.junit.jupiter.api.Test; @@ -150,12 +150,12 @@ static class GeneratedRegular { public String lastname; - @Generated(GenerationTime.ALWAYS) + @Generated( event = { EventType.INSERT, EventType.UPDATE} ) @Column(columnDefinition = "varchar(600) generated always as (firstname || ' ' || lastname) stored") private String fullName; @Temporal(value = TemporalType.TIMESTAMP) - @Generated(GenerationTime.INSERT) + @Generated( event = {EventType.INSERT} ) @Column(columnDefinition = "timestamp") @ColumnDefault("current_timestamp") public Date createdAt; @@ -166,7 +166,6 @@ static class GeneratedRegular { @CurrentUser.LoggedUserStageAlways public String updatedBy; - @Generated(GenerationTime.NEVER) public String never; public GeneratedRegular() { @@ -189,12 +188,12 @@ static class GeneratedWithIdentity { public String lastname; - @Generated(GenerationTime.ALWAYS) + @Generated( event = {EventType.INSERT, EventType.UPDATE} ) @Column(columnDefinition = "varchar(600) generated always as (firstname || ' ' || lastname) stored") private String fullName; @Temporal(value = TemporalType.TIMESTAMP) - @Generated(GenerationTime.INSERT) + @Generated( event = {EventType.INSERT} ) @Column(columnDefinition = "timestamp") @ColumnDefault("current_timestamp") public Date createdAt; @@ -205,7 +204,6 @@ static class GeneratedWithIdentity { @CurrentUser.LoggedUserStageAlways public String updatedBy; - @Generated(GenerationTime.NEVER) public String never; public GeneratedWithIdentity() { diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/GeneratedPropertyUnionSubclassesTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/GeneratedPropertyUnionSubclassesTest.java index b024372a8..f2b4ecdfe 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/GeneratedPropertyUnionSubclassesTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/GeneratedPropertyUnionSubclassesTest.java @@ -11,9 +11,9 @@ import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.Generated; -import org.hibernate.annotations.GenerationTime; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; +import org.hibernate.generator.EventType; import org.hibernate.reactive.annotations.DisabledFor; import org.junit.jupiter.api.Test; @@ -111,7 +111,7 @@ static class GeneratedRegularParent { public String lastname; - @Generated(GenerationTime.ALWAYS) + @Generated( event = {EventType.INSERT, EventType.UPDATE} ) @Column(columnDefinition = "varchar(600) generated always as (firstname || ' ' || lastname) stored") public String fullName; @@ -130,7 +130,7 @@ public GeneratedRegularParent(String firstname, String lastname) { @Entity(name = "GeneratedRegular") static class GeneratedRegular extends GeneratedRegularParent { @Temporal(value = TemporalType.TIMESTAMP) - @Generated(GenerationTime.INSERT) + @Generated( event = EventType.INSERT ) @Column(columnDefinition = "timestamp") @ColumnDefault("current_timestamp") public Date createdAt; @@ -138,7 +138,6 @@ static class GeneratedRegular extends GeneratedRegularParent { @CurrentUser.LoggedUserStageAlways public String updatedBy; - @Generated(GenerationTime.NEVER) public String never; public GeneratedRegular() { 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/IdentityGenerationWithBatchingTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGenerationWithBatchingTest.java index acad8feba..3492532c1 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGenerationWithBatchingTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGenerationWithBatchingTest.java @@ -63,7 +63,7 @@ public void test(VertxTestContext context) { } private Book[] asBooks(String[] titles) { - return Arrays.asList( titles ).stream().map( Book::new ).toArray( Book[]::new ); + return Arrays.stream( titles ).map( Book::new ).toArray( Book[]::new ); } @Entity(name = "Book") diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGeneratorDynamicInsertTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGeneratorDynamicInsertTest.java index 2454de0d3..9be6f748a 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGeneratorDynamicInsertTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGeneratorDynamicInsertTest.java @@ -70,7 +70,7 @@ private CompletionStage populateDb() { return getSessionFactory() .withTransaction( (session, tx) -> session.persist( identities.toArray() ) ) .thenAccept( ignore -> { - Long assignedId = 0L; + long assignedId = 0L; for ( EntityWithIdentity identity : identities ) { assertNotNull( identity.id ); assertTrue( identity.id > assignedId ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGeneratorTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGeneratorTest.java index 61cc6c503..1d9a6c8b3 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGeneratorTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGeneratorTest.java @@ -68,7 +68,7 @@ private CompletionStage populateDb() { return getSessionFactory() .withTransaction( (session, tx) -> session.persist( identities.toArray() ) ) .thenAccept( ignore -> { - Long assignedId = 0L; + long assignedId = 0L; for ( EntityWithIdentity identity : identities ) { assertNotNull( identity.id ); assertTrue( identity.id > assignedId ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGeneratorTypeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGeneratorTypeTest.java index a6dfe601a..606f0bf8b 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGeneratorTypeTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGeneratorTypeTest.java @@ -97,7 +97,6 @@ public void integerIdentityType(VertxTestContext context) { assertType( context, IntegerTypeEntity.class, new IntegerTypeEntity(), 1 ); } - interface TypeIdentity { T getId(); } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGeneratorWithColumnTransformerTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGeneratorWithColumnTransformerTest.java index 3982e0c6b..a4d584719 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGeneratorWithColumnTransformerTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGeneratorWithColumnTransformerTest.java @@ -72,7 +72,7 @@ private CompletionStage populateDb() { return getSessionFactory() .withTransaction( (session, tx) -> session.persist( identities.toArray() ) ) .thenAccept( ignore -> { - Long assignedId = 0L; + long assignedId = 0L; for ( EntityWithIdentity identity : identities ) { assertNotNull( identity.id ); assertTrue( identity.id > assignedId ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedInheritanceBatchTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedInheritanceBatchTest.java new file mode 100644 index 000000000..0c49d883e --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedInheritanceBatchTest.java @@ -0,0 +1,130 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletionStage; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +@Timeout(value = 10, timeUnit = MINUTES) +public class JoinedInheritanceBatchTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( ClientA.class, Client.class ); + } + + @Override + protected CompletionStage cleanDb() { + return voidFuture(); + } + + @Test + public void test(VertxTestContext context) { + final ClientA client1 = new ClientA("Client 1", "email@c1", "123456"); + + test( context, getMutinySessionFactory().withTransaction( session -> { + session.setBatchSize( 5 ); + return session.persist( client1 ); + } ) + .chain( () -> getMutinySessionFactory().withTransaction( session -> session + .createQuery( "select c from Client c", Client.class ) + .getResultList() + .invoke( persistedClients -> assertThat( persistedClients ) + .as( "Clients has not bee persisted" ) + .isNotEmpty() ) ) ) + ); + } + + @Entity(name = "Client") + @Table(name = "`Client`") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Client { + + @Id + @SequenceGenerator(name = "seq", sequenceName = "id_seq", allocationSize = 1) + @GeneratedValue(generator = "seq", strategy = GenerationType.SEQUENCE) + private Long id; + + private String name; + + private String email; + + private String phone; + + public Client() { + } + + public Client(String name, String email, String phone) { + this.name = name; + this.email = email; + this.phone = phone; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + } + + @Entity + @Table(name = "`ClientA`") + public static class ClientA extends Client { + + public ClientA() { + } + + public ClientA(String name, String email, String phone) { + super( name, email, phone ); + } + } + +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/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/LazyInitializationExceptionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyInitializationExceptionTest.java index bb04c0fda..55268f906 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyInitializationExceptionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyInitializationExceptionTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import io.smallrye.mutiny.Uni; import io.vertx.junit5.Timeout; import io.vertx.junit5.VertxTestContext; import jakarta.persistence.Entity; @@ -32,6 +33,7 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.reactive.testing.ReactiveAssertions.assertThrown; +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -61,6 +63,31 @@ public void populateDB(VertxTestContext context) { test( context, getSessionFactory().withTransaction( session -> session.persist( artemisia, liuto, sev ) ) ); } + @Test + public void testLazyInitializationExceptionWithTransactionWithMutiny(VertxTestContext context) { + test( context, assertThrown( LazyInitializationException.class, getMutinySessionFactory() + .withSession( ms -> ms + .createSelectionQuery( "from Artist", Artist.class ) + .getSingleResult() ) + .call( artist -> getMutinySessionFactory().withTransaction( s -> Uni.createFrom() + // .size should throw LazyInitializationException + .item( artist.getPaintings().size() ) ) ) + ) + ); + } + + @Test + public void testLazyInitializationExceptionWithTransactionWithStage(VertxTestContext context) { + test( context, assertThrown( LazyInitializationException.class, getSessionFactory() + .withSession( ss -> ss + .createSelectionQuery( "from Artist", Artist.class ) + .getSingleResult() ) + .thenCompose( artist -> getSessionFactory() + .withTransaction( s -> completedFuture( artist.getPaintings().size() ) ) ) + ) + ); + } + @Test public void testLazyInitializationExceptionWithMutiny(VertxTestContext context) { test( context, assertThrown( LazyInitializationException.class, openMutinySession() diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToManyMapTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToManyMapTest.java index 4c94668d5..9b7ddd627 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToManyMapTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToManyMapTest.java @@ -7,8 +7,11 @@ import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; import org.hibernate.Hibernate; @@ -25,6 +28,7 @@ import jakarta.persistence.Table; 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.assertTrue; @@ -45,61 +49,49 @@ public void test(VertxTestContext context) { Author author = new Author( "Iain M Banks" ); book1.authors.put( "a", author ); book2.authors.put( "b", author ); - author.books.put( "a", book1 ); - author.books.put( "b", book2 ); + author.books.add( book1 ); + author.books.add( book2 ); test( context, getMutinySessionFactory() - .withTransaction( (session, transaction) -> session.persistAll( book1, book2, author ) ) + .withTransaction( session -> session.persistAll( book1, book2, author ) ) .chain( () -> getMutinySessionFactory() - .withTransaction( (session, transaction) -> session.find( Book.class, book1.id ) + .withTransaction( session -> session.find( Book.class, book1.id ) .invoke( b -> assertFalse( Hibernate.isInitialized( b.authors ) ) ) .chain( b -> session.fetch( b.authors ) ) .invoke( authors -> assertEquals( 1, authors.size() ) ) ) ) .chain( () -> getMutinySessionFactory() - .withTransaction( (session, transaction) -> session.find( Author.class, author.id ) + .withTransaction( session -> session.find( Author.class, author.id ) .invoke( a -> assertFalse( Hibernate.isInitialized( a.books ) ) ) .chain( a -> session.fetch( a.books ) ) - .invoke( books -> { - assertEquals( 2, books.size() ); - assertEquals( book1.title, books.get( "a" ).title ); - assertEquals( book2.title, books.get( "b" ).title ); - - } ) - ) - ) - .chain( () -> getMutinySessionFactory() - .withTransaction( (session, transaction) -> session.createSelectionQuery( - "select distinct a from Author a left join fetch a.books", - Author.class - ) - .getSingleResult() - .invoke( a -> assertTrue( Hibernate.isInitialized( a.books ) ) ) - .invoke( a -> { - assertEquals( 2, a.books.size() ); - assertEquals( book1.title, a.books.get( "a" ).title ); - assertEquals( book2.title, a.books.get( "b" ).title ); - } ) + .invoke( books -> assertThat( books ).containsExactlyInAnyOrder( book1, book2 ) ) ) ) + .chain( () -> getMutinySessionFactory().withTransaction( session -> session + .createSelectionQuery( "select distinct a from Author a left join fetch a.books", Author.class ) + .getSingleResult() + .invoke( a -> assertTrue( Hibernate.isInitialized( a.books ) ) ) + .invoke( a -> assertThat( a.books ).containsExactlyInAnyOrder( book1, book2 ) ) + ) ) ); } @Entity(name = "Book") @Table(name = "MTMMBook") static class Book { - Book(String title) { - this.title = title; - } Book() { } - @GeneratedValue + Book(String title) { + this.title = title; + } + @Id + @GeneratedValue long id; @Basic(optional = false) @@ -108,6 +100,25 @@ static class Book { @ManyToMany @MapKeyColumn(name = "mapkey") Map authors = new HashMap<>(); + + @Override + public boolean equals(Object object) { + if ( object == null || getClass() != object.getClass() ) { + return false; + } + Book book = (Book) object; + return Objects.equals( title, book.title ); + } + + @Override + public int hashCode() { + return Objects.hash( title ); + } + + @Override + public String toString() { + return id + ":" + title; + } } @Entity(name = "Author") @@ -120,15 +131,28 @@ static class Author { public Author() { } - @GeneratedValue @Id + @GeneratedValue long id; @Basic(optional = false) String name; @ManyToMany(mappedBy = "authors") - @MapKeyColumn(name = "mapkey") - Map books = new HashMap<>(); + Set books = new HashSet<>(); + + @Override + public boolean equals(Object object) { + if ( object == null || getClass() != object.getClass() ) { + return false; + } + Author author = (Author) object; + return Objects.equals( name, author.name ); + } + + @Override + public int hashCode() { + return Objects.hashCode( name ); + } } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToManyWithCompositeIdTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToManyWithCompositeIdTest.java new file mode 100644 index 000000000..8c72c38d3 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToManyWithCompositeIdTest.java @@ -0,0 +1,293 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import io.vertx.junit5.Timeout; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import java.util.concurrent.CompletionStage; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +@Timeout(value = 10, timeUnit = MINUTES) +public class ManyToManyWithCompositeIdTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( CarsClients.class, ClientA.class, Client.class, Car.class ); + } + + @Override + protected CompletionStage cleanDb() { + return voidFuture(); + } + + @Test + public void test(VertxTestContext context) { + List clients = new ArrayList<>(); + for ( int i = 0; i < 5; i++ ) { + ClientA client = new ClientA(); + client.setName( "name" + i ); + client.setEmail( "email" + i ); + client.setPhone( "phone" + i ); + clients.add( client ); + } + + List cars = new ArrayList<>(); + for ( int i = 0; i < 2; i++ ) { + Car car = new Car(); + car.setBrand( "brand" + i ); + car.setModel( "model" + i ); + cars.add( car ); + } + + test( context, getMutinySessionFactory() + .withSession( session -> { + session.setBatchSize( 5 ); + return session.persistAll( cars.toArray() ) + .chain( () -> session + .persistAll( clients.toArray() ) + .chain( session::flush ) ) + .chain( () -> { + List carsClientsList = new ArrayList<>(); + for ( Client client : clients ) { + for ( Car car : cars ) { + CarsClients carsClients = new CarsClients( "location" ); + carsClientsList.add( carsClients ); + car.addClient( carsClients ); + client.addCar( carsClients ); + } + } + return session + .persistAll( carsClientsList.toArray() ) + .chain( session::flush ); + } ); + } ) + ); + } + + @Entity(name = "Car") + @Table(name = "Car_Table") + public static class Car { + + @Id + @SequenceGenerator(name = "seq_car", sequenceName = "id_seq_car", allocationSize = 1) + @GeneratedValue(generator = "seq_car", strategy = GenerationType.SEQUENCE) + private Long id; + + public String brand; + + + private String model; + + @OneToMany(mappedBy = "car") + private Set clients = new HashSet<>(); + + public Car() { + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getBrand() { + return brand; + } + + public void setBrand(String brand) { + this.brand = brand; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public Set getClients() { + return clients; + } + + public void setClients(Set clients) { + this.clients = clients; + } + + public void addClient(CarsClients carsClients) { + carsClients.setCar( this ); + clients.add( carsClients ); + } + } + + @Entity + @Table(name = "Client_Table") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Client { + + @Id + @SequenceGenerator(name = "seq", sequenceName = "id_seq", allocationSize = 1) + @GeneratedValue(generator = "seq", strategy = GenerationType.SEQUENCE) + private Long id; + + private String name; + + private String email; + + private String phone; + + @OneToMany(mappedBy = "client") + private Set cars = new HashSet<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public Set getCars() { + return cars; + } + + public void setCars(Set cars) { + this.cars = cars; + } + + public void addCar(CarsClients carsClients) { + carsClients.setClient( this ); + cars.add( carsClients ); + } + } + + @Entity + @Table(name = "ClientA_Table") + public static class ClientA extends Client { + + public ClientA() { + } + } + + @Entity + @IdClass(CarsClientsId.class) + @Table(name = "cars_clients_table") + public static class CarsClients { + + @Id + @ManyToOne + private Car car; + + @Id + @ManyToOne + private Client client; + + private String location; + + public CarsClients() { + } + + public CarsClients(String location) { + this.location = location; + } + + public Car getCar() { + return car; + } + + public void setCar(Car car) { + this.car = car; + } + + public Client getClient() { + return client; + } + + public void setClient(Client client) { + this.client = client; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + } + + public static class CarsClientsId { + private Car car; + + private Client client; + + public CarsClientsId() { + } + + public Car getCar() { + return car; + } + + public void setCar(Car car) { + this.car = car; + } + + public Client getClient() { + return client; + } + + public void setClient(Client client) { + this.client = client; + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToOneIdClassTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToOneIdClassTest.java new file mode 100644 index 000000000..73dead49a --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToOneIdClassTest.java @@ -0,0 +1,135 @@ +/* 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 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.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.ManyToOne; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; + +@Timeout(value = 1, timeUnit = MINUTES) +public class ManyToOneIdClassTest extends BaseReactiveTest { + + private final static String USER_NAME = "user"; + private final static String SUBSYSTEM_ID = "1"; + + @Override + protected Collection> annotatedEntities() { + return List.of( SystemUser.class, Subsystem.class ); + } + + @BeforeEach + public void populateDb(VertxTestContext context) { + Subsystem subsystem = new Subsystem( SUBSYSTEM_ID, "sub 1" ); + SystemUser systemUser = new SystemUser( subsystem, USER_NAME, "system 1" ); + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.persistAll( subsystem, systemUser ) ) + ); + } + + @Test + public void testQuery(VertxTestContext context) { + test( + context, openSession().thenAccept( session -> session + .createQuery( "FROM SystemUser s", SystemUser.class ) + .getResultList().thenAccept( list -> { + assertThat( list ).hasSize( 1 ); + SystemUser systemUser = list.get( 0 ); + assertThat( systemUser.getSubsystem().getId() ).isEqualTo( SUBSYSTEM_ID ); + assertThat( systemUser.getUsername() ).isEqualTo( USER_NAME ); + } ) ) + ); + } + + @Entity(name = "SystemUser") + @IdClass(PK.class) + public static class SystemUser { + + @Id + @ManyToOne(fetch = FetchType.LAZY) + private Subsystem subsystem; + + @Id + private String username; + + private String name; + + public SystemUser() { + } + + public SystemUser(Subsystem subsystem, String username, String name) { + this.subsystem = subsystem; + this.username = username; + this.name = name; + } + + public Subsystem getSubsystem() { + return subsystem; + } + + public String getUsername() { + return username; + } + + public String getName() { + return name; + } + } + + @Entity(name = "Subsystem") + public static class Subsystem { + + @Id + private String id; + + private String description; + + public Subsystem() { + } + + public Subsystem(String id, String description) { + this.id = id; + this.description = description; + } + + public String getId() { + return id; + } + + public String getDescription() { + return description; + } + } + + public static class PK { + + private Subsystem subsystem; + + private String username; + + public PK(Subsystem subsystem, String username) { + this.subsystem = subsystem; + this.username = username; + } + + private PK() { + } + } + +} 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/MultithreadedIdentityGenerationTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedIdentityGenerationTest.java index 073ab6f92..a41cefd72 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedIdentityGenerationTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedIdentityGenerationTest.java @@ -14,6 +14,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.hibernate.AssertionFailure; import org.hibernate.SessionFactory; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; @@ -45,6 +46,7 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.Table; +import org.jetbrains.annotations.NotNull; import static java.util.concurrent.TimeUnit.MINUTES; import static org.hibernate.cfg.AvailableSettings.SHOW_SQL; @@ -67,7 +69,8 @@ @Timeout(value = MultithreadedIdentityGenerationTest.TIMEOUT_MINUTES, timeUnit = MINUTES) public class MultithreadedIdentityGenerationTest { - /* The number of threads should be higher than the default size of the connection pool so that + /* + * The number of threads should be higher than the default size of the connection pool so that * this test is also effective in detecting problems with resource starvation. */ private static final int N_THREADS = 48; @@ -93,14 +96,7 @@ public class MultithreadedIdentityGenerationTest { @BeforeAll public static void setupSessionFactory() { - final VertxOptions vertxOptions = new VertxOptions(); - vertxOptions.setEventLoopPoolSize( N_THREADS ); - //We relax the blocked thread checks as we'll actually use latches to block them - //intentionally for the purpose of the test; functionally this isn't required - //but it's useful as self-test in the design of this, to ensure that the way - //things are setup are indeed being run in multiple, separate threads. - vertxOptions.setBlockedThreadCheckInterval( TIMEOUT_MINUTES ); - vertxOptions.setBlockedThreadCheckIntervalUnit( TimeUnit.MINUTES ); + final VertxOptions vertxOptions = createVertxOptions(); vertx = Vertx.vertx( vertxOptions ); Configuration configuration = new Configuration(); setDefaultProperties( configuration ); @@ -116,6 +112,18 @@ public static void setupSessionFactory() { stageSessionFactory = sessionFactory.unwrap( Stage.SessionFactory.class ); } + private static @NotNull VertxOptions createVertxOptions() { + final VertxOptions vertxOptions = new VertxOptions(); + vertxOptions.setEventLoopPoolSize( N_THREADS ); + //We relax the blocked thread checks as we'll actually use latches to block them + //intentionally for the purpose of the test; functionally this isn't required, + //but it's useful as self-test in the design of this, to ensure that the way + //things are set up are indeed being run in multiple, separate threads. + vertxOptions.setBlockedThreadCheckInterval( TIMEOUT_MINUTES ); + vertxOptions.setBlockedThreadCheckIntervalUnit( TimeUnit.MINUTES ); + return vertxOptions; + } + @AfterAll public static void closeSessionFactory() { stageSessionFactory.close(); @@ -123,9 +131,11 @@ public static void closeSessionFactory() { private ReactiveGeneratorWrapper getIdGenerator() { final ReactiveSessionFactoryImpl hibernateSessionFactory = (ReactiveSessionFactoryImpl) sessionFactory; - final ReactiveGeneratorWrapper identifierGenerator = (ReactiveGeneratorWrapper) hibernateSessionFactory.getIdentifierGenerator( - "org.hibernate.reactive.MultithreadedIdentityGenerationTest$EntityWithGeneratedId" ); - return identifierGenerator; + return (ReactiveGeneratorWrapper) hibernateSessionFactory + .getRuntimeMetamodels() + .getMappingMetamodel() + .getEntityDescriptor( "org.hibernate.reactive.MultithreadedIdentityGenerationTest$EntityWithGeneratedId" ) + .getGenerator(); } @Test @@ -150,7 +160,7 @@ public void testIdentityGenerator(VertxTestContext context) { } } ) .onFailure( context::failNow ) - .eventually( unused -> vertx.close() ); + .eventually( () -> vertx.close() ); } private boolean allResultsAreUnique(ResultsCollector allResults) { @@ -190,24 +200,23 @@ public void start(Promise startPromise) { startLatch.reached(); startLatch.waitForEveryone();//Not essential, but to ensure a good level of parallelism final String initialThreadName = Thread.currentThread().getName(); - stageSessionFactory.withSession( - s -> generateMultipleIds( idGenerator, s, generatedIds ) - ) - .whenComplete( (o, throwable) -> { - endLatch.reached(); - if ( throwable != null ) { - startPromise.fail( throwable ); - } - else { - if ( !initialThreadName.equals( Thread.currentThread().getName() ) ) { - startPromise.fail( "Thread switch detected!" ); + stageSessionFactory + .withSession( s -> generateMultipleIds( idGenerator, s, generatedIds ) ) + .whenComplete( (o, throwable) -> { + endLatch.reached(); + if ( throwable != null ) { + startPromise.fail( throwable ); } else { - allResults.deliverResulst( generatedIds ); - startPromise.complete(); + if ( !initialThreadName.equals( Thread.currentThread().getName() ) ) { + startPromise.fail( "Thread switch detected!" ); + } + else { + allResults.deliverResulst( generatedIds ); + startPromise.complete(); + } } - } - } ); + } ); } catch (RuntimeException e) { startPromise.fail( e ); @@ -222,7 +231,7 @@ public void stop() { private static class ResultsCollector { - private final ConcurrentMap> resultsByThread = new ConcurrentHashMap<>(); + private final ConcurrentMap> resultsByThread = new ConcurrentHashMap<>(); public void deliverResulst(List generatedIds) { final String threadName = Thread.currentThread().getName(); @@ -242,8 +251,8 @@ private static CompletionStage generateIds( Stage.Session s, ArrayList collector) { final Thread beforeOperationThread = Thread.currentThread(); - return idGenerator.generate( ( (StageSessionImpl) s ) - .unwrap( ReactiveConnectionSupplier.class ), new EntityWithGeneratedId() ) + return idGenerator + .generate( ( (StageSessionImpl) s ).unwrap( ReactiveConnectionSupplier.class ), new EntityWithGeneratedId() ) .thenAccept( o -> { if ( beforeOperationThread != Thread.currentThread() ) { throw new IllegalStateException( "Detected an unexpected switch of carrier threads!" ); @@ -256,7 +265,7 @@ private static CompletionStage generateIds( * Trivial entity using a Sequence for Id generation */ @Entity - @Table(name="Entity") + @Table(name = "Entity") private static class EntityWithGeneratedId { @Id @GeneratedValue @@ -288,10 +297,12 @@ public void reached() { public void waitForEveryone() { try { - countDownLatch.await( TIMEOUT_MINUTES, TimeUnit.MINUTES ); + if ( !countDownLatch.await( TIMEOUT_MINUTES, MINUTES ) ) { + throw new AssertionFailure( "Time out reached!" ); + } prettyOut( "Everyone has now breached '" + label + "'" ); } - catch ( InterruptedException e ) { + catch (InterruptedException e) { e.printStackTrace(); } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedInsertionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedInsertionTest.java index f82bc77eb..bd2a36d1e 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedInsertionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedInsertionTest.java @@ -37,6 +37,7 @@ import jakarta.persistence.Table; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.fail; import static org.hibernate.cfg.AvailableSettings.SHOW_SQL; import static org.hibernate.reactive.BaseReactiveTest.setDefaultProperties; import static org.hibernate.reactive.provider.Settings.POOL_CONNECT_TIMEOUT; @@ -137,7 +138,7 @@ public void testIdentityGenerator(VertxTestContext context) { context.completeNow(); } ) .onFailure( context::failNow ) - .eventually( unused -> vertx.close() ); + .eventually( () -> vertx.close() ); } private static class InsertEntitiesVerticle extends AbstractVerticle { @@ -233,11 +234,16 @@ public void reached() { public void waitForEveryone() { try { - countDownLatch.await( TIMEOUT_MINUTES, TimeUnit.MINUTES ); - prettyOut( "Everyone has now breached '" + label + "'" ); + boolean reachedZero = countDownLatch.await( TIMEOUT_MINUTES, MINUTES ); + if ( reachedZero ) { + prettyOut( "Everyone has now breached '" + label + "'" ); + } + else { + fail( "Time out reached" ); + } } catch ( InterruptedException e ) { - e.printStackTrace(); + fail( e ); } } } 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 new file mode 100644 index 000000000..8314179c9 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinyStatelessSessionTest.java @@ -0,0 +1,279 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.*; +import jakarta.persistence.criteria.*; +import org.junit.jupiter.api.Test; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.junit.jupiter.api.Assertions.*; + +@Timeout(value = 10, timeUnit = MINUTES) + +public class MutinyStatelessSessionTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( GuineaPig.class ); + } + + @Test + public void testStatelessSession(VertxTestContext context) { + GuineaPig pig = new GuineaPig( "Aloi" ); + test( context, getMutinySessionFactory().withStatelessSession( ss -> ss + .insert( pig ) + .chain( v -> ss.createSelectionQuery( "from GuineaPig where name=:n", GuineaPig.class ) + .setParameter( "n", pig.name ) + .getResultList() ) + .invoke( list -> { + assertFalse( list.isEmpty() ); + assertEquals( 1, list.size() ); + assertThatPigsAreEqual( pig, list.get( 0 ) ); + } ) + .chain( v -> ss.get( GuineaPig.class, pig.id ) ) + .chain( p -> { + assertThatPigsAreEqual( pig, p ); + p.name = "X"; + return ss.update( p ); + } ) + .chain( v -> ss.refresh( pig ) ) + .invoke( v -> assertEquals( pig.name, "X" ) ) + .chain( v -> ss.createMutationQuery( "update GuineaPig set name='Y'" ).executeUpdate() ) + .chain( v -> ss.refresh( pig ) ) + .invoke( v -> assertEquals( pig.name, "Y" ) ) + .chain( v -> ss.delete( pig ) ) + .chain( v -> ss.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList() ) + .invoke( list -> assertTrue( list.isEmpty() ) ) ) + ); + } + + @Test + public void testStatelessSessionWithNamed(VertxTestContext context) { + GuineaPig pig = new GuineaPig( "Aloi" ); + test( context, getMutinySessionFactory().withStatelessSession( ss -> ss + .insert( pig ) + .chain( v -> ss.createNamedQuery( "findbyname", GuineaPig.class ) + .setParameter( "n", pig.name ) + .getResultList() ) + .invoke( list -> { + assertFalse( list.isEmpty() ); + assertEquals( 1, list.size() ); + assertThatPigsAreEqual( pig, list.get( 0 ) ); + } ) + .chain( v -> ss.get( GuineaPig.class, pig.id ) ) + .chain( p -> { + assertThatPigsAreEqual( pig, p ); + p.name = "X"; + return ss.update( p ); + } ) + .chain( v -> ss.refresh( pig ) ) + .invoke( v -> assertEquals( pig.name, "X" ) ) + .chain( v -> ss.createNamedQuery( "updatebyname" ).executeUpdate() ) + .chain( v -> ss.refresh( pig ) ) + .invoke( v -> assertEquals( pig.name, "Y" ) ) + .chain( v -> ss.delete( pig ) ) + .chain( v -> ss.createNamedQuery( "findall" ).getResultList() ) + .invoke( list -> assertTrue( list.isEmpty() ) ) ) + ); + } + + @Test + public void testStatelessSessionWithNative(VertxTestContext context) { + GuineaPig pig = new GuineaPig( "Aloi" ); + test( context, getMutinySessionFactory().openStatelessSession() + .chain( ss -> ss.insert( pig ) + .chain( v -> ss + .createNativeQuery( "select * from Piggy where name=:n", GuineaPig.class ) + .setParameter( "n", pig.name ) + .getResultList() ) + .invoke( list -> { + assertFalse( list.isEmpty() ); + assertEquals( 1, list.size() ); + assertThatPigsAreEqual( pig, list.get( 0 ) ); + } ) + .chain( v -> ss.get( GuineaPig.class, pig.id ) ) + .chain( p -> { + assertThatPigsAreEqual( pig, p ); + p.name = "X"; + return ss.update( p ); + } ) + .chain( v -> ss.refresh( pig ) ) + .invoke( v -> assertEquals( pig.name, "X" ) ) + .chain( v -> ss.createNativeQuery( "update Piggy set name='Y'" ) + .executeUpdate() ) + .invoke( rows -> assertEquals( 1, rows ) ) + .chain( v -> ss.refresh( pig ) ) + .invoke( v -> assertEquals( pig.name, "Y" ) ) + .chain( v -> ss.delete( pig ) ) + .chain( v -> ss.createNativeQuery( "select id from Piggy" ).getResultList() ) + .invoke( list -> assertTrue( list.isEmpty() ) ) + .chain( v -> ss.close() ) ) + ); + } + + @Test + public void testStatelessSessionGetMultiple(VertxTestContext context) { + GuineaPig a = new GuineaPig("A"); + GuineaPig b = new GuineaPig("B"); + GuineaPig c = new GuineaPig("C"); + test( context, getMutinySessionFactory().openStatelessSession() + .chain( ss -> ss.insertMultiple( List.of(a, b, c) ) + .chain( v -> ss.get( GuineaPig.class, a.id, c.id ) ) + .invoke( list -> { + assertEquals( 2, list.size() ); + assertThatPigsAreEqual( a, list.get( 0 ) ); + assertThatPigsAreEqual( c, list.get( 1 ) ); + }) + .chain( v -> ss.close() ) ) + ); + } + + @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 ); + Root updatedPig = update.from(GuineaPig.class); + update.set( "name", "Bob" ); + update.where( updatedPig.get( "mate" ).isNotNull() ); + + CriteriaDelete delete = cb.createCriteriaDelete( GuineaPig.class ); + Root deletedPig = delete.from( GuineaPig.class ); + delete.where( deletedPig.get( "mate" ).isNotNull() ); + + test( context, getMutinySessionFactory().openStatelessSession() + .chain( ss -> ss.insertMultiple( List.of(mate, pig) ) + .chain( v -> ss.createQuery( query ) + .setParameter( "n", pig.name ) + .getResultList() ) + .invoke( list -> { + assertFalse( list.isEmpty() ); + assertEquals( 1, list.size() ); + assertThatPigsAreEqual( pig, list.get( 0 ) ); + } ) + .chain( v -> ss.createQuery( update ).executeUpdate() ) + .invoke( rows -> assertEquals( 1, rows ) ) + .chain( v -> ss.createQuery( delete ).executeUpdate() ) + .invoke( rows -> assertEquals( 1, rows ) ) + .chain( v -> ss.close() ) ) + ); + } + + @Test + public void testTransactionPropagation(VertxTestContext context) { + test( context, getMutinySessionFactory().withStatelessSession( + session -> session.withTransaction( transaction -> session.createSelectionQuery( "from GuineaPig", GuineaPig.class ) + .getResultList() + .chain( list -> { + assertNotNull( session.currentTransaction() ); + assertFalse( session.currentTransaction().isMarkedForRollback() ); + session.currentTransaction().markForRollback(); + assertTrue( session.currentTransaction().isMarkedForRollback() ); + assertTrue( transaction.isMarkedForRollback() ); + return session.withTransaction( t -> { + assertTrue( t.isMarkedForRollback() ); + return session.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList(); + } ); + } ) ) + ) ); + } + + @Test + public void testSessionPropagation(VertxTestContext context) { + test( context, getMutinySessionFactory().withStatelessSession( + session -> session.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList() + .chain( list -> getMutinySessionFactory().withStatelessSession( s -> { + assertEquals( session, s ); + return s.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList(); + } ) ) + ) ); + } + + private void assertThatPigsAreEqual( GuineaPig expected, GuineaPig actual) { + assertNotNull( actual ); + assertEquals( expected.getId(), actual.getId() ); + assertEquals( expected.getName(), actual.getName() ); + } + + @NamedQuery(name = "findbyname", query = "from GuineaPig where name=:n") + @NamedQuery(name = "updatebyname", query = "update GuineaPig set name='Y'") + @NamedQuery(name = "findall", query = "from GuineaPig") + + @Entity(name = "GuineaPig") + @Table(name = "Piggy") + public static class GuineaPig { + @Id + @GeneratedValue + private Integer id; + private String name; + @Version + private int version; + + @ManyToOne + private GuineaPig mate; + + public GuineaPig() { + } + + public GuineaPig(String name) { + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return id + ": " + name; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + GuineaPig guineaPig = (GuineaPig) o; + return Objects.equals( name, guineaPig.name ); + } + + @Override + public int hashCode() { + return Objects.hash( name ); + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyArrayMergeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyArrayMergeTest.java new file mode 100644 index 000000000..8553f1624 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyArrayMergeTest.java @@ -0,0 +1,188 @@ +/* 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.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.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; + +@Timeout(value = 2, timeUnit = MINUTES) +public class OneToManyArrayMergeTest extends BaseReactiveTest { + + private final static Long USER_ID = 1L; + private final static Long ADMIN_ROLE_ID = 2L; + private final static Long USER_ROLE_ID = 3L; + private final static String UPDATED_FIRSTNAME = "UPDATED FIRSTNAME"; + private final static String UPDATED_LASTNAME = "UPDATED LASTNAME"; + + @Override + protected Collection> annotatedEntities() { + return List.of( User.class, Role.class ); + } + + @BeforeEach + public void populateDb(VertxTestContext context) { + Role adminRole = new Role( ADMIN_ROLE_ID, "admin" ); + Role userRole = new Role( USER_ROLE_ID, "user" ); + User user = new User( USER_ID, "first", "last", adminRole ); + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.persistAll( user, adminRole, userRole ) ) + ); + } + + @Test + public void testMerge(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.find( User.class, USER_ID ) ) + .chain( user -> getMutinySessionFactory() + .withTransaction( s -> s + .createQuery( "FROM Role", Role.class ) + .getResultList() ) + .map( roles -> { + user.addAll( roles ); + user.setFirstname( UPDATED_FIRSTNAME ); + user.setLastname( UPDATED_LASTNAME ); + return user; + } ) + ) + .chain( user -> { + assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME ); + assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME ); + assertThat( user.getRoles() ).hasSize( 2 ); + return getMutinySessionFactory() + .withTransaction( s -> s.merge( user ) ); + } + ) + .chain( v -> getMutinySessionFactory() + .withTransaction( s -> s.find( User.class, USER_ID ) ) + ) + .invoke( user -> { + Role adminRole = new Role( ADMIN_ROLE_ID, "admin" ); + Role userRole = new Role( USER_ROLE_ID, "user" ); + assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME ); + assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME ); + assertThat( user.getRoles() ).containsExactlyInAnyOrder( + adminRole, + userRole + ); + } + ) + ); + } + + @Entity(name = "User") + @Table(name = "USER_TABLE") + public static class User { + + @Id + private Long id; + + private String firstname; + + private String lastname; + + @OneToMany(fetch = FetchType.EAGER) + private Role[] roles; + + public User() { + } + + public User(Long id, String firstname, String lastname, Role... roles) { + this.id = id; + this.firstname = firstname; + this.lastname = lastname; + this.roles = new Role[roles.length]; + System.arraycopy( roles, 0, this.roles, 0, roles.length ); + } + + public Long getId() { + return id; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public Role[] getRoles() { + return roles; + } + + public void addAll(List roles) { + this.roles = new Role[roles.size()]; + for ( int i = 0; i < roles.size(); i++ ) { + this.roles[i] = roles.get( i ); + } + } + } + + @Entity(name = "Role") + @Table(name = "ROLE_TABLE") + public static class Role { + + @Id + private Long id; + private String code; + + public Role() { + } + + public Role(Long id, String code) { + this.id = id; + this.code = code; + } + + public Object getId() { + return id; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Role role = (Role) o; + return Objects.equals( id, role.id ) && Objects.equals( code, role.code ); + } + + @Override + public int hashCode() { + return Objects.hash( id, code ); + } + + @Override + public String toString() { + return "Role{" + code + '}'; + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMapMergeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMapMergeTest.java new file mode 100644 index 000000000..7a1096d3d --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMapMergeTest.java @@ -0,0 +1,199 @@ +/* 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.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +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.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; + +@Timeout(value = 2, timeUnit = MINUTES) +public class OneToManyMapMergeTest extends BaseReactiveTest { + + private final static Long USER_ID = 1L; + private final static Long ADMIN_ROLE_ID = 2L; + private final static Long USER_ROLE_ID = 3L; + private final static String UPDATED_FIRSTNAME = "UPDATED FIRSTNAME"; + private final static String UPDATED_LASTNAME = "UPDATED LASTNAME"; + + @Override + protected Collection> annotatedEntities() { + return List.of( User.class, Role.class ); + } + + @BeforeEach + public void populateDb(VertxTestContext context) { + Role adminRole = new Role( ADMIN_ROLE_ID, "admin" ); + Role userRole = new Role( USER_ROLE_ID, "user" ); + User user = new User( USER_ID, "first", "last", adminRole ); + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.persistAll( user, adminRole, userRole ) ) + ); + } + + @Test + public void testMerge(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.find( User.class, USER_ID ) ) + .chain( user -> getMutinySessionFactory() + .withTransaction( s -> s + .createQuery( "FROM Role", Role.class ) + .getResultList() ) + .map( roles -> { + user.addAll( roles ); + user.setFirstname( UPDATED_FIRSTNAME ); + user.setLastname( UPDATED_LASTNAME ); + return user; + } ) + ) + .chain( user -> { + assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME ); + assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME ); + assertThat( user.getRoles() ).hasSize( 2 ); + return getMutinySessionFactory() + .withTransaction( s -> s.merge( user ) ); + } + ) + .chain( v -> getMutinySessionFactory() + .withTransaction( s -> s.find( User.class, USER_ID ) ) + ) + .invoke( user -> { + Role adminRole = new Role( ADMIN_ROLE_ID, "admin" ); + Role userRole = new Role( USER_ROLE_ID, "user" ); + assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME ); + assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME ); + assertThat( user.getRoles() ).containsEntry( + adminRole.getCode(), + adminRole + ); + assertThat( user.getRoles() ).containsEntry( + userRole.getCode(), + userRole + ); + } + ) + ); + } + + @Entity(name = "User") + @Table(name = "USER_TABLE") + public static class User { + + @Id + private Long id; + + private String firstname; + + private String lastname; + + @OneToMany(fetch = FetchType.EAGER) + private Map roles = new HashMap(); + + public User() { + } + + public User(Long id, String firstname, String lastname, Role... roles) { + this.id = id; + this.firstname = firstname; + this.lastname = lastname; + for ( Role role : roles ) { + this.roles.put( role.getCode(), role ); + } + } + + public Long getId() { + return id; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public Map getRoles() { + return roles; + } + + public void addAll(List roles) { + this.roles.clear(); + for ( Role role : roles ) { + this.roles.put( role.getCode(), role ); + } + } + } + + @Entity(name = "Role") + @Table(name = "ROLE_TABLE") + public static class Role { + + @Id + private Long id; + private String code; + + public Role() { + } + + public Role(Long id, String code) { + this.id = id; + this.code = code; + } + + public Object getId() { + return id; + } + + public String getCode() { + return code; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Role role = (Role) o; + return Objects.equals( id, role.id ) && Objects.equals( code, role.code ); + } + + @Override + public int hashCode() { + return Objects.hash( id, code ); + } + + @Override + public String toString() { + return "Role{" + code + '}'; + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMergeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMergeTest.java new file mode 100644 index 000000000..76d7f7732 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMergeTest.java @@ -0,0 +1,227 @@ +/* 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.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.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; + +@Timeout(value = 2, timeUnit = MINUTES) +public class OneToManyMergeTest extends BaseReactiveTest { + + private final static Long USER_ID = 1L; + private final static Long ADMIN_ROLE_ID = 2L; + private final static Long USER_ROLE_ID = 3L; + private final static String UPDATED_FIRSTNAME = "UPDATED FIRSTNAME"; + private final static String UPDATED_LASTNAME = "UPDATED LASTNAME"; + + @Override + protected Collection> annotatedEntities() { + return List.of( User.class, Role.class ); + } + + @BeforeEach + public void populateDb(VertxTestContext context) { + Role adminRole = new Role( ADMIN_ROLE_ID, "admin" ); + Role userRole = new Role( USER_ROLE_ID, "user" ); + User user = new User( USER_ID, "first", "last", adminRole ); + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.persistAll( user, adminRole, userRole ) ) + ); + } + + @Test + public void testMerge(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.find( User.class, USER_ID ) ) + .chain( user -> getMutinySessionFactory() + .withTransaction( s -> s + .createQuery( "FROM Role", Role.class ) + .getResultList() ) + .map( roles -> { + user.getRoles().clear(); + user.getRoles().addAll( roles ); + user.setFirstname( UPDATED_FIRSTNAME ); + user.setLastname( UPDATED_LASTNAME ); + return user; + } ) + ) + .chain( user -> { + assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME ); + assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME ); + assertThat( user.getRoles() ).hasSize( 2 ); + return getMutinySessionFactory() + .withTransaction( s -> s.merge( user ) ); + } + ) + .chain( v -> getMutinySessionFactory() + .withTransaction( s -> s.find( User.class, USER_ID ) ) + ) + .invoke( user -> { + Role adminRole = new Role( ADMIN_ROLE_ID, "admin" ); + Role userRole = new Role( USER_ROLE_ID, "user" ); + assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME ); + assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME ); + assertThat( user.getRoles() ).containsExactlyInAnyOrder( + adminRole, + userRole + ); + } + ) + ); + } + + @Test + public void testMergeRemovingCollectionElements(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.find( User.class, USER_ID ) ) + .chain( user -> getMutinySessionFactory() + .withTransaction( s -> s + .createQuery( "FROM Role", Role.class ) + .getResultList() ) + .map( roles -> { + user.clearRoles(); + user.setFirstname( UPDATED_FIRSTNAME ); + user.setLastname( UPDATED_LASTNAME ); + return user; + } ) + ) + .chain( user -> { + assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME ); + assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME ); + assertThat( user.getRoles() ).isNull(); + return getMutinySessionFactory() + .withTransaction( s -> s.merge( user ) ); + } + ) + .chain( v -> getMutinySessionFactory() + .withTransaction( s -> s.find( User.class, USER_ID ) ) + ) + .invoke( user -> { + assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME ); + assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME ); + assertThat( user.getRoles() ).isNullOrEmpty(); + } + ) + ); + } + + @Entity(name = "User") + @Table(name = "USER_TABLE") + public static class User { + + @Id + private Long id; + + private String firstname; + + private String lastname; + + @OneToMany(fetch = FetchType.EAGER) + private List roles; + + public User() { + } + + public User(Long id, String firstname, String lastname, Role... roles) { + this.id = id; + this.firstname = firstname; + this.lastname = lastname; + this.roles = List.of( roles ); + } + + public User(Long id, String firstname, String lastname) { + this.id = id; + this.firstname = firstname; + this.lastname = lastname; + } + + public Long getId() { + return id; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public List getRoles() { + return roles; + } + + public void clearRoles() { + this.roles = null; + } + } + + @Entity(name = "Role") + @Table(name = "ROLE_TABLE") + public static class Role { + + @Id + private Long id; + private String code; + + public Role() { + } + + public Role(Long id, String code) { + this.id = id; + this.code = code; + } + + public Object getId() { + return id; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Role role = (Role) o; + return Objects.equals( id, role.id ) && Objects.equals( code, role.code ); + } + + @Override + public int hashCode() { + return Objects.hash( id, code ); + } + + @Override + public String toString() { + return "Role{" + code + '}'; + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyTest.java index c3bec53b1..f0242ed71 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyTest.java @@ -45,9 +45,6 @@ public void testPersistAll(VertxTestContext context) { Author author = new Author( "Iain M Banks" ); author.books.add( book1 ); author.books.add( book2 ); - final Book[] bookArray = new Book[2]; - bookArray[0] = book1; - bookArray[1] = book2; test( context, getMutinySessionFactory() .withTransaction( session -> session.persistAll( book1, book2, author ) ) @@ -67,9 +64,6 @@ public void testFetchJoinQueryGetSingleResult(VertxTestContext context) { Author author = new Author( "Iain M Banks" ); author.books.add( book1 ); author.books.add( book2 ); - final Book[] bookArray = new Book[2]; - bookArray[0] = book1; - bookArray[1] = book2; test( context, getMutinySessionFactory() .withTransaction( session -> session.persistAll( book1, book2, author ) ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToOneGeneratedIdWithMapsIdTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToOneGeneratedIdWithMapsIdTest.java new file mode 100644 index 000000000..4da8823a9 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToOneGeneratedIdWithMapsIdTest.java @@ -0,0 +1,153 @@ +/* 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.junit.jupiter.api.Test; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.MapsId; +import jakarta.persistence.OneToOne; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; + +@Timeout(value = 10, timeUnit = MINUTES) +public class OneToOneGeneratedIdWithMapsIdTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( Person.class, NaturalPerson.class ); + } + + @Test + public void testPersist(VertxTestContext context) { + NaturalPerson naturalPerson = new NaturalPerson( "natual" ); + Person person = new Person( "person", naturalPerson ); + + test( + context, getMutinySessionFactory() + .withTransaction( session -> session.persist( person ) ) + .chain( () -> getMutinySessionFactory() + .withTransaction( session -> session + .find( Person.class, person.getId() ) + .invoke( result -> { + assertThat( result ).isNotNull(); + assertThat( result.getNaturalPerson() ).isNotNull(); + assertThat( result.getNaturalPerson().getId() ).isEqualTo( result.getId() ); + } ) ) + ) + ); + } + + @Entity + public static class Person { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + + @OneToOne(mappedBy = "person", cascade = CascadeType.ALL) + private NaturalPerson naturalPerson; + + public Person() { + } + + public Person(String name, NaturalPerson naturalPerson) { + this.name = name; + this.naturalPerson = naturalPerson; + naturalPerson.person = this; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public NaturalPerson getNaturalPerson() { + return naturalPerson; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Person person = (Person) o; + return Objects.equals( name, person.name ); + } + + @Override + public int hashCode() { + return Objects.hashCode( name ); + } + } + + @Entity + public static class NaturalPerson { + + @Id + private Long id; + + @Column + private String name; + + @OneToOne(fetch = FetchType.LAZY) + @MapsId + private Person person; + + public NaturalPerson() { + } + + public NaturalPerson(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Person getPerson() { + return person; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + NaturalPerson that = (NaturalPerson) o; + return Objects.equals( name, that.name ); + } + + @Override + public int hashCode() { + return Objects.hashCode( name ); + } + } + + +} 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/QueryTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java index d6146b2e5..c65c79ee4 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java @@ -138,7 +138,7 @@ public void testCriteriaEntityQuery(VertxTestContext context) { update.set( b.get( "title" ), "XXX" ); CriteriaDelete delete = builder.createCriteriaDelete( Book.class ); - b = delete.from( Book.class ); + delete.from( Book.class ); test( context, 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 73de9fd30..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 @@ -15,7 +15,9 @@ import org.hibernate.reactive.stage.Stage; import org.hibernate.reactive.util.impl.CompletionStages; + import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import io.vertx.junit5.Timeout; @@ -30,11 +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.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) @@ -83,14 +81,15 @@ public void reactiveFind(VertxTestContext context) { public void reactiveFindMultipleIds(VertxTestContext context) { final GuineaPig rump = new GuineaPig( 55, "Rumpelstiltskin" ); final GuineaPig emma = new GuineaPig( 77, "Emma" ); - test( context, populateDB() - .thenCompose( v -> getSessionFactory().withTransaction( s -> s.persist( emma, rump ) ) ) - .thenCompose( v -> getSessionFactory().withTransaction( s -> s - .find( GuineaPig.class, emma.getId(), rump.getId() ) ) - ) - .thenAccept( pigs -> { - org.assertj.core.api.Assertions.assertThat( pigs ).containsExactlyInAnyOrder( emma, rump ); - } ) + test( + context, populateDB() + .thenCompose( v -> getSessionFactory().withTransaction( s -> s.persist( emma, rump ) ) ) + .thenCompose( v -> getSessionFactory().withTransaction( s -> s + .find( GuineaPig.class, emma.getId(), rump.getId() ) ) + ) + .thenAccept( pigs -> { + org.assertj.core.api.Assertions.assertThat( pigs ).containsExactlyInAnyOrder( emma, rump ); + } ) ); } @@ -116,22 +115,24 @@ public void sessionClear(VertxTestContext context) { @Test public void reactiveWithTransactionSession(VertxTestContext context) { final GuineaPig guineaPig = new GuineaPig( 61, "Mr. Peanutbutter" ); - test( context, getSessionFactory() - .withTransaction( session -> session.persist( guineaPig ) ) - .thenCompose( v -> getSessionFactory() - .withSession( session -> session.find( GuineaPig.class, guineaPig.getId() ) ) ) - .thenAccept( result -> assertThatPigsAreEqual( guineaPig, result ) ) + test( + context, getSessionFactory() + .withTransaction( session -> session.persist( guineaPig ) ) + .thenCompose( v -> getSessionFactory() + .withSession( session -> session.find( GuineaPig.class, guineaPig.getId() ) ) ) + .thenAccept( result -> assertThatPigsAreEqual( guineaPig, result ) ) ); } @Test public void reactiveWithTransactionStatelessSession(VertxTestContext context) { final GuineaPig guineaPig = new GuineaPig( 61, "Mr. Peanutbutter" ); - test( context, getSessionFactory() - .withStatelessTransaction( session -> session.insert( guineaPig ) ) - .thenCompose( v -> getSessionFactory() - .withSession( session -> session.find( GuineaPig.class, guineaPig.getId() ) ) ) - .thenAccept( result -> assertThatPigsAreEqual( guineaPig, result ) ) + test( + context, getSessionFactory() + .withStatelessTransaction( session -> session.insert( guineaPig ) ) + .thenCompose( v -> getSessionFactory() + .withSession( session -> session.find( GuineaPig.class, guineaPig.getId() ) ) ) + .thenAccept( result -> assertThatPigsAreEqual( guineaPig, result ) ) ); } @@ -164,32 +165,37 @@ public void reactivePersistFindDelete(VertxTestContext context) { public void reactiveFindWithLock(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); - test( context, populateDB().thenCompose( v -> getSessionFactory() - .withTransaction( (session, tx) -> session - .find( GuineaPig.class, expectedPig.getId(), LockMode.PESSIMISTIC_WRITE ) - .thenAccept( actualPig -> { - assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( session.getLockMode( actualPig ), LockMode.PESSIMISTIC_WRITE ); - } ) - ) ) + test( + context, populateDB().thenCompose( v -> getSessionFactory() + .withTransaction( (session, tx) -> session + .find( GuineaPig.class, expectedPig.getId(), LockMode.PESSIMISTIC_WRITE ) + .thenAccept( actualPig -> { + assertThatPigsAreEqual( expectedPig, actualPig ); + assertEquals( session.getLockMode( actualPig ), LockMode.PESSIMISTIC_WRITE ); + } ) + ) ) ); } @Test public void reactiveFindRefreshWithLock(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); - test( context, populateDB() - .thenCompose( v -> getSessionFactory() - .withTransaction( (session, tx) -> session - .find( GuineaPig.class, expectedPig.getId() ) - .thenCompose( pig -> session - .refresh( pig, LockMode.PESSIMISTIC_WRITE ) - .thenAccept( vv -> { - assertThatPigsAreEqual( expectedPig, pig ); - assertEquals( session.getLockMode( pig ), LockMode.PESSIMISTIC_WRITE ); - } ) - ) - ) ) + test( + context, populateDB() + .thenCompose( v -> getSessionFactory() + .withTransaction( (session, tx) -> session + .find( GuineaPig.class, expectedPig.getId() ) + .thenCompose( pig -> session + .refresh( pig, LockMode.PESSIMISTIC_WRITE ) + .thenAccept( vv -> { + assertThatPigsAreEqual( expectedPig, pig ); + assertEquals( + session.getLockMode( pig ), + LockMode.PESSIMISTIC_WRITE + ); + } ) + ) + ) ) ); } @@ -231,40 +237,42 @@ public void reactiveFindReadOnlyRefreshWithLock(VertxTestContext context) { @Test public void reactiveFindThenUpgradeLock(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); - test( context, populateDB() - .thenCompose( unused -> getSessionFactory() - .withTransaction( (session, tx) -> session - .find( GuineaPig.class, expectedPig.getId() ) - .thenCompose( pig -> session - .lock( pig, LockMode.PESSIMISTIC_READ ) - .thenAccept( v -> { - assertThatPigsAreEqual( expectedPig, pig ); - assertEquals( - session.getLockMode( pig ), - LockMode.PESSIMISTIC_READ - ); - } ) + test( + context, populateDB() + .thenCompose( unused -> getSessionFactory() + .withTransaction( (session, tx) -> session + .find( GuineaPig.class, expectedPig.getId() ) + .thenCompose( pig -> session + .lock( pig, LockMode.PESSIMISTIC_READ ) + .thenAccept( v -> { + assertThatPigsAreEqual( expectedPig, pig ); + assertEquals( + session.getLockMode( pig ), + LockMode.PESSIMISTIC_READ + ); + } ) + ) ) ) - ) ); } @Test public void reactiveFindThenWriteLock(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); - test( context, populateDB().thenCompose( v -> getSessionFactory() - .withTransaction( (session, tx) -> session - .find( GuineaPig.class, expectedPig.getId() ) - .thenCompose( pig -> session - .lock( pig, LockMode.PESSIMISTIC_WRITE ) - .thenAccept( vv -> { - assertThatPigsAreEqual( expectedPig, pig ); - assertEquals( session.getLockMode( pig ), LockMode.PESSIMISTIC_WRITE ); - assertEquals( pig.version, 0 ); - } ) - ) - ) ) + test( + context, populateDB().thenCompose( v -> getSessionFactory() + .withTransaction( (session, tx) -> session + .find( GuineaPig.class, expectedPig.getId() ) + .thenCompose( pig -> session + .lock( pig, LockMode.PESSIMISTIC_WRITE ) + .thenAccept( vv -> { + assertThatPigsAreEqual( expectedPig, pig ); + assertEquals( session.getLockMode( pig ), LockMode.PESSIMISTIC_WRITE ); + assertEquals( pig.version, 0 ); + } ) + ) + ) ) ); } @@ -302,7 +310,8 @@ public void reactiveFindThenForceLock(VertxTestContext context) { ); assertEquals( actualPig.version, 2 ); } ) - .thenCompose( v -> session.createSelectionQuery( "select version from GuineaPig", Integer.class ) + .thenCompose( v -> session + .createSelectionQuery( "select version from GuineaPig", Integer.class ) .getSingleResult() ) .thenAccept( version -> assertEquals( 2, version ) ) ) @@ -315,16 +324,11 @@ public void reactiveFindWithPessimisticIncrementLock(VertxTestContext context) { test( context, populateDB() - .thenCompose( v -> getSessionFactory().withTransaction( - (session, transaction) -> session.find( - GuineaPig.class, - expectedPig.getId(), - LockMode.PESSIMISTIC_FORCE_INCREMENT ) + .thenCompose( v -> getSessionFactory() + .withTransaction( session -> session.find( GuineaPig.class, expectedPig.getId(), LockMode.PESSIMISTIC_FORCE_INCREMENT ) .thenAccept( actualPig -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( - session.getLockMode( actualPig ), - LockMode.PESSIMISTIC_FORCE_INCREMENT ); // grrr, lame + assertEquals( LockMode.PESSIMISTIC_FORCE_INCREMENT, session.getLockMode( actualPig ) ); // grrr, lame assertEquals( 1, actualPig.version ); } ) ) ) @@ -349,8 +353,8 @@ public void reactiveFindWithOptimisticIncrementLock(VertxTestContext context) { .thenAccept( actualPig -> { assertThatPigsAreEqual( expectedPig, actualPig ); assertEquals( - session.getLockMode( actualPig ), - LockMode.OPTIMISTIC_FORCE_INCREMENT + LockMode.OPTIMISTIC_FORCE_INCREMENT, + session.getLockMode( actualPig ) ); assertEquals( 0, actualPig.version ); } ) @@ -431,12 +435,13 @@ public void reactiveFindWithOptimisticVerifyLock(VertxTestContext context) { .find( GuineaPig.class, expectedPig.getId(), LockMode.OPTIMISTIC ) .thenAccept( actualPig -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( session.getLockMode( actualPig ), LockMode.OPTIMISTIC ); + assertEquals( LockMode.OPTIMISTIC, session.getLockMode( actualPig ) ); assertEquals( 0, actualPig.version ); } ) ) ) .thenCompose( v -> openSession() ) .thenCompose( session -> session.find( GuineaPig.class, expectedPig.getId() ) ) - .thenAccept( actualPig -> assertEquals( 0, actualPig.version ) ) ); + .thenAccept( actualPig -> assertEquals( 0, actualPig.version ) ) + ); } @Test @@ -450,7 +455,7 @@ public void reactiveLockWithOptimisticVerify(VertxTestContext context) { .thenCompose( actualPig -> session.lock( actualPig, LockMode.OPTIMISTIC ) .thenAccept( vv -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( session.getLockMode( actualPig ), LockMode.OPTIMISTIC ); + assertEquals( LockMode.OPTIMISTIC, session.getLockMode( actualPig ) ); assertEquals( 0, actualPig.version ); } ) ) ) ) .thenCompose( v -> openSession() ) @@ -493,7 +498,7 @@ public void reactiveLockWithPessimisticRead(VertxTestContext context) { .thenCompose( actualPig -> session.lock( actualPig, LockMode.PESSIMISTIC_READ ) .thenAccept( vv -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( session.getLockMode( actualPig ), LockMode.PESSIMISTIC_READ ); + assertEquals( LockMode.PESSIMISTIC_READ, session.getLockMode( actualPig ) ); assertEquals( 0, actualPig.version ); } ) ) ) ) .thenCompose( v -> openSession() ) @@ -508,13 +513,13 @@ public void reactiveFindWithPessimisticWrite(VertxTestContext context) { test( context, populateDB() - .thenCompose( v -> getSessionFactory () + .thenCompose( v -> getSessionFactory() .withTransaction( (session, transaction) -> session // does a select ... for update .find( GuineaPig.class, expectedPig.getId(), LockMode.PESSIMISTIC_WRITE ) .thenAccept( actualPig -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( session.getLockMode( actualPig ), LockMode.PESSIMISTIC_WRITE ); + assertEquals( LockMode.PESSIMISTIC_WRITE, session.getLockMode( actualPig ) ); assertEquals( 0, actualPig.version ); } ) ) ) .thenCompose( v -> openSession() ) @@ -536,9 +541,7 @@ public void reactiveLockWithPessimisticWrite(VertxTestContext context) { .thenCompose( actualPig -> session.lock( actualPig, LockMode.PESSIMISTIC_WRITE ) .thenAccept( vv -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( - session.getLockMode( actualPig ), - LockMode.PESSIMISTIC_WRITE ); + assertEquals( LockMode.PESSIMISTIC_WRITE, session.getLockMode( actualPig ) ); assertEquals( 0, actualPig.version ); } ) ) ) ) .thenCompose( v -> openSession() ) @@ -560,9 +563,7 @@ public void reactiveQueryWithLock(VertxTestContext context) { .getSingleResult() .thenAccept( actualPig -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( - session.getLockMode( actualPig ), - LockMode.PESSIMISTIC_WRITE ); + assertEquals( LockMode.PESSIMISTIC_WRITE, session.getLockMode( actualPig ) ); } ) ) ) ); } @@ -570,21 +571,22 @@ public void reactiveQueryWithLock(VertxTestContext context) { @Test public void reactiveQueryWithAliasedLock(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); - test( context, populateDB() - .thenCompose( - v -> getSessionFactory().withTransaction( - (session, tx) -> session.createSelectionQuery( "from GuineaPig pig", GuineaPig.class ) + test( + context, populateDB() + .thenCompose( v -> getSessionFactory() + .withTransaction( session -> session + .createSelectionQuery( "from GuineaPig pig", GuineaPig.class ) .setLockMode( "pig", LockMode.PESSIMISTIC_WRITE ) .getSingleResult() .thenAccept( actualPig -> { assertThatPigsAreEqual( expectedPig, actualPig ); assertEquals( - session.getLockMode( actualPig ), - LockMode.PESSIMISTIC_WRITE + LockMode.PESSIMISTIC_WRITE, + session.getLockMode( actualPig ) ); } ) + ) ) - ) ); } @@ -608,7 +610,7 @@ public void reactivePersistInTx(VertxTestContext context) { context, openSession() .thenCompose( s -> s - .withTransaction( t -> s.persist( new GuineaPig( 10, "Tulip" ) )) + .withTransaction( t -> s.persist( new GuineaPig( 10, "Tulip" ) ) ) .thenCompose( v -> s.close() ) ) .thenCompose( vv -> selectNameFromId( 10 ) ) .thenAccept( selectRes -> assertEquals( "Tulip", selectRes ) ) @@ -623,32 +625,33 @@ public void reactiveRollbackTx(VertxTestContext context) { .thenCompose( s -> s .withTransaction( t -> s .persist( new GuineaPig( 10, "Tulip" ) ) - .thenCompose( v -> s.flush() ) - .thenAccept( v -> { - throw new RuntimeException( "No Panic: This is just a test" ); - } ) - ) - .thenCompose( v -> s.close() ) + .thenCompose( v -> s.flush() ) + .thenAccept( v -> { + throw new RuntimeException( "No Panic: This is just a test" ); + } ) + ) + .thenCompose( v -> s.close() ) ) .handle( (v, e) -> null ) .thenCompose( vv -> selectNameFromId( 10 ) ) - .thenAccept( Assertions::assertNull) + .thenAccept( Assertions::assertNull ) ); } @Test public void reactiveMarkedRollbackTx(VertxTestContext context) { - test( context, openSession() - .thenCompose( s -> s - .withTransaction( t -> s - .persist( new GuineaPig( 10, "Tulip" ) ) - .thenCompose( vv -> s.flush() ) - .thenAccept( vv -> t.markForRollback() ) + test( + context, openSession() + .thenCompose( s -> s + .withTransaction( t -> s + .persist( new GuineaPig( 10, "Tulip" ) ) + .thenCompose( vv -> s.flush() ) + .thenAccept( vv -> t.markForRollback() ) + ) + .thenCompose( v -> s.close() ) ) - .thenCompose( v -> s.close() ) - ) - .thenCompose( vv -> selectNameFromId( 10 ) ) - .thenAccept( Assertions::assertNull ) + .thenCompose( vv -> selectNameFromId( 10 ) ) + .thenAccept( Assertions::assertNull ) ); } @@ -698,7 +701,7 @@ public void reactiveUpdate(VertxTestContext context) { .thenAccept( pig -> { assertNotNull( pig ); // Checking we are actually changing the name - assertNotEquals( pig.getName(), NEW_NAME ); + assertNotEquals( NEW_NAME, pig.getName() ); pig.setName( NEW_NAME ); } ) .thenCompose( v -> session.flush() ) @@ -720,8 +723,8 @@ public void reactiveUpdateVersion(VertxTestContext context) { .thenAccept( pig -> { assertNotNull( pig ); // Checking we are actually changing the name - assertNotEquals( pig.getName(), NEW_NAME ); - assertEquals( pig.version, 0 ); + assertNotEquals( NEW_NAME, pig.getName() ); + assertEquals( 0, pig.version ); pig.setName( NEW_NAME ); pig.version = 10; //ignored by Hibernate } ) @@ -730,22 +733,24 @@ public void reactiveUpdateVersion(VertxTestContext context) { ) .thenCompose( v -> openSession() ) .thenCompose( s -> s.find( GuineaPig.class, 5 ) - .thenAccept( pig -> assertEquals( pig.version, 1 ) ) ) + .thenAccept( pig -> assertEquals( 1, pig.version ) ) ) ); } @Test public void reactiveClose(VertxTestContext context) { - test( context, openSession() - .thenCompose( session -> { - assertTrue( session.isOpen() ); - return session.close() - .thenAccept( v -> assertFalse( session.isOpen() ) ); - } ) + test( + context, openSession() + .thenCompose( session -> { + assertTrue( session.isOpen() ); + return session.close() + .thenAccept( v -> assertFalse( session.isOpen() ) ); + } ) ); } @Test + @Disabled public void testSessionWithNativeAffectedEntities(VertxTestContext context) { GuineaPig pig = new GuineaPig( 3, "Rorshach" ); AffectedEntities affectsPigs = new AffectedEntities( GuineaPig.class ); @@ -753,7 +758,8 @@ public void testSessionWithNativeAffectedEntities(VertxTestContext context) { context, openSession().thenCompose( s -> s .persist( pig ) - .thenCompose( v -> s.createNativeQuery( "select * from pig where name=:n", GuineaPig.class, affectsPigs ) + .thenCompose( v -> s + .createNativeQuery( "select * from pig where name=:n", GuineaPig.class, affectsPigs ) .setParameter( "n", pig.name ) .getResultList() ) .thenAccept( list -> { @@ -769,7 +775,7 @@ public void testSessionWithNativeAffectedEntities(VertxTestContext context) { .thenCompose( v -> s.createNativeQuery( "update pig set name='Y' where name='X'", affectsPigs ).executeUpdate() ) .thenAccept( rows -> assertEquals( 1, rows ) ) .thenCompose( v -> s.refresh( pig ) ) - .thenAccept( v -> assertEquals( pig.name, "Y" ) ) + .thenAccept( v -> assertEquals( "Y", pig.name ) ) .thenAccept( v -> pig.name = "Z" ) .thenCompose( v -> s.createNativeQuery( "delete from pig where name='Z'", affectsPigs ).executeUpdate() ) .thenAccept( rows -> assertEquals( 1, rows ) ) @@ -786,46 +792,54 @@ public void testMetamodel() { assertEquals( "GuineaPig", pig.getName() ); } - @Test void testFactory(VertxTestContext context) { - test( context, getSessionFactory().withSession( session -> { - session.getFactory().getCache().evictAll(); - session.getFactory().getMetamodel().entity(GuineaPig.class); - session.getFactory().getCriteriaBuilder().createQuery(GuineaPig.class); - session.getFactory().getStatistics().isStatisticsEnabled(); - return CompletionStages.voidFuture(); - } ) ); + @Test + void testFactory(VertxTestContext context) { + test( + context, getSessionFactory().withSession( session -> { + session.getFactory().getCache().evictAll(); + session.getFactory().getMetamodel().entity( GuineaPig.class ); + session.getFactory().getCriteriaBuilder().createQuery( GuineaPig.class ); + session.getFactory().getStatistics().isStatisticsEnabled(); + return CompletionStages.voidFuture(); + } ) + ); } @Test public void testTransactionPropagation(VertxTestContext context) { - test( context, getSessionFactory().withTransaction( - (session, transaction) -> session.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList() - .thenCompose( list -> { - assertNotNull( session.currentTransaction() ); - assertFalse( session.currentTransaction().isMarkedForRollback() ); - session.currentTransaction().markForRollback(); - assertTrue( session.currentTransaction().isMarkedForRollback() ); - assertTrue( transaction.isMarkedForRollback() ); - return session.withTransaction( t -> { - assertEquals( t, transaction ); - assertTrue( t.isMarkedForRollback() ); - return session.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList(); - } ); - } ) - ) ); + test( + context, getSessionFactory().withTransaction( + (session, transaction) -> session.createSelectionQuery( "from GuineaPig", GuineaPig.class ) + .getResultList() + .thenCompose( list -> { + assertNotNull( session.currentTransaction() ); + assertFalse( session.currentTransaction().isMarkedForRollback() ); + session.currentTransaction().markForRollback(); + assertTrue( session.currentTransaction().isMarkedForRollback() ); + assertTrue( transaction.isMarkedForRollback() ); + return session.withTransaction( t -> { + assertEquals( t, transaction ); + assertTrue( t.isMarkedForRollback() ); + return session.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList(); + } ); + } ) + ) + ); } @Test public void testSessionPropagation(VertxTestContext context) { - test( context, getSessionFactory().withSession( session -> { - assertFalse( session.isDefaultReadOnly() ); - session.setDefaultReadOnly( true ); - return session.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList() - .thenCompose( list -> getSessionFactory().withSession( s -> { - assertTrue( s.isDefaultReadOnly() ); - return s.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList(); - } ) ); - } ) ); + test( + context, getSessionFactory().withSession( session -> { + assertFalse( session.isDefaultReadOnly() ); + session.setDefaultReadOnly( true ); + return session.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList() + .thenCompose( list -> getSessionFactory().withSession( s -> { + assertTrue( s.isDefaultReadOnly() ); + return s.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList(); + } ) ); + } ) + ); } @Test @@ -838,8 +852,8 @@ public void testDupeException(VertxTestContext context) { .withTransaction( (s, t) -> s.persist( new GuineaPig( 10, "Tulip" ) ) ) ).handle( (i, t) -> { assertNotNull( t ); - assertTrue( t instanceof CompletionException ); - assertTrue( t.getCause() instanceof PersistenceException ); + assertInstanceOf( CompletionException.class, t ); + assertInstanceOf( PersistenceException.class, t.getCause() ); return null; } ) ); @@ -848,43 +862,49 @@ public void testDupeException(VertxTestContext context) { @Test public void testExceptionInWithSession(VertxTestContext context) { final Stage.Session[] savedSession = new Stage.Session[1]; - test( context, getSessionFactory().withSession( session -> { - assertTrue( session.isOpen() ); - savedSession[0] = session; - throw new RuntimeException( "No Panic: This is just a test" ); - } ).handle( (o, t) -> { - assertNotNull( t ); - assertFalse( savedSession[0].isOpen(), "Session should be closed" ); - return null; - } ) ); + test( + context, getSessionFactory().withSession( session -> { + assertTrue( session.isOpen() ); + savedSession[0] = session; + throw new RuntimeException( "No Panic: This is just a test" ); + } ).handle( (o, t) -> { + assertNotNull( t ); + assertFalse( savedSession[0].isOpen(), "Session should be closed" ); + return null; + } ) + ); } @Test public void testExceptionInWithTransaction(VertxTestContext context) { final Stage.Session[] savedSession = new Stage.Session[1]; - test( context, getSessionFactory().withTransaction( (session, tx) -> { - assertTrue( session.isOpen() ); - savedSession[0] = session; - throw new RuntimeException( "No Panic: This is just a test" ); - } ).handle( (o, t) -> { - assertNotNull( t ); - assertFalse( savedSession[0].isOpen(), "Session should be closed" ); - return null; - } ) ); + test( + context, getSessionFactory().withTransaction( (session, tx) -> { + assertTrue( session.isOpen() ); + savedSession[0] = session; + throw new RuntimeException( "No Panic: This is just a test" ); + } ).handle( (o, t) -> { + assertNotNull( t ); + assertFalse( savedSession[0].isOpen(), "Session should be closed" ); + return null; + } ) + ); } @Test public void testExceptionInWithStatelessSession(VertxTestContext context) { final Stage.StatelessSession[] savedSession = new Stage.StatelessSession[1]; - test( context, getSessionFactory().withStatelessSession( session -> { - assertTrue( session.isOpen() ); - savedSession[0] = session; - throw new RuntimeException( "No Panic: This is just a test" ); - } ).handle( (o, t) -> { - assertNotNull( t ); - assertFalse( savedSession[0].isOpen(), "Session should be closed" ); - return null; - } ) ); + test( + context, getSessionFactory().withStatelessSession( session -> { + assertTrue( session.isOpen() ); + savedSession[0] = session; + throw new RuntimeException( "No Panic: This is just a test" ); + } ).handle( (o, t) -> { + assertNotNull( t ); + assertFalse( savedSession[0].isOpen(), "Session should be closed" ); + return null; + } ) + ); } @Test @@ -892,49 +912,91 @@ public void testCreateSelectionQueryMultiple(VertxTestContext context) { final GuineaPig aloiPig = new GuineaPig( 10, "Aloi" ); final GuineaPig bloiPig = new GuineaPig( 11, "Bloi" ); - test( context, openSession() - .thenCompose( s -> s.withTransaction( t -> s.persist( aloiPig, bloiPig ) ) - .thenCompose( v -> openSession() ) - .thenCompose( session -> session.createSelectionQuery( "from GuineaPig", GuineaPig.class ) - .getResultList() - .thenAccept( resultList -> assertThat( resultList ).containsExactlyInAnyOrder( aloiPig, bloiPig ) ) + test( + context, openSession() + .thenCompose( s -> s.withTransaction( t -> s.persist( aloiPig, bloiPig ) ) + .thenCompose( v -> openSession() ) + .thenCompose( session -> session + .createSelectionQuery( "from GuineaPig", GuineaPig.class ) + .getResultList() + .thenAccept( resultList -> assertThat( resultList ).containsExactlyInAnyOrder( aloiPig, bloiPig ) ) ) + .thenCompose( v -> openSession() ) + .thenCompose( session -> session + .createSelectionQuery( "from GuineaPig", GuineaPig.class ) + .getResultList() + .thenAccept( resultList -> assertThat( resultList ).containsExactlyInAnyOrder( aloiPig, bloiPig ) ) ) ) - .thenCompose( v -> openSession() ) - .thenCompose( session -> session - .createSelectionQuery( "from GuineaPig", GuineaPig.class ) - .getResultList() - .thenAccept( resultList -> assertThat( resultList ).containsExactlyInAnyOrder( aloiPig, bloiPig ) ) ) ) ); } @Test public void testCreateSelectionQuerySingle(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 10, "Aloi" ); - test( context, openSession() - .thenCompose( s -> s - .withTransaction( t -> s.persist( new GuineaPig( 10, "Aloi" ) ) ) - .thenCompose( v -> openSession() ) + test( + context, openSession() + .thenCompose( s -> s + .withTransaction( t -> s.persist( new GuineaPig( 10, "Aloi" ) ) ) + .thenCompose( v -> openSession() ) + .thenCompose( session -> session + .createSelectionQuery( "from GuineaPig", GuineaPig.class ) + .getSingleResult() + .thenAccept( actualPig -> assertThatPigsAreEqual( expectedPig, actualPig ) ) ) + .thenCompose( v -> openSession() ) + .thenCompose( session -> session + .createSelectionQuery( "from GuineaPig", GuineaPig.class ) + .getSingleResult() + .thenAccept( actualPig -> assertThatPigsAreEqual( expectedPig, actualPig ) ) ) + ) + ); + } + + @Test + public void testCreateSelectionQueryNull(VertxTestContext context) { + test( + context, openSession() .thenCompose( session -> session.createSelectionQuery( "from GuineaPig", GuineaPig.class ) - .getSingleResult() - .thenAccept( actualPig -> assertThatPigsAreEqual( expectedPig, actualPig ) ) ) + .getSingleResultOrNull() + .thenAccept( Assertions::assertNull ) ) .thenCompose( v -> openSession() ) .thenCompose( session -> session.createSelectionQuery( "from GuineaPig", GuineaPig.class ) - .getSingleResult() - .thenAccept( actualPig -> assertThatPigsAreEqual( expectedPig, actualPig ) ) ) + .getSingleResultOrNull() + .thenAccept( Assertions::assertNull ) ) + ); + } + + @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 testCreateSelectionQueryNull(VertxTestContext context) { - test( context, openSession() - .thenCompose( session -> session.createSelectionQuery( "from GuineaPig", GuineaPig.class ) - .getSingleResultOrNull() - .thenAccept( Assertions::assertNull ) ) - .thenCompose( v -> openSession() ) - .thenCompose( session -> session.createSelectionQuery( "from GuineaPig", GuineaPig.class ) - .getSingleResultOrNull() - .thenAccept( Assertions::assertNull ) ) + 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())) ); } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessProxyUpdateTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessProxyUpdateTest.java index 477996fc8..3927279d7 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessProxyUpdateTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessProxyUpdateTest.java @@ -30,6 +30,7 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.hibernate.reactive.testing.ReactiveAssertions.assertThrown; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -83,26 +84,47 @@ public void testUnfetchedEntityException(VertxTestContext context) { } @Test - public void testLazyInitializationException(VertxTestContext context) { + public void testLazyInitializationExceptionWithMutiny(VertxTestContext context) { Game lol = new Game( "League of Legends" ); GameCharacter ck = new GameCharacter( "Caitlyn Kiramman" ); ck.setGame( lol ); - test( context, assertThrown( LazyInitializationException.class, getMutinySessionFactory() + test( context, getMutinySessionFactory() .withTransaction( s -> s.persistAll( lol, ck ) ) .chain( targetId -> getMutinySessionFactory() .withStatelessSession( session -> session.get( GameCharacter.class, ck.getId() ) ) ) - .call( charFound -> getMutinySessionFactory() - .withStatelessTransaction( s -> { + .call( charFound -> assertThrown( + LazyInitializationException.class, getMutinySessionFactory().withStatelessTransaction( s -> { Game game = charFound.getGame(); // LazyInitializationException here because we haven't fetched the entity game.setGameTitle( "League of Legends V2" ); - context.failNow( "We were expecting a LazyInitializationException" ); - return null; + return Uni.createFrom().voidItem(); } ) + ) ) + ); + } + + @Test + public void testLazyInitializationExceptionWithStage(VertxTestContext context) { + Game lol = new Game( "League of Legends" ); + GameCharacter ck = new GameCharacter( "Caitlyn Kiramman" ); + ck.setGame( lol ); + + test( context, getSessionFactory() + .withTransaction( s -> s.persist( lol, ck ) ) + .thenCompose( targetId -> getSessionFactory() + .withStatelessSession( session -> session.get( GameCharacter.class, ck.getId() ) ) ) - ) ); + .thenCompose( charFound -> assertThrown( + LazyInitializationException.class, getSessionFactory().withStatelessTransaction( s -> { + Game game = charFound.getGame(); + // LazyInitializationException here because we haven't fetched the entity + game.setGameTitle( "League of Legends V2" ); + return voidFuture(); + } ) + ) ) + ); } @Test diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessSessionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessSessionTest.java index c2f6ac31b..b4d2cbf1f 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessSessionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessSessionTest.java @@ -135,6 +135,23 @@ public void testStatelessSessionWithNative(VertxTestContext context) { ); } + @Test + public void testStatelessSessionGetMultiple(VertxTestContext context) { + GuineaPig a = new GuineaPig("A"); + GuineaPig b = new GuineaPig("B"); + GuineaPig c = new GuineaPig("C"); + test( context, getSessionFactory().openStatelessSession() + .thenCompose( ss -> ss.insertMultiple( List.of(a, b, c) ) + .thenCompose( v -> ss.get( GuineaPig.class, a.id, c.id ) ) + .thenAccept( list -> { + assertEquals( 2, list.size() ); + assertThatPigsAreEqual( a, list.get( 0 ) ); + assertThatPigsAreEqual( c, list.get( 1 ) ); + }) + .thenCompose( v -> ss.close() ) ) + ); + } + @Test public void testStatelessSessionCriteria(VertxTestContext context) { GuineaPig pig = new GuineaPig( "Aloi" ); 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 new file mode 100644 index 000000000..38e386bf2 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessWithBatchTest.java @@ -0,0 +1,483 @@ +/* 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.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Configuration; +import org.hibernate.reactive.annotations.EnabledFor; +import org.hibernate.reactive.testing.SqlStatementTracker; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.smallrye.mutiny.Uni; +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletionStage; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.POSTGRESQL; + +/** + * Test the stateless session actually execute the operations in batch. + */ +@Timeout(value = 10, timeUnit = MINUTES) +public class ReactiveStatelessWithBatchTest extends BaseReactiveTest { + private static SqlStatementTracker sqlTracker; + + private static final Object[] PIGS = { + new GuineaPig( 11, "One" ), + new GuineaPig( 22, "Two" ), + new GuineaPig( 33, "Three" ), + new GuineaPig( 44, "Four" ), + new GuineaPig( 55, "Five" ), + new GuineaPig( 66, "Six" ) + }; + + private static final Object[] PIGS_AFTER_DELETE = List.of( PIGS ) + .subList( 2, PIGS.length ) + .toArray(); + + private static final Object[] PIGS_AFTER_UPDATE = { + new GuineaPig( 11, "One updated" ), + new GuineaPig( 22, "Two updated" ), + new GuineaPig( 33, "Three" ), + new GuineaPig( 44, "Four" ), + new GuineaPig( 55, "Five" ), + new GuineaPig( 66, "Six" ) + }; + + @Override + protected Set> annotatedEntities() { + return Set.of( GuineaPig.class ); + } + + @Override + protected Configuration constructConfiguration() { + Configuration configuration = super.constructConfiguration(); + + // Construct a tracker that collects query statements via the SqlStatementLogger framework. + // Pass in configuration properties to hand off any actual logging properties + sqlTracker = new SqlStatementTracker( + ReactiveStatelessWithBatchTest::filter, + configuration.getProperties() + ); + return configuration; + } + + @BeforeEach + public void clearTracker() { + sqlTracker.clear(); + } + + @Override + protected void addServices(StandardServiceRegistryBuilder builder) { + sqlTracker.registerService( builder ); + } + + private static boolean filter(String s) { + String[] accepted = { "merge ", "insert ", "update ", "delete " }; + for ( String valid : accepted ) { + if ( s.toLowerCase().startsWith( valid ) ) { + return true; + } + } + return false; + } + + @Test + @EnabledFor(POSTGRESQL) + public void testMutinyMergeUpsertAll(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.upsertAll( PIGS ) ) + .invoke( () -> assertSqlLogTracker( "merge into pig as t using (.*)" ) ) + .chain( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS ) ) ) + ); + } + + @Test + @EnabledFor(POSTGRESQL) + public void testMutinyMergeUpsertAllWithBatchSize(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.upsertAll( 10, PIGS ) ) + .invoke( () -> assertSqlLogTracker( "merge into pig as t using (.*)" ) ) + .chain( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS ) ) ) + ); + } + + @Test + @EnabledFor(POSTGRESQL) + public void testMutinyMergeUpsertMultiple(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.upsertMultiple( List.of( PIGS ) ) ) + .invoke( () -> assertSqlLogTracker( "merge into pig as t using (.*)" ) ) + .chain( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS ) ) ) + ); + } + + @Test + @EnabledFor(POSTGRESQL) + public void testStageMergeUpsertAll(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.upsertAll( PIGS ) ) + .thenRun( () -> assertSqlLogTracker( "merge into pig as t using (.*)" ) ) + .thenCompose( v -> assertExpectedResult( PIGS ) ) + ); + } + + @Test + @EnabledFor(POSTGRESQL) + public void testStageMergeUpsertAllWithBatchSize(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.upsertAll( 10, PIGS ) ) + .thenRun(() -> assertSqlLogTracker( "merge into pig as t using (.*)" ) ) + .thenCompose( v -> assertExpectedResult( PIGS ) ) + ); + } + + @Test + @EnabledFor(POSTGRESQL) + public void testStageMergeUpsertMultiple(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.upsertMultiple( List.of( PIGS ) ) ) + .thenRun( () -> assertSqlLogTracker( "merge into pig as t using (.*)" ) ) + .thenCompose( v -> assertExpectedResult( PIGS ) ) + ); + } + + @Test + public void testMutinyBatchingInsert(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) + .invoke( () -> assertSqlLogTracker( "insert into pig \\(name,id\\) values (.*)" ) ) + .chain( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS ) ) ) + ); + } + + @Test + public void testMutinyBatchingInsertMultiple(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertMultiple( List.of( PIGS ) ) ) + .invoke( () -> assertSqlLogTracker( "insert into pig \\(name,id\\) values (.*)" ) ) + .chain( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS ) ) ) + ); + } + + @Test + public void testMutinyBatchingInsertAllNoBatchSizeParameter(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( PIGS ) ) + .invoke( () -> assertSqlLogTracker( "insert into pig \\(name,id\\) values (.*)" ) ) + .chain( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS ) ) ) + ); + } + + @Test + public void testStageBatchingInsert(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenAccept( v -> assertSqlLogTracker( "insert into pig \\(name,id\\) values (.*)" ) ) + .thenCompose( v -> assertExpectedResult( PIGS ) ) + ); + } + + @Test + public void testStageBatchingInsertMultiple(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insertMultiple( List.of( PIGS ) ) ) + .thenAccept( v -> assertSqlLogTracker( "insert into pig \\(name,id\\) values (.*)" ) ) + .thenCompose( v -> assertExpectedResult( PIGS ) ) + ); + } + + @Test + public void testStageBatchingInsertNoBatchSizeParameter(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( PIGS ) ) + .thenAccept( v -> assertSqlLogTracker( "insert into pig \\(name,id\\) values (.*)" ) ) + .thenCompose( v -> assertExpectedResult( PIGS ) ) + ); + } + + @Test + public void testMutinyBatchingDelete(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p", GuineaPig.class ).getResultList() + ) ) + .chain( pigs -> getMutinySessionFactory().withStatelessTransaction( s -> s + .deleteAll( 10, pigs.subList( 0, 2 ).toArray() ) + ) ) + .invoke( () -> assertSqlLogTracker( "delete from pig where id=.*" ) ) + .call( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS_AFTER_DELETE ) ) ) + ); + } + + @Test + public void testMutinyBatchingDeleteMultiple(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p", GuineaPig.class ).getResultList() + ) ) + .chain( pigs -> getMutinySessionFactory().withStatelessTransaction( s -> s + .deleteMultiple( pigs.subList( 0, 2 ) ) ) + ) + .invoke( () -> assertSqlLogTracker( "delete from pig where id=.*" ) ) + .call( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS_AFTER_DELETE ) ) ) + ); + } + + @Test + public void testMutinyBatchingDeleteAllNoBatchSizeParameter(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( PIGS ) ) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p", GuineaPig.class ).getResultList() + ) ) + .chain( pigs -> getMutinySessionFactory().withStatelessTransaction( s -> s + .deleteAll( pigs.subList( 0, 2 ).toArray() ) + ) ) + .invoke( () -> assertSqlLogTracker( "delete from pig where id=.*" ) ) + .call( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS_AFTER_DELETE ) ) ) + ); + } + + @Test + public void testStageBatchingDelete(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenRun( sqlTracker::clear ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p", GuineaPig.class ).getResultList() + .thenCompose( pigs -> s.delete( 10, pigs.subList( 0, 2 ).toArray() ) ) + ) ) + .thenAccept( v -> assertSqlLogTracker( "delete from pig where id=.*" ) ) + .thenCompose( v -> assertExpectedResult( PIGS_AFTER_DELETE ) ) + ); + } + + @Test + public void testStageBatchingDeleteMultiple(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenRun( sqlTracker::clear ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p", GuineaPig.class ).getResultList() + .thenCompose( pigs -> s.deleteMultiple( pigs.subList( 0, 2 ) ) ) + ) ) + .thenAccept( v -> assertSqlLogTracker( "delete from pig where id=.*" ) ) + .thenCompose( v -> assertExpectedResult( PIGS_AFTER_DELETE ) ) + ); + } + + @Test + public void testStageBatchingDeleteNoBatchSizeParameter(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenRun( sqlTracker::clear ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p", GuineaPig.class ).getResultList() + .thenCompose( pigs -> s.delete( pigs.subList( 0, 2 ).toArray() ) ) + ) ) + .thenAccept( v -> assertSqlLogTracker( "delete from pig where id=.*" ) ) + .thenCompose( v -> assertExpectedResult( PIGS_AFTER_DELETE ) ) + ); + } + + @Test + public void testMutinyBatchingUpdate(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .chain( pigs -> { + pigs.get( 0 ).setName( "One updated" ); + pigs.get( 1 ).setName( "Two updated" ); + return s.updateAll( 10, pigs.toArray() ); + } ) + ) ) + .invoke( () -> assertSqlLogTracker( "update pig set name=.* where id=.*" ) ) + .call( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS_AFTER_UPDATE ) ) ) + ); + } + + @Test + public void testMutinyBatchingUpdateMultiple(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .chain( pigs -> { + pigs.get( 0 ).setName( "One updated" ); + pigs.get( 1 ).setName( "Two updated" ); + return s.updateMultiple( pigs.subList( 0, 2 ) ); + } ) ) + ) + .invoke( () -> assertSqlLogTracker( "update pig set name=.* where id=.*" ) ) + .call( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS_AFTER_UPDATE ) ) ) + ); + } + + @Test + public void testMutinyBatchingUpdateAllNoBatchSizeParameter(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insertAll( 10, PIGS ) ) + .invoke( sqlTracker::clear ) + .chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s + .createQuery( "select p from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .chain( pigs -> { + pigs.get( 0 ).setName( "One updated" ); + pigs.get( 1 ).setName( "Two updated" ); + return s.updateAll( pigs.toArray() ); + } ) ) + ) + .invoke( () -> assertSqlLogTracker( "update pig set name=.* where id=.*" ) ) + .call( () -> Uni.createFrom().completionStage( assertExpectedResult( PIGS_AFTER_UPDATE ) ) ) + ); + } + + @Test + public void testStageBatchingUpdate(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenAccept( v -> sqlTracker.clear() ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .thenCompose( pigs -> { + pigs.get( 0 ).setName( "One updated" ); + pigs.get( 1 ).setName( "Two updated" ); + return s.update( 10, pigs.toArray() ); + } ) + ) ) + .thenAccept( v -> assertSqlLogTracker( "update pig set name=.* where id=.*" ) ) + .thenCompose( v -> assertExpectedResult( PIGS_AFTER_UPDATE ) ) + ); + } + + @Test + public void testStageBatchingUpdateMultiple(VertxTestContext context) { + test( context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenRun( sqlTracker::clear ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .thenCompose( pigs -> { + pigs.get( 0 ).setName( "One updated" ); + pigs.get( 1 ).setName( "Two updated" ); + return s.updateMultiple( pigs ); + } ) + ) ) + .thenAccept( v -> assertSqlLogTracker( "update pig set name=.* where id=.*" ) ) + .thenCompose( v -> assertExpectedResult( PIGS_AFTER_UPDATE ) ) + ); + } + + @Test + public void testStageBatchingUpdateNoBatchSizeParameter(VertxTestContext context) { + test(context, getSessionFactory() + .withStatelessTransaction( s -> s.insert( 10, PIGS ) ) + .thenRun( sqlTracker::clear ) + .thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p order by p.id", GuineaPig.class ) + .getResultList() + .thenCompose( pigs -> { + pigs.get( 0 ).setName( "One updated" ); + pigs.get( 1 ).setName( "Two updated" ); + return s.update( pigs.get( 0 ), pigs.get( 1 ) ); + } ) + ) ) + .thenAccept( v -> assertSqlLogTracker( "update pig set name=.* where id=.*" ) ) + .thenCompose( v -> assertExpectedResult( PIGS_AFTER_UPDATE ) ) + ); + } + + private CompletionStage assertExpectedResult(Object[] expected) { + return getSessionFactory().withStatelessTransaction( s -> s + .createQuery( "from GuineaPig p order by id", Object.class ) + .getResultList() + .thenAccept( pigs -> assertThat( pigs ).containsExactly( expected ) ) ); + } + + private static void assertSqlLogTracker(String queryRegex) { + // We expect only one query for each batched operations + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so the regex must keep that in consideration + assertThat( sqlTracker.getLoggedQueries() ).allMatch( s -> s.matches( queryRegex ) ); + } + + @Entity(name = "GuineaPig") + @Table(name = "pig") + public static class GuineaPig { + @Id + private Integer id; + private String name; + + public GuineaPig() { + } + + public GuineaPig(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return id + ": " + name; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + GuineaPig guineaPig = (GuineaPig) o; + return Objects.equals( name, guineaPig.name ); + } + + @Override + public int hashCode() { + return Objects.hash( name ); + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SoftDeleteTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SoftDeleteTest.java index 4ba2e844e..f77d86f61 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SoftDeleteTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SoftDeleteTest.java @@ -172,7 +172,7 @@ private void testSoftDelete( } ) ) // Delete an entity - .call( deleteEntity::get ) + .call( deleteEntity ) // Test select all .call( () -> getMutinySessionFactory().withTransaction( s -> s .createSelectionQuery( "from " + entityClass.getSimpleName() + " order by id", Object.class ) 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/TableGeneratorTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/TableGeneratorTest.java index cf0f9be7d..19eed09ec 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/TableGeneratorTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/TableGeneratorTest.java @@ -34,36 +34,33 @@ protected Collection> annotatedEntities() { @Test public void testTableGenerator(VertxTestContext context) { - TableId b = new TableId(); b.string = "Hello World"; - test( - context, - openSession() - .thenCompose( s -> s.persist( b ).thenCompose( v -> s.flush() ) ) - .thenCompose( v -> openSession() ) - .thenCompose( s2 -> - s2.find( TableId.class, b.getId() ) - .thenAccept( bb -> { - assertNotNull( bb ); - assertEquals( bb.id, 6 ); - assertEquals( bb.string, b.string ); - assertEquals( bb.version, 0 ); - - bb.string = "Goodbye"; - } ) - .thenCompose( vv -> s2.flush() ) - .thenCompose( vv -> s2.find( TableId.class, b.getId() ) ) - .thenAccept( bt -> { - assertEquals( bt.version, 1 ); - } ) ) - .thenCompose( v -> openSession() ) - .thenCompose( s3 -> s3.find( TableId.class, b.getId() ) ) + test( context, openSession() + .thenCompose( s -> s.persist( b ).thenCompose( v -> s.flush() ) ) + .thenCompose( v -> openSession() ) + .thenCompose( s2 -> s2 + .find( TableId.class, b.getId() ) .thenAccept( bb -> { - assertEquals( bb.version, 1 ); - assertEquals( bb.string, "Goodbye" ); + assertNotNull( bb ); + assertEquals( 6, bb.id ); + assertEquals( bb.string, b.string ); + assertEquals( 0, bb.version ); + + bb.string = "Goodbye"; } ) + .thenCompose( vv -> s2.flush() ) + .thenCompose( vv -> s2.find( TableId.class, b.getId() ) ) + .thenAccept( bt -> { + assertEquals( 1, bt.version ); + } ) ) + .thenCompose( v -> openSession() ) + .thenCompose( s3 -> s3.find( TableId.class, b.getId() ) ) + .thenAccept( bb -> { + assertEquals( 1, bb.version ); + assertEquals( "Goodbye", bb.string ); + } ) ); } 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 103723127..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.0" ) ) + 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 ); @@ -51,7 +51,6 @@ private String address() { // Calling start() will start the container (if not already started) // It is required to call start() before obtaining the JDBC URL because it will contain a randomized port cockroachDb.start(); - enableTemporaryTables(); return disableSslMode( cockroachDb.getJdbcUrl() ); } @@ -62,13 +61,6 @@ private static String disableSslMode(String url) { return url + "?sslmode=disable"; } - /** - * Temporary tables support is experimental but we need it when updating entities in a hierarchy - */ - private static void enableTemporaryTables() { - runSql( "SET CLUSTER SETTING sql.defaults.experimental_temporary_tables.enabled = 'true';" ); - } - private static void runSql(String command) { Container.ExecResult execResult; try { diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java index b7ac63e4d..dfeeaf15c 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java @@ -73,11 +73,11 @@ class DB2Database implements TestableDatabase { expectedDBTypeForClass.put( Character.class, "CHARACTER" ); expectedDBTypeForClass.put( char.class, "CHARACTER" ); expectedDBTypeForClass.put( String.class, "VARCHAR" ); - expectedDBTypeForClass.put( String[].class, "VARBINARY" ); - expectedDBTypeForClass.put( Long[].class, "VARBINARY" ); - expectedDBTypeForClass.put( BigDecimal[].class, "VARBINARY" ); - expectedDBTypeForClass.put( BigInteger[].class, "VARBINARY" ); - expectedDBTypeForClass.put( Boolean[].class, "VARBINARY" ); + expectedDBTypeForClass.put( String[].class, "XML" ); + expectedDBTypeForClass.put( Long[].class, "XML" ); + expectedDBTypeForClass.put( BigDecimal[].class, "XML" ); + expectedDBTypeForClass.put( BigInteger[].class, "XML" ); + expectedDBTypeForClass.put( Boolean[].class, "XML" ); }} /** 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/MSSQLServerDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MSSQLServerDatabase.java index 8078d1b7e..aeb1b8fb0 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MSSQLServerDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MSSQLServerDatabase.java @@ -77,16 +77,16 @@ class MSSQLServerDatabase implements TestableDatabase { expectedDBTypeForClass.put( BigDecimal.class, "numeric" ); expectedDBTypeForClass.put( Serializable.class, "varbinary" ); expectedDBTypeForClass.put( UUID.class, "binary" ); - expectedDBTypeForClass.put( Instant.class, "datetime2" ); + expectedDBTypeForClass.put( Instant.class, "datetimeoffset" ); expectedDBTypeForClass.put( Duration.class, "bigint" ); expectedDBTypeForClass.put( Character.class, "char" ); expectedDBTypeForClass.put( char.class, "char" ); expectedDBTypeForClass.put( String.class, "varchar" ); - expectedDBTypeForClass.put( String[].class, "varbinary" ); - expectedDBTypeForClass.put( Long[].class, "varbinary" ); - expectedDBTypeForClass.put( BigDecimal[].class, "varbinary" ); - expectedDBTypeForClass.put( BigInteger[].class, "varbinary" ); - expectedDBTypeForClass.put( Boolean[].class, "varbinary" ); + expectedDBTypeForClass.put( String[].class, "xml" ); + expectedDBTypeForClass.put( Long[].class, "xml" ); + expectedDBTypeForClass.put( BigDecimal[].class, "xml" ); + expectedDBTypeForClass.put( BigInteger[].class, "xml" ); + expectedDBTypeForClass.put( Boolean[].class, "xml" ); }} /** diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java index 575c5290d..16a5f97ff 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java @@ -6,12 +6,27 @@ package org.hibernate.reactive.containers; -import static org.hibernate.reactive.containers.DockerImage.imageName; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; import org.testcontainers.containers.MariaDBContainer; +import static org.hibernate.reactive.containers.DockerImage.imageName; + class MariaDatabase extends MySQLDatabase { + private static final Map, String> expectedDBTypeForClass = new HashMap<>(); + + static {{ + expectedDBTypeForClass.putAll( MySQLDatabase.expectedDBTypeForClass ); + + // Even if the column is created using `json`, the client will return `longtext` as the type. + expectedDBTypeForClass.put( BigDecimal[].class, "longtext" ); + expectedDBTypeForClass.put( BigInteger[].class, "longtext" ); + }} + static MariaDatabase INSTANCE = new MariaDatabase(); /** @@ -21,7 +36,7 @@ class MariaDatabase extends MySQLDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final MariaDBContainer maria = new MariaDBContainer<>( imageName( "mariadb", "11.4.2" ) ) + public static final MariaDBContainer maria = new MariaDBContainer<>( imageName( "mariadb", "11.7.2" ) ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) @@ -31,6 +46,11 @@ private String getRegularJdbcUrl() { return "jdbc:mariadb://localhost:3306/" + maria.getDatabaseName(); } + @Override + public String getExpectedNativeDatatype(Class dataType) { + return expectedDBTypeForClass.get( dataType ); + } + @Override public String getJdbcUrl() { return buildJdbcUrlWithCredentials( address() ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java index 9b94cf8ed..e0aa9ef3a 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java @@ -34,15 +34,15 @@ class MySQLDatabase implements TestableDatabase { static MySQLDatabase INSTANCE = new MySQLDatabase(); - private static Map, String> expectedDBTypeForClass = new HashMap<>(); + protected static Map, String> expectedDBTypeForClass = new HashMap<>(); static {{ expectedDBTypeForClass.put( boolean.class, "bit" ); expectedDBTypeForClass.put( Boolean.class, "bit" ); expectedDBTypeForClass.put( NumericBooleanConverter.class, "int" ); - expectedDBTypeForClass.put( YesNoConverter.class, "char" ); - expectedDBTypeForClass.put( TrueFalseConverter.class, "char" ); + expectedDBTypeForClass.put( YesNoConverter.class, "varchar" ); + expectedDBTypeForClass.put( TrueFalseConverter.class, "varchar" ); expectedDBTypeForClass.put( byte[].class, "varbinary" ); // expectedDBTypeForClass.put( TextType.class, "text" ); @@ -75,9 +75,9 @@ class MySQLDatabase implements TestableDatabase { expectedDBTypeForClass.put( String.class, "varchar" ); expectedDBTypeForClass.put( String[].class, "varchar" ); expectedDBTypeForClass.put( Long[].class, "varbinary" ); - expectedDBTypeForClass.put( BigDecimal[].class, "varbinary" ); - expectedDBTypeForClass.put( BigInteger[].class, "varbinary" ); expectedDBTypeForClass.put( Boolean[].class, "varbinary" ); + expectedDBTypeForClass.put( BigDecimal[].class, "json" ); + expectedDBTypeForClass.put( BigInteger[].class, "json" ); }}; /** @@ -87,7 +87,7 @@ class MySQLDatabase implements TestableDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final MySQLContainer mysql = new MySQLContainer<>( imageName( "mysql", "8.4.0") ) + public static final MySQLContainer mysql = new MySQLContainer<>( imageName( "mysql", "9.2.0") ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/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 9ad2f8136..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,25 +57,25 @@ 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" ); expectedDBTypeForClass.put( TimeZone.class, "VARCHAR2" ); expectedDBTypeForClass.put( Date.class, "DATE" ); - expectedDBTypeForClass.put( Timestamp.class, "TIMESTAMP(6)" ); - expectedDBTypeForClass.put( Time.class, "TIMESTAMP(6)" ); + expectedDBTypeForClass.put( Timestamp.class, "TIMESTAMP(9)" ); + expectedDBTypeForClass.put( Time.class, "TIMESTAMP(0)" ); expectedDBTypeForClass.put( LocalDate.class, "DATE" ); expectedDBTypeForClass.put( LocalTime.class, "DATE" ); - expectedDBTypeForClass.put( LocalDateTime.class, "TIMESTAMP(6)" ); + expectedDBTypeForClass.put( LocalDateTime.class, "TIMESTAMP(9)" ); expectedDBTypeForClass.put( BigInteger.class, "NUMBER" ); expectedDBTypeForClass.put( BigDecimal.class, "NUMBER" ); expectedDBTypeForClass.put( Serializable.class, "RAW" ); expectedDBTypeForClass.put( UUID.class, "RAW" ); - expectedDBTypeForClass.put( Instant.class, "TIMESTAMP(6)" ); + expectedDBTypeForClass.put( Instant.class, "TIMESTAMP(9) WITH TIME ZONE" ); expectedDBTypeForClass.put( Duration.class, "NUMBER" ); expectedDBTypeForClass.put( Character.class, "CHAR" ); expectedDBTypeForClass.put( char.class, "CHAR" ); @@ -83,7 +83,7 @@ class OracleDatabase implements TestableDatabase { expectedDBTypeForClass.put( String[].class, "STRINGARRAY" ); expectedDBTypeForClass.put( Long[].class, "LONGARRAY" ); expectedDBTypeForClass.put( BigDecimal[].class, "BIGDECIMALARRAY" ); - expectedDBTypeForClass.put( BigInteger[].class, "BIGINTEGERARRAY" ); + expectedDBTypeForClass.put( BigInteger[].class, "BIGINTEGERBIGDECIMALARRAY" ); expectedDBTypeForClass.put( Boolean[].class, "BOOLEANARRAY" ); } } 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 038733e7e..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 @@ -68,7 +68,7 @@ class PostgreSQLDatabase implements TestableDatabase { expectedDBTypeForClass.put( BigDecimal.class, "numeric" ); expectedDBTypeForClass.put( Serializable.class, "bytea" ); expectedDBTypeForClass.put( UUID.class, "binary" ); - expectedDBTypeForClass.put( Instant.class, "timestamp without time zone" ); + expectedDBTypeForClass.put( Instant.class, "timestamp with time zone" ); expectedDBTypeForClass.put( Duration.class, "bigint" ); expectedDBTypeForClass.put( Character.class, "character" ); expectedDBTypeForClass.put( char.class, "character" ); @@ -87,7 +87,7 @@ class PostgreSQLDatabase implements TestableDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final PostgreSQLContainer postgresql = new PostgreSQLContainer<>( imageName( "postgres", "16.3" ) ) + public static final PostgreSQLContainer postgresql = new PostgreSQLContainer<>( imageName( "postgres", "17.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/dynamic/DynamicEntityTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/dynamic/DynamicEntityTest.java index bbdad20cc..2b4050657 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/dynamic/DynamicEntityTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/dynamic/DynamicEntityTest.java @@ -10,17 +10,22 @@ import java.util.List; import java.util.Map; +import org.hibernate.cfg.Configuration; import org.hibernate.reactive.BaseReactiveTest; -import org.hibernate.tuple.DynamicMapInstantiator; import org.junit.jupiter.api.Test; import io.vertx.junit5.Timeout; import io.vertx.junit5.VertxTestContext; +import static java.util.Map.entry; import static java.util.concurrent.TimeUnit.MINUTES; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.metamodel.internal.AbstractDynamicMapInstantiator.TYPE_KEY; +/** + * Copied from Hibernate ORM: org.hibernate.orm.test.mapping.dynamic.DynamicEntityTest + */ @Timeout(value = 10, timeUnit = MINUTES) public class DynamicEntityTest extends BaseReactiveTest { @@ -30,22 +35,29 @@ protected Collection mappings() { return List.of( "org/hibernate/reactive/dynamic/Book.hbm.xml" ); } + @Override + protected Configuration constructConfiguration() { + return super.constructConfiguration() + .setProperty( "hibernate.default_entity_mode", "dynamic-map" ); + } + @Test public void test(VertxTestContext context) { Map book = new HashMap<>(); book.put( "ISBN", "9781932394153" ); book.put( "title", "Hibernate in Action" ); book.put( "author", "Christian Bauer and Gavin King" ); - book.put( DynamicMapInstantiator.KEY, "Book" ); - - test( - context, - getMutinySessionFactory() - .withTransaction( session -> session.persist( book ) ) - .chain( v -> getMutinySessionFactory() - .withSession( session -> session.createSelectionQuery( "from Book", Map.class ).getSingleResult() ) - .invoke( map -> assertEquals( "Christian Bauer and Gavin King", map.get( "author" ) ) ) ) + + test( context, getMutinySessionFactory() + .withTransaction( session -> session.persist( "Book", book ) ) + .chain( v -> getMutinySessionFactory() + .withSession( session -> session.createSelectionQuery( "from Book", Map.class ).getSingleResult() ) + .invoke( map -> assertThat( map.entrySet() ).containsExactlyInAnyOrder( + entry( "author", "Christian Bauer and Gavin King" ), + entry( "ISBN", "9781932394153" ), + entry( "title", "Hibernate in Action" ), + entry( TYPE_KEY, "Book" ) + ) ) ) ); } - } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/ColumnTypesMappingTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/ColumnTypesMappingTest.java index c57621608..892db5e1e 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/ColumnTypesMappingTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/ColumnTypesMappingTest.java @@ -13,12 +13,14 @@ import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.TimeZone; +import java.util.concurrent.CompletionStage; import org.hibernate.reactive.BaseReactiveTest; @@ -36,12 +38,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.reactive.containers.DatabaseConfiguration.expectedDatatype; import static org.hibernate.reactive.containers.DatabaseConfiguration.getDatatypeQuery; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** * Check that each property is mapped as the expected type in the database. */ @Timeout(value = 10, timeUnit = MINUTES) - public class ColumnTypesMappingTest extends BaseReactiveTest { @Override @@ -49,12 +51,21 @@ protected Collection> annotatedEntities() { return List.of( BasicTypesTestEntity.class ); } + @Override + public CompletionStage deleteEntities(Class... entities) { + // Skip delete step. + // We don't insert any value, so there's nothing to delete + // Avoid having extra stuff in the log that's not relevant to the test + return voidFuture(); + } + private void testDatatype(VertxTestContext context, String columnName, Class type) { - test( context, openSession() - .thenCompose( s -> s + test( context, getSessionFactory() + .withTransaction( s -> s .createNativeQuery( getDatatypeQuery( BasicTypesTestEntity.TABLE_NAME, columnName ), String.class ) .getSingleResult() - .thenAccept( typeOnTheDb -> assertThat( toString( typeOnTheDb ) ).isEqualTo( expectedDatatype( type ) ) ) ) + .thenAccept( typeOnTheDb -> assertThat( toString( typeOnTheDb ) ).isEqualTo( expectedDatatype( type ) ) ) + ) ); } @@ -75,6 +86,16 @@ public void testBigDecimal(VertxTestContext context) { testDatatype( context, "bigDecimal", BigDecimal.class ); } + @Test + public void testBigDecimalArray(VertxTestContext context) { + testDatatype( context, "bigDecimalArray", BigDecimal[].class ); + } + + @Test + public void testBigIntegerArray(VertxTestContext context) { + testDatatype( context, "bigIntegerArray", BigInteger[].class ); + } + @Test public void testStringType(VertxTestContext context) { testDatatype( context, "aString", String.class ); @@ -215,4 +236,9 @@ public void testLocalDateTimeType(VertxTestContext context) { public void testSerializableType(VertxTestContext context) { testDatatype( context, "serializable", Serializable.class ); } + + @Test + public void testInstantType(VertxTestContext context) { + testDatatype( context, "instant", Instant.class ); + } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateTest.java new file mode 100644 index 000000000..febcda93b --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateTest.java @@ -0,0 +1,146 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.schema; + +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.hibernate.HibernateException; +import org.hibernate.cfg.Configuration; +import org.hibernate.reactive.BaseReactiveTest; +import org.hibernate.reactive.annotations.DisabledFor; +import org.hibernate.reactive.provider.Settings; +import org.hibernate.reactive.stage.Stage; + +import org.junit.jupiter.api.AfterEach; +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 static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.cfg.SchemaToolingSettings.HBM2DDL_JDBC_METADATA_EXTRACTOR_STRATEGY; +import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; +import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.SQLSERVER; +import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType; +import static org.hibernate.reactive.testing.ReactiveAssertions.assertThrown; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; +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; + +/** + * Schema update will run different queries when the table already exists or + * when columns are missing. + */ +@DisabledFor(value = DB2, reason = "No InformationExtractor for Dialect [org.hibernate.dialect.DB2Dialect..]") +public class SchemaUpdateTest extends BaseReactiveTest { + + static Stream settings() { + return Stream.of( + arguments( INDIVIDUALLY.toString(), null ), + arguments( GROUPED.toString(), null ), + arguments( INDIVIDUALLY.toString(), "VARBINARY" ), + arguments( GROUPED.toString(), "VARBINARY" ) + ); + } + + @Override + public CompletionStage deleteEntities(Class... entities) { + return voidFuture(); + } + + protected Configuration constructConfiguration(String action, String strategy, String type) { + Configuration configuration = super.constructConfiguration(); + configuration.setProperty( Settings.HBM2DDL_AUTO, action ); + configuration.setProperty( HBM2DDL_JDBC_METADATA_EXTRACTOR_STRATEGY, strategy ); + configuration.addAnnotatedClass( BasicTypesTestEntity.class ); + if ( type != null ) { + // The entity we are using for testing has some arrays. The default behaviour is to store them as XML and + // the Vert.x client doesn't support it at the moment. + configuration.setProperty( "hibernate.type.preferred_array_jdbc_type", "VARBINARY" ); + } + return configuration; + } + + @Override + public void before(VertxTestContext context) { + // Do nothing, we prepare everything in the test so that we can use a parameterized test + } + + @AfterEach + @Override + public void after(VertxTestContext context) { + super.after( context ); + closeFactory( context ); + } + + /** + * Test creation of missing columns + */ + @ParameterizedTest + @MethodSource("settings") + @Timeout(value = 10, timeUnit = MINUTES) + public void testMissingColumnsCreation(final String strategy, final String type, VertxTestContext context) { + final Supplier> testSupplier = () -> setupSessionFactory( constructConfiguration( "drop", strategy, type ) ) + .thenCompose( v -> getSessionFactory().withTransaction( SchemaUpdateTest::createTable ) ) + .whenComplete( (u, throwable) -> factoryManager.stop() ) + .thenCompose( vv -> setupSessionFactory( constructConfiguration( "update", strategy, type ) ) ) + .thenCompose( u -> getSessionFactory().withSession( SchemaUpdateTest::checkAllColumnsExist ) ); + if ( dbType() == SQLSERVER && type == null ) { + test( context, assertThrown( HibernateException.class, testSupplier.get() ) + .thenAccept( e -> assertThat( e.getMessage() ).startsWith( "HR000081: " ) ) ); + } + else { + test( context, testSupplier.get() ); + } + } + + /** + * Test creation of missing table + */ + @ParameterizedTest + @MethodSource("settings") + @Timeout(value = 10, timeUnit = MINUTES) + public void testWholeTableCreation(final String strategy, final String type, VertxTestContext context) { + final Supplier> testSupplier = () -> setupSessionFactory( constructConfiguration( "drop", strategy, type ) ) + .whenComplete( (u, throwable) -> factoryManager.stop() ) + .thenCompose( v -> setupSessionFactory( constructConfiguration( "update", strategy, type ) ) + .thenCompose( vv -> getSessionFactory().withSession( SchemaUpdateTest::checkAllColumnsExist ) ) ); + if ( dbType() == SQLSERVER && type == null ) { + test( context, assertThrown( HibernateException.class, testSupplier.get() ) + .thenAccept( e -> assertThat( e.getMessage() ).startsWith( "HR000081: " ) ) ); + } + else { + test( context, testSupplier.get() ); + } + } + + // I don't think it's possible to create a table without columns, so we add + // a column that's not mapped by the entity. + // We expect the other columns to be created during the update schema phase. + private static CompletionStage createTable(Stage.Session session, Stage.Transaction transaction) { + return session + .createNativeQuery( "create table " + BasicTypesTestEntity.TABLE_NAME + " (unmapped_column " + columnType() + ")" ) + .executeUpdate(); + } + + private static String columnType() { + return dbType() == SQLSERVER ? "int" : "integer"; + } + + /** + * The table is empty, we just want to check that a query runs without errors. + * The query throws an exception if one of the columns is missing + */ + private static CompletionStage checkAllColumnsExist(Stage.Session session) { + return session.find( BasicTypesTestEntity.class, 10 ); + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateTestBase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateTestBase.java deleted file mode 100644 index 7cc63bc4c..000000000 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateTestBase.java +++ /dev/null @@ -1,127 +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.schema; - -import java.util.concurrent.CompletionStage; - -import org.hibernate.cfg.Configuration; -import org.hibernate.reactive.BaseReactiveTest; -import org.hibernate.reactive.provider.Settings; -import org.hibernate.reactive.stage.Stage; -import org.hibernate.reactive.annotations.DisabledFor; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import io.vertx.junit5.Timeout; -import io.vertx.junit5.VertxTestContext; - -import static java.util.concurrent.TimeUnit.MINUTES; -import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; -import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.SQLSERVER; -import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.GROUPED; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.INDIVIDUALLY; - -/** - * Schema update will run different queries when the table already exists or - * when columns are missing. - */ -@DisabledFor(value = DB2, reason = "No InformationExtractor for Dialect [org.hibernate.dialect.DB2Dialect..]") -public abstract class SchemaUpdateTestBase extends BaseReactiveTest { - - @Timeout(value = 10, timeUnit = MINUTES) - public static class IndividuallyStrategyTest extends SchemaUpdateTestBase { - - @Override - protected Configuration constructConfiguration(String hbm2DdlOption) { - final Configuration configuration = super.constructConfiguration( hbm2DdlOption ); - configuration.setProperty( Settings.HBM2DDL_JDBC_METADATA_EXTRACTOR_STRATEGY, INDIVIDUALLY.toString() ); - return configuration; - } - } - - @Timeout(value = 10, timeUnit = MINUTES) - public static class GroupedStrategyTest extends SchemaUpdateTestBase { - - @Override - protected Configuration constructConfiguration(String hbm2DdlOption) { - final Configuration configuration = super.constructConfiguration( hbm2DdlOption ); - configuration.setProperty( Settings.HBM2DDL_JDBC_METADATA_EXTRACTOR_STRATEGY, GROUPED.toString() ); - return configuration; - } - } - - protected Configuration constructConfiguration(String action) { - Configuration configuration = super.constructConfiguration(); - configuration.setProperty( Settings.HBM2DDL_AUTO, action ); - configuration.addAnnotatedClass( BasicTypesTestEntity.class ); - return configuration; - } - - @BeforeEach - @Override - public void before(VertxTestContext context) { - // For these tests we create the factory when we need it - context.completeNow(); - } - - @AfterEach - @Override - public void after(VertxTestContext context) { - super.after( context ); - closeFactory( context ); - } - - /** - * Test missing columns creation during schema update - */ - @Test - public void testMissingColumnsCreation(VertxTestContext context) { - test( context, - setupSessionFactory( constructConfiguration( "drop" ) ) - .thenCompose( v -> getSessionFactory().withTransaction( SchemaUpdateTestBase::createTable ) ) - .whenComplete( (u, throwable) -> factoryManager.stop() ) - .thenCompose( vv -> setupSessionFactory( constructConfiguration( "update" ) ) - .thenCompose( u -> getSessionFactory().withSession( SchemaUpdateTestBase::checkAllColumnsExist ) ) ) - ); - } - - /** - * Test table creation during schema update - */ - @Test - public void testWholeTableCreation(VertxTestContext context) { - test( context, - setupSessionFactory( constructConfiguration( "drop" ) ) - .whenComplete( (u, throwable) -> factoryManager.stop() ) - .thenCompose( v -> setupSessionFactory( constructConfiguration( "update" ) ) - .thenCompose( vv -> getSessionFactory().withSession( SchemaUpdateTestBase::checkAllColumnsExist ) ) ) - ); - } - - // I don't think it's possible to create a table without columns, so we add - // a column that's not mapped by the entity. - // We expect the other columns to be created during the update schema phase. - private static CompletionStage createTable(Stage.Session session, Stage.Transaction transaction) { - return session - .createNativeQuery( "create table " + BasicTypesTestEntity.TABLE_NAME + " (unmapped_column " + columnType() + ")" ) - .executeUpdate(); - } - - private static String columnType() { - return dbType() == SQLSERVER ? "int" : "integer"; - } - - /** - * The table is empty, we just want to check that a query runs without errors. - * The query throws an exception if one of the columns is missing - */ - private static CompletionStage checkAllColumnsExist(Stage.Session session) { - return session.find( BasicTypesTestEntity.class, 10 ); - } -} 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 new file mode 100644 index 000000000..56def4ef9 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java @@ -0,0 +1,150 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.schema; + + +import java.util.concurrent.CompletionStage; +import java.util.stream.Stream; + +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Configuration; +import org.hibernate.reactive.BaseReactiveTest; +import org.hibernate.reactive.annotations.DisabledFor; +import org.hibernate.reactive.provider.Settings; +import org.hibernate.tool.schema.spi.SchemaManagementException; + +import org.junit.jupiter.api.AfterEach; +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.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.cfg.SchemaToolingSettings.HBM2DDL_JDBC_METADATA_EXTRACTOR_STRATEGY; +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; + +/** + * Test schema validation at startup for all the supported types: + * - Missing table validation error + * - No validation error when everything is fine + * - TODO: Test that validation fails when a column is missing + * - TODO: Test that validation fails when a column is the wrong type + */ +@DisabledFor(value = DB2, reason = "We don't have an information extractor. See https://github.com/hibernate/hibernate-reactive/issues/911") +@DisabledFor(value = {MARIA, MYSQL}, reason = "HHH-18869: Schema creation creates an invalid schema") +public class SchemaValidationTest extends BaseReactiveTest { + + static Stream settings() { + return Stream.of( + arguments( INDIVIDUALLY.toString(), null ), + arguments( GROUPED.toString(), null ), + arguments( INDIVIDUALLY.toString(), "VARBINARY" ), + arguments( GROUPED.toString(), "VARBINARY" ) + ); + } + + protected Configuration constructConfiguration(String action, String strategy, String type) { + Configuration configuration = super.constructConfiguration(); + configuration.setProperty( Settings.HBM2DDL_AUTO, action ); + configuration.setProperty( HBM2DDL_JDBC_METADATA_EXTRACTOR_STRATEGY, strategy ); + if ( type != null ) { + // ORM 7 stores arrays as json for MariaDB and MySQL. By setting this property, users + // can keep the behaviour backward compatible. We need to test both. + configuration.setProperty( "hibernate.type.preferred_array_jdbc_type", type ); + } + return configuration; + } + + @Override + public void before(VertxTestContext context) { + // Do nothing, we prepare everything in the test so that we can use a parameterized test + } + + public CompletionStage setupFactory(String strategy, String type) { + Configuration createConf = constructConfiguration( "create", strategy, type ); + createConf.addAnnotatedClass( BasicTypesTestEntity.class ); + + // Make sure that the extra table is not in the db + Configuration dropConf = constructConfiguration( "drop", strategy, type ); + dropConf.addAnnotatedClass( Extra.class ); + + return setupSessionFactory( dropConf ) + .thenCompose( v -> factoryManager.stop() ) + .thenCompose( v -> setupSessionFactory( createConf ) ) + .thenCompose( v -> factoryManager.stop() ); + } + + @AfterEach + @Override + public void after(VertxTestContext context) { + super.after( context ); + closeFactory( context ); + } + + // When we have created the table, the validation should pass + @ParameterizedTest + @MethodSource("settings") + @Timeout(value = 10, timeUnit = MINUTES) + public void testValidationSucceeds(final String strategy, final String type, VertxTestContext context) { + test( + context, setupFactory( strategy, type ) + .thenCompose( v -> { + Configuration validateConf = constructConfiguration( "validate", strategy, type ); + validateConf.addAnnotatedClass( BasicTypesTestEntity.class ); + new StandardServiceRegistryBuilder().applySettings( validateConf.getProperties() ); + return setupSessionFactory( validateConf ); + } ) + ); + } + + + // Validation should fail if a table is missing + @ParameterizedTest + @MethodSource("settings") + @Timeout(value = 10, timeUnit = MINUTES) + public void testValidationFails(String strategy, String type, VertxTestContext context) { + final String errorMessage = "Schema-validation: missing table [" + Extra.TABLE_NAME + "]"; + test( + context, setupFactory( strategy, type ) + .thenCompose( v -> { + Configuration validateConf = constructConfiguration( "validate", strategy, type ); + validateConf.addAnnotatedClass( BasicTypesTestEntity.class ); + // The table mapping this entity shouldn't be in the db + validateConf.addAnnotatedClass( Extra.class ); + return assertThrown( SchemaManagementException.class, setupSessionFactory( validateConf ) ) + .thenAccept( throwable -> assertThat( throwable ).hasMessage( errorMessage ) ); + } ) + ); + } + + /** + * An extra entity used for validation, + * it should not be created at start up + */ + @Entity(name = "Extra") + @Table(name = Extra.TABLE_NAME) + public static class Extra { + public static final String TABLE_NAME = "EXTRA_TABLE"; + @Id + @GeneratedValue + private Integer id; + + private String description; + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTestBase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTestBase.java deleted file mode 100644 index 4a5117fee..000000000 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTestBase.java +++ /dev/null @@ -1,142 +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.schema; - - -import org.hibernate.boot.registry.StandardServiceRegistryBuilder; -import org.hibernate.cfg.Configuration; -import org.hibernate.reactive.BaseReactiveTest; -import org.hibernate.reactive.provider.Settings; -import org.hibernate.reactive.annotations.DisabledFor; -import org.hibernate.tool.schema.spi.SchemaManagementException; - -import org.junit.jupiter.api.AfterEach; -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.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.Table; - -import static java.util.concurrent.TimeUnit.MINUTES; -import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.GROUPED; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.INDIVIDUALLY; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -/** - * Test schema validation at startup for all the supported types: - * - Missing table validation error - * - No validation error when everything is fine - * - TODO: Missing column - * - TODO: Wrong column type - */ -@DisabledFor(value = DB2, reason = "We don't have an information extractor. See https://github.com/hibernate/hibernate-reactive/issues/911") -public abstract class SchemaValidationTestBase extends BaseReactiveTest { - - public static class IndividuallyStrategyTest extends SchemaValidationTestBase { - - @Override - protected Configuration constructConfiguration(String hbm2DdlOption) { - final Configuration configuration = super.constructConfiguration( hbm2DdlOption ); - configuration.setProperty( Settings.HBM2DDL_JDBC_METADATA_EXTRACTOR_STRATEGY, INDIVIDUALLY.toString() ); - return configuration; - } - } - - public static class GroupedStrategyTest extends SchemaValidationTestBase { - - @Override - protected Configuration constructConfiguration(String hbm2DdlOption) { - final Configuration configuration = super.constructConfiguration( hbm2DdlOption ); - configuration.setProperty( Settings.HBM2DDL_JDBC_METADATA_EXTRACTOR_STRATEGY, GROUPED.toString() ); - return configuration; - } - } - - protected Configuration constructConfiguration(String action) { - Configuration configuration = super.constructConfiguration(); - configuration.setProperty( Settings.HBM2DDL_JDBC_METADATA_EXTRACTOR_STRATEGY, INDIVIDUALLY.toString() ); - configuration.setProperty( Settings.HBM2DDL_AUTO, action ); - return configuration; - } - - @BeforeEach - @Override - public void before(VertxTestContext context) { - Configuration createConf = constructConfiguration( "create" ); - createConf.addAnnotatedClass( BasicTypesTestEntity.class ); - - // Make sure that the extra table is not in the db - Configuration dropConf = constructConfiguration( "drop" ); - dropConf.addAnnotatedClass( Extra.class ); - - test( context, setupSessionFactory( dropConf ) - .thenCompose( v -> factoryManager.stop() ) - .thenCompose( v -> setupSessionFactory( createConf ) ) - .thenCompose( v -> factoryManager.stop() ) - ); - } - - @AfterEach - @Override - public void after(VertxTestContext context) { - super.after( context ); - closeFactory( context ); - } - - // When we have created the table, the validation should pass - @Test - @Timeout(value = 10, timeUnit = MINUTES) - public void testValidationSucceeds(VertxTestContext context) { - Configuration validateConf = constructConfiguration( "validate" ); - validateConf.addAnnotatedClass( BasicTypesTestEntity.class ); - - StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder() - .applySettings( validateConf.getProperties() ); - test( context, setupSessionFactory( validateConf ) ); - } - - - // Validation should fail if a table is missing - @Test - @Timeout(value = 10, timeUnit = MINUTES) - public void testValidationFails(VertxTestContext context) { - Configuration validateConf = constructConfiguration( "validate" ); - validateConf.addAnnotatedClass( BasicTypesTestEntity.class ); - // The table mapping this entity shouldn't be in the db - validateConf.addAnnotatedClass( Extra.class ); - - final String errorMessage = "Schema-validation: missing table [" + Extra.TABLE_NAME + "]"; - test( context, setupSessionFactory( validateConf ) - .handle( (unused, throwable) -> { - assertNotNull( throwable ); - assertEquals( throwable.getClass(), SchemaManagementException.class ); - assertEquals( throwable.getMessage(), errorMessage ); - return null; - } ) - ); - } - - /** - * An extra entity used for validation, - * it should not be created at start up - */ - @Entity(name = "Extra") - @Table(name = Extra.TABLE_NAME) - public static class Extra { - public static final String TABLE_NAME = "EXTRA_TABLE"; - @Id - @GeneratedValue - private Integer id; - - private String description; - } -} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/TemporaryIdTableStrategyTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/TemporaryIdTableStrategyTest.java new file mode 100644 index 000000000..15ed24341 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/TemporaryIdTableStrategyTest.java @@ -0,0 +1,235 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.schema; + +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.CompletionStage; +import java.util.stream.Stream; + +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableStrategy; +import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableStrategy; +import org.hibernate.reactive.BaseReactiveTest; +import org.hibernate.reactive.annotations.EnabledFor; +import org.hibernate.reactive.provider.Settings; +import org.hibernate.reactive.testing.SqlStatementTracker; +import org.hibernate.reactive.util.impl.CompletionStages; + +import org.junit.jupiter.api.AfterEach; +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.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.COCKROACHDB; +import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.ORACLE; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * Test enabling and disabling of strategies for the creation of temporary tables to store ids. + * + * @see GlobalTemporaryTableStrategy + * @see PersistentTableStrategy + */ +@Timeout(value = 10, timeUnit = MINUTES) +public class TemporaryIdTableStrategyTest extends BaseReactiveTest { + private static SqlStatementTracker sqlStatementTracker; + + final static Dialect[] dialect = new Dialect[1]; + + @Override + protected Collection> annotatedEntities() { + return Set.of( Engineer.class, Doctor.class, Person.class ); + } + + public static Stream settings() { + return Stream.of( + arguments( true, 1, true, 1 ), + arguments( true, 1, false, 0 ), + // I'm assuming Hibernate won't drop the id tables if they haven't been created + arguments( false, 0, true, 0 ), + arguments( false, 0, false, 0 ) + ); + } + + @Override + protected Configuration constructConfiguration() { + Configuration configuration = super.constructConfiguration(); + configuration.setProperty( Settings.HBM2DDL_AUTO, "create" ); + // Collect all the logs, we are going to filter them later + sqlStatementTracker = new SqlStatementTracker( s -> true, configuration.getProperties() ); + return configuration; + } + + @Override + public CompletionStage deleteEntities(Class... entities) { + // Deleting entities is not necessary for this test + return voidFuture(); + } + + @Override + public void before(VertxTestContext context) { + // We need to start and close our own session factories for the test + } + + @AfterEach + @Override + public void after(VertxTestContext context) { + sqlStatementTracker.clear(); + super.after( context ); + } + + @Override + protected void addServices(StandardServiceRegistryBuilder builder) { + if ( sqlStatementTracker != null ) { + sqlStatementTracker.registerService( builder ); + } + } + + @ParameterizedTest(name = "Global Temporary tables - create: {0}, drop: {2}") + @MethodSource("settings") + @EnabledFor(value = ORACLE, reason = "It uses GlobalTemporaryTableStrategy by default") + public void testGlobalTemporaryTablesStrategy( + boolean enableCreateIdTables, + // Expected number of temporary tables created + int expectedTempTablesCreated, + boolean enableDropIdTables, + // Expected number of temporary tables dropped + int expectedTempTablesDropped, + VertxTestContext context) { + Configuration configuration = constructConfiguration(); + configuration.setProperty( GlobalTemporaryTableStrategy.CREATE_ID_TABLES, enableCreateIdTables ); + configuration.setProperty( GlobalTemporaryTableStrategy.DROP_ID_TABLES, enableDropIdTables ); + + testTemporaryIdTablesCreationAndDropping( configuration, expectedTempTablesCreated, expectedTempTablesDropped, context ); + } + + @ParameterizedTest(name = "Persistent tables - create: {0}, drop: {2}") + @MethodSource("settings") + @EnabledFor(value = COCKROACHDB, reason = "It uses PersistentTemporaryTableStrategy by default") + public void testPersistentTemporaryTablesStrategy( + boolean enableCreateIdTables, + // Expected number of temporary tables created + int expectedTempTablesCreated, + boolean enableDropIdTables, + // Expected number of temporary tables dropped + int expectedTempTablesDropped, + VertxTestContext context) { + + Configuration configuration = constructConfiguration(); + configuration.setProperty( PersistentTableStrategy.CREATE_ID_TABLES, enableCreateIdTables ); + configuration.setProperty( PersistentTableStrategy.DROP_ID_TABLES, enableDropIdTables ); + + testTemporaryIdTablesCreationAndDropping( configuration, expectedTempTablesCreated, expectedTempTablesDropped, context ); + } + + private void testTemporaryIdTablesCreationAndDropping( + Configuration configure, + int expectedTempTablesCreated, + int expectedTempTablesDropped, + VertxTestContext context) { + test( context, setupSessionFactory( configure ) + .thenAccept( v -> { + dialect[0] = getDialect(); + assertThat( commandsCount( dialect[0].getTemporaryTableCreateCommand() ) ) + .as( "Unexpected number of temporary tables for ids CREATED" ) + .isEqualTo( expectedTempTablesCreated ); + sqlStatementTracker.clear(); + } ) + // to ensure the factory is always closed even in case of exceptions + .handle( CompletionStages::handle ) + .thenCompose( this::closeFactory ) + .thenAccept( v -> assertThat( commandsCount( dialect[0].getTemporaryTableDropCommand() ) ) + .as( "Unexpected number of temporary tables for ids DROPPED" ) + .isEqualTo( expectedTempTablesDropped ) ) + ); + } + + // Always try to close the factory without losing the original error (if there was one) + private CompletionStage closeFactory(CompletionStages.CompletionStageHandler handler) { + return factoryManager.stop() + .handle( CompletionStages::handle ) + .thenCompose( factoryHandler -> handler + .getResultAsCompletionStage() + // When there's already an exception, we don't care about errors closing the factory + .thenCompose( factoryHandler::getResultAsCompletionStage ) ); + } + + private static long commandsCount(String temporaryTableCommand) { + return sqlStatementTracker.getLoggedQueries().stream() + .filter( q -> q.startsWith( temporaryTableCommand ) && q.contains( TemporaryTable.ID_TABLE_PREFIX ) ) + .count(); + } + + @Entity(name = "Person") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Person { + + @Id + @GeneratedValue + private Long id; + + private String name; + + private boolean employed; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isEmployed() { + return employed; + } + + public void setEmployed(boolean employed) { + this.employed = employed; + } + } + + @Entity(name = "Doctor") + public static class Doctor extends Person { + } + + @Entity(name = "Engineer") + public static class Engineer extends Person { + + private boolean fellow; + + public boolean isFellow() { + return fellow; + } + + public void setFellow(boolean fellow) { + this.fellow = fellow; + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/testing/SessionFactoryManager.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/testing/SessionFactoryManager.java index 8262b7c22..be4362a02 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/testing/SessionFactoryManager.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/testing/SessionFactoryManager.java @@ -5,20 +5,13 @@ */ package org.hibernate.reactive.testing; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.CompletionStage; import java.util.function.Supplier; import org.hibernate.SessionFactory; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.spi.MappingMetamodelImplementor; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableStrategy; import org.hibernate.reactive.pool.ReactiveConnectionPool; -import org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactivePersistentTableStrategy; -import static org.hibernate.reactive.util.impl.CompletionStages.loop; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** @@ -60,24 +53,8 @@ public ReactiveConnectionPool getReactiveConnectionPool() { public CompletionStage stop() { CompletionStage releasedStage = voidFuture(); if ( sessionFactory != null && sessionFactory.isOpen() ) { - SessionFactoryImplementor sessionFactoryImplementor = sessionFactory.unwrap( SessionFactoryImplementor.class ); - MappingMetamodelImplementor mappingMetamodel = sessionFactoryImplementor - .getRuntimeMetamodels() - .getMappingMetamodel(); - final List reactiveStrategies = new ArrayList<>(); - mappingMetamodel.forEachEntityDescriptor( - entityPersister -> addPersistentTableStrategy( reactiveStrategies, entityPersister ) - ); - if ( !reactiveStrategies.isEmpty() ) { - releasedStage = loop( reactiveStrategies, strategy -> { - ( (PersistentTableStrategy) strategy ) - .release( sessionFactory.unwrap( SessionFactoryImplementor.class ), null ); - return strategy.getDropTableActionStage(); - } ); - - releasedStage = releasedStage - .whenComplete( (unused, throwable) -> sessionFactory.close() ); - } + releasedStage = releasedStage + .whenComplete( (unused, throwable) -> sessionFactory.close() ); } return releasedStage .thenCompose( unused -> { @@ -93,13 +70,4 @@ public CompletionStage stop() { return closeFuture; } ); } - - private void addPersistentTableStrategy(List reactiveStrategies, EntityPersister entityPersister) { - if ( entityPersister.getSqmMultiTableMutationStrategy() instanceof ReactivePersistentTableStrategy ) { - reactiveStrategies.add( (ReactivePersistentTableStrategy) entityPersister.getSqmMultiTableMutationStrategy() ); - } - if ( entityPersister.getSqmMultiTableInsertStrategy() instanceof ReactivePersistentTableStrategy ) { - reactiveStrategies.add( (ReactivePersistentTableStrategy) entityPersister.getSqmMultiTableInsertStrategy() ); - } - } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/AutoZonedTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/AutoZonedTest.java index a799f5d6b..eb502bdb1 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/AutoZonedTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/AutoZonedTest.java @@ -19,6 +19,7 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; import java.util.Collection; import java.util.List; @@ -29,8 +30,11 @@ import static org.hibernate.cfg.AvailableSettings.TIMEZONE_DEFAULT_STORAGE; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.testing.ReactiveAssertions.assertWithTruncationThat; -import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision; +import static org.hibernate.type.descriptor.DateTimeUtils.adjustToDefaultPrecision; +/** + * Test adapted from {@link org.hibernate.orm.test.timezones.AutoZonedTest} + */ @Timeout(value = 10, timeUnit = MINUTES) @DisabledFor(value = DB2, reason = "Exception: IllegalStateException: Needed to have 6 in buffer but only had 0") public class AutoZonedTest extends BaseReactiveTest { @@ -48,8 +52,16 @@ protected void setProperties(Configuration configuration) { @Test public void test(VertxTestContext context) { - ZonedDateTime nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of( "CET" ) ); - OffsetDateTime nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours( 3 ) ); + final ZonedDateTime nowZoned; + final OffsetDateTime nowOffset; + if ( getDialect().getDefaultTimestampPrecision() == 6 ) { + nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") ).truncatedTo( ChronoUnit.MICROS ); + nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) ).truncatedTo( ChronoUnit.MICROS ); + } + else { + nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") ); + nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) ); + } test( context, getSessionFactory() .withTransaction( s -> { Zoned z = new Zoned(); @@ -60,10 +72,10 @@ public void test(VertxTestContext context) { .thenCompose( zid -> openSession() .thenCompose( s -> s.find( Zoned.class, zid ) .thenAccept( z -> { - assertWithTruncationThat( roundToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) ) - .isEqualTo( roundToDefaultPrecision( nowZoned.toInstant(), getDialect() ) ); - assertWithTruncationThat( roundToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) ) - .isEqualTo( roundToDefaultPrecision( nowOffset.toInstant(), getDialect() ) ); + assertWithTruncationThat( adjustToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) ) + .isEqualTo( adjustToDefaultPrecision( nowZoned.toInstant(), getDialect() ) ); + assertWithTruncationThat( adjustToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) ) + .isEqualTo( adjustToDefaultPrecision( nowOffset.toInstant(), getDialect() ) ); assertThat( z.zonedDateTime.toOffsetDateTime().getOffset() ) .isEqualTo( nowZoned.toOffsetDateTime().getOffset() ); assertThat( z.offsetDateTime.getOffset() ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/ColumnZonedTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/ColumnZonedTest.java index f10e394e1..6e33da4f4 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/ColumnZonedTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/ColumnZonedTest.java @@ -9,6 +9,7 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; import java.util.Collection; import java.util.List; @@ -29,8 +30,11 @@ import static org.hibernate.cfg.AvailableSettings.TIMEZONE_DEFAULT_STORAGE; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.testing.ReactiveAssertions.assertWithTruncationThat; -import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision; +import static org.hibernate.type.descriptor.DateTimeUtils.adjustToDefaultPrecision; +/** + * Test adapted from {@link org.hibernate.orm.test.timezones.ColumnZonedTest} + */ @Timeout(value = 10, timeUnit = MINUTES) @DisabledFor(value = DB2, reason = "java.sql.SQLException: An error occurred with a DB2 operation, SQLCODE=-180 SQLSTATE=22007") public class ColumnZonedTest extends BaseReactiveTest { @@ -48,8 +52,16 @@ protected void setProperties(Configuration configuration) { @Test public void test(VertxTestContext context) { - ZonedDateTime nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of( "CET" ) ); - OffsetDateTime nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours( 3 ) ); + final ZonedDateTime nowZoned; + final OffsetDateTime nowOffset; + if ( getDialect().getDefaultTimestampPrecision() == 6 ) { + nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") ).truncatedTo( ChronoUnit.MICROS ); + nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) ).truncatedTo( ChronoUnit.MICROS ); + } + else { + nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") ); + nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) ); + } test( context, getSessionFactory() .withTransaction( s -> { Zoned z = new Zoned(); @@ -60,10 +72,10 @@ public void test(VertxTestContext context) { .thenCompose( zid -> openSession() .thenCompose( s -> s.find( Zoned.class, zid ) .thenAccept( z -> { - assertWithTruncationThat( roundToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) ) - .isEqualTo( roundToDefaultPrecision( nowZoned.toInstant(), getDialect() ) ); - assertWithTruncationThat( roundToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) ) - .isEqualTo( roundToDefaultPrecision( nowOffset.toInstant(), getDialect() ) ); + assertWithTruncationThat( adjustToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) ) + .isEqualTo( adjustToDefaultPrecision( nowZoned.toInstant(), getDialect() ) ); + assertWithTruncationThat( adjustToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) ) + .isEqualTo( adjustToDefaultPrecision( nowOffset.toInstant(), getDialect() ) ); assertThat( z.zonedDateTime.toOffsetDateTime().getOffset() ) .isEqualTo( nowZoned.toOffsetDateTime().getOffset() ); assertThat( z.offsetDateTime.getOffset() ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/DefaultZonedTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/DefaultZonedTest.java index 6752b7219..4ce4dbea8 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/DefaultZonedTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/DefaultZonedTest.java @@ -9,6 +9,7 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; import java.util.Collection; import java.util.List; @@ -28,8 +29,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.testing.ReactiveAssertions.assertWithTruncationThat; -import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision; +import static org.hibernate.type.descriptor.DateTimeUtils.adjustToDefaultPrecision; +/** + * Test adapted from {@link org.hibernate.orm.test.timezones.DefaultZonedTest} + */ @Timeout(value = 10, timeUnit = MINUTES) @DisabledFor(value = DB2, reason = "Exception: IllegalStateException: Needed to have 6 in buffer but only had 0") public class DefaultZonedTest extends BaseReactiveTest { @@ -41,8 +45,16 @@ protected Collection> annotatedEntities() { @Test public void test(VertxTestContext context) { - ZonedDateTime nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of( "CET" ) ); - OffsetDateTime nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours( 3 ) ); + final ZonedDateTime nowZoned; + final OffsetDateTime nowOffset; + if ( getDialect().getDefaultTimestampPrecision() == 6 ) { + nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") ).truncatedTo( ChronoUnit.MICROS ); + nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) ).truncatedTo( ChronoUnit.MICROS ); + } + else { + nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") ); + nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) ); + } test( context, getSessionFactory() .withTransaction( s -> { Zoned z = new Zoned(); @@ -53,10 +65,10 @@ public void test(VertxTestContext context) { .thenCompose( zid -> openSession() .thenCompose( s -> s.find( Zoned.class, zid ) .thenAccept( z -> { - assertWithTruncationThat( roundToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) ) - .isEqualTo( roundToDefaultPrecision( nowZoned.toInstant(), getDialect() ) ); - assertWithTruncationThat( roundToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) ) - .isEqualTo( roundToDefaultPrecision( nowOffset.toInstant(), getDialect() ) ); + assertWithTruncationThat( adjustToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) ) + .isEqualTo( adjustToDefaultPrecision( nowZoned.toInstant(), getDialect() ) ); + assertWithTruncationThat( adjustToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) ) + .isEqualTo( adjustToDefaultPrecision( nowOffset.toInstant(), getDialect() ) ); if ( getDialect().getTimeZoneSupport() == TimeZoneSupport.NATIVE ) { assertThat( z.zonedDateTime.toOffsetDateTime().getOffset() ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/JDBCTimeZoneZonedTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/JDBCTimeZoneZonedTest.java index 5b928c217..71966bfc1 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/JDBCTimeZoneZonedTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/JDBCTimeZoneZonedTest.java @@ -10,6 +10,7 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; import java.util.Collection; import java.util.List; @@ -31,8 +32,11 @@ import static org.hibernate.cfg.AvailableSettings.TIMEZONE_DEFAULT_STORAGE; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.testing.ReactiveAssertions.assertWithTruncationThat; -import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision; +import static org.hibernate.type.descriptor.DateTimeUtils.adjustToDefaultPrecision; +/** + * Test adapted from {@link org.hibernate.orm.test.timezones.JDBCTimeZoneZonedTest} + */ @Timeout(value = 10, timeUnit = MINUTES) @DisabledFor(value = DB2, reason = "Exception: IllegalStateException: Needed to have 6 in buffer but only had 0") public class JDBCTimeZoneZonedTest extends BaseReactiveTest { @@ -51,8 +55,16 @@ protected void setProperties(Configuration configuration) { @Test public void test(VertxTestContext context) { - ZonedDateTime nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of( "CET" ) ); - OffsetDateTime nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours( 3 ) ); + final ZonedDateTime nowZoned; + final OffsetDateTime nowOffset; + if ( getDialect().getDefaultTimestampPrecision() == 6 ) { + nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") ).truncatedTo( ChronoUnit.MICROS ); + nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) ).truncatedTo( ChronoUnit.MICROS ); + } + else { + nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") ); + nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) ); + } test( context, getSessionFactory() .withTransaction( s -> { Zoned z = new Zoned(); @@ -63,11 +75,11 @@ public void test(VertxTestContext context) { .thenCompose( zid -> openSession() .thenCompose( s -> s.find( Zoned.class, zid ) .thenAccept( z -> { - assertWithTruncationThat( roundToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) ) - .isEqualTo( roundToDefaultPrecision( nowZoned.toInstant(), getDialect() ) ); + assertWithTruncationThat( adjustToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) ) + .isEqualTo( adjustToDefaultPrecision( nowZoned.toInstant(), getDialect() ) ); - assertWithTruncationThat( roundToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) ) - .isEqualTo( roundToDefaultPrecision( nowOffset.toInstant(), getDialect() ) ); + assertWithTruncationThat( adjustToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) ) + .isEqualTo( adjustToDefaultPrecision( nowOffset.toInstant(), getDialect() ) ); ZoneId systemZone = ZoneId.systemDefault(); ZoneOffset systemOffset = systemZone.getRules().getOffset( Instant.now() ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/PassThruZonedTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/PassThruZonedTest.java index 988db4a58..3ce9f4256 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/PassThruZonedTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/PassThruZonedTest.java @@ -10,6 +10,7 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; import java.util.Collection; import java.util.List; @@ -30,8 +31,11 @@ import static org.hibernate.cfg.AvailableSettings.TIMEZONE_DEFAULT_STORAGE; import static org.hibernate.reactive.testing.ReactiveAssertions.assertWithTruncationThat; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; -import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision; +import static org.hibernate.type.descriptor.DateTimeUtils.adjustToDefaultPrecision; +/** + * Test adapted from {@link org.hibernate.orm.test.timezones.PassThruZonedTest} + */ @Timeout(value = 10, timeUnit = MINUTES) @DisabledFor(value = DB2, reason = "Exception: SQLException: An error occurred with a DB2 operation, SQLCODE=-180 SQLSTATE=22007") public class PassThruZonedTest extends BaseReactiveTest { @@ -49,8 +53,16 @@ protected void setProperties(Configuration configuration) { @Test public void test(VertxTestContext context) { - ZonedDateTime nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of( "CET" ) ); - OffsetDateTime nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours( 3 ) ); + final ZonedDateTime nowZoned; + final OffsetDateTime nowOffset; + if ( getDialect().getDefaultTimestampPrecision() == 6 ) { + nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") ).truncatedTo( ChronoUnit.MICROS ); + nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) ).truncatedTo( ChronoUnit.MICROS ); + } + else { + nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") ); + nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) ); + } test( context, getSessionFactory() .withTransaction( s -> { Zoned z = new Zoned(); @@ -61,10 +73,10 @@ public void test(VertxTestContext context) { .thenCompose( zid -> openSession() .thenCompose( s -> s.find( Zoned.class, zid ) .thenAccept( z -> { - assertWithTruncationThat( roundToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) ) - .isEqualTo( roundToDefaultPrecision( nowZoned.toInstant(), getDialect() ) ); - assertWithTruncationThat( roundToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) ) - .isEqualTo( roundToDefaultPrecision( nowOffset.toInstant(), getDialect() ) ); + assertWithTruncationThat( adjustToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) ) + .isEqualTo( adjustToDefaultPrecision( nowZoned.toInstant(), getDialect() ) ); + assertWithTruncationThat( adjustToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) ) + .isEqualTo( adjustToDefaultPrecision( nowOffset.toInstant(), getDialect() ) ); ZoneId systemZone = ZoneId.systemDefault(); ZoneOffset systemOffset = systemZone.getRules().getOffset( Instant.now() ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/UTCNormalizedZonedTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/UTCNormalizedZonedTest.java index 9943a0f97..474f5df7b 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/UTCNormalizedZonedTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/UTCNormalizedZonedTest.java @@ -29,7 +29,7 @@ import static org.hibernate.cfg.AvailableSettings.TIMEZONE_DEFAULT_STORAGE; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.testing.ReactiveAssertions.assertWithTruncationThat; -import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision; +import static org.hibernate.type.descriptor.DateTimeUtils.adjustToDefaultPrecision; @Timeout(value = 10, timeUnit = MINUTES) @DisabledFor(value = DB2, reason = "Exception: IllegalStateException: Needed to have 6 in buffer but only had 0") @@ -60,10 +60,10 @@ public void test(VertxTestContext context) { .thenCompose( zid -> openSession() .thenCompose( s -> s.find( Zoned.class, zid ) .thenAccept( z -> { - assertWithTruncationThat( roundToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) ) - .isEqualTo( roundToDefaultPrecision( nowZoned.toInstant(), getDialect() ) ); - assertWithTruncationThat( roundToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) ) - .isEqualTo( roundToDefaultPrecision( nowOffset.toInstant(), getDialect() ) ); + assertWithTruncationThat( adjustToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) ) + .isEqualTo( adjustToDefaultPrecision( nowZoned.toInstant(), getDialect() ) ); + assertWithTruncationThat( adjustToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) ) + .isEqualTo( adjustToDefaultPrecision( nowOffset.toInstant(), getDialect() ) ); assertThat( z.offsetDateTime.getOffset() ).isEqualTo( ZoneId.of( "Z" ) ); assertThat( z.zonedDateTime.getZone() ).isEqualTo( ZoneOffset.ofHours( 0 ) ); } ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JavaTypesArrayTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JavaTypesArrayTest.java index 66fb6dbbd..c66efa587 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JavaTypesArrayTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JavaTypesArrayTest.java @@ -13,12 +13,15 @@ import java.time.Month; import java.util.Calendar; import java.util.Date; +import java.util.List; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletionStage; import java.util.function.Consumer; import java.util.function.Predicate; import org.hibernate.AssertionFailure; +import org.hibernate.HibernateException; import org.hibernate.annotations.Array; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.Configuration; @@ -40,8 +43,14 @@ import static java.lang.Boolean.TRUE; import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; +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.containers.DatabaseConfiguration.DBType.ORACLE; +import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.SQLSERVER; import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType; +import static org.hibernate.reactive.testing.ReactiveAssertions.assertThrown; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -66,6 +75,13 @@ protected Configuration constructConfiguration() { return configuration; } + @Override + public CompletionStage deleteEntities(Class... entities) { + // Deleting the entities after each test is not really necessary, and sometimes it causes errors in the log + // that make it harder to figure out what's going on + return voidFuture(); + } + @Override protected void addServices(StandardServiceRegistryBuilder builder) { sqlTracker.registerService( builder ); @@ -80,18 +96,25 @@ protected Set> annotatedEntities() { return Set.of( Basic.class ); } - private void testField( - VertxTestContext context, Basic - original, Consumer consumer) { - test( context, getSessionFactory() - .withTransaction( s -> s.persist( original ) ) - .thenCompose( v -> getSessionFactory().withSession( s -> s - .find( Basic.class, original.id ) - .thenAccept( found -> { - assertNotNull( found ); - consumer.accept( found ); - } ) ) ) - ); + private void testField(VertxTestContext context, Basic original, Consumer consumer) { + if ( List.of( DB2, SQLSERVER ).contains( dbType() ) ) { + test( context, assertThrown( HibernateException.class, getSessionFactory() + .withTransaction( s -> s.persist( original ) ) ) + .thenAccept( e -> assertThat( e.getMessage() ).startsWith( "HR000081: " ) ) + ); + } + else { + test( + context, getSessionFactory() + .withTransaction( s -> s.persist( original ) ) + .thenCompose( v -> getSessionFactory().withSession( s -> s + .find( Basic.class, original.id ) + .thenAccept( found -> { + assertNotNull( found ); + consumer.accept( found ); + } ) ) ) + ); + } } @Test @@ -101,8 +124,8 @@ public void testStringArrayType(VertxTestContext context) { basic.stringArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.stringArray ); validateArrayColumn( "stringArray", null, 255 ); + assertArrayEquals( dataArray, found.stringArray ); } ); } @@ -113,8 +136,8 @@ public void testStringArrayTypeWithArrayAnnotation(VertxTestContext context) { basic.stringArrayWithArrayAnnotation = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.stringArrayWithArrayAnnotation ); validateArrayColumn( "stringArrayWithArrayAnnotation", 5, null ); + assertArrayEquals( dataArray, found.stringArrayWithArrayAnnotation ); } ); } @@ -125,8 +148,8 @@ public void testStringArrayTypeWithColumnAnnotation(VertxTestContext context) { basic.stringArrayWithColumnAnnotation = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.stringArrayWithColumnAnnotation ); validateArrayColumn( "stringArrayWithColumnAnnotation", null, 200 ); + assertArrayEquals( dataArray, found.stringArrayWithColumnAnnotation ); } ); } @@ -137,8 +160,8 @@ public void testStringArrayTypeWithBothAnnotations(VertxTestContext context) { basic.stringArrayWithBothAnnotations = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.stringArrayWithBothAnnotations ); validateArrayColumn( "stringArrayWithBothAnnotations", 5, 200 ); + assertArrayEquals( dataArray, found.stringArrayWithBothAnnotations ); } ); } @@ -148,8 +171,10 @@ public void testBooleanArrayType(VertxTestContext context) { Boolean[] dataArray = {TRUE, FALSE, null, TRUE}; basic.booleanArray = dataArray; - testField( context, basic, found -> assertArrayEquals( dataArray, found.booleanArray ) ); - validateArrayColumn( "booleanArray", null, null ); + testField( context, basic, found -> { + validateArrayColumn( "booleanArray", null, null ); + assertArrayEquals( dataArray, found.booleanArray ); + } ); } @Test @@ -159,8 +184,8 @@ public void testPrimitiveBooleanArrayType(VertxTestContext context) { basic.primitiveBooleanArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.primitiveBooleanArray ); - validateArrayColumn( "primitiveBooleanArray", null, null ); + validateArrayColumn( "primitiveBooleanArray", null, null ); + assertArrayEquals( dataArray, found.primitiveBooleanArray ); } ); } @@ -171,8 +196,8 @@ public void testIntegerArrayType(VertxTestContext context) { basic.integerArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.integerArray ); validateArrayColumn( "integerArray", null, null ); + assertArrayEquals( dataArray, found.integerArray ); } ); } @@ -183,8 +208,8 @@ public void testPrimitiveIntegerArrayType(VertxTestContext context) { basic.primitiveIntegerArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.primitiveIntegerArray ); validateArrayColumn( "primitiveIntegerArray", null, null ); + assertArrayEquals( dataArray, found.primitiveIntegerArray ); } ); } @@ -195,8 +220,8 @@ public void testLongArrayType(VertxTestContext context) { basic.longArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.longArray ); validateArrayColumn( "longArray", null, null ); + assertArrayEquals( dataArray, found.longArray ); } ); } @@ -207,8 +232,8 @@ public void testPrimitiveLongArrayType(VertxTestContext context) { basic.primitiveLongArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.primitiveLongArray ); validateArrayColumn( "primitiveLongArray", null, null ); + assertArrayEquals( dataArray, found.primitiveLongArray ); } ); } @@ -231,8 +256,8 @@ public void testPrimitiveFloatArrayType(VertxTestContext context) { basic.primitiveFloatArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.primitiveFloatArray ); validateArrayColumn( "primitiveFloatArray", null, null ); + assertArrayEquals( dataArray, found.primitiveFloatArray ); } ); } @@ -243,8 +268,8 @@ public void testDoubleArrayType(VertxTestContext context) { basic.doubleArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.doubleArray ); validateArrayColumn( "doubleArray", null, null ); + assertArrayEquals( dataArray, found.doubleArray ); } ); } @@ -255,8 +280,8 @@ public void testPrimitiveDoubleArrayType(VertxTestContext context) { basic.primitiveDoubleArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.primitiveDoubleArray ); validateArrayColumn( "primitiveDoubleArray", null, null ); + assertArrayEquals( dataArray, found.primitiveDoubleArray ); } ); } @@ -271,8 +296,8 @@ public void testUUIDArrayType(VertxTestContext context) { basic.uuidArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.uuidArray ); validateArrayColumn( "uuidArray", null, null ); + assertArrayEquals( dataArray, found.uuidArray ); } ); } @@ -283,8 +308,8 @@ public void testEnumArrayType(VertxTestContext context) { basic.enumArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.enumArray ); validateArrayColumn( "enumArray", null, null ); + assertArrayEquals( dataArray, found.enumArray ); } ); } @@ -295,8 +320,8 @@ public void testShortArrayType(VertxTestContext context) { basic.shortArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.shortArray ); validateArrayColumn( "shortArray", null, null ); + assertArrayEquals( dataArray, found.shortArray ); } ); } @@ -307,8 +332,8 @@ public void testPrimitiveShortArrayType(VertxTestContext context) { basic.primitiveShortArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.primitiveShortArray ); validateArrayColumn( "primitiveShortArray", null, null ); + assertArrayEquals( dataArray, found.primitiveShortArray ); } ); } @@ -324,20 +349,21 @@ public void testLocalDateArrayType(VertxTestContext context) { basic.localDateArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.localDateArray ); validateArrayColumn( "localDateArray", null, null ); + assertArrayEquals( dataArray, found.localDateArray ); } ); } @Test + @DisabledFor(value = {MYSQL, MARIA}, reason = "HHH-18881: Problem with the conversion of dates") public void testDateArrayType(VertxTestContext context) { Basic basic = new Basic(); Date[] dataArray = {Calendar.getInstance().getTime(), Calendar.getInstance().getTime()}; basic.dateArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.dateArray ); validateArrayColumn( "dateArray", null, null ); + assertArrayEquals( dataArray, found.dateArray ); } ); } @@ -353,12 +379,13 @@ public void testLocalTimeArrayType(VertxTestContext context) { basic.localTimeArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.localTimeArray ); validateArrayColumn( "localTimeArray", null, null ); + assertArrayEquals( dataArray, found.localTimeArray ); } ); } @Test + @DisabledFor(value = {MYSQL, MARIA}, reason = "HHH-18881: Problem with the conversion of dates") public void testLocalDateTimeArrayType(VertxTestContext context) { Basic basic = new Basic(); LocalDateTime[] dataArray = { @@ -374,8 +401,8 @@ public void testLocalDateTimeArrayType(VertxTestContext context) { basic.localDateTimeArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.localDateTimeArray ); validateArrayColumn( "localDateTimeArray", null, null ); + assertArrayEquals( dataArray, found.localDateTimeArray ); } ); } @@ -386,8 +413,8 @@ public void testBigIntegerArrayType(VertxTestContext context) { basic.bigIntegerArray = dataArray; testField( context, basic, found -> { - assertArrayEquals( dataArray, found.bigIntegerArray ); validateArrayColumn( "bigIntegerArray", null, 5000 ); + assertArrayEquals( dataArray, found.bigIntegerArray ); } ); } @@ -398,10 +425,10 @@ public void testBigDecimalArrayType(VertxTestContext context) { basic.bigDecimalArray = dataArray; testField( context, basic, found -> { + validateArrayColumn( "bigDecimalArray", null, 5000 ); assertEquals( dataArray.length, found.bigDecimalArray.length ); assertEquals( 0, dataArray[0].compareTo( found.bigDecimalArray[0] ) ); assertEquals( 0, dataArray[1].compareTo( found.bigDecimalArray[1] ) ); - validateArrayColumn( "bigDecimalArray", null, 5000 ); } ); } @@ -412,10 +439,10 @@ public void testBigDecimalArrayTypeWithArrayAnnotation(VertxTestContext context) basic.bigDecimalArrayWithArrayAnnotation = dataArray; testField( context, basic, found -> { + validateArrayColumn( "bigDecimalArrayWithArrayAnnotation", 5, 5000 ); assertEquals( dataArray.length, found.bigDecimalArrayWithArrayAnnotation.length ); assertEquals( 0, dataArray[0].compareTo( found.bigDecimalArrayWithArrayAnnotation[0] ) ); assertEquals( 0, dataArray[1].compareTo( found.bigDecimalArrayWithArrayAnnotation[1] ) ); - validateArrayColumn( "bigDecimalArrayWithArrayAnnotation", 5, 5000 ); } ); } @@ -426,16 +453,20 @@ private void validateArrayColumn(String columnName, Integer arrayLength, Integer } // A predicate that checks we apply the right size to the array when required - private static Predicate arrayColumnPredicate(String columnName, Integer arrayLength, Integer columnLength) { + private static Predicate arrayColumnPredicate( + String columnName, + Integer arrayLength, + Integer columnLength) { switch ( dbType() ) { case POSTGRESQL: case COCKROACHDB: return postgresPredicate( columnName, arrayLength, columnLength ); case MYSQL: case MARIA: + return arrayAsJsonPredicate( columnName ); case SQLSERVER: case DB2: - return arrayAsVarbinaryPredicate( columnName, columnLength ); + return arrayAsXmlPredicate( columnName ); default: throw new AssertionFailure( "Unexpected database: " + dbType() ); } @@ -444,9 +475,9 @@ private static Predicate arrayColumnPredicate(String columnName, Integer /** * For Postgres, we expect arrays to be defined as {@code array}. *

    - * For example: {@code varchar(255) array[2]} + * For example: {@code varchar(255) array[2]} *

    - */ + */ private static Predicate postgresPredicate(String columnName, Integer arrayLength, Integer columnLength) { StringBuilder regexBuilder = new StringBuilder(); regexBuilder.append( ".*" ); @@ -468,6 +499,15 @@ private static Predicate postgresPredicate(String columnName, Integer ar return s -> s.matches( regexBuilder.toString() ); } + private static Predicate arrayAsJsonPredicate(String columnName) { + return s -> s.contains( columnName + " json" ); + } + + private static Predicate arrayAsXmlPredicate(String columnName) { + // Example of correct query definition: columnName xml + return s -> s.contains( columnName + " xml" ); + } + private static Predicate arrayAsVarbinaryPredicate(String columnName, Integer columnLength) { StringBuilder regexBuilder = new StringBuilder(); // Example of correct query definition: columnName varbinary(255) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/Json.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/Json.java index 2df6f008a..9af3a2b76 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/Json.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/Json.java @@ -61,7 +61,7 @@ public void nullSafeSet(PreparedStatement st, JsonObject value, int index, Share @Override public JsonObject deepCopy(JsonObject value) { - return value == null ? null : ( (JsonObject) value ).copy(); + return value == null ? null : value.copy(); } @Override diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JsonQueryTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JsonQueryTest.java index 7af5d42c7..f97f354fd 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JsonQueryTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JsonQueryTest.java @@ -150,6 +150,30 @@ public void nativeQueryWithJson(VertxTestContext context) { ); } + @Test + @Disabled("https://github.com/hibernate/hibernate-reactive/issues/1999") + public void nativeQueryWithEscapedQuestionMark(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withTransaction( s -> s + .createNativeQuery( "select * from BookWithJson where author -> 'name' \\? 'Jo'", Book.class ) + .getSingleResult() + ) + .invoke( result -> assertThat( result ).isEqualTo( fakeHistory ) ) + ); + } + + @Test + @Disabled("https://github.com/hibernate/hibernate-reactive/issues/2012") + public void nativeQuerySelectScalarWithEscapedQuestionMark(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withTransaction( s -> s + .createNativeQuery( "select 123 from BookWithJson where author -> 'name' \\? 'Jo'", Object.class ) + .getSingleResult() + ) + .invoke( result -> assertThat( result ).isEqualTo( 123 ) ) + ); + } + @Entity(name = "Book") @Table(name = "BookWithJson") public static class Book { 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/main/java/org/hibernate/reactive/it/lazytoone/Ship.java b/integration-tests/bytecode-enhancements-it/src/main/java/org/hibernate/reactive/it/lazytoone/Ship.java index 60bf2a278..11ed5a200 100644 --- a/integration-tests/bytecode-enhancements-it/src/main/java/org/hibernate/reactive/it/lazytoone/Ship.java +++ b/integration-tests/bytecode-enhancements-it/src/main/java/org/hibernate/reactive/it/lazytoone/Ship.java @@ -15,9 +15,6 @@ import jakarta.persistence.OneToOne; import jakarta.persistence.Table; -import org.hibernate.annotations.LazyToOne; -import org.hibernate.annotations.LazyToOneOption; - @Entity(name = "Ship") @Table(name = "Ship") public class Ship { @@ -30,7 +27,6 @@ public class Ship { @Basic(fetch = FetchType.LAZY) private byte[] picture; - @LazyToOne(LazyToOneOption.NO_PROXY) @OneToOne(fetch = FetchType.LAZY, mappedBy = "ship", cascade = CascadeType.ALL) private Captain captain; diff --git a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java index 72ad3273d..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:16.3" ) + .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 017a52919..abd6e2bc0 100644 --- a/integration-tests/hibernate-validator-postgres-it/build.gradle +++ b/integration-tests/hibernate-validator-postgres-it/build.gradle @@ -17,12 +17,12 @@ description = 'Quarkus QE integration tests' ext { log4jVersion = '2.20.0' - assertjVersion = '3.26.3' + assertjVersion = '3.27.3' } dependencies { implementation project(':hibernate-reactive-core') - implementation "org.hibernate.validator:hibernate-validator:8.0.1.Final" + implementation "org.hibernate.validator:hibernate-validator:8.0.2.Final" runtimeOnly 'org.glassfish.expressly:expressly:5.0.0' // JPA metamodel generation for criteria queries (optional) @@ -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 7272f13f2..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:16.3" ) + .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 eab23a6ff..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:16.3"; + 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/lgtm.yml b/lgtm.yml deleted file mode 100644 index 8c608f1f0..000000000 --- a/lgtm.yml +++ /dev/null @@ -1,8 +0,0 @@ -##################################################### -# LGTM.com configuration file -##################################################### - -extraction: - java: - index: - java_version: 11 diff --git a/podman.md b/podman.md index dc81fb828..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:14.0 +podman run --rm --name HibernateTestingPGSQL postgres:17.5 ``` becomes for Docker: ``` -sudo docker run --rm --name HibernateTestingPGSQL postgres:14.0 +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:16.3 + -p 5432:5432 docker.io/postgres:17.5 ``` When the database has started, you can run the tests on PostgreSQL with: @@ -67,7 +67,7 @@ and schema to run the tests: ``` podman run --rm --name HibernateTestingMariaDB \ -e MYSQL_ROOT_PASSWORD=hreact -e MYSQL_DATABASE=hreact -e MYSQL_USER=hreact -e MYSQL_PASSWORD=hreact \ - -p 3306:3306 docker.io/mariadb:11.4.2 + -p 3306:3306 docker.io/mariadb:11.7.2 ``` When the database has started, you can run the tests on MariaDB with: @@ -94,7 +94,7 @@ and schema to run the tests: ``` podman run --rm --name HibernateTestingMySQL \ -e MYSQL_ROOT_PASSWORD=hreact -e MYSQL_DATABASE=hreact -e MYSQL_USER=hreact -e MYSQL_PASSWORD=hreact \ - -p 3306:3306 docker.io/mysql:8.4.0 + -p 3306:3306 docker.io/mysql:9.2.0 ``` When the database has started, you can run the tests on MySQL with: @@ -121,7 +121,7 @@ configured to run the tests: ``` podman run --rm --name=HibernateTestingCockroachDB \ --hostname=roachrr1 -p 26257:26257 -p 8080:8080 \ - docker.io/cockroachdb/cockroach:v24.1.0 start-single-node --insecure + docker.io/cockroachdb/cockroach:v24.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 3c48e1298..f77b26959 100644 --- a/publish.gradle +++ b/publish.gradle @@ -1,13 +1,13 @@ +apply plugin: 'java' apply plugin: 'maven-publish' -tasks.register( 'sourcesJar', Jar ) { - from sourceSets.main.allJava - archiveClassifier = 'sources' -} +// Java / publishing -tasks.register( 'javadocJar', Jar ) { - from javadoc - archiveClassifier = 'javadoc' +java { + // Configure the Java "software component" to include javadoc and sources jars in addition to the classes jar. + // Ultimately, this component is what makes up the publication for this project. + withJavadocJar() + withSourcesJar() } jar { @@ -35,14 +35,9 @@ javadoc { publishing { publications { - logger.lifecycle "Publishing groupId: '" + project.group + "', version: '" + project.version + "'" - - publishedArtifacts(MavenPublication) { - groupId = project.group - version = project.version + register( "publishedArtifacts", MavenPublication) { from components.java - artifact sourcesJar - artifact javadocJar + pom { name = project.mavenPomName description = project.description @@ -79,4 +74,17 @@ publishing { } } } + repositories { + maven { + name = "staging" + url = rootProject.layout.buildDirectory.dir("staging-deploy${File.separator}maven") + } + 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) + } + } } 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/settings.gradle b/settings.gradle index af4c707eb..e997f3e73 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,7 +13,7 @@ pluginManagement { rootProject.name = 'hibernate-reactive' -gradle.ext.baselineJavaVersion = JavaLanguageVersion.of( 11 ) +gradle.ext.baselineJavaVersion = JavaLanguageVersion.of( 17 ) // Gradle does bytecode transformation on tests. // You can't use bytecode higher than what Gradle supports, even with toolchains. diff --git a/tooling/jbang/CockroachDBReactiveTest.java.qute b/tooling/jbang/CockroachDBReactiveTest.java.qute index 17eecb479..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.11} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.11} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0.Final} -//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.4 +//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.0" ) ); + 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 2e6ae3623..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.11} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.11} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0.Final} -//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.4 +//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 fe0b312e8..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.11} -//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.11} -//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.11} -//DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:2.4.0.Final} +//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:16.3
    + *                      -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 b7592b05b..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.11} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.11} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0.Final} -//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.4 +//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; @@ -69,7 +69,7 @@ public class {baseName} { } @ClassRule - public final static MariaDBContainer database = new MariaDBContainer<>( imageName( "docker.io", "mariadb", "11.4.2" ) ); + public final static MariaDBContainer database = new MariaDBContainer<>( imageName( "docker.io", "mariadb", "11.7.2" ) ); private Mutiny.SessionFactory sessionFactory; diff --git a/tooling/jbang/MySQLReactiveTest.java.qute b/tooling/jbang/MySQLReactiveTest.java.qute index 7cc856c4a..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.11} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.11} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0.Final} -//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.4 +//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; @@ -72,7 +72,7 @@ public class {baseName} { } @ClassRule - public final static MySQLContainer database = new MySQLContainer<>( imageName( "docker.io", "mysql", "8.4.0" ) ); + public final static MySQLContainer database = new MySQLContainer<>( imageName( "docker.io", "mysql", "9.2.0" ) ); private Mutiny.SessionFactory sessionFactory; diff --git a/tooling/jbang/PostgreSQLReactiveTest.java.qute b/tooling/jbang/PostgreSQLReactiveTest.java.qute index 306c30476..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.11} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.11} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0.Final} -//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.4 +//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", "16.3" ) ); + 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 c79d14fe5..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.11} -//DEPS com.ongres.scram:client:2.1 -//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.11} -//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.11} -//DEPS io.vertx:vertx-unit:${vertx.version:4.5.11} -//DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:2.4.0.Final} -//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.4 -//DEPS org.testcontainers:mysql:1.20.4 -//DEPS org.testcontainers:db2:1.20.4 -//DEPS org.testcontainers:mariadb:1.20.4 -//DEPS org.testcontainers:cockroachdb:1.20.4 +//DEPS org.testcontainers:postgresql:1.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:16.3" ) ), - MYSQL( () -> new MySQLContainer( "mysql:8.4.0" ) ), + 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.4.2" ) ), - COCKROACHDB( () -> new CockroachContainer( "cockroachdb/cockroach:v24.1.0" ) ); + MARIADB( () -> new MariaDBContainer( "mariadb:11.7.2" ) ), + COCKROACHDB( () -> new CockroachContainer( "cockroachdb/cockroach:v24.3.13" ) ); private final Supplier> containerSupplier;