diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..fbc73fcb5
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,68 @@
+# 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/*"
+
+ - package-ecosystem: "gradle"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "tuesday"
+ open-pull-requests-limit: 20
+ groups:
+ hibernate-validator:
+ patterns:
+ - "org.hibernate.validator*"
+ - "org.glassfish.expressly*"
+ hibernate:
+ patterns:
+ - "org.hibernate*"
+ vertx:
+ patterns:
+ - "io.vertx*"
+ mutiny:
+ patterns:
+ - "io.smallrye.reactive*"
+ # Testcontainers plus the JDBC driver we need for testing
+ testcontainers:
+ patterns:
+ - "org.testcontainers*"
+ - "com.ibm.db2*"
+ - "com.microsoft.sqlserver*"
+ - "org.postgresql*"
+ - "con.ongres.scram*"
+ - "com.fasterxml.jackson.core*"
+ - "com.mysql*"
+ - "org.mariadb.jdbc*"
+
+ ignore:
+ # For Hibernate Validator, we will need to update major version manually as needed (but we only use it in tests)
+ - dependency-name: "org.glassfish.expressly*"
+ update-types: ["version-update-:semver-major"]
+ # Only patches for Hibernate ORM and Vert.x
+ - dependency-name: "org.hibernate*"
+ update-types: ["version-update:semver-major", "version-update:semver-minor"]
+ - dependency-name: "io.vertx*"
+ update-types: ["version-update:semver-major", "version-update:semver-minor"]
+
+ # Dockerfiles in tooling/docker/, and database services we use for examples (MySQL and PostgreSQL)
+ - package-ecosystem: "docker"
+ directory: "/tooling/docker"
+ schedule:
+ interval: "weekly"
+ allow:
+ - dependency-type: "all"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 6c90f0b9c..9a9333466 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -5,12 +5,17 @@ on:
branches:
- 'main'
- 'wip/**'
+ - '2.*'
+ - '3.*'
tags:
- '2.*'
+ - '3.*'
pull_request:
branches:
- 'main'
- 'wip/**'
+ - '2.*'
+ - '3.*'
# For building snapshots
workflow_call:
inputs:
@@ -44,8 +49,7 @@ jobs:
services:
# Label used to access the service container
mysql:
- # Docker Hub image
- image: mysql:8.4.0
+ image: container-registry.oracle.com/mysql/community-server:9.3.0
env:
MYSQL_ROOT_PASSWORD: hreact
MYSQL_DATABASE: hreact
@@ -61,7 +65,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 +80,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 +89,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@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
id: cache-gradle
with:
path: |
@@ -95,7 +99,7 @@ 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
+ uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
with:
distribution: 'temurin'
java-version: 11
@@ -104,7 +108,7 @@ jobs:
- name: Run examples in '${{ matrix.example }}' on ${{ matrix.db }}
run: ./gradlew :${{ matrix.example }}:runAllExamplesOn${{ matrix.db }}
- name: Upload reports (if build failed)
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
if: failure()
with:
name: reports-examples-${{ matrix.db }}
@@ -118,7 +122,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 +131,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@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
id: cache-gradle
with:
path: |
@@ -137,7 +141,7 @@ 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
+ uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
with:
distribution: 'temurin'
java-version: 11
@@ -146,7 +150,7 @@ jobs:
- name: Build and Test with ${{ matrix.db }}
run: ./gradlew build -PshowStandardOutput -Pdocker -Pdb=${{ matrix.db }}
- name: Upload reports (if build failed)
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
if: failure()
with:
name: reports-db-${{ matrix.db }}
@@ -172,12 +176,12 @@ jobs:
# and it's useful to test that.
- { name: "20", java_version_numeric: 20, jvm_args: '--enable-preview' }
- { name: "21", java_version_numeric: 21, jvm_args: '--enable-preview' }
- - { name: "23", java_version_numeric: 23, from: 'jdk.java.net', jvm_args: '--enable-preview' }
- - { name: "24-ea", java_version_numeric: 24, from: 'jdk.java.net', jvm_args: '--enable-preview' }
- - { name: "25-ea", java_version_numeric: 25, from: 'jdk.java.net', jvm_args: '--enable-preview' }
+ - { name: "24", java_version_numeric: 24, jvm_args: '--enable-preview' }
+ - { name: "25", java_version_numeric: 25, from: 'jdk.java.net', jvm_args: '--enable-preview' }
+ - { name: "26-ea", java_version_numeric: 26, 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 +202,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@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
# if it's not a pull request, we restore and save the cache
if: github.event_name != 'pull_request'
with:
@@ -215,7 +219,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@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
# if it's a pull request, we restore the cache, but we don't save it
if: github.event_name == 'pull_request'
with:
@@ -231,13 +235,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@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
with:
distribution: 'temurin'
java-version: ${{ matrix.java.java_version_numeric }}
@@ -247,7 +251,7 @@ jobs:
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
+ uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
with:
distribution: 'temurin'
java-version: 11
@@ -266,7 +270,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@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
if: failure()
with:
name: reports-java${{ matrix.java.name }}
diff --git a/.gitignore b/.gitignore
index 8016117ce..750319d47 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,6 +43,3 @@ bin
# Vim
*.swp
*.swo
-
-# Release scripts downloaded from hibernate/hibernate-release-scripts
-.release
diff --git a/.release/.gitignore b/.release/.gitignore
new file mode 100644
index 000000000..cdd9a17d6
--- /dev/null
+++ b/.release/.gitignore
@@ -0,0 +1,3 @@
+# The folder into which we checkout our release scripts into
+*
+!.gitignore
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..3e99a1d42
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,107 @@
+# Contributing
+
+Contributions from the community are essential in keeping Hibernate (and any Open Source
+project really) strong and successful.
+
+# Legal
+
+All original contributions to Hibernate are licensed under the
+[Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0).
+
+The Apache 2.0 license text is included verbatim in the [LICENSE](LICENSE) file in the root directory
+of the Hibernate Reactive repository.
+
+All contributions are subject to the [Developer Certificate of Origin (DCO)](https://developercertificate.org/).
+
+The DCO text is available verbatim in the [dco.txt](dco.txt) file in the root directory
+of the Hibernate Reactive repository.
+
+## Guidelines
+
+While we try to keep requirements for contributing to a minimum, there are a few guidelines
+we ask that you mind.
+
+For code contributions, these guidelines include:
+* Respect the project code style - find templates for [IntelliJ IDEA](https://hibernate.org/community/contribute/intellij-idea/) or [Eclipse](https://hibernate.org/community/contribute/eclipse-ide/)
+* Have a corresponding GitHub [issue](https://github.com/hibernate/hibernate-reactive/issues) and be sure to include
+ the key for this issue in your commit messages.
+* Have a set of appropriate tests.
+ For your convenience, a [set of test templates](https://github.com/hibernate/hibernate-test-case-templates/tree/main/reactive)
+ have been made available.
+
+ When submitting bug reports, the tests should reproduce the initially reported bug and illustrate that your solution addresses the issue.
+ For features/enhancements, the tests should demonstrate that the feature works as intended.
+ In both cases, be sure to incorporate your tests into the project to protect against possible regressions.
+* If applicable, documentation should be updated to reflect the introduced changes
+* The code compiles and the tests pass (`./gradlew clean build`)
+
+For documentation contributions, mainly to respect the project code style, especially in regard
+to the use of tabs - as mentioned above, code style templates are available for both IntelliJ IDEA and Eclipse
+IDEs. Ideally, these contributions would also have a corresponding issue, although this
+is less necessary for documentation contributions.
+
+## Getting Started
+
+If you are just getting started with Git, GitHub, and/or contributing to Hibernate via
+GitHub there are a few pre-requisite steps to follow:
+
+* Make sure you have a [GitHub account](https://github.com/signup/free)
+* [Fork](https://help.github.com/articles/fork-a-repo) the Hibernate Reactive repository. As discussed in
+the linked page, this also includes:
+ * [set up your local git install](https://help.github.com/articles/set-up-git)
+ * clone your fork
+* Instruct git to ignore certain commits when using `git blame`. From the directory of your local clone, run this: `git config blame.ignoreRevsFile .git-blame-ignore-revs`
+* See the wiki pages for setting up your IDE, whether you use
+[IntelliJ IDEA](https://hibernate.org/community/contribute/intellij-idea/)
+or [Eclipse](https://hibernate.org/community/contribute/eclipse-ide/)(1).
+
+
+## Create the working (topic) branch
+
+Create a [topic branch](https://git-scm.com/book/en/Git-Branching-Branching-Workflows#Topic-Branches)
+on which you will work. The convention is to incorporate the JIRA issue key in the name of this branch,
+although this is more of a mnemonic strategy than a hard-and-fast rule - but doing so helps:
+* Remember what each branch is for
+* Isolate the work from other contributions you may be working on
+
+_If there is not already a GitHub issue covering the work you want to do, create one._
+
+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..6ba9fcc5c 100644
--- a/README.md
+++ b/README.md
@@ -29,22 +29,26 @@ Learn more at .
Hibernate Reactive has been tested with:
-- Java 11, 17, 21, 23
-- PostgreSQL 16
-- MySQL 8
+- Java 11, 17, 21, 24
+- PostgreSQL 17
+- 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][] 6.6
+- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5
+- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5
+- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5
+- [Vert.x Reactive MS SQL Server Client](https://vertx.io/docs/vertx-mssql-client/java/) 4.5
+- [Vert.x Reactive Oracle Client](https://vertx.io/docs/vertx-oracle-client/java/) 4.5
- [Quarkus][Quarkus] via the Hibernate Reactive extension
+The exact version of the libraries and images are in the
+[catalog](https://github.com/hibernate/hibernate-reactive/blob/2.4/gradle/libs.versions.toml)
+and in the [tooling/docker](https://github.com/hibernate/hibernate-reactive/tree/2.4/tooling/docker) folder.
+
[PostgreSQL]: https://www.postgresql.org
[MySQL]: https://www.mysql.com
[MariaDB]: https://mariadb.com
diff --git a/build.gradle b/build.gradle
index 5aee62970..9d43d4bca 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,54 +3,14 @@ plugins {
id 'java-library'
id 'maven-publish'
- id 'com.diffplug.spotless' version '6.25.0'
- id 'org.asciidoctor.jvm.convert' version '4.0.2' apply false
- id 'io.github.gradle-nexus.publish-plugin' version '1.3.0'
+ alias(libs.plugins.com.diffplug.spotless)
+ alias(libs.plugins.org.asciidoctor.jvm.convert) apply false
}
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:
-//
-// ./gradlew clean build -PhibernateOrmVersion=5.6.15-SNAPSHOT
-ext {
- // Mainly, to allow CI to test the latest versions of Vert.X
- // Example:
- // ./gradlew build -PvertxSqlClientVersion=4.0.0-SNAPSHOT
- if ( !project.hasProperty( 'vertxSqlClientVersion' ) ) {
- vertxSqlClientVersion = '4.5.11'
- }
-
- testcontainersVersion = '1.20.4'
-
- 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'
@@ -77,9 +37,9 @@ subprojects {
// Useful for local development, it should be disabled otherwise
mavenLocal()
}
- // Example: ./gradlew build -PenableSonatypeOpenSourceSnapshotsRep
- if ( project.hasProperty('enableSonatypeOpenSourceSnapshotsRep') ) {
- maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
+ // Example: ./gradlew build -PenableCentralSonatypeSnapshotsRep
+ if ( project.hasProperty('enableCentralSonatypeSnapshotsRep') ) {
+ maven { url 'https://central.sonatype.com/repository/maven-snapshots/' }
}
mavenCentral()
@@ -91,6 +51,12 @@ subprojects {
options.encoding = 'UTF-8'
}
+ // Configure test tasks for all subprojects
+ tasks.withType( Test ).configureEach {
+ // Set the project root for finding Docker files - available to all modules
+ systemProperty 'hibernate.reactive.project.root', rootProject.projectDir.absolutePath
+ }
+
if ( !gradle.ext.javaToolchainEnabled ) {
sourceCompatibility = JavaVersion.toVersion( gradle.ext.baselineJavaVersion )
targetCompatibility = JavaVersion.toVersion( gradle.ext.baselineJavaVersion )
@@ -156,3 +122,10 @@ subprojects {
}
}
+rootProject.afterEvaluate {
+ // Workaround since "libs.versions.NAME" notation cannot be used here
+ def libs = project.extensions.getByType(VersionCatalogsExtension).named('libs')
+ logger.lifecycle "ORM version: ${libs.findVersion('hibernateOrmVersion').get().requiredVersion}"
+ logger.lifecycle "ORM Gradle plugin version: ${libs.findVersion('hibernateOrmGradlePluginVersion').get().requiredVersion}"
+ logger.lifecycle "Vert.x SQL Client version: ${libs.findVersion('vertxSqlClientVersion').get().requiredVersion}"
+}
diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile
index bb4315fb8..037d6eefb 100644
--- a/ci/release/Jenkinsfile
+++ b/ci/release/Jenkinsfile
@@ -1,9 +1,7 @@
#! /usr/bin/groovy
/*
- * Hibernate, Relational Persistence for Idiomatic Java
- *
- * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
- * See the lgpl.txt file in the root directory or .
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright Red Hat Inc. and Hibernate Authors
*/
/*
@@ -17,11 +15,10 @@ import org.hibernate.jenkins.pipeline.helpers.version.Version
// Global build configuration
env.PROJECT = "reactive"
env.JIRA_KEY = "HREACT"
-def RELEASE_ON_PUSH = false // Set to `true` *only* on branches where you want a release on each push.
+def RELEASE_ON_SCHEDULE = true // Set to `true` *only* on branches where you want a scheduled release.
print "INFO: env.PROJECT = ${env.PROJECT}"
print "INFO: env.JIRA_KEY = ${env.JIRA_KEY}"
-print "INFO: RELEASE_ON_PUSH = ${RELEASE_ON_PUSH}"
// --------------------------------------------
// Build conditions
@@ -34,10 +31,17 @@ if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) {
}
def manualRelease = currentBuild.getBuildCauses().toString().contains( 'UserIdCause' )
+def cronRelease = currentBuild.getBuildCauses().toString().contains( 'TimerTriggerCause' )
// Only do automatic release on branches where we opted in
-if ( !manualRelease && !RELEASE_ON_PUSH ) {
- print "INFO: Build skipped because automated releases are disabled on this branch. See constant RELEASE_ON_PUSH in ci/release/Jenkinsfile"
+if ( !manualRelease && !cronRelease ) {
+ print "INFO: Build skipped because automated releases on push are disabled on this branch."
+ currentBuild.result = 'NOT_BUILT'
+ return
+}
+
+if ( !manualRelease && cronRelease && !RELEASE_ON_SCHEDULE ) {
+ print "INFO: Build skipped because automated releases are disabled on this branch. See constant RELEASE_ON_SCHEDULE in ci/release/Jenkinsfile"
currentBuild.result = 'NOT_BUILT'
return
}
@@ -53,19 +57,23 @@ def checkoutReleaseScripts() {
}
}
+
// --------------------------------------------
// Pipeline
pipeline {
agent {
- label 'Worker&&Containers'
+ label 'Release'
+ }
+ triggers {
+ // Run every week Sunday 1 AM
+ cron('0 1 * * 0')
}
tools {
jdk 'OpenJDK 11 Latest'
}
options {
buildDiscarder logRotator(daysToKeepStr: '30', numToKeepStr: '10')
- rateLimitBuilds(throttle: [count: 1, durationName: 'day', userBoost: true])
disableConcurrentBuilds(abortPrevious: false)
preserveStashes()
}
@@ -89,9 +97,13 @@ pipeline {
)
}
stages {
- stage('Release check') {
+ stage('Check') {
steps {
script {
+ print "INFO: params.RELEASE_VERSION = ${params.RELEASE_VERSION}"
+ print "INFO: params.DEVELOPMENT_VERSION = ${params.DEVELOPMENT_VERSION}"
+ print "INFO: params.RELEASE_DRY_RUN? = ${params.RELEASE_DRY_RUN}"
+
checkoutReleaseScripts()
def currentVersion = Version.parseDevelopmentVersion( sh(
@@ -107,7 +119,9 @@ pipeline {
echo "Release was requested manually"
if ( !params.RELEASE_VERSION ) {
- throw new IllegalArgumentException( 'Missing value for parameter RELEASE_VERSION. This parameter must be set explicitly to prevent mistakes.' )
+ throw new IllegalArgumentException(
+ 'Missing value for parameter RELEASE_VERSION. This parameter must be set explicitly to prevent mistakes.'
+ )
}
releaseVersion = Version.parseReleaseVersion( params.RELEASE_VERSION )
@@ -118,12 +132,15 @@ pipeline {
else {
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)
- if (lastCommitter == 'Hibernate-CI' && secondLastCommitter == 'Hibernate-CI') {
- print "INFO: Automatic release skipped because last commits were for the previous release"
- currentBuild.result = 'ABORTED'
+ // Avoid doing an automatic release if there are no "releasable" commits since the last release (see release scripts for determination)
+ def releasableCommitCount = sh(
+ script: ".release/scripts/count-releasable-commits.sh ${env.PROJECT}",
+ returnStdout: true
+ ).trim().toInteger()
+ if ( releasableCommitCount <= 0 ) {
+ print "INFO: Automatic release skipped because no releasable commits were pushed since the previous release"
+ currentBuild.getRawBuild().getExecutor().interrupt(Result.NOT_BUILT)
+ sleep(1) // Interrupt is not blocking and does not take effect immediately.
return
}
@@ -147,16 +164,38 @@ pipeline {
env.RELEASE_VERSION = releaseVersion.toString()
env.DEVELOPMENT_VERSION = developmentVersion.toString()
- // Dry run is not supported at the moment
- env.SCRIPT_OPTIONS = params.RELEASE_DRY_RUN ? "-d" : ""
+ env.SCRIPT_OPTIONS = params.RELEASE_DRY_RUN ? "-d" : ""
+ env.JRELEASER_DRY_RUN = params.RELEASE_DRY_RUN
+ }
+ }
+ }
+ stage('Prepare') {
+ steps {
+ script {
+ checkoutReleaseScripts()
- // 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)
- // sh ".release/scripts/determine-jira-version-id.sh ${env.JIRA_KEY} ${releaseVersion.withoutFinalQualifier}"
+ configFileProvider([
+ configFile(fileId: 'release.config.ssh', targetLocation: "${env.HOME}/.ssh/config"),
+ configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts")
+ ]) {
+ sshagent(['ed25519.Hibernate-CI.github.com']) {
+ // set release version
+ // update changelog from JIRA
+ // tags the version
+ // changes the version to the provided development version
+ withEnv([
+ "DISABLE_REMOTE_GRADLE_CACHE=true",
+ // 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}"
+ }
+ }
+ }
}
}
}
- stage('Release prepare') {
+ stage('Publish') {
steps {
script {
checkoutReleaseScripts()
@@ -166,22 +205,20 @@ 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'),
- file(credentialsId: 'release.gpg.private-key', variable: 'RELEASE_GPG_PRIVATE_KEY_PATH'),
- string(credentialsId: 'release.gpg.passphrase', variable: 'RELEASE_GPG_PASSPHRASE')
+ usernamePassword(credentialsId: 'central.sonatype.com', passwordVariable: 'JRELEASER_MAVENCENTRAL_TOKEN', usernameVariable: 'JRELEASER_MAVENCENTRAL_USERNAME'),
+ gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default'),
+ file(credentialsId: 'release.gpg.private-key', variable: 'RELEASE_GPG_PRIVATE_KEY_PATH'),
+ string(credentialsId: 'release.gpg.passphrase', variable: 'JRELEASER_GPG_PASSPHRASE'),
+ string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN')
]) {
- sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) {
- // set release version
- // update changelog from JIRA
- // tags the version
- // changes the version to the provided development version
+ sshagent(['ed25519.Hibernate-CI.github.com', 'jenkins.in.relation.to']) {
+ // performs documentation upload and Sonatype release
+ // push to github
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'"
+ "DISABLE_REMOTE_GRADLE_CACHE=true"
]) {
- sh ".release/scripts/prepare-release.sh ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION}"
+ def ghReleaseNote = sh('realpath -e github_release_notes.md 2>/dev/null', returnStdout: true).trim()
+ sh ".release/scripts/publish.sh -j ${ghReleaseNote != '' ? '--notes=' + ghReleaseNote : ''} ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION} ${env.GIT_BRANCH}"
}
}
}
@@ -189,7 +226,7 @@ pipeline {
}
}
}
- stage('Publish release') {
+ stage('Update website') {
steps {
script {
checkoutReleaseScripts()
@@ -199,16 +236,17 @@ 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'),
- 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')
+ gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default')
]) {
- 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}"
+ sshagent( ['ed25519.Hibernate-CI.github.com'] ) {
+ dir( '.release/hibernate.org' ) {
+ checkout scmGit(
+ branches: [[name: '*/production']],
+ extensions: [],
+ userRemoteConfigs: [[credentialsId: 'ed25519.Hibernate-CI.github.com', url: 'https://github.com/hibernate/hibernate.org.git']]
+ )
+ sh "../scripts/website-release.sh ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION}"
+ }
}
}
}
@@ -223,4 +261,4 @@ pipeline {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile
index c95f6663f..8af583e13 100644
--- a/ci/snapshot-publish.Jenkinsfile
+++ b/ci/snapshot-publish.Jenkinsfile
@@ -10,9 +10,17 @@ 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'
@@ -30,16 +38,22 @@ 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([
+ // 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: 'central.sonatype.com', 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 +65,4 @@ pipeline {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/documentation/build.gradle b/documentation/build.gradle
index aa6a27622..95c3dcb2b 100644
--- a/documentation/build.gradle
+++ b/documentation/build.gradle
@@ -52,7 +52,7 @@ def aggregateJavadocsTask = tasks.register( 'aggregateJavadocs', Javadoc ) {
use = true
options.encoding = 'UTF-8'
- def matcher = hibernateOrmVersion =~ /\d+\.\d+/
+ def matcher = libs.versions.hibernateOrmVersion =~ /\d+\.\d+/
def ormMinorVersion = matcher.find() ? matcher.group() : "5.6";
links = [
diff --git a/examples/native-sql-example/build.gradle b/examples/native-sql-example/build.gradle
index fabdbc7fa..50c5a4117 100644
--- a/examples/native-sql-example/build.gradle
+++ b/examples/native-sql-example/build.gradle
@@ -8,9 +8,9 @@ buildscript {
mavenLocal()
}
// Optional: Enables snapshots repository
- // Example: ./gradlew build -PenableSonatypeOpenSourceSnapshotsRep
- if ( project.hasProperty('enableSonatypeOpenSourceSnapshotsRep') ) {
- maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
+ // Example: ./gradlew build -PenableCentralSonatypeSnapshotsRep
+ if ( project.hasProperty('enableCentralSonatypeSnapshotsRep') ) {
+ maven { url 'https://central.sonatype.com/repository/maven-snapshots/' }
}
mavenCentral()
}
@@ -18,7 +18,7 @@ buildscript {
plugins {
// Optional: Hibernate Gradle plugin to enable bytecode enhancements
- id "org.hibernate.orm" version "${hibernateOrmGradlePluginVersion}"
+ alias(libs.plugins.org.hibernate.orm)
}
description = 'Hibernate Reactive native SQL Example'
@@ -27,20 +27,20 @@ dependencies {
implementation project( ':hibernate-reactive-core' )
// Hibernate Validator (optional)
- implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final'
- runtimeOnly 'org.glassfish.expressly:expressly:5.0.0'
+ implementation(libs.org.hibernate.validator.hibernate.validator)
+ runtimeOnly(libs.org.glassfish.expressly.expressly)
// JPA metamodel generation for criteria queries (optional)
- annotationProcessor "org.hibernate.orm:hibernate-jpamodelgen:${hibernateOrmVersion}"
+ annotationProcessor(libs.org.hibernate.orm.hibernate.jpamodelgen)
// database driver for PostgreSQL
- runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}"
+ runtimeOnly(libs.io.vertx.vertx.pg.client)
// logging (optional)
- runtimeOnly "org.apache.logging.log4j:log4j-core:2.20.0"
+ runtimeOnly(libs.org.apache.logging.log4j.log4j.core)
// Allow authentication to PostgreSQL using SCRAM:
- runtimeOnly 'com.ongres.scram:client:2.1'
+ runtimeOnly(libs.com.ongres.scram.client)
}
// Optional: enable the bytecode enhancements
@@ -75,3 +75,36 @@ tasks.register( "runAllExamples" ) {
dependsOn = ["runAllExamplesOnPostgreSQL"]
description = "Run all examples on ${dbs}"
}
+
+// Optional: Task to print the resolved versions of Hibernate ORM and Vert.x
+tasks.register( "printResolvedVersions" ) {
+ description = "Print the resolved hibernate-orm-core and vert.x versions"
+ doLast {
+ def hibernateCoreVersion = "n/a"
+ def vertxVersion = "n/a"
+
+ // Resolve Hibernate Core and Vert.x versions from compile classpath
+ configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts.each { artifact ->
+ if (artifact.moduleVersion.id.name == 'hibernate-core') {
+ hibernateCoreVersion = artifact.moduleVersion.id.version
+ }
+ if (artifact.moduleVersion.id.group == 'io.vertx' && artifact.moduleVersion.id.name == 'vertx-sql-client') {
+ vertxVersion = artifact.moduleVersion.id.version
+ }
+ }
+
+ // Print the resolved versions
+ println "Resolved Hibernate ORM Core Version: ${hibernateCoreVersion}"
+ println "Resolved Vert.x SQL client Version: ${vertxVersion}"
+ }
+}
+
+// Make the version printing task run before tests and JavaExec tasks
+tasks.withType( Test ).configureEach {
+ dependsOn printResolvedVersions
+}
+
+tasks.withType( JavaExec ).configureEach {
+ dependsOn printResolvedVersions
+}
+
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..41f9135a5 100644
--- a/examples/session-example/build.gradle
+++ b/examples/session-example/build.gradle
@@ -8,9 +8,9 @@ buildscript {
mavenLocal()
}
// Optional: Enables snapshots repository
- // Example: ./gradlew build -PenableSonatypeOpenSourceSnapshotsRep
- if ( project.hasProperty('enableSonatypeOpenSourceSnapshotsRep') ) {
- maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
+ // Example: ./gradlew build -PenableCentralSonatypeSnapshotsRep
+ if ( project.hasProperty('enableCentralSonatypeSnapshotsRep') ) {
+ maven { url 'https://central.sonatype.com/repository/maven-snapshots/' }
}
mavenCentral()
}
@@ -18,7 +18,7 @@ buildscript {
plugins {
// Optional: Hibernate Gradle plugin to enable bytecode enhancements
- id "org.hibernate.orm" version "${hibernateOrmGradlePluginVersion}"
+ alias(libs.plugins.org.hibernate.orm)
}
description = 'Hibernate Reactive Session Examples'
@@ -27,21 +27,21 @@ dependencies {
implementation project( ':hibernate-reactive-core' )
// Hibernate Validator (optional)
- implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final'
- runtimeOnly 'org.glassfish.expressly:expressly:5.0.0'
+ implementation(libs.org.hibernate.validator.hibernate.validator)
+ runtimeOnly(libs.org.glassfish.expressly.expressly)
// JPA metamodel generation for criteria queries (optional)
- annotationProcessor "org.hibernate.orm:hibernate-jpamodelgen:${hibernateOrmVersion}"
+ annotationProcessor(libs.org.hibernate.orm.hibernate.jpamodelgen)
// database drivers for PostgreSQL and MySQL
- runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}"
- runtimeOnly "io.vertx:vertx-mysql-client:${vertxSqlClientVersion}"
+ runtimeOnly(libs.io.vertx.vertx.pg.client)
+ runtimeOnly(libs.io.vertx.vertx.mysql.client)
// logging (optional)
- runtimeOnly "org.apache.logging.log4j:log4j-core:2.20.0"
+ runtimeOnly(libs.org.apache.logging.log4j.log4j.core)
// Allow authentication to PostgreSQL using SCRAM:
- runtimeOnly 'com.ongres.scram:client:2.1'
+ runtimeOnly(libs.com.ongres.scram.client)
}
// Optional: enable the bytecode enhancements
@@ -81,3 +81,35 @@ tasks.register( "runAllExamples" ) {
dependsOn = ["runAllExamplesOnPostgreSQL", "runAllExamplesOnMySQL"]
description = "Run all examples on ${dbs}"
}
+
+// Optional: Task to print the resolved versions of Hibernate ORM and Vert.x
+tasks.register( "printResolvedVersions" ) {
+ description = "Print the resolved hibernate-orm-core and vert.x versions"
+ doLast {
+ def hibernateCoreVersion = "n/a"
+ def vertxVersion = "n/a"
+
+ // Resolve Hibernate Core and Vert.x versions from compile classpath
+ configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts.each { artifact ->
+ if (artifact.moduleVersion.id.name == 'hibernate-core') {
+ hibernateCoreVersion = artifact.moduleVersion.id.version
+ }
+ if (artifact.moduleVersion.id.group == 'io.vertx' && artifact.moduleVersion.id.name == 'vertx-sql-client') {
+ vertxVersion = artifact.moduleVersion.id.version
+ }
+ }
+
+ // Print the resolved versions
+ println "Resolved Hibernate ORM Core Version: ${hibernateCoreVersion}"
+ println "Resolved Vert.x SQL client Version: ${vertxVersion}"
+ }
+}
+
+// Make the version printing task run before tests and JavaExec tasks
+tasks.withType( Test ).configureEach {
+ dependsOn printResolvedVersions
+}
+
+tasks.withType( JavaExec ).configureEach {
+ dependsOn printResolvedVersions
+}
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/github_release_notes.md b/github_release_notes.md
new file mode 100644
index 000000000..db3469c5d
--- /dev/null
+++ b/github_release_notes.md
@@ -0,0 +1,3 @@
+
+* See the [website](https://hibernate.org/reactive/releases/{{releaseVersionFamily}}) for requirements and compatibilities.
+* See the [What's New](https://hibernate.org/reactive/releases/{{releaseVersionFamily}}/#whats-new) guide for details about new features and capabilities.
diff --git a/gradle.properties b/gradle.properties
index 55ec51fc8..346e9fa42 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -18,7 +18,9 @@ org.gradle.java.installations.auto-download=false
#########################################################################
# Additional custom gradle build properties.
-# Please, leave these properties commented upstream
+# Please, leave these properties commented upstream.
+# They are meant to be used for local builds or WIP branches.
+# The same properties can be set from the command line.
##########################################################################
# Enable Testcontainers + Docker when present (value ignored)
@@ -28,28 +30,28 @@ org.gradle.java.installations.auto-download=false
# Db2, MySql, PostgreSQL, CockroachDB, SqlServer, Oracle
#db = MSSQL
-# Enable the SonatypeOS maven repository (mainly for Vert.x snapshots) when present (value ignored)
-#enableSonatypeOpenSourceSnapshotsRep = true
+# Enable the maven Central Snapshot repository, when set to any value (the value is ignored)
+#enableCentralSonatypeSnapshotsRep = true
# Enable the maven local repository (for local development when needed) when present (value ignored)
#enableMavenLocalRepo = true
+### Settings the following properties will override the version defined in gradle/libs.versions.toml
+
# The default Hibernate ORM version (override using `-PhibernateOrmVersion=the.version.you.want`)
-hibernateOrmVersion = 6.6.3.Final
+#hibernateOrmVersion = 7.0.2.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.2.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 = 4.5.16-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 = 4.5.16
+#vertxWebtClientVersion = 4.5.16
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 000000000..00dcf0fbb
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,60 @@
+[versions]
+assertjVersion = "3.27.6"
+hibernateOrmVersion = "6.6.34.Final"
+hibernateOrmGradlePluginVersion = "6.6.34.Final"
+jacksonDatabindVersion = "2.20.1"
+jbossLoggingAnnotationVersion = "3.0.1.Final"
+jbossLoggingVersion = "3.5.0.Final"
+junitVersion = "5.13.3"
+junitPlatformVersion = "1.13.3"
+log4jVersion = "2.25.2"
+testcontainersVersion = "1.21.3"
+vertxSqlClientVersion = "4.5.22"
+vertxWebVersion= "4.5.22"
+vertxWebClientVersion = "4.5.22"
+
+[libraries]
+com-fasterxml-jackson-core-jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jacksonDatabindVersion" }
+com-ibm-db2-jcc = { group = "com.ibm.db2", name = "jcc", version = "12.1.2.0" }
+com-microsoft-sqlserver-mssql-jdbc = { group = "com.microsoft.sqlserver", name = "mssql-jdbc", version = "13.2.1.jre11" }
+com-mysql-mysql-connector-j = { group = "com.mysql", name = "mysql-connector-j", version = "9.5.0" }
+com-ongres-scram-client = { group = "com.ongres.scram", name = "client", version = "2.1" }
+io-smallrye-reactive-mutiny = { group = "io.smallrye.reactive", name = "mutiny", version = "2.7.0" }
+io-vertx-vertx-db2-client = { group = "io.vertx", name = "vertx-db2-client", version.ref = "vertxSqlClientVersion" }
+io-vertx-vertx-junit5 = { group = "io.vertx", name = "vertx-junit5", version.ref = "vertxSqlClientVersion" }
+io-vertx-vertx-micrometer-metrics = { group = "io.vertx", name = "vertx-micrometer-metrics", version.ref = "vertxSqlClientVersion" }
+io-vertx-vertx-mssql-client = { group = "io.vertx", name = "vertx-mssql-client", version.ref = "vertxSqlClientVersion" }
+io-vertx-vertx-mysql-client = { group = "io.vertx", name = "vertx-mysql-client", version.ref = "vertxSqlClientVersion" }
+io-vertx-vertx-oracle-client = { group = "io.vertx", name = "vertx-oracle-client", version.ref = "vertxSqlClientVersion" }
+io-vertx-vertx-pg-client = { group = "io.vertx", name = "vertx-pg-client", version.ref = "vertxSqlClientVersion" }
+io-vertx-vertx-sql-client = { group = "io.vertx", name = "vertx-sql-client", version.ref = "vertxSqlClientVersion" }
+io-vertx-vertx-web = { group = "io.vertx", name = "vertx-web", version.ref = "vertxWebVersion" }
+io-vertx-vertx-web-client = { group = "io.vertx", name = "vertx-web-client", version.ref = "vertxWebClientVersion" }
+org-apache-logging-log4j-log4j-core = { group = "org.apache.logging.log4j", name = "log4j-core", version.ref = "log4jVersion" }
+org-assertj-assertj-core = { group = "org.assertj", name = "assertj-core", version.ref = "assertjVersion" }
+org-ehcache-ehcache = { group = "org.ehcache", name = "ehcache", version = "3.11.1" }
+org-glassfish-expressly-expressly = { group = "org.glassfish.expressly", name = "expressly", version = "5.0.0" }
+org-hibernate-orm-hibernate-core = { group = "org.hibernate.orm", name = "hibernate-core", version.ref = "hibernateOrmVersion" }
+org-hibernate-orm-hibernate-jcache = { group = "org.hibernate.orm", name = "hibernate-jcache", version.ref = "hibernateOrmVersion" }
+org-hibernate-orm-hibernate-jpamodelgen = { group = "org.hibernate.orm", name = "hibernate-jpamodelgen", version.ref = "hibernateOrmVersion" }
+org-hibernate-validator-hibernate-validator = { group = "org.hibernate.validator", name = "hibernate-validator", version = "8.0.3.Final" }
+org-jboss-logging-jboss-logging = { group = "org.jboss.logging", name = "jboss-logging", version.ref = "jbossLoggingVersion" }
+org-jboss-logging-jboss-logging-annotations = { group = "org.jboss.logging", name = "jboss-logging-annotations", version.ref = "jbossLoggingAnnotationVersion" }
+org-jboss-logging-jboss-logging-processor = { group = "org.jboss.logging", name = "jboss-logging-processor", version.ref = "jbossLoggingAnnotationVersion" }
+org-junit-jupiter-junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junitVersion" }
+org-junit-jupiter-junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junitVersion" }
+org-junit-platform-junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher", version = "junitPlatformVersion" }
+org-mariadb-jdbc-mariadb-java-client = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version = "3.5.6" }
+org-postgresql-postgresql = { group = "org.postgresql", name = "postgresql", version = "42.7.8" }
+org-testcontainers-cockroachdb = { group = "org.testcontainers", name = "cockroachdb", version.ref = "testcontainersVersion" }
+org-testcontainers-db2 = { group = "org.testcontainers", name = "db2", version.ref = "testcontainersVersion" }
+org-testcontainers-mariadb = { group = "org.testcontainers", name = "mariadb", version.ref = "testcontainersVersion" }
+org-testcontainers-mssqlserver = { group = "org.testcontainers", name = "mssqlserver", version.ref = "testcontainersVersion" }
+org-testcontainers-mysql = { group = "org.testcontainers", name = "mysql", version.ref = "testcontainersVersion" }
+org-testcontainers-oracle-xe = { group = "org.testcontainers", name = "oracle-xe", version.ref = "testcontainersVersion" }
+org-testcontainers-postgresql = { group = "org.testcontainers", name = "postgresql", version.ref = "testcontainersVersion" }
+
+[plugins]
+com-diffplug-spotless = { id = "com.diffplug.spotless", version = "7.1.0" }
+org-asciidoctor-jvm-convert = { id = "org.asciidoctor.jvm.convert", version = "4.0.5" }
+org-hibernate-orm = { id = "org.hibernate.orm", version.ref = "hibernateOrmGradlePluginVersion" }
diff --git a/gradle/version.properties b/gradle/version.properties
index dd20a5fa4..85fe62918 100644
--- a/gradle/version.properties
+++ b/gradle/version.properties
@@ -1 +1 @@
-projectVersion=2.4.3-SNAPSHOT
\ No newline at end of file
+projectVersion=2.4.9-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..ba8630386 100644
--- a/hibernate-reactive-core/build.gradle
+++ b/hibernate-reactive-core/build.gradle
@@ -8,76 +8,77 @@ apply from: publishScript
dependencies {
- api "org.hibernate.orm:hibernate-core:${hibernateOrmVersion}"
+ api(libs.org.hibernate.orm.hibernate.core)
- api 'io.smallrye.reactive:mutiny:2.7.0'
+ api(libs.io.smallrye.reactive.mutiny)
//Logging
- implementation 'org.jboss.logging:jboss-logging:3.5.0.Final'
- compileOnly 'org.jboss.logging:jboss-logging-annotations:2.2.1.Final'
+ implementation(libs.org.jboss.logging.jboss.logging)
+ compileOnly(libs.org.jboss.logging.jboss.logging.annotations)
- 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'
+ annotationProcessor(libs.org.jboss.logging.jboss.logging)
+ annotationProcessor(libs.org.jboss.logging.jboss.logging.annotations)
+ annotationProcessor(libs.org.jboss.logging.jboss.logging.processor)
//Specific implementation details of Hibernate Reactive:
- implementation "io.vertx:vertx-sql-client:${vertxSqlClientVersion}"
+ implementation(libs.io.vertx.vertx.sql.client)
// Testing
- testImplementation 'org.assertj:assertj-core:3.26.3'
- testImplementation "io.vertx:vertx-junit5:${vertxSqlClientVersion}"
+ testImplementation(libs.org.assertj.assertj.core)
+ testImplementation(libs.io.vertx.vertx.junit5)
// Drivers
- testImplementation "io.vertx:vertx-pg-client:${vertxSqlClientVersion}"
- testImplementation "io.vertx:vertx-mysql-client:${vertxSqlClientVersion}"
- testImplementation "io.vertx:vertx-db2-client:${vertxSqlClientVersion}"
- testImplementation "io.vertx:vertx-mssql-client:${vertxSqlClientVersion}"
- testImplementation "io.vertx:vertx-oracle-client:${vertxSqlClientVersion}"
+ testImplementation(libs.io.vertx.vertx.pg.client)
+ testImplementation(libs.io.vertx.vertx.mysql.client)
+ testImplementation(libs.io.vertx.vertx.db2.client)
+ testImplementation(libs.io.vertx.vertx.mssql.client)
+ testImplementation(libs.io.vertx.vertx.oracle.client)
// Metrics
- testImplementation "io.vertx:vertx-micrometer-metrics:${vertxSqlClientVersion}"
+ testImplementation(libs.io.vertx.vertx.micrometer.metrics)
// Optional dependency of vertx-pg-client, essential when connecting via SASL SCRAM
- testImplementation 'com.ongres.scram:client:2.1'
+ testImplementation(libs.com.ongres.scram.client)
// JUnit Jupiter
- testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.3'
- testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.11.3'
+ testImplementation(libs.org.junit.jupiter.junit.jupiter.api)
+ testRuntimeOnly(libs.org.junit.jupiter.junit.jupiter.engine)
+ testRuntimeOnly(libs.org.junit.platform.junit.platform.launcher)
// JDBC driver to test with ORM and PostgreSQL
- testRuntimeOnly "org.postgresql:postgresql:42.7.4"
+ testRuntimeOnly(libs.org.postgresql.postgresql)
// JDBC driver for Testcontainers with MS SQL Server
- testRuntimeOnly "com.microsoft.sqlserver:mssql-jdbc:12.8.1.jre11"
+ testRuntimeOnly(libs.com.microsoft.sqlserver.mssql.jdbc)
// JDBC driver for Testcontainers with MariaDB Server
- testRuntimeOnly "org.mariadb.jdbc:mariadb-java-client:3.5.1"
+ testRuntimeOnly(libs.org.mariadb.jdbc.mariadb.java.client)
// JDBC driver for Testcontainers with MYSQL Server
- testRuntimeOnly "com.mysql:mysql-connector-j:9.1.0"
+ testRuntimeOnly(libs.com.mysql.mysql.connector.j)
// JDBC driver for Db2 server, for testing
- testRuntimeOnly "com.ibm.db2:jcc:12.1.0.0"
+ testRuntimeOnly(libs.com.ibm.db2.jcc)
// EHCache
- testRuntimeOnly ("org.ehcache:ehcache:3.10.8") {
+ testRuntimeOnly(libs.org.ehcache.ehcache) {
capabilities {
requireCapability 'org.ehcache.modules:ehcache-xml-jakarta'
}
}
- testRuntimeOnly ("org.hibernate.orm:hibernate-jcache:${hibernateOrmVersion}")
+ testRuntimeOnly(libs.org.hibernate.orm.hibernate.jcache)
// log4j
- testRuntimeOnly 'org.apache.logging.log4j:log4j-core:2.20.0'
+ testRuntimeOnly(libs.org.apache.logging.log4j.log4j.core)
// Testcontainers
- testImplementation "org.testcontainers:postgresql:${testcontainersVersion}"
- testImplementation "org.testcontainers:mysql:${testcontainersVersion}"
- testImplementation "org.testcontainers:mariadb:${testcontainersVersion}"
- testImplementation "org.testcontainers:db2:${testcontainersVersion}"
- testImplementation "org.testcontainers:cockroachdb:${testcontainersVersion}"
- testImplementation "org.testcontainers:mssqlserver:${testcontainersVersion}"
- testImplementation "org.testcontainers:oracle-xe:${testcontainersVersion}"
+ testImplementation(libs.org.testcontainers.postgresql)
+ testImplementation(libs.org.testcontainers.mysql)
+ testImplementation(libs.org.testcontainers.mariadb)
+ testImplementation(libs.org.testcontainers.db2)
+ testImplementation(libs.org.testcontainers.cockroachdb)
+ testImplementation(libs.org.testcontainers.mssqlserver)
+ testImplementation(libs.org.testcontainers.oracle.xe)
}
// Print a summary of the results of the tests (number of failures, successes and skipped)
@@ -157,3 +158,35 @@ tasks.register( "testAll", Test ) {
description = "Run tests for ${dbs}"
dependsOn = dbs.collect( [] as HashSet ) { db -> "testDb${db}" }
}
+
+// Task to print the resolved versions of Hibernate ORM and Vert.x
+tasks.register( "printResolvedVersions" ) {
+ description = "Print the resolved hibernate-orm-core and vert.x versions"
+ doLast {
+ def hibernateCoreVersion = "n/a"
+ def vertxVersion = "n/a"
+
+ // Resolve Hibernate Core and Vert.x versions from compile classpath
+ configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts.each { artifact ->
+ if (artifact.moduleVersion.id.name == 'hibernate-core') {
+ hibernateCoreVersion = artifact.moduleVersion.id.version
+ }
+ if (artifact.moduleVersion.id.group == 'io.vertx' && artifact.moduleVersion.id.name == 'vertx-sql-client') {
+ vertxVersion = artifact.moduleVersion.id.version
+ }
+ }
+
+ // Print the resolved versions
+ println "Resolved Hibernate ORM Core Version: ${hibernateCoreVersion}"
+ println "Resolved Vert.x SQL client Version: ${vertxVersion}"
+ }
+}
+
+// Make the version printing task run before tests and JavaExec tasks
+tasks.withType( Test ).configureEach {
+ dependsOn printResolvedVersions
+}
+
+tasks.withType( JavaExec ).configureEach {
+ dependsOn printResolvedVersions
+}
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..41dd0162b
--- /dev/null
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java
@@ -0,0 +1,490 @@
+/* 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.SessionImplementor;
+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
+ // 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>
+ && result instanceof PersistentCollection> ) {
+ PersistentCollection> resultPersistentCollection = (PersistentCollection>) result;
+ PersistentCollection> originalPersistentCollection = (PersistentCollection>) original;
+ return preserveSnapshot(
+ originalPersistentCollection, resultPersistentCollection,
+ elemType, owner, copyCache, session
+ ).thenApply( v -> {
+ if ( !originalPersistentCollection.isDirty() ) {
+ resultPersistentCollection.clearDirty();
+ }
+ return result;
+ } );
+ }
+ else {
+ return completedFuture( result );
+ }
+ } );
+ }
+
+ private static CompletionStage replaceMapTypeElements(
+ CollectionType type,
+ Map original,
+ Map target,
+ Object owner,
+ Map copyCache,
+ SessionImplementor session) {
+ final CollectionPersister persister = session.getFactory().getRuntimeMetamodels()
+ .getMappingMetamodel().getCollectionDescriptor( type.getRole() );
+ final Map result = target;
+ result.clear();
+
+ 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 -> result.put( key, value ) )
+ );
+ }
+ ).thenApply( unused -> result );
+ }
+
+ private static CompletionStage replaceArrayTypeElements(
+ CollectionType type,
+ Object original,
+ Object target,
+ Object owner,
+ Map copyCache,
+ SessionImplementor 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,
+ SessionImplementor session,
+ Map copyCache) {
+ return getReplace( elemType, o, null, owner, session, copyCache );
+ }
+
+ private static CompletionStage getReplace(
+ Type elemType,
+ Object o,
+ Object target,
+ Object owner,
+ SessionImplementor session,
+ Map copyCache) {
+ if ( elemType instanceof EntityType ) {
+ return EntityTypes.replace( (EntityType) elemType, o, target, session, owner, copyCache );
+ }
+ else {
+ final Object replace = elemType.replace( o, target, session, owner, copyCache );
+ return completedFuture( replace );
+ }
+ }
+
+ /**
+ * @see CollectionType#preserveSnapshot(PersistentCollection, PersistentCollection, Type, Object, Map, SharedSessionContractImplementor)
+ */
+ private static CompletionStage preserveSnapshot(
+ PersistentCollection> original,
+ PersistentCollection> result,
+ Type elemType,
+ Object owner,
+ Map copyCache,
+ SessionImplementor session) {
+ final CollectionEntry ce = session.getPersistenceContextInternal().getCollectionEntry( result );
+ if ( ce != null ) {
+ return createSnapshot( original, result, elemType, owner, copyCache, session )
+ .thenAccept( serializable -> ce.resetStoredSnapshot( result, serializable ) );
+ }
+ return voidFuture();
+ }
+
+ /**
+ * @see CollectionType#createSnapshot(PersistentCollection, PersistentCollection, Type, Object, Map, SharedSessionContractImplementor)
+ */
+ private static CompletionStage createSnapshot(
+ PersistentCollection> original,
+ PersistentCollection> result,
+ Type elemType,
+ Object owner,
+ Map copyCache,
+ SessionImplementor session) {
+ final Serializable originalSnapshot = original.getStoredSnapshot();
+ if ( originalSnapshot instanceof List> ) {
+ List> list = (List>) originalSnapshot;
+ return createListSnapshot( list, elemType, owner, copyCache, session );
+ }
+ else if ( originalSnapshot instanceof Map, ?> ) {
+ Map, ?> map = (Map, ?>) originalSnapshot;
+ return createMapSnapshot( map, result, elemType, owner, copyCache, session );
+ }
+ else if ( originalSnapshot instanceof Object[] ) {
+ Object[] array = (Object[]) originalSnapshot;
+ 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,
+ SessionImplementor 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,
+ SessionImplementor session) {
+ final Map, ?> resultSnapshot = (Map, ?>) result.getStoredSnapshot();
+ final Map targetMap;
+ if ( map instanceof SortedMap, ?> ) {
+ SortedMap, ?> sortedMap = (SortedMap, ?>) map;
+ //noinspection unchecked, rawtypes
+ targetMap = new TreeMap( sortedMap.comparator() );
+ }
+ else {
+ targetMap = mapOfSize( map.size() );
+ }
+ return loop(
+ map.entrySet(), entry ->
+ getReplace( elemType, entry.getValue(), resultSnapshot, owner, session, copyCache )
+ .thenAccept( newValue -> {
+ final Object key = entry.getKey();
+ targetMap.put( key == entry.getValue() ? newValue : key, newValue );
+ } )
+ ).thenApply( v -> (Serializable) targetMap );
+ }
+
+ /**
+ * @see CollectionType#createListSnapshot(List, Type, Object, Map, SharedSessionContractImplementor)
+ */
+ private static CompletionStage createListSnapshot(
+ List> list,
+ Type elemType,
+ Object owner,
+ Map copyCache,
+ SessionImplementor 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> && ( (PersistentCollection>) target).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..fb1c29fe4 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
@@ -25,6 +25,7 @@
import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister;
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;
@@ -137,7 +138,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;
@@ -158,42 +159,8 @@ public static CompletionStage replace(
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();
- }
- } )
+ return loop(
+ 0, types.length, i -> replace( original, target, types, session, owner, copyCache, i, copied )
).thenApply( v -> copied );
}
@@ -209,43 +176,9 @@ public static CompletionStage replace(
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();
- }
- } )
+ return loop(
+ 0, types.length,
+ i -> replace( original, target, types, session, owner, copyCache, foreignKeyDirection, i, copied )
).thenApply( v -> copied );
}
@@ -272,7 +205,7 @@ 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,
@@ -336,15 +269,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 ( (ReactiveSessionImpl) session )
+ .reactiveFetch( id, true )
.thenCompose( fetched -> {
- Object idOrUniqueKey = entityType.getIdentifierOrUniqueKeyType( session.getFactory() )
+ Object idOrUniqueKey = entityType
+ .getIdentifierOrUniqueKeyType( session.getFactory() )
.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 );
} );
} );
@@ -426,9 +360,9 @@ private static CompletionStage getIdentifierFromHibernateProxy(
if ( type.isEntityIdentifierMapping() ) {
propertyValue = getIdentifier( (EntityType) type, propertyValue, (SessionImplementor) session );
}
- return completedFuture( propertyValue );
+ return propertyValue;
}
- return nullFuture();
+ return null;
} );
}
@@ -450,4 +384,100 @@ private static CompletionStage loadHibernateProxyEntity(
}
}
+ private static CompletionStage replace(
+ Object[] original,
+ Object[] target,
+ Type[] types,
+ SessionImplementor 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,
+ SessionImplementor 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/event/impl/AbstractReactiveSaveEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java
index e9d68b00e..c0ef66b17 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
@@ -144,55 +144,59 @@ 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;
+ }
+ );
}
/**
@@ -232,10 +236,7 @@ protected CompletionStage reactivePerformSave(
if ( persister.getGenerator() instanceof Assigned ) {
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()'" ) );
}
}
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..6b2392e1e 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
@@ -48,6 +48,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,7 +200,7 @@ private CompletionStage fetchAndDelete(DeleteEvent event, DeleteContext tr
}
//Object entity = persistenceContext.unproxyAndReassociate( event.getObject() );
- return ( (ReactiveSession) source )
+ return ( (ReactiveQueryProducer) source )
.reactiveFetch( objectEvent, true )
.thenCompose( entity -> delete( event, transientEntities, entity ) );
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java
index 7832298e0..a75ac1194 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
@@ -35,6 +35,7 @@
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 org.hibernate.pretty.MessageHelper.infoString;
@@ -76,7 +77,7 @@ public CompletionStage reactiveOnLock(LockEvent event) throws HibernateExc
//TODO: if object was an uninitialized proxy, this is inefficient,
// resulting in two SQL selects
- return ( (ReactiveSession) source ).reactiveFetch( event.getObject(), true )
+ return ( (ReactiveQueryProducer) source ).reactiveFetch( event.getObject(), true )
.thenCompose( entity -> reactiveOnLock( event, entity ) );
}
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java
index f5e5eb1b4..ba3edda4a 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java
@@ -37,7 +37,7 @@
import org.hibernate.reactive.logging.impl.Log;
import org.hibernate.reactive.logging.impl.LoggerFactory;
import org.hibernate.reactive.persister.entity.impl.ReactiveAbstractEntityPersister;
-import org.hibernate.reactive.session.ReactiveSession;
+import org.hibernate.reactive.session.ReactiveQueryProducer;
import org.hibernate.type.CollectionType;
import org.hibernate.type.CompositeType;
import org.hibernate.type.Type;
@@ -84,7 +84,7 @@ public CompletionStage reactiveOnRefresh(RefreshEvent event, RefreshContex
// Hibernate Reactive doesn't support detached instances in refresh()
throw new IllegalArgumentException( "Unmanaged instance passed to refresh()" );
}
- return ( (ReactiveSession) source )
+ return ( (ReactiveQueryProducer) source )
.reactiveFetch( event.getObject(), true )
.thenCompose( entity -> reactiveOnRefresh( event, refreshedAlready, entity ) );
}
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java
index 72760a26b..03fd83457 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,6 +7,7 @@
+import java.sql.SQLException;
import java.sql.SQLWarning;
import jakarta.persistence.PersistenceException;
@@ -32,8 +33,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 +259,13 @@ 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 = 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();
+
+ @LogMessage(level = ERROR)
+ @Message(id = 86, value = "Error closing reactive connection")
+ void errorClosingConnection(@Cause Throwable throwable);
+
// Same method that exists in CoreMessageLogger
@LogMessage(level = WARN)
@Message(id = 104, value = "firstResult/maxResults specified with collection fetch; applying in memory!" )
@@ -300,4 +308,8 @@ 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);
}
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Version.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Version.java
new file mode 100644
index 000000000..2ed6d6b9d
--- /dev/null
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Version.java
@@ -0,0 +1,32 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive.logging.impl;
+
+/**
+ * Information about the version of Hibernate Reactive.
+ *
+ * @author Steve Ebersole
+ */
+public final class Version {
+
+ private static final String VERSION;
+ static {
+ final String version = Version.class.getPackage().getImplementationVersion();
+ VERSION = version != null ? version : "[WORKING]";
+ }
+
+ private Version() {
+ }
+
+ /**
+ * Access to the Hibernate Reactive version.
+ *
+ * @return The version
+ */
+ public static String getVersionString() {
+ return VERSION;
+ }
+}
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveEmbeddedIdentifierMappingImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveEmbeddedIdentifierMappingImpl.java
index 3f5244fa9..204a23d3a 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveEmbeddedIdentifierMappingImpl.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveEmbeddedIdentifierMappingImpl.java
@@ -13,6 +13,7 @@
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.EmbeddedIdentifierMappingImpl;
+import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableFetchImpl;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.spi.SqlSelection;
@@ -20,6 +21,7 @@
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
+import org.hibernate.sql.results.graph.Fetchable;
public class ReactiveEmbeddedIdentifierMappingImpl extends AbstractCompositeIdentifierMapping {
@@ -104,4 +106,13 @@ public String getSqlAliasStem() {
public String getFetchableName() {
return delegate.getFetchableName();
}
+
+ @Override
+ public Fetchable getFetchable(int position) {
+ Fetchable fetchable = delegate.getFetchable( position );
+ if ( fetchable instanceof ToOneAttributeMapping ) {
+ return new ReactiveToOneAttributeMapping( (ToOneAttributeMapping) fetchable );
+ }
+ return fetchable;
+ }
}
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java
index 65b0ad906..90972f9c5 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
@@ -2279,6 +2279,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 2.4.7
+ */
+ 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 2.4.7
+ */
+ StatelessSession getCurrentStatelessSession();
+
/**
* Destroy the session factory and clean up its connection pool.
*/
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinySessionDelegator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinySessionDelegator.java
new file mode 100644
index 000000000..09d85913a
--- /dev/null
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinySessionDelegator.java
@@ -0,0 +1,341 @@
+/* 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.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;
+import org.hibernate.Incubating;
+import org.hibernate.LockMode;
+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.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 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.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 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 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 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..152140006
--- /dev/null
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinyStatelessSessionDelegator.java
@@ -0,0 +1,191 @@
+/* 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.criteria.CriteriaQuery;
+import org.hibernate.Incubating;
+import org.hibernate.LockMode;
+import org.hibernate.reactive.common.ResultSetMapping;
+import org.hibernate.reactive.mutiny.Mutiny;
+
+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 Mutiny.SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping) {
+ return delegate().createNativeQuery(queryString, resultSetMapping);
+ }
+
+ public Uni get(Class entityClass, Object id, LockMode lockMode) {
+ return delegate().get(entityClass, id, lockMode);
+ }
+
+ public boolean isOpen() {
+ return delegate().isOpen();
+ }
+
+ 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.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 deleteAll(Object... entities) {
+ return delegate().deleteAll(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);
+ }
+
+ 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 Uni withTransaction(Function> work) {
+ return delegate().withTransaction(work);
+ }
+
+ public Uni fetch(T association) {
+ return delegate().fetch(association);
+ }
+
+ @Incubating
+ public Uni upsert(String entityName, Object entity) {
+ return delegate().upsert(entityName, entity);
+ }
+
+ @Incubating
+ public Mutiny.Transaction currentTransaction() {
+ return delegate().currentTransaction();
+ }
+
+ public EntityGraph createEntityGraph(Class rootType) {
+ return delegate().createEntityGraph(rootType);
+ }
+
+ public Uni insert(Object entity) {
+ return delegate().insert(entity);
+ }
+
+ public Uni refresh(Object entity, LockMode lockMode) {
+ return delegate().refresh(entity, lockMode);
+ }
+
+ public Uni deleteAll(int batchSize, Object... entities) {
+ return delegate().deleteAll(batchSize, entities);
+ }
+}
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..23feaba57 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java
@@ -148,6 +148,16 @@ private CompletionStage connection(String tenantId) {
: connectionPool.getConnection( tenantId );
}
+ @Override
+ public Mutiny.Session getCurrentSession() {
+ return context.get( contextKeyForSession );
+ }
+
+ @Override
+ public Mutiny.StatelessSession getCurrentStatelessSession() {
+ return context.get( contextKeyForStatelessSession );
+ }
+
@Override
public Uni withSession(Function> work) {
Objects.requireNonNull( work, "parameter 'work' is required" );
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/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/mutation/ReactiveUpdateCoordinatorStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveUpdateCoordinatorStandard.java
index d3237cf72..f0aa8bdc9 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
@@ -31,7 +31,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;
@@ -194,7 +194,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/provider/impl/ReactiveIntegrator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveIntegrator.java
index 0d1876320..cda361d5a 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
@@ -28,6 +28,7 @@
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;
@@ -54,7 +55,7 @@ public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactor
private void attachEventContextManagingListenersIfRequired(ServiceRegistry serviceRegistry) {
if ( ReactiveModeCheck.isReactiveRegistry( serviceRegistry ) ) {
- LOG.startHibernateReactive();
+ LOG.startHibernateReactive( Version.getVersionString() );
EventListenerRegistry eventListenerRegistry = serviceRegistry.getService( EventListenerRegistry.class );
eventListenerRegistry.addDuplicationStrategy( ReplacementDuplicationStrategy.INSTANCE );
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProvider.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProvider.java
index 5c3380f65..a02c3e152 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProvider.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProvider.java
@@ -6,10 +6,14 @@
package org.hibernate.reactive.provider.service;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
+import org.hibernate.reactive.logging.impl.Log;
import java.sql.Connection;
import java.sql.SQLException;
+import static java.lang.invoke.MethodHandles.lookup;
+import static org.hibernate.reactive.logging.impl.LoggerFactory.make;
+
/**
* A dummy Hibernate {@link ConnectionProvider} throws an
* exception if a JDBC connection is requested.
@@ -17,12 +21,13 @@
* @author Gavin King
*/
public class NoJdbcConnectionProvider implements ConnectionProvider {
+ private static final Log LOG = make( Log.class, lookup() );
public static final NoJdbcConnectionProvider INSTANCE = new NoJdbcConnectionProvider();
@Override
public Connection getConnection() throws SQLException {
- throw new SQLException( "Not using JDBC" );
+ throw LOG.notUsingJdbc();
}
@Override
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java
index 67852da79..21f2a0dc1 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
@@ -185,12 +185,45 @@ protected String getResultSetIsNullableLabel() {
@Override
protected int dataTypeCode(String typeName) {
- // ORACLE only supports "float" sql type for double precision
- // so return code for double for both double and float column types
- if ( typeName.equalsIgnoreCase( "float" ) ||
- typeName.toLowerCase().startsWith( "double" ) ) {
+ if ( typeName.equalsIgnoreCase( "float" )
+ || typeName.toLowerCase().startsWith( "double" )
+ || typeName.equalsIgnoreCase( "binary_double" ) ) {
return Types.DOUBLE;
}
- return super.dataTypeCode( typeName );
+ if ( typeName.equalsIgnoreCase( "timestamp" ) ) {
+ return Types.TIMESTAMP;
+ }
+ if ( typeName.equalsIgnoreCase( "timestamp with time zone" )
+ || typeName.equalsIgnoreCase( "timestamp with local time zone" ) ) {
+ return Types.TIMESTAMP_WITH_TIMEZONE;
+ }
+ if ( typeName.equalsIgnoreCase( "clob" ) ) {
+ return Types.CLOB;
+ }
+ if ( typeName.equalsIgnoreCase( "blob" ) ) {
+ return Types.BLOB;
+ }
+ if ( typeName.equalsIgnoreCase( "raw" ) ) {
+ return Types.VARBINARY;
+ }
+ if ( typeName.equalsIgnoreCase( "long raw" ) ) {
+ return Types.LONGVARBINARY;
+ }
+ if ( typeName.equalsIgnoreCase( "ref cursor" ) ) {
+ return Types.REF_CURSOR;
+ }
+ if ( typeName.equalsIgnoreCase( "number" ) ) {
+ return Types.NUMERIC;
+ }
+ if ( typeName.equalsIgnoreCase( "date" ) ) {
+ return Types.DATE;
+ }
+ if ( typeName.equalsIgnoreCase( "nvarchar2" ) ) {
+ return Types.NVARCHAR;
+ }
+ if ( typeName.equalsIgnoreCase( "varchar2" ) ) {
+ return Types.VARCHAR;
+ }
+ return 0;
}
}
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..9d639d099 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
@@ -52,6 +52,11 @@ public Class> getResultMappingClass() {
return delegate.getResultMappingClass();
}
+ @Override
+ public Class> getResultType() {
+ return delegate.getResultType();
+ }
+
@Override
public Integer getFirstResult() {
return delegate.getFirstResult();
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..a75872f43 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;
@@ -179,7 +179,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..86f0a8bea 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
@@ -43,10 +43,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;
@@ -575,7 +575,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/temptable/ReactiveGlobalTemporaryTableStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableStrategy.java
index 37c3d23bc..d92062e2f 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
@@ -65,22 +65,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 +148,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/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/ReactiveTemporaryTableHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java
index d027235a2..4eaecd04f 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
@@ -68,11 +68,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 +113,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) -> {
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..a46185772 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
@@ -135,6 +135,7 @@
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;
@@ -963,7 +964,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()
@@ -1616,7 +1617,23 @@ public void close() throws HibernateException {
@Override
public CompletionStage reactiveClose() {
- super.close();
+ try {
+ super.close();
+ return closeConnection();
+ }
+ catch (RuntimeException e) {
+ return closeConnection()
+ .handle( CompletionStages::handle )
+ .thenCompose( closeConnectionHandler -> {
+ if ( closeConnectionHandler.hasFailed() ) {
+ LOG.errorClosingConnection( closeConnectionHandler.getThrowable() );
+ }
+ return failedFuture( e );
+ } );
+ }
+ }
+
+ private CompletionStage closeConnection() {
return reactiveConnection != null
? reactiveConnection.close()
: voidFuture();
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..8a5ec18f9 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,19 @@ 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 );
+ return fetch instanceof EntityFetchSelectImpl
+ ? new ReactiveEntityFetchSelectImpl( (EntityFetchSelectImpl) fetch )
+ : 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..d5f9db5e2 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,19 @@
*/
package org.hibernate.reactive.sql.results.graph.embeddable.internal;
+
import java.util.concurrent.CompletionStage;
import java.util.function.BiFunction;
+import org.hibernate.engine.spi.SessionFactoryImplementor;
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,12 +26,19 @@
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 {
+ private final SessionFactoryImplementor sessionFactory;
+
private static class ReactiveEmbeddableInitializerData extends EmbeddableInitializerData {
public ReactiveEmbeddableInitializerData(
@@ -33,6 +47,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;
}
@@ -45,6 +73,7 @@ public ReactiveEmbeddableInitializerImpl(
AssemblerCreationState creationState,
boolean isResultInitializer) {
super( resultDescriptor, discriminatorFetch, parent, creationState, isResultInitializer );
+ sessionFactory = creationState.getSqlAstCreationContext().getSessionFactory();
}
@Override
@@ -54,10 +83,129 @@ 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 )
+ .thenCompose( 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> ) {
+ return ( (ReactiveDomainResultsAssembler>) assembler )
+ .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, sessionFactory );
+ data.setState( State.RESOLVED );
+ return completedFuture( instance );
+ }
+
@Override
public CompletionStage reactiveInitializeInstance(EmbeddableInitializerData data) {
super.initializeInstance( data );
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