"""
+ }
+
+ emailext(
+ subject: subject,
+ body: body,
+ to: buildEnv.notificationRecipients
+ )
+ }
+}
+
+@NonCPS
+String getParallelResult( RunWrapper build, String parallelBranchName ) {
+ def visitor = new PipelineNodeGraphVisitor( build.rawBuild )
+ def branch = visitor.pipelineNodes.find{ it.type == FlowNodeWrapper.NodeType.PARALLEL && parallelBranchName == it.displayName }
+ if ( branch == null ) {
+ echo "Couldn't find parallel branch name '$parallelBranchName'. Available parallel branch names:"
+ visitor.pipelineNodes.findAll{ it.type == FlowNodeWrapper.NodeType.PARALLEL }.each{
+ echo " - ${it.displayName}"
+ }
+ return null;
+ }
+ return branch.status.result
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index dc84c8ec51c1..fabf623322a5 100644
--- a/README.md
+++ b/README.md
@@ -8,8 +8,7 @@ It also provides an implementation of the JPA specification, which is the standa
This is the repository of its source code: see [Hibernate.org](https://hibernate.org/orm/) for additional information.
-[](https://ci.hibernate.org/job/hibernate-orm-main-h2-main/)
-[](https://lgtm.com/projects/g/hibernate/hibernate-orm/context:java)
+[](https://ci.hibernate.org/job/hibernate-orm-pipeline/job/5.6/)
Building from sources
=========
diff --git a/build.gradle b/build.gradle
index 15ab92bf1c80..df33316ae2bd 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,25 +14,17 @@ buildscript {
classpath 'org.hibernate.build.gradle:hibernate-matrix-testing:3.0.0.Final'
classpath 'org.hibernate.build.gradle:version-injection-plugin:1.0.0'
classpath 'gradle.plugin.com.github.lburgazzoli:gradle-karaf-plugin:0.5.1'
- classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.7'
+ classpath 'org.asciidoctor:asciidoctor-gradle-jvm:3.3.2'
classpath 'de.thetaphi:forbiddenapis:3.0.1'
}
}
plugins {
id 'me.champeau.buildscan-recipes' version '0.2.3'
- id 'io.github.gradle-nexus.publish-plugin' version '1.1.0'
- id 'nu.studer.credentials' version '2.1'
id 'org.hibernate.build.xjc' version '2.0.1' apply false
- id 'org.hibernate.build.maven-repo-auth' version '3.0.3' apply false
id 'biz.aQute.bnd' version '5.1.1' apply false
}
-ext {
- sonatypeOssrhUser = project.findProperty( 'SONATYPE_OSSRH_USER' )
- sonatypeOssrhPassword = project.findProperty( 'SONATYPE_OSSRH_PASSWORD' )
-}
-
File versionFile = file( "${rootProject.projectDir}/gradle/version.properties" )
ext {
@@ -51,15 +43,6 @@ ext {
group = 'org.hibernate'
version = project.ormVersion.fullName
-nexusPublishing {
- repositories {
- sonatype {
- username = project.sonatypeOssrhUser
- password = project.sonatypeOssrhPassword
- }
- }
-}
-
allprojects {
repositories {
mavenCentral()
diff --git a/changelog.txt b/changelog.txt
index 5849eccb6de3..61d9ea5793f6 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -3,6 +3,197 @@ Hibernate 5 Changelog
Note: Please refer to JIRA to learn more about each issue.
+Changes in 5.6.15.Final (February 06, 2023)
+------------------------------------------------------------------------------------------------------------------------
+
+https://hibernate.atlassian.net/projects/HHH/versions/32121
+
+** Bug
+ * [HHH-16049] - Setting a property to its current value with bytecode enhancement enabled results in unnecessary SQL Update in some (many) cases
+ * [HHH-15665] - Mariadb is missing identifier quote on SEQUENCE QUERY
+ * [HHH-15618] - Procedure should accept TypedParameterValue as parameter
+
+** Improvement
+ * [HHH-15693] - Introduce a fast-path access for ClassLoaderService being retrieved from ServiceRegistry
+ * [HHH-15690] - HQLQueryPlan to have a direct reference to QueryTranslatorFactory
+ * [HHH-15685] - Improve efficiency of Dialect lookup in Loader and HqlSqlWalker
+
+** Patch
+ * [HHH-15792] - Explicitly add JavaDoc to make @deprecated hint for createSQLQuery visible in Eclipse
+
+
+Changes in 5.6.14.Final (November 04, 2022)
+------------------------------------------------------------------------------------------------------------------------
+
+https://hibernate.atlassian.net/projects/HHH/versions/32120
+
+** Improvement
+ * [HHH-15662] - ClasscastException caused by check for Managed rather than ManagedEntity
+
+
+Changes in 5.6.13.Final (November 03, 2022)
+------------------------------------------------------------------------------------------------------------------------
+
+https://hibernate.atlassian.net/projects/HHH/versions/32112
+
+** Bug
+ * [HHH-15634] - Lazy basic property does not get updated on change
+ * [HHH-15561] - Function "IDENTITY" not found when inserting audited revision using Hibernate Envers
+ * [HHH-15554] - Merge of an Entity with an immutable composite user type throws Exception
+
+** Improvement
+ * [HHH-15649] - Additional performance fixes relating to Klass's _secondary_super_cache interaction with entity enhancement
+ * [HHH-15639] - Upgrade to ByteBuddy 1.12.18
+ * [HHH-15637] - Upgrade to Byteman 4.0.20
+ * [HHH-15616] - Mitigate performance impact of entity enhancement on Klass's _secondary_super_cache
+ * [HHH-15585] - Add support for DB2 aliases for schema validation
+ * [HHH-15575] - Make getter org.hibernate.criterion.SimpleExpression#getOp() public
+
+** Task
+ * [HHH-15594] - Remove Oracle RDS and all test matrix uses
+
+
+Changes in 5.6.12.Final (September 27, 2022)
+------------------------------------------------------------------------------------------------------------------------
+
+https://hibernate.atlassian.net/projects/HHH/versions/32105
+
+** Bug
+ * [HHH-15523] - Missing use of SqlStringGenerationContext in MapBinder#getFromAndWhereFormula
+ * [HHH-15522] - Hibernate.isInitialized method not working for Envers Collections
+ * [HHH-15520] - ValueGeneration on @OneToOne leads to boot error
+ * [HHH-15505] - Getter of loaded entity returns null when using bytecode enhancement on entity whose field is defined both in mapped superclass and concrete entity
+ * [HHH-15235] - PropertyAccessException on OneToOne mapping after migration to Hibernate 5.6
+ * [HHH-15216] - Cannot change MetadataProvider implementation because JPAXMLOverriddenMetadataProvider is final and precisely expected by a cast operator
+ * [HHH-15045] - onFlushDirty() invoked on parent entity in a @OneToOne relationship when no table columns are changed
+ * [HHH-14943] - byNaturalId API creates unparseable query (AND keyword instead of WHERE)
+
+** Deprecation
+ * [HHH-15536] - Deprecate SharedSessionContractImplementor#getTransactionStartTimestamp() and CacheTransactionSynchronization#getCurrentTransactionStartTimestamp()
+
+** New Feature
+ * [HHH-15508] - Backport Session#getReference(Object) to branch 5.6
+
+** Task
+ * [HHH-15555] - Remove use of @AutomaticFeature in GraalVM module
+ * [HHH-15538] - Move Jenkinsfile timeout around shell command
+
+
+Changes in 5.6.11.Final (August 30, 2022)
+------------------------------------------------------------------------------------------------------------------------
+
+https://hibernate.atlassian.net/projects/HHH/versions/32089
+
+** Bug
+ * [HHH-15468] - contributor-build.yml has no explicit permissions set
+ * [HHH-15454] - Primitive type requested from tuple throws exception
+ * [HHH-15440] - @OneToOne and @OptimisticLock(excluded = true) not working correctly
+ * [HHH-15425] - org.hibernate.QueryException: could not resolve property is thrown when Hibernate criteria tries to select the id of an association annotated with @NotFound
+ * [HHH-15359] - The entity returned by a merge doesn't contain @ManyToMany relation when the collection resides in @Embeddable
+ * [HHH-15100] - Limitation of metamodel imports cache causes severe performance drops in large projects
+
+** Improvement
+ * [HHH-15466] - Compatibility with Jandex 3.0.0
+
+** Task
+ * [HHH-15451] - Upgrade PostgreSQL JDBC driver to 42.5.0
+ * [HHH-15388] - Upgrade to Micrometer 1.9.3
+
+
+Changes in 5.6.10.Final (July 07, 2022)
+------------------------------------------------------------------------------------------------------------------------
+
+https://hibernate.atlassian.net/projects/HHH/versions/32076
+
+** Bug
+ * [HHH-15281] - INSERTs/UPDATEs no longer executed as JDBC Batch statements if hibernate.temp.use_jdbc_metadata_defaults is set to false
+ * [HHH-15218] - @OptimisticLocking(DIRTY) leads to wrong query during delete of circular reference
+ * [HHH-7525] - @Formula annotation with native query returning entity value causes NullPointerException
+
+** Improvement
+ * [HHH-15325] - Avoid allocations from BitSet.stream() in AbstractEntityPersister.resolveDirtyAttributeIndexes()
+
+** Task
+ * [HHH-15322] - Allow JNDI lookups using the osgi scheme
+
+
+Changes in 5.6.9.Final (May 14, 2022)
+------------------------------------------------------------------------------------------------------------------------
+
+https://hibernate.atlassian.net/projects/HHH/versions/32067
+
+** Bug
+ * [HHH-15270] - Inconsistent precedence of orm.xml implicit catalog over "default_catalog" in XML-mapped entities
+ * [HHH-15265] - SchemaExport.execute does not add the configured schema to comments
+ * [HHH-15212] - SchemaExport.execute does not replace the ${schema}-placeholder in HBM database-object with configured schema
+ * [HHH-15142] - CriteriaQuery with Like predicate fails when repeated with java.lang.IllegalArgumentException: Parameter value [] did not match expected type [java.lang.String (n/a)]
+ * [HHH-15134] - Update a bytecode enhanced Entity with a Version attribute causes OptimisticLockException
+ * [HHH-15091] - EntityManager.persist does not verify the existence of the one side of a many-to-one relationship, introduced 5.4.17
+
+** Improvement
+ * [HHH-4384] - @JoinColumn must be set for @AssociationOverride to work
+
+** Task
+ * [HHH-15274] - Small optimisation for how LazyAttributeLoadingInterceptor is dealing with lazy fields
+ * [HHH-15222] - Introduce an helper class SPI for decorating a Session instance when the instance is lazily provided
+ * [HHH-15178] - Backport Jenkinsfile and GH actions
+
+
+Changes in 5.6.8.Final (April 13, 2022)
+------------------------------------------------------------------------------------------------------------------------
+
+https://hibernate.atlassian.net/projects/HHH/versions/32056
+
+** Bug
+ * [HHH-15147] - hibernate-jpamodelgen-jakarta annotation processor ignores jakarta.* annotations
+ * [HHH-15141] - Bytecode enhancement fails for a protected, embedded field in a MappedSuperclass from a different package than the entity
+ * [HHH-15118] - PooledOptimizer generates duplicate ids when several JVMs initialize optimizer and sequence value is the initial value
+ * [HHH-14487] - PropertyAccessStrategyMapImpl imports wrong class
+ * [HHH-13694] - Numeric Overflow Exception when retrieving the Meta-data for sequences from Oracle Database
+
+** Task
+ * [HHH-15209] - Upgrade to bytebuddy 1.12.9
+ * [HHH-15146] - Run tests against hibernate-jpamodelgen-jakarta
+
+
+Changes in 5.6.7.Final (March 16, 2022)
+------------------------------------------------------------------------------------------------------------------------
+
+https://hibernate.atlassian.net/projects/HHH/versions/32053
+
+** Improvement
+ * [HHH-15124] - Relax usage of DeprecationLogger: avoid some confusing reports
+ * [HHH-15067] - Make NonNullableTransientDependencies.(String propertyName, Object transientEntity) method public
+
+
+Changes in 5.6.6.Final (March 15, 2022)
+------------------------------------------------------------------------------------------------------------------------
+
+https://hibernate.atlassian.net/projects/HHH/versions/32031
+
+** Bug
+ * [HHH-15115] - Deleting an entity with Joined inheritance and default schema set is throwing and error
+ * [HHH-15113] - Exception setting ParameterExpressions on Update Queries
+ * [HHH-15105] - Getting the CacheRegionStatistics before executing a query leads to a NPE later on
+ * [HHH-15097] - Hibernate fails to detect SQL type for AttributeConverter to UUID
+ * [HHH-15084] - JpaCompliantLifecycleStrategy uses deprecated BeanManager method that's gone in CDI 4.0
+ * [HHH-15082] - JDBC Statement leaks after exceptions other than SQLException during insert/update/...
+ * [HHH-15069] - Backwards-incompatible changes in SequenceStyleGenerator (and others) following default_schema changes
+ * [HHH-15060] - Fix handling of associations with @NotFound
+ * [HHH-15051] - Association with id class misses property mapping for target FK attributes
+ * [HHH-14932] - Spatial support for PostgreSQL 10+ uses invalid WKB dialect
+ * [HHH-14817] - hibernate-core-jakarta source jar does not contain source
+ * [HHH-15090] - Access to public field with extended bytecode enhancement returns null for entity lazy-loaded from polymorphic toOne association
+
+** Improvement
+ * [HHH-15106] - fk() SQM function
+ * [HHH-15094] - Handle http://hibernate.org and https://* for all DTDs in LocalXmlResourceResolver
+
+** Task
+ * [HHH-15119] - Upgrade to ByteBuddy 1.12.8
+ * [HHH-14996] - Upgrade to JBoss Logging Processor (and matching Annotations) 2.2.1.Final
+
+
Changes in 5.6.5.Final (January 25, 2022)
------------------------------------------------------------------------------------------------------------------------
@@ -385,7 +576,7 @@ https://hibernate.atlassian.net/projects/HHH/versions/31844
* [HHH-14257] - An Entity A with a map collection having as index an Embeddable with a an association to the Entity A fails with a NPE
* [HHH-14251] - Invalid SQL for @Embedded UPDATE
* [HHH-14249] - MultiLineImport fails when script contains blank spaces or tabs at the end of the last sql statement
-
+ * [HHH-14216] - Second-level cache doesn't support @OneToOne
Changes in 5.4.14.Final (April 6, 2020)
------------------------------------------------------------------------------------------------------------------------
diff --git a/ci/build.sh b/ci/build.sh
index 30176554d921..49f32442aed7 100755
--- a/ci/build.sh
+++ b/ci/build.sh
@@ -3,9 +3,17 @@
goal=
if [ "$RDBMS" == "derby" ]; then
goal="-Pdb=derby"
+elif [ "$RDBMS" == "hsqldb" ]; then
+ goal="-Pdb=hsqldb"
+elif [ "$RDBMS" == "mysql8" ]; then
+ goal="-Pdb=mysql_ci"
+elif [ "$RDBMS" == "mysql" ]; then
+ goal="-Pdb=mysql_ci"
elif [ "$RDBMS" == "mariadb" ]; then
goal="-Pdb=mariadb_ci"
-elif [ "$RDBMS" == "postgresql" ]; then
+elif [ "$RDBMS" == "postgresql_9_5" ]; then
+ goal="-Pdb=pgsql_ci"
+elif [ "$RDBMS" == "postgresql_13" ]; then
goal="-Pdb=pgsql_ci"
elif [ "$RDBMS" == "oracle" ]; then
# I have no idea why, but these tests don't work on GH Actions
@@ -16,6 +24,8 @@ elif [ "$RDBMS" == "mssql" ]; then
goal="-Pdb=mssql_ci"
elif [ "$RDBMS" == "hana" ]; then
goal="-Pdb=hana_ci"
+elif [ "$RDBMS" == "sybase" ]; then
+ goal="-Pdb=sybase_ci"
fi
exec ./gradlew check ${goal} -Plog-test-progress=true --stacktrace
diff --git a/ci/database-start.sh b/ci/database-start.sh
index e603a9631bfe..997a450ee9f0 100755
--- a/ci/database-start.sh
+++ b/ci/database-start.sh
@@ -8,14 +8,18 @@ elif [ "$RDBMS" == 'mysql8' ]; then
bash $DIR/../docker_db.sh mysql_8_0
elif [ "$RDBMS" == 'mariadb' ]; then
bash $DIR/../docker_db.sh mariadb
-elif [ "$RDBMS" == 'postgresql' ]; then
+elif [ "$RDBMS" == 'postgresql_9_5' ]; then
bash $DIR/../docker_db.sh postgresql_9_5
+elif [ "$RDBMS" == 'postgresql_13' ]; then
+ bash $DIR/../docker_db.sh postgresql_13
elif [ "$RDBMS" == 'db2' ]; then
bash $DIR/../docker_db.sh db2
elif [ "$RDBMS" == 'oracle' ]; then
- bash $DIR/../docker_db.sh oracle
+ bash $DIR/../docker_db.sh oracle_18
elif [ "$RDBMS" == 'mssql' ]; then
bash $DIR/../docker_db.sh mssql
elif [ "$RDBMS" == 'hana' ]; then
bash $DIR/../docker_db.sh hana
+elif [ "$RDBMS" == 'sybase' ]; then
+ bash $DIR/../docker_db.sh sybase
fi
\ No newline at end of file
diff --git a/ci/jpa-2.2-tck.Jenkinsfile b/ci/jpa-2.2-tck.Jenkinsfile
index 396536f53c0b..427be4da3fbd 100644
--- a/ci/jpa-2.2-tck.Jenkinsfile
+++ b/ci/jpa-2.2-tck.Jenkinsfile
@@ -7,6 +7,11 @@ pipeline {
tools {
jdk 'OpenJDK 8 Latest'
}
+ options {
+ rateLimitBuilds(throttle: [count: 1, durationName: 'day', userBoost: true])
+ buildDiscarder(logRotator(numToKeepStr: '3', artifactNumToKeepStr: '3'))
+ disableConcurrentBuilds(abortPrevious: true)
+ }
parameters {
booleanParam(name: 'NO_SLEEP', defaultValue: true, description: 'Whether the NO_SLEEP patch should be applied to speed up the TCK execution')
}
diff --git a/ci/jpa-3.0-tck.Jenkinsfile b/ci/jpa-3.0-tck.Jenkinsfile
index 3d1aab779c82..150c84175025 100644
--- a/ci/jpa-3.0-tck.Jenkinsfile
+++ b/ci/jpa-3.0-tck.Jenkinsfile
@@ -7,6 +7,11 @@ pipeline {
tools {
jdk 'OpenJDK 8 Latest'
}
+ options {
+ rateLimitBuilds(throttle: [count: 1, durationName: 'day', userBoost: true])
+ buildDiscarder(logRotator(numToKeepStr: '3', artifactNumToKeepStr: '3'))
+ disableConcurrentBuilds(abortPrevious: true)
+ }
parameters {
choice(name: 'IMAGE_JDK', choices: ['jdk8', 'jdk11'], description: 'The JDK base image version to use for the TCK image.')
string(name: 'TCK_VERSION', defaultValue: '3.0.0', description: 'The version of the Jakarta JPA TCK i.e. `2.2.0` or `3.0.1`')
diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile
new file mode 100644
index 000000000000..59c9c661b7bf
--- /dev/null
+++ b/ci/release/Jenkinsfile
@@ -0,0 +1,286 @@
+#! /usr/bin/groovy
+/*
+ * See https://github.com/hibernate/hibernate-jenkins-pipeline-helpers
+ */
+@Library('hibernate-jenkins-pipeline-helpers') _
+
+import org.hibernate.jenkins.pipeline.helpers.version.Version
+
+// --------------------------------------------
+// Global build configuration
+env.PROJECT = "orm"
+env.JIRA_KEY = "HHH"
+def RELEASE_ON_SCHEDULE = false // 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}"
+
+// --------------------------------------------
+// Build conditions
+
+// Avoid running the pipeline on branch indexing
+if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) {
+ print "INFO: Build skipped due to trigger being Branch Indexing"
+ currentBuild.result = 'NOT_BUILT'
+ return
+}
+
+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 && !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
+}
+
+// --------------------------------------------
+// Reusable methods
+
+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
+
+pipeline {
+ agent {
+ label 'Release'
+ }
+ triggers {
+ // Run every week Sunday midnight
+ cron('0 0 * * 0')
+ }
+ tools {
+ jdk 'OpenJDK 11 Latest'
+ }
+ options {
+ buildDiscarder logRotator(daysToKeepStr: '30', numToKeepStr: '10')
+ disableConcurrentBuilds(abortPrevious: false)
+ preserveStashes()
+ }
+ parameters {
+ string(
+ name: 'RELEASE_VERSION',
+ defaultValue: '',
+ description: 'The version to be released, e.g. 6.2.1.Final. Mandatory for manual releases, to prevent mistakes.',
+ trim: true
+ )
+ string(
+ name: 'DEVELOPMENT_VERSION',
+ defaultValue: '',
+ description: 'The next version to be used after the release, e.g. 6.2.2-SNAPSHOT. If not set, determined automatically from the release version.',
+ trim: true
+ )
+ booleanParam(
+ name: 'RELEASE_DRY_RUN',
+ defaultValue: false,
+ description: 'If true, just simulate the release, without pushing any commits or tags, and without uploading any artifacts or documentation.'
+ )
+ }
+ stages {
+ stage('Release 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(
+ script: ".release/scripts/determine-current-version.sh ${env.PROJECT}",
+ returnStdout: true
+ ).trim() )
+ echo "Workspace version: ${currentVersion}"
+
+ def releaseVersion
+ def developmentVersion
+
+ def lastCommitter = sh(script: 'git show -s --format=\'%an\'', returnStdout: true).trim()
+ def secondLastCommitter = sh(script: 'git show -s --format=\'%an\' HEAD~1', returnStdout: true).trim()
+ def isCiLastCommiter = lastCommitter == 'Hibernate-CI' && secondLastCommitter == 'Hibernate-CI'
+
+ echo "Last two commits were performed by '${lastCommitter}'/'${secondLastCommitter}'."
+ echo "Is 'Hibernate-CI' the last commiter: '${isCiLastCommiter}'."
+
+ if ( manualRelease ) {
+ 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.'
+ )
+ }
+ releaseVersion = Version.parseReleaseVersion( params.RELEASE_VERSION )
+
+ if ( !releaseVersion.toString().startsWith( currentVersion.family + '.' ) ) {
+ throw new IllegalArgumentException( "RELEASE_VERSION = $releaseVersion, which is different from the family of CURRENT_VERSION = $currentVersion. Did you make a mistake?" )
+ }
+ }
+ else {
+ echo "Release was triggered automatically"
+
+ // Avoid doing an automatic release for commits from a release
+
+ if (isCiLastCommiter) {
+ print "INFO: Automatic release skipped because last commits were for the previous release"
+ currentBuild.getRawBuild().getExecutor().interrupt(Result.NOT_BUILT)
+ sleep(1) // Interrupt is not blocking and does not take effect immediately.
+ return
+ }
+
+ releaseVersion = Version.parseReleaseVersion( sh(
+ script: ".release/scripts/determine-release-version.sh ${currentVersion}",
+ returnStdout: true
+ ).trim() )
+ }
+ echo "Release version: ${releaseVersion}"
+
+ if ( !params.DEVELOPMENT_VERSION ) {
+ developmentVersion = Version.parseDevelopmentVersion( sh(
+ script: ".release/scripts/determine-development-version.sh ${releaseVersion}",
+ returnStdout: true
+ ).trim() )
+ }
+ else {
+ developmentVersion = Version.parseDevelopmentVersion( params.DEVELOPMENT_VERSION )
+ }
+ echo "Development version: ${developmentVersion}"
+
+ env.RELEASE_VERSION = releaseVersion.toString()
+ env.DEVELOPMENT_VERSION = developmentVersion.toString()
+ env.SCRIPT_OPTIONS = params.RELEASE_DRY_RUN ? "-d" : ""
+ env.JRELEASER_DRY_RUN = params.RELEASE_DRY_RUN
+
+ // Determine version id to check if Jira version exists
+ sh ".release/scripts/determine-jira-version-id.sh ${env.JIRA_KEY} ${releaseVersion.withoutFinalQualifier}"
+ }
+ }
+ }
+ stage('Release prepare') {
+ steps {
+ script {
+ checkoutReleaseScripts()
+
+ 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', 'hibernate-ci.frs.sourceforge.net']) {
+ // 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('Publish') {
+ steps {
+ script {
+ checkoutReleaseScripts()
+
+ configFileProvider([
+ configFile(fileId: 'release.config.ssh', targetLocation: "${env.HOME}/.ssh/config"),
+ configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts")
+ ]) {
+ withCredentials([
+ usernamePassword(credentialsId: 'central.sonatype.com', passwordVariable: 'JRELEASER_MAVENCENTRAL_TOKEN', usernameVariable: 'JRELEASER_MAVENCENTRAL_USERNAME'),
+ // https://docs.gradle.org/current/userguide/publishing_gradle_plugins.html#account_setup
+ usernamePassword(credentialsId: 'gradle-plugin-portal-api-key', passwordVariable: 'GRADLE_PUBLISH_SECRET', usernameVariable: 'GRADLE_PUBLISH_KEY'),
+ 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', 'jenkins.in.relation.to', 'hibernate-ci.frs.sourceforge.net']) {
+ // performs documentation upload and Sonatype release
+ // push to github
+ withEnv([
+ "DISABLE_REMOTE_GRADLE_CACHE=true"
+ ]) {
+ def notesFiles = findFiles(glob: 'release_notes.md')
+ if ( notesFiles.length < 1 ) {
+ throw new IllegalStateException( "Could not locate `release_notes.md`" )
+ }
+ if ( notesFiles.length > 1 ) {
+ throw new IllegalStateException( "Located more than 1 `release_notes.md`" )
+ }
+
+ sh ".release/scripts/publish.sh -j --notes=${notesFiles[0].path} ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION} ${env.GIT_BRANCH} "
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ stage('Release on Jira') {
+ steps {
+ script {
+ checkoutReleaseScripts()
+
+ withCredentials([string(credentialsId: 'release-webhook.hibernate.atlassian.net', variable: 'JIRA_WEBHOOK_SECRET')]) {
+ sh ".release/scripts/jira-release.sh ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION}"
+ }
+ }
+ }
+ }
+ stage('Update website') {
+ steps {
+ script {
+ checkoutReleaseScripts()
+
+ configFileProvider([
+ configFile(fileId: 'release.config.ssh', targetLocation: "${env.HOME}/.ssh/config"),
+ configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts")
+ ]) {
+ withCredentials([
+ gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default')
+ ]) {
+ 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}"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ post {
+ always {
+ configFileProvider([configFile(fileId: 'job-configuration.yaml', variable: 'JOB_CONFIGURATION_FILE')]) {
+ notifyBuildResult maintainers: (String) readYaml(file: env.JOB_CONFIGURATION_FILE).notification?.email?.recipients
+ }
+ }
+ }
+}
diff --git a/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile
new file mode 100644
index 000000000000..0059d95d0a96
--- /dev/null
+++ b/ci/snapshot-publish.Jenkinsfile
@@ -0,0 +1,74 @@
+/*
+ * See https://github.com/hibernate/hibernate-jenkins-pipeline-helpers
+ */
+@Library('hibernate-jenkins-pipeline-helpers@1.5') _
+
+// Avoid running the pipeline on branch indexing
+if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) {
+ print "INFO: Build skipped due to trigger being Branch Indexing"
+ currentBuild.result = 'ABORTED'
+ return
+}
+
+def checkoutReleaseScripts() {
+ dir('.release/scripts') {
+ checkout scmGit(branches: [[name: '*/main']], extensions: [],
+ userRemoteConfigs: [[credentialsId: 'ed25519.Hibernate-CI.github.com',
+ url: 'https://github.com/hibernate/hibernate-release-scripts.git']])
+ }
+}
+
+pipeline {
+ agent {
+ label 'Release'
+ }
+ tools {
+ jdk 'OpenJDK 8 Latest'
+ }
+ options {
+ rateLimitBuilds(throttle: [count: 1, durationName: 'hour', userBoost: true])
+ buildDiscarder(logRotator(numToKeepStr: '3', artifactNumToKeepStr: '3'))
+ disableConcurrentBuilds(abortPrevious: true)
+ }
+ stages {
+ stage('Checkout') {
+ steps {
+ checkout scm
+ }
+ }
+ stage('Publish') {
+ steps {
+ script {
+ withCredentials([
+ // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh
+ // 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'),
+ string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN'),
+ // https://docs.gradle.org/current/userguide/publishing_gradle_plugins.html#account_setup
+ usernamePassword(credentialsId: 'gradle-plugin-portal-api-key', passwordVariable: 'GRADLE_PUBLISH_SECRET', usernameVariable: 'GRADLE_PUBLISH_KEY'),
+ gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default')
+ ]) {
+ withEnv([
+ "DISABLE_REMOTE_GRADLE_CACHE=true"
+ ]) {
+ checkoutReleaseScripts()
+ def version = sh(
+ script: ".release/scripts/determine-current-version.sh orm",
+ returnStdout: true
+ ).trim()
+ echo "Current version: '${version}'"
+ sh "bash -xe .release/scripts/snapshot-deploy.sh orm ${version}"
+ }
+ }
+ }
+ }
+ }
+ }
+ post {
+ always {
+ configFileProvider([configFile(fileId: 'job-configuration.yaml', variable: 'JOB_CONFIGURATION_FILE')]) {
+ notifyBuildResult maintainers: (String) readYaml(file: env.JOB_CONFIGURATION_FILE).notification?.email?.recipients
+ }
+ }
+ }
+}
diff --git a/databases/cockroachdb/matrix.gradle b/databases/cockroachdb/matrix.gradle
index c6ed30abc639..797dbd1d257c 100644
--- a/databases/cockroachdb/matrix.gradle
+++ b/databases/cockroachdb/matrix.gradle
@@ -11,4 +11,4 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or .
*/
-jdbcDependency 'org.postgresql:postgresql:42.2.8'
\ No newline at end of file
+jdbcDependency 'org.postgresql:postgresql:42.5.0'
\ No newline at end of file
diff --git a/databases/pgsql/matrix.gradle b/databases/pgsql/matrix.gradle
index b8ac50d60726..21b9703e577c 100644
--- a/databases/pgsql/matrix.gradle
+++ b/databases/pgsql/matrix.gradle
@@ -4,4 +4,4 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or .
*/
-jdbcDependency 'org.postgresql:postgresql:42.2.19'
+jdbcDependency 'org.postgresql:postgresql:42.5.0'
diff --git a/docker_db.sh b/docker_db.sh
index e88589cada38..6317b5c8755e 100755
--- a/docker_db.sh
+++ b/docker_db.sh
@@ -1,45 +1,123 @@
#! /bin/bash
+if command -v podman > /dev/null; then
+ CONTAINER_CLI=$(command -v podman)
+ HEALTCHECK_PATH="{{.State.Healthcheck.Status}}"
+ # Only use sudo for podman
+ if command -v sudo > /dev/null; then
+ PRIVILEGED_CLI="sudo"
+ else
+ PRIVILEGED_CLI=""
+ fi
+else
+ CONTAINER_CLI=$(command -v docker)
+ HEALTCHECK_PATH="{{.State.Health.Status}}"
+ PRIVILEGED_CLI=""
+fi
+
mysql_5_7() {
- docker rm -f mysql || true
- docker run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -p3306:3306 -d mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
+ $CONTAINER_CLI rm -f mysql || true
+ $CONTAINER_CLI run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d docker.io/mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-character-set-client-handshake --log-bin-trust-function-creators=1
+ # Give the container some time to start
+ OUTPUT=
+ n=0
+ until [ "$n" -ge 5 ]
+ do
+ # Need to access STDERR. Thanks for the snippet https://stackoverflow.com/a/56577569/412446
+ { OUTPUT="$( { $CONTAINER_CLI logs mysql; } 2>&1 1>&3 3>&- )"; } 3>&1;
+ if [[ $OUTPUT == *"ready for connections"* ]]; then
+ break;
+ fi
+ n=$((n+1))
+ echo "Waiting for MySQL to start..."
+ sleep 3
+ done
+ if [ "$n" -ge 5 ]; then
+ echo "MySQL failed to start and configure after 15 seconds"
+ else
+ echo "MySQL successfully started"
+ fi
}
mysql_8_0() {
- docker rm -f mysql || true
- docker run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -p3306:3306 -d mysql:8.0.21 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
+ $CONTAINER_CLI rm -f mysql || true
+ $CONTAINER_CLI run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d docker.io/mysql:8.0.21 --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_as_cs --skip-character-set-client-handshake --log-bin-trust-function-creators=1
+ # Give the container some time to start
+ OUTPUT=
+ n=0
+ until [ "$n" -ge 5 ]
+ do
+ # Need to access STDERR. Thanks for the snippet https://stackoverflow.com/a/56577569/412446
+ { OUTPUT="$( { $CONTAINER_CLI logs mysql; } 2>&1 1>&3 3>&- )"; } 3>&1;
+ if [[ $OUTPUT == *"ready for connections"* ]]; then
+ break;
+ fi
+ n=$((n+1))
+ echo "Waiting for MySQL to start..."
+ sleep 3
+ done
+ if [ "$n" -ge 5 ]; then
+ echo "MySQL failed to start and configure after 15 seconds"
+ else
+ echo "MySQL successfully started"
+ fi
}
mariadb() {
- docker rm -f mariadb || true
- docker run --name mariadb -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d mariadb:10.5.8 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
+ $CONTAINER_CLI rm -f mariadb || true
+ $CONTAINER_CLI run --name mariadb -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d docker.io/mariadb:10.5.8 --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-character-set-client-handshake
+ OUTPUT=
+ n=0
+ until [ "$n" -ge 5 ]
+ do
+ # Need to access STDERR. Thanks for the snippet https://stackoverflow.com/a/56577569/412446
+ { OUTPUT="$( { $CONTAINER_CLI logs mariadb; } 2>&1 1>&3 3>&- )"; } 3>&1;
+ if [[ $OUTPUT == *"ready for connections"* ]]; then
+ break;
+ fi
+ n=$((n+1))
+ echo "Waiting for MariaDB to start..."
+ sleep 3
+ done
+ if [ "$n" -ge 5 ]; then
+ echo "MariaDB failed to start and configure after 15 seconds"
+ else
+ echo "MariaDB successfully started"
+ fi
}
postgresql_9_5() {
- docker rm -f postgres || true
- docker run --name postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 -d postgres:9.5
+ $CONTAINER_CLI rm -f postgres || true
+ $CONTAINER_CLI run --name postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 -d docker.io/postgis/postgis:9.5-2.5
}
-postgis(){
- docker rm -f postgis || true
- docker run --name postgis -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 -d postgis/postgis:11-2.5
+postgresql_13() {
+ $CONTAINER_CLI rm -f postgres || true
+ $CONTAINER_CLI run --name postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 -d docker.io/postgis/postgis:13-3.1
+}
+
+edb() {
+ #$CONTAINER_CLI login containers.enterprisedb.com
+ $CONTAINER_CLI rm -f edb || true
+ $CONTAINER_CLI run --name edb -e ACCEPT_EULA=Yes -e DATABASE_USER=hibernate_orm_test -e DATABASE_USER_PASSWORD=hibernate_orm_test -e ENTERPRISEDB_PASSWORD=hibernate_orm_test -e DATABASE_NAME=hibernate_orm_test -e PGPORT=5433 -p 5433:5433 --mount type=tmpfs,destination=/edbvolume -d containers.enterprisedb.com/edb/edb-as-lite:v11
}
db2() {
- docker rm -f db2 || true
- docker run --name db2 --privileged -e DB2INSTANCE=orm_test -e DB2INST1_PASSWORD=orm_test -e DBNAME=orm_test -e LICENSE=accept -e AUTOCONFIG=false -e ARCHIVE_LOGS=false -e TO_CREATE_SAMPLEDB=false -e REPODB=false -p 50000:50000 -d ibmcom/db2:11.5.5.0
+ echo $CONTAINER_CLI
+ $PRIVILEGED_CLI $CONTAINER_CLI rm -f db2 || true
+ $PRIVILEGED_CLI $CONTAINER_CLI run --name db2 --privileged -e DB2INSTANCE=orm_test -e DB2INST1_PASSWORD=orm_test -e DBNAME=orm_test -e LICENSE=accept -e AUTOCONFIG=false -e ARCHIVE_LOGS=false -e TO_CREATE_SAMPLEDB=false -e REPODB=false -p 50000:50000 -d docker.io/ibmcom/db2:11.5.7.0
# Give the container some time to start
OUTPUT=
while [[ $OUTPUT != *"INSTANCE"* ]]; do
echo "Waiting for DB2 to start..."
sleep 10
- OUTPUT=$(docker logs db2)
+ OUTPUT=$($PRIVILEGED_CLI $CONTAINER_CLI logs db2)
done
- docker exec -t db2 su - orm_test bash -c ". /database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2 'connect to orm_test' && /database/config/orm_test/sqllib/bin/db2 'CREATE USER TEMPORARY TABLESPACE usr_tbsp MANAGED BY AUTOMATIC STORAGE'"
+ $PRIVILEGED_CLI $CONTAINER_CLI exec -t db2 su - orm_test bash -c ". /database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2 'connect to orm_test' && /database/config/orm_test/sqllib/bin/db2 'CREATE USER TEMPORARY TABLESPACE usr_tbsp MANAGED BY AUTOMATIC STORAGE'"
}
db2_spatial() {
- docker rm -f db2spatial || true
+ $PRIVILEGED_CLI $CONTAINER_CLI rm -f db2spatial || true
temp_dir=$(mktemp -d)
cat <${temp_dir}/ewkt.sql
create or replace function db2gse.asewkt(geometry db2gse.st_geometry)
@@ -78,35 +156,35 @@ CREATE TRANSFORM FOR db2gse.ST_Geometry DB2_PROGRAM (
TO SQL WITH FUNCTION db2gse.geomfromewkt(varchar(32000)) )
;
EOF
- docker run --name db2spatial --privileged -e DB2INSTANCE=orm_test -e DB2INST1_PASSWORD=orm_test -e DBNAME=orm_test -e LICENSE=accept -e AUTOCONFIG=false -e ARCHIVE_LOGS=false -e TO_CREATE_SAMPLEDB=false -e REPODB=false \
+ $PRIVILEGED_CLI $CONTAINER_CLI run --name db2spatial --privileged -e DB2INSTANCE=orm_test -e DB2INST1_PASSWORD=orm_test -e DBNAME=orm_test -e LICENSE=accept -e AUTOCONFIG=false -e ARCHIVE_LOGS=false -e TO_CREATE_SAMPLEDB=false -e REPODB=false \
-v ${temp_dir}:/conf \
- -p 50000:50000 -d ibmcom/db2:11.5.5.0
+ -p 50000:50000 -d docker.io/ibmcom/db2:11.5.5.0
# Give the container some time to start
OUTPUT=
while [[ $OUTPUT != *"Setup has completed."* ]]; do
echo "Waiting for DB2 to start..."
sleep 10
- OUTPUT=$(docker logs db2spatial)
+ OUTPUT=$($PRIVILEGED_CLI $CONTAINER_CLI logs db2spatial)
done
sleep 10
echo "Enabling spatial extender"
- docker exec -t db2spatial su - orm_test bash -c "/database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2se enable_db orm_test"
+ $PRIVILEGED_CLI $CONTAINER_CLI exec -t db2spatial su - orm_test bash -c "/database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2se enable_db orm_test"
echo "Installing required transform group"
- docker exec -t db2spatial su - orm_test bash -c "/database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2 'connect to orm_test' && /database/config/orm_test/sqllib/bin/db2 -tvf /conf/ewkt.sql"
+ $PRIVILEGED_CLI $CONTAINER_CLI exec -t db2spatial su - orm_test bash -c "/database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2 'connect to orm_test' && /database/config/orm_test/sqllib/bin/db2 -tvf /conf/ewkt.sql"
}
mssql() {
- docker rm -f mssql || true
- docker run --name mssql -d -p 1433:1433 -e "SA_PASSWORD=Hibernate_orm_test" -e ACCEPT_EULA=Y mcr.microsoft.com/mssql/server:2017-CU13
+ $CONTAINER_CLI rm -f mssql || true
+ $CONTAINER_CLI run --name mssql -d -p 1433:1433 -e "SA_PASSWORD=Hibernate_orm_test" -e ACCEPT_EULA=Y mcr.microsoft.com/mssql/server:2017-CU13
sleep 5
n=0
until [ "$n" -ge 5 ]
do
# We need a database that uses a non-lock based MVCC approach
# https://github.com/microsoft/homebrew-mssql-release/issues/2#issuecomment-682285561
- docker exec mssql bash -c 'echo "create database hibernate_orm_test collate SQL_Latin1_General_CP1_CI_AS; alter database hibernate_orm_test set READ_COMMITTED_SNAPSHOT ON" | /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P Hibernate_orm_test -i /dev/stdin' && break
+ $CONTAINER_CLI exec mssql bash -c 'echo "create database hibernate_orm_test collate SQL_Latin1_General_CP1_CS_AS; alter database hibernate_orm_test set READ_COMMITTED_SNAPSHOT ON" | /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P Hibernate_orm_test -i /dev/stdin' && break
echo "Waiting for SQL Server to start..."
n=$((n+1))
sleep 5
@@ -118,19 +196,146 @@ mssql() {
fi
}
-oracle() {
- docker rm -f oracle || true
- # We need to use the defaults
- # SYSTEM/Oracle18
- docker run --shm-size=1536m --name oracle -d -p 1521:1521 --ulimit nofile=1048576:1048576 quillbuilduser/oracle-18-xe
- until [ "`docker inspect -f {{.State.Health.Status}} oracle`" == "healthy" ];
+sybase() {
+ $CONTAINER_CLI rm -f sybase || true
+ # Yup, that sucks, but on ubuntu we need to use -T11889 as per: https://github.com/DataGrip/docker-env/issues/12
+ $CONTAINER_CLI run -d -p 5000:5000 -p 5001:5001 --name sybase --entrypoint /bin/bash docker.io/nguoianphu/docker-sybase -c "source /opt/sybase/SYBASE.sh
+/opt/sybase/ASE-16_0/bin/dataserver \
+-d/opt/sybase/data/master.dat \
+-e/opt/sybase/ASE-16_0/install/MYSYBASE.log \
+-c/opt/sybase/ASE-16_0/MYSYBASE.cfg \
+-M/opt/sybase/ASE-16_0 \
+-N/opt/sybase/ASE-16_0/sysam/MYSYBASE.properties \
+-i/opt/sybase \
+-sMYSYBASE \
+-T11889
+RET=\$?
+exit 0
+"
+
+ sybase_check() {
+ $CONTAINER_CLI exec sybase bash -c "source /opt/sybase/SYBASE.sh;
+/opt/sybase/OCS-16_0/bin/isql -Usa -P myPassword -S MYSYBASE < 0
+go
+quit
+EOF
+"
+}
+ START_STATUS=0
+ j=1
+ while (( $j < 30 )); do
+ echo "Waiting for Sybase to start..."
+ sleep 1
+ j=$((j+1))
+ START_STATUS=$(sybase_check | grep '(0 rows affected)' | wc -c)
+ if (( $START_STATUS > 0 )); then
+ break
+ fi
+ done
+ if (( $j == 30 )); then
+ echo "Failed starting Sybase"
+ $CONTAINER_CLI ps -a
+ $CONTAINER_CLI logs sybase
+ sybase_check
+ exit 1
+ fi
+
+ export SYBASE_DB=hibernate_orm_test
+ export SYBASE_USER=hibernate_orm_test
+ export SYBASE_PASSWORD=hibernate_orm_test
+ $CONTAINER_CLI exec sybase bash -c "source /opt/sybase/SYBASE.sh;
+cat <<-EOSQL > init1.sql
+use master
+go
+disk resize name='master', size='256m'
+go
+create database $SYBASE_DB on master = '96m'
+go
+sp_dboption $SYBASE_DB, \"single user\", true
+go
+alter database $SYBASE_DB log on master = '50m'
+go
+use $SYBASE_DB
+go
+exec sp_extendsegment logsegment, $SYBASE_DB, master
+go
+use master
+go
+sp_dboption $SYBASE_DB, \"single user\", false
+go
+use $SYBASE_DB
+go
+checkpoint
+go
+use master
+go
+create login $SYBASE_USER with password $SYBASE_PASSWORD
+go
+exec sp_dboption $SYBASE_DB, 'abort tran on log full', true
+go
+exec sp_dboption $SYBASE_DB, 'allow nulls by default', true
+go
+exec sp_dboption $SYBASE_DB, 'ddl in tran', true
+go
+exec sp_dboption $SYBASE_DB, 'trunc log on chkpt', true
+go
+exec sp_dboption $SYBASE_DB, 'full logging for select into', true
+go
+exec sp_dboption $SYBASE_DB, 'full logging for alter table', true
+go
+sp_dboption $SYBASE_DB, \"select into\", true
+go
+sp_dboption tempdb, 'ddl in tran', true
+go
+EOSQL
+
+/opt/sybase/OCS-16_0/bin/isql -Usa -P myPassword -S MYSYBASE -i ./init1.sql
+
+echo =============== CREATING DB ==========================
+cat <<-EOSQL > init2.sql
+use $SYBASE_DB
+go
+sp_adduser '$SYBASE_USER', '$SYBASE_USER', null
+go
+grant create default to $SYBASE_USER
+go
+grant create table to $SYBASE_USER
+go
+grant create view to $SYBASE_USER
+go
+grant create rule to $SYBASE_USER
+go
+grant create function to $SYBASE_USER
+go
+grant create procedure to $SYBASE_USER
+go
+commit
+go
+EOSQL
+
+/opt/sybase/OCS-16_0/bin/isql -Usa -P myPassword -S MYSYBASE -i ./init2.sql"
+ echo "Sybase successfully started"
+}
+
+oracle_setup() {
+ HEALTHSTATUS=
+ until [ "$HEALTHSTATUS" == "healthy" ];
do
echo "Waiting for Oracle to start..."
- sleep 10;
+ sleep 5;
+ # On WSL, health-checks intervals don't work for Podman, so run them manually
+ if command -v podman > /dev/null; then
+ $CONTAINER_CLI healthcheck run oracle > /dev/null
+ fi
+ HEALTHSTATUS="`$CONTAINER_CLI inspect -f $HEALTCHECK_PATH oracle`"
+ HEALTHSTATUS=${HEALTHSTATUS##+( )} #Remove longest matching series of spaces from the front
+ HEALTHSTATUS=${HEALTHSTATUS%%+( )} #Remove longest matching series of spaces from the back
done
+ sleep 2;
echo "Oracle successfully started"
# We increase file sizes to avoid online resizes as that requires lots of CPU which is restricted in XE
- docker exec oracle bash -c "source /home/oracle/.bashrc; bash -c \"
+ $CONTAINER_CLI exec oracle bash -c "source /home/oracle/.bashrc; bash -c \"
cat <$temp_dir/password.json
chmod 777 -R $temp_dir
- docker rm -f hana || true
- docker run -d --name hana -p 39013:39013 -p 39017:39017 -p 39041-39045:39041-39045 -p 1128-1129:1128-1129 -p 59013-59014:59013-59014 \
+ $CONTAINER_CLI rm -f hana || true
+ $CONTAINER_CLI run -d --name hana -p 39013:39013 -p 39017:39017 -p 39041-39045:39041-39045 -p 1128-1129:1128-1129 -p 59013-59014:59013-59014 \
--memory=8g \
--ulimit nofile=1048576:1048576 \
--sysctl kernel.shmmax=1073741824 \
@@ -204,7 +461,7 @@ hana() {
--sysctl kernel.shmmni=4096 \
--sysctl kernel.shmall=8388608 \
-v $temp_dir:/config \
- store/saplabs/hanaexpress:2.00.045.00.20200121.1 \
+ docker.io/store/saplabs/hanaexpress:2.00.045.00.20200121.1 \
--passwords-url file:///config/password.json \
--agree-to-sap-license
# Give the container some time to start
@@ -212,22 +469,22 @@ hana() {
while [[ $OUTPUT != *"Startup finished"* ]]; do
echo "Waiting for HANA to start..."
sleep 10
- OUTPUT=$(docker logs hana)
+ OUTPUT=$($CONTAINER_CLI logs hana)
done
echo "HANA successfully started"
}
cockroachdb() {
- docker rm -f cockroach || true
- docker run -d --name=cockroach -p 26257:26257 -p 8080:8080 cockroachdb/cockroach:v20.2.4 start-single-node --insecure
+ $CONTAINER_CLI rm -f cockroach || true
+ $CONTAINER_CLI run -d --name=cockroach -p 26257:26257 -p 8080:8080 docker.io/cockroachdb/cockroach:v20.2.4 start-single-node --insecure
OUTPUT=
while [[ $OUTPUT != *"CockroachDB node starting"* ]]; do
echo "Waiting for CockroachDB to start..."
sleep 10
- OUTPUT=$(docker logs cockroach)
+ OUTPUT=$($CONTAINER_CLI logs cockroach)
done
echo "Enabling experimental box2d operators"
- docker exec -it cockroach bash -c "cat <
apply from: rootProject.file( 'gradle/java-module.gradle' )
-apply plugin: 'org.asciidoctor.convert'
+apply plugin: 'org.asciidoctor.jvm.convert'
apply plugin: 'hibernate-matrix-testing'
@@ -180,8 +184,6 @@ task renderTopicalGuides(type: AsciidoctorTask, group: 'Documentation') {
description = 'Renders the Topical Guides in HTML format using Asciidoctor.'
sourceDir = file( 'src/main/asciidoc/topical' )
outputDir = new File("$buildDir/asciidoc/topical/html_single")
- backends "html5"
- separateOutputDirs false
options logDocuments: true
attributes icons: 'font',
experimental: true,
@@ -205,8 +207,6 @@ task renderGettingStartedGuides(type: AsciidoctorTask, group: 'Documentation') {
include 'index.adoc'
}
outputDir = new File("$buildDir/asciidoc/quickstart/html_single")
- backends "html5"
- separateOutputDirs false
options logDocuments: true
attributes icons: 'font', experimental: true, 'source-highlighter': 'prettify'
}
@@ -216,10 +216,10 @@ task buildTutorialZip(type: Zip) {
from 'src/main/asciidoc/quickstart/tutorials'
destinationDir = tasks.renderGettingStartedGuides.outputDir
archiveName = 'hibernate-tutorials.zip'
- expand(
+ expand(
version: project.version,
slf4j: "1.7.5",
- junit: project.junitVersion,
+ junit: project.junit4Version,
h2: project.h2Version
)
}
@@ -235,8 +235,6 @@ task renderUserGuide(type: AsciidoctorTask, group: 'Documentation') {
include 'Hibernate_User_Guide.adoc'
}
outputDir = new File("$buildDir/asciidoc/userguide/html_single")
- backends "html5"
- separateOutputDirs false
options logDocuments: true
attributes icons: 'font', experimental: true,
'source-highlighter': 'prettify',
@@ -272,8 +270,6 @@ task renderIntegrationGuide(type: AsciidoctorTask, group: 'Documentation') {
include 'Hibernate_Integration_Guide.adoc'
}
outputDir = new File("$buildDir/asciidoc/integrationguide/html_single")
- backends "html5"
- separateOutputDirs false
options logDocuments: true
attributes icons: 'font',
experimental: true,
@@ -320,3 +316,10 @@ buildDocsForPublishing.dependsOn renderIntegrationGuide
checkstyleMain.exclude '**/org/hibernate/userguide/model/*'
+tasks.withType(AsciidoctorTask).configureEach {
+ baseDirFollowsSourceDir()
+ outputOptions {
+ separateOutputDirs = false
+ backends 'html5'
+ }
+}
diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc
index 1e12b48ce4dd..a70735be9f3f 100644
--- a/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc
+++ b/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc
@@ -417,21 +417,39 @@ include::{extrasdir}/associations-many-to-many-bidirectional-with-link-entity-li
There is only one delete statement executed because, this time, the association is controlled by the `@ManyToOne` side which only has to monitor the state of the underlying foreign key relationship to trigger the right DML statement.
[[associations-not-found]]
-==== `@NotFound` association mapping
+==== `@NotFound`
-When dealing with associations which are not enforced by a Foreign Key,
-it's possible to bump into inconsistencies if the child record cannot reference a parent entity.
+When dealing with associations which are not enforced by a physical foreign-key, it is possible
+for a non-null foreign-key value to point to a non-existent value on the associated entity's table.
-By default, Hibernate will complain whenever a child association references a non-existing parent record.
-However, you can configure this behavior so that Hibernate can ignore such an Exception and simply assign `null` as a parent object referenced.
+[WARNING]
+====
+Not enforcing physical foreign-keys at the database level is highly discouraged.
+====
-To ignore non-existing parent entity references, even though not really recommended, it's possible to use the annotation `org.hibernate.annotation.NotFound` annotation with a value of `org.hibernate.annotations.NotFoundAction.IGNORE`.
+Hibernate provides support for such models using the `@NotFound` annotation, which accepts a
+`NotFoundAction` value which indicates how Hibernate should behave when such broken foreign-keys
+are encountered -
-[NOTE]
+EXCEPTION:: (default) Hibernate will throw an exception (`FetchNotFoundException`)
+IGNORE:: the association will be treated as `null`
+
+Both `@NotFound(IGNORE)` and `@NotFound(EXCEPTION)` cause Hibernate to assume that there is
+no physical foreign-key.
+
+`@ManyToOne` and `@OneToOne` associations annotated with `@NotFound` are always fetched eagerly even
+if the `fetch` strategy is set to `FetchType.LAZY`.
+
+
+[TIP]
====
-The `@ManyToOne` and `@OneToOne` associations that are annotated with `@NotFound(action = NotFoundAction.IGNORE)` are always fetched eagerly even if the `fetch` strategy is set to `FetchType.LAZY`.
+If the application itself manages the referential integrity and can guarantee that there are no
+broken foreign-keys, `jakarta.persistence.ForeignKey(NO_CONSTRAINT)` can be used instead.
+This will force Hibernate to not export physical foreign-keys, but still behave as if there is
+in terms of avoiding the downsides to `@NotFound`.
====
+
Considering the following `City` and `Person` entity mappings:
[[associations-not-found-domain-model-example]]
@@ -457,29 +475,29 @@ include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-persist-examp
When loading the `Person` entity, Hibernate is able to locate the associated `City` parent entity:
[[associations-not-found-find-example]]
-.`@NotFound` find existing entity example
+.`@NotFound` - find existing entity example
====
[source,java]
----
-include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-find-example,indent=0]
+include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-find-baseline,indent=0]
----
====
-However, if we change the `cityName` attribute to a non-existing city's name:
+However, if we break the foreign-key:
[[associations-not-found-non-existing-persist-example]]
-.`@NotFound` change to non-existing City example
+.Break the foreign-key
====
[source,java]
----
-include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-non-existing-persist-example,indent=0]
+include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-break-fk,indent=0]
----
====
Hibernate is not going to throw any exception, and it will assign a value of `null` for the non-existing `City` entity reference:
[[associations-not-found-non-existing-find-example]]
-.`@NotFound` find non-existing City example
+.`@NotFound` - find non-existing City example
====
[source,java]
----
@@ -487,6 +505,62 @@ include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-non-existing-
----
====
+`@NotFound` also affects how the association is treated as "implicit joins" in HQL and Criteria.
+When there is a physical foreign-key, Hibernate can safely assume that the value in the foreign-key's
+key-column(s) will match the value in the target-column(s) because the database makes sure that
+is the case. However, `@NotFound` forces Hibernate to perform a physical join for implicit joins
+when it might not be needed otherwise.
+
+Using the `Person` / `City` model, consider the query `from Person p where p.city.id is null`.
+
+Normally Hibernate would not need the join between the `Person` table and the `City` table because
+a physical foreign-key would ensure that any non-null value in the `Person.cityName` column
+has a matching non-null value in the `City.name` column.
+
+However, with `@NotFound` mappings it is possible to have a broken association because there is no
+physical foreign-key enforcing the relation. As seen in <>,
+the `Person.cityName` column for John Doe has been changed from "New York" to "Atlantis" even though
+there is no `City` in the database named "Atlantis". Hibernate is not able to trust the referring
+foreign-key value ("Atlantis") has a matching target value, so it must join to the `City` table to
+resolve the `city.id` value.
+
+
+[[associations-not-found-implicit-join-example]]
+.Implicit join example
+====
+[source,java]
+----
+include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-implicit-join-example,indent=0]
+----
+====
+
+Neither result includes a match for "John Doe" because the inner-join filters out that row.
+
+Hibernate does support a means to refer specifically to the key column (`Person.cityName`) in a query
+using the special `fk(..)` function. E.g.
+
+[[associations-not-found-implicit-join-example]]
+.Implicit join example
+====
+[source,java]
+----
+include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-fk-function-example,indent=0]
+----
+====
+
+With Hibernate Criteria it is possible to use `Projections.fk(...)` to select the foreign key value of an association
+and `Restrictions.fkEq(...)`, `Restrictions.fkNe(...)`, `Restrictions.fkIsNotNull(...)` and ``Restrictions.fkIsNull(...)`, E.g.
+
+[[associations-not-found-implicit-join-example]]
+.Implicit join example
+====
+[source,java]
+----
+include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-fk-criteria-example,indent=0]
+----
+====
+
+
[[associations-any]]
==== `@Any` mapping
diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/NotFoundTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/NotFoundTest.java
index 004c51a5c63b..e903fa2b100d 100644
--- a/documentation/src/test/java/org/hibernate/userguide/associations/NotFoundTest.java
+++ b/documentation/src/test/java/org/hibernate/userguide/associations/NotFoundTest.java
@@ -1,21 +1,35 @@
package org.hibernate.userguide.associations;
import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
import javax.persistence.Entity;
-import javax.persistence.FetchType;
-import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
+import org.hibernate.Criteria;
+import org.hibernate.Session;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
+import org.hibernate.criterion.ForeignKeyExpression;
+import org.hibernate.criterion.ForeingKeyProjection;
+import org.hibernate.criterion.ProjectionList;
+import org.hibernate.criterion.Projections;
+import org.hibernate.criterion.PropertyProjection;
+import org.hibernate.criterion.Restrictions;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
+import org.hibernate.testing.jdbc.SQLStatementInterceptor;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
+import static org.hibernate.testing.transaction.TransactionUtil2.inTransaction;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -24,6 +38,13 @@
*/
public class NotFoundTest extends BaseEntityManagerFunctionalTestCase {
+ private SQLStatementInterceptor sqlStatementInterceptor;
+
+ @Override
+ protected void addConfigOptions(Map options) {
+ sqlStatementInterceptor = new SQLStatementInterceptor( options );
+ }
+
@Override
protected Class>[] getAnnotatedClasses() {
return new Class>[] {
@@ -32,75 +53,182 @@ protected Class>[] getAnnotatedClasses() {
};
}
- @Test
- public void test() {
- doInJPA( this::entityManagerFactory, entityManager -> {
+ @Before
+ public void createTestData() {
+ inTransaction( entityManagerFactory(), (entityManager) -> {
//tag::associations-not-found-persist-example[]
- City _NewYork = new City();
- _NewYork.setName( "New York" );
- entityManager.persist( _NewYork );
-
- Person person = new Person();
- person.setId( 1L );
- person.setName( "John Doe" );
- person.setCityName( "New York" );
+ City newYork = new City( 1, "New York" );
+ entityManager.persist( newYork );
+
+ Person person = new Person( 1, "John Doe", newYork );
entityManager.persist( person );
//end::associations-not-found-persist-example[]
} );
+ }
- doInJPA( this::entityManagerFactory, entityManager -> {
- //tag::associations-not-found-find-example[]
- Person person = entityManager.find( Person.class, 1L );
- assertEquals( "New York", person.getCity().getName() );
- //end::associations-not-found-find-example[]
+ @After
+ public void dropTestData() {
+ inTransaction( entityManagerFactory(), (em) -> {
+ em.createQuery( "delete Person" ).executeUpdate();
+ em.createQuery( "delete City" ).executeUpdate();
+ } );
+ }
- //tag::associations-not-found-non-existing-persist-example[]
- person.setCityName( "Atlantis" );
- //end::associations-not-found-non-existing-persist-example[]
+ @Test
+ public void test() {
+ doInJPA(this::entityManagerFactory, entityManager -> {
+ //tag::associations-not-found-find-baseline[]
+ Person person = entityManager.find(Person.class, 1);
+ assertEquals("New York", person.getCity().getName());
+ //end::associations-not-found-find-baseline[]
+ });
- } );
+ breakForeignKey();
- doInJPA( this::entityManagerFactory, entityManager -> {
+ doInJPA(this::entityManagerFactory, entityManager -> {
//tag::associations-not-found-non-existing-find-example[]
- Person person = entityManager.find( Person.class, 1L );
+ Person person = entityManager.find(Person.class, 1);
- assertEquals( "Atlantis", person.getCityName() );
- assertNull( null, person.getCity() );
+ assertNull(null, person.getCity());
//end::associations-not-found-non-existing-find-example[]
+ });
+ }
+
+ private void breakForeignKey() {
+ inTransaction( entityManagerFactory(), (em) -> {
+ //tag::associations-not-found-break-fk[]
+ // the database allows this because there is no physical foreign-key
+ em.createQuery( "delete City" ).executeUpdate();
+ //end::associations-not-found-break-fk[]
+ } );
+ }
+
+ @Test
+ public void queryTest() {
+ breakForeignKey();
+
+ inTransaction( entityManagerFactory(), (entityManager) -> {
+ //tag::associations-not-found-implicit-join-example[]
+ final List nullResults = entityManager
+ .createQuery( "from Person p where p.city.id is null", Person.class )
+ .list();
+ assertThat( nullResults ).isEmpty();
+
+ final List nonNullResults = entityManager
+ .createQuery( "from Person p where p.city.id is not null", Person.class )
+ .list();
+ assertThat( nonNullResults ).isEmpty();
+ //end::associations-not-found-implicit-join-example[]
+ } );
+ }
+
+ @Test
+ public void queryTestFk() {
+ breakForeignKey();
+
+ inTransaction( entityManagerFactory(), (entityManager) -> {
+ sqlStatementInterceptor.clear();
+ //tag::associations-not-found-fk-function-example[]
+ final List nullResults = entityManager
+ .createQuery( "select p.name from Person p where fk( p.city ) is null", String.class )
+ .list();
+
+ assertThat( nullResults ).isEmpty();
+
+ final List nonNullResults = entityManager
+ .createQuery( "select p.name from Person p where fk( p.city ) is not null", String.class )
+ .list();
+ assertThat( nonNullResults ).hasSize( 1 );
+ assertThat( nonNullResults.get( 0 ) ).isEqualTo( "John Doe" );
+ //end::associations-not-found-fk-function-example[]
+
+ // In addition, make sure that the two executed queries do not create a join
+ assertThat( sqlStatementInterceptor.getQueryCount() ).isEqualTo( 2 );
+ assertThat( sqlStatementInterceptor.getSqlQueries().get( 0 ) ).doesNotContain( " join " );
+ assertThat( sqlStatementInterceptor.getSqlQueries().get( 1 ) ).doesNotContain( " join " );
+ } );
+ }
+
+ @Test
+ public void cirteriaTestFk() {
+ breakForeignKey();
+
+ inTransaction( entityManagerFactory(), (entityManager) -> {
+ sqlStatementInterceptor.clear();
+ Session session = entityManager.unwrap( Session.class );
+ //tag::associations-not-found-fk-criteria-example[]
+ Criteria criteria = session.createCriteria( Person.class );
+ ProjectionList projList = Projections.projectionList();
+ projList.add( Projections.property( "name" ) );
+ criteria.setProjection( projList );
+ criteria.add( Restrictions.fkIsNull( "city" ) );
+ final List nullResults = criteria.list();
+
+ assertThat( nullResults ).isEmpty();
+
+ criteria = session.createCriteria( Person.class );
+ projList = Projections.projectionList();
+ projList.add( Projections.property( "name" ) );
+ criteria.setProjection( projList );
+ criteria.add( Restrictions.fkIsNotNull( "city" ) );
+ final List nonNullResults = criteria.list();
+
+ assertThat( nonNullResults ).hasSize( 1 );
+ assertThat( nonNullResults.get( 0 ) ).isEqualTo( "John Doe" );
+
+ // selecting Person -> city Foreign key
+ criteria = session.createCriteria( Person.class );
+ projList = Projections.projectionList();
+ projList.add( Projections.fk( "city" ) );
+ criteria.setProjection( projList );
+ criteria.add( Restrictions.fkIsNotNull( "city" ) );
+
+ final List foreigKeyResults = criteria.list();
+ assertThat( foreigKeyResults ).hasSize( 1 );
+ assertThat( foreigKeyResults.get( 0 ) ).isEqualTo( 1 );
+ //end::associations-not-found-fk-criteria-example[]
+
+ // In addition, make sure that the two executed queries do not create a join
+ assertThat( sqlStatementInterceptor.getQueryCount() ).isEqualTo( 3 );
+ assertThat( sqlStatementInterceptor.getSqlQueries().get( 0 ) ).doesNotContain( " join " );
+ assertThat( sqlStatementInterceptor.getSqlQueries().get( 1 ) ).doesNotContain( " join " );
+ assertThat( sqlStatementInterceptor.getSqlQueries().get( 2 ) ).doesNotContain( " join " );
} );
}
//tag::associations-not-found-domain-model-example[]
- @Entity
- @Table( name = "Person" )
+ @Entity(name = "Person")
+ @Table(name = "Person")
public static class Person {
@Id
- private Long id;
-
+ private Integer id;
private String name;
- private String cityName;
-
@ManyToOne
- @NotFound ( action = NotFoundAction.IGNORE )
- @JoinColumn(
- name = "cityName",
- referencedColumnName = "name",
- insertable = false,
- updatable = false
- )
+ @NotFound(action = NotFoundAction.IGNORE)
+ @JoinColumn(name = "city_fk", referencedColumnName = "id")
private City city;
//Getters and setters are omitted for brevity
- //end::associations-not-found-domain-model-example[]
+ //end::associations-not-found-domain-model-example[]
+
- public Long getId() {
+ public Person() {
+ }
+
+ public Person(Integer id, String name, City city) {
+ this.id = id;
+ this.name = name;
+ this.city = city;
+ }
+
+ public Integer getId() {
return id;
}
- public void setId(Long id) {
+ public void setId(Integer id) {
this.id = id;
}
@@ -112,40 +240,39 @@ public void setName(String name) {
this.name = name;
}
- public String getCityName() {
- return cityName;
- }
-
- public void setCityName(String cityName) {
- this.cityName = cityName;
- this.city = null;
- }
-
public City getCity() {
return city;
}
- //tag::associations-not-found-domain-model-example[]
+ //tag::associations-not-found-domain-model-example[]
}
- @Entity
- @Table( name = "City" )
+ @Entity(name = "City")
+ @Table(name = "City")
public static class City implements Serializable {
@Id
- @GeneratedValue
- private Long id;
+ private Integer id;
private String name;
//Getters and setters are omitted for brevity
- //end::associations-not-found-domain-model-example[]
+ //end::associations-not-found-domain-model-example[]
+
- public Long getId() {
+ public City() {
+ }
+
+ public City(Integer id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public Integer getId() {
return id;
}
- public void setId(Long id) {
+ public void setId(Integer id) {
this.id = id;
}
@@ -156,7 +283,7 @@ public String getName() {
public void setName(String name) {
this.name = name;
}
- //tag::associations-not-found-domain-model-example[]
+ //tag::associations-not-found-domain-model-example[]
}
//end::associations-not-found-domain-model-example[]
-}
\ No newline at end of file
+}
diff --git a/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java b/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java
index a5602e7ff903..f545f0d7ebe6 100644
--- a/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java
+++ b/documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java
@@ -22,23 +22,27 @@
import org.hibernate.Session;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.NaturalId;
+import org.hibernate.annotations.NaturalIdCache;
import org.hibernate.cfg.AvailableSettings;
+import org.hibernate.internal.SessionFactoryImpl;
import org.hibernate.jpa.QueryHints;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.stat.CacheRegionStatistics;
import org.hibernate.stat.Statistics;
+import org.hibernate.testing.TestForIssue;
import org.junit.Ignore;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* @author Vlad Mihalcea
*/
-@Ignore
+
//@FailureExpected( jiraKey = "HHH-12146", message = "No idea why those changes cause this to fail, especially in the way it does" )
public class SecondLevelCacheTest extends BaseEntityManagerFunctionalTestCase {
@@ -251,10 +255,89 @@ public void testCache() {
});
}
+ @Test
+ @TestForIssue( jiraKey = "HHH-14944") // issue is also reproduceable in Hibernate 5.4
+ public void testCacheVerifyHits() {
+ doInJPA( this::entityManagerFactory, entityManager -> {
+ entityManager.persist( new Person() );
+ Person aPerson= new Person();
+ aPerson.setName( "John Doe" );
+ aPerson.setCode( "unique-code" );
+ entityManager.persist( aPerson );
+ Session session = entityManager.unwrap(Session.class);
+ SessionFactoryImpl sfi = (SessionFactoryImpl) session.getSessionFactory();
+ sfi.getStatistics().clear();
+ return aPerson;
+ });
+
+ doInJPA(this::entityManagerFactory, entityManager -> {
+ log.info("Native load by natural-id, generate first hit");
+
+ Session session = entityManager.unwrap(Session.class);
+ SessionFactoryImpl sfi = (SessionFactoryImpl) session.getSessionFactory();
+ //tag::caching-entity-natural-id-example[]
+ Person person = session
+ .byNaturalId(Person.class)
+ .using("code", "unique-code")
+ .load();
+
+ assertNotNull(person);
+ log.info("NaturalIdCacheHitCount: " + sfi.getStatistics().getNaturalIdCacheHitCount());
+ log.info("SecondLevelCacheHitCount: " + sfi.getStatistics().getSecondLevelCacheHitCount());
+ assertEquals(1, sfi.getStatistics().getNaturalIdCacheHitCount());
+ assertEquals(1, sfi.getStatistics().getSecondLevelCacheHitCount());
+ //end::caching-entity-natural-id-example[]
+ });
+
+ doInJPA(this::entityManagerFactory, entityManager -> {
+ log.info("Native load by natural-id, generate second hit");
+
+ Session session = entityManager.unwrap(Session.class);
+ SessionFactoryImpl sfi = (SessionFactoryImpl) session.getSessionFactory();
+ //tag::caching-entity-natural-id-example[]
+ Person person = session.bySimpleNaturalId(Person.class).load("unique-code");
+ assertNotNull(person);
+
+ // resolve in persistence context (first level cache)
+ session.bySimpleNaturalId(Person.class).load("unique-code");
+ log.info("NaturalIdCacheHitCount: " + sfi.getStatistics().getNaturalIdCacheHitCount());
+ log.info("SecondLevelCacheHitCount: " + sfi.getStatistics().getSecondLevelCacheHitCount());
+ assertEquals(2, sfi.getStatistics().getNaturalIdCacheHitCount());
+ assertEquals(2, sfi.getStatistics().getSecondLevelCacheHitCount());
+
+ session.clear();
+ // persistence context (first level cache) empty, should resolve from second level cache
+ log.info("Native load by natural-id, generate third hit");
+ person = session.bySimpleNaturalId(Person.class).load("unique-code");
+ log.info("NaturalIdCacheHitCount: " + sfi.getStatistics().getNaturalIdCacheHitCount());
+ log.info("SecondLevelCacheHitCount: " + sfi.getStatistics().getSecondLevelCacheHitCount());
+ assertNotNull(person);
+ assertEquals(3, sfi.getStatistics().getNaturalIdCacheHitCount());
+ assertEquals(3, sfi.getStatistics().getSecondLevelCacheHitCount());
+
+ //Remove the entity from the persistence context
+ Long id = person.getId();
+
+ entityManager.detach(person); // still it should resolve from second level cache after this
+
+ log.info("Native load by natural-id, generate 4. hit");
+ person = session.bySimpleNaturalId(Person.class).load("unique-code");
+ log.info("NaturalIdCacheHitCount: " + sfi.getStatistics().getNaturalIdCacheHitCount());
+ assertEquals("we expected now 4 hits" , 4, sfi.getStatistics().getNaturalIdCacheHitCount());
+ assertNotNull(person);
+ session.delete(person); // evicts natural-id from first & second level cache
+ person = session.bySimpleNaturalId(Person.class).load("unique-code");
+ assertEquals(4, sfi.getStatistics().getNaturalIdCacheHitCount()); // thus hits should not increment
+
+ //end::caching-entity-natural-id-example[]
+ });
+ }
+
//tag::caching-entity-natural-id-mapping-example[]
@Entity(name = "Person")
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
+ @NaturalIdCache
public static class Person {
@Id
diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java
index b6fc6d3bdc5e..65d7480b6fb7 100644
--- a/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java
+++ b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java
@@ -9,7 +9,6 @@
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.Column;
diff --git a/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java b/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java
index 98070dc5f67a..95027b62eb96 100644
--- a/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java
+++ b/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java
@@ -31,6 +31,7 @@
import org.hibernate.dialect.Oracle8iDialect;
import org.hibernate.dialect.PostgreSQL81Dialect;
import org.hibernate.dialect.SQLServerDialect;
+import org.hibernate.dialect.SybaseASE15Dialect;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.type.StringType;
import org.hibernate.userguide.model.AddressType;
@@ -1314,6 +1315,7 @@ public void test_hql_sqrt_function_example() {
@Test
@SkipForDialect(SQLServerDialect.class)
+ @SkipForDialect(value = SybaseASE15Dialect.class, comment = "current_date requires parenthesis which we don't render")
@SkipForDialect(value = DerbyDialect.class, comment = "Comparisons between 'DATE' and 'TIMESTAMP' are not supported")
public void test_hql_current_date_function_example() {
doInJPA( this::entityManagerFactory, entityManager -> {
@@ -1356,6 +1358,7 @@ public void test_hql_current_time_function_example() {
}
@Test
+ @SkipForDialect(value = SybaseASE15Dialect.class, comment = "current_timestamp requires parenthesis which we don't render")
public void test_hql_current_timestamp_function_example() {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::hql-current-timestamp-function-example[]
@@ -1428,6 +1431,7 @@ public void test_hql_year_function_example() {
@Test
@SkipForDialect(SQLServerDialect.class)
+ @SkipForDialect(value = SybaseASE15Dialect.class, comment = "No proper implementation for the STR function available")
public void test_hql_str_function_example() {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::hql-str-function-example[]
diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserTypeTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserTypeTest.java
index 6a7a7ffb52f7..91c7d25e3597 100644
--- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserTypeTest.java
+++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BitSetUserTypeTest.java
@@ -7,6 +7,7 @@
package org.hibernate.userguide.mapping.basic;
import java.util.BitSet;
+import javax.persistence.Column;
import javax.persistence.ColumnResult;
import javax.persistence.ConstructorResult;
import javax.persistence.Entity;
@@ -94,7 +95,7 @@ protected boolean isCleanupTestDataRequired() {
query =
"SELECT " +
" pr.id AS \"pr.id\", " +
- " pr.bitset AS \"pr.bitset\" " +
+ " pr.bitset_col AS \"pr.bitset\" " +
"FROM Product pr " +
"WHERE pr.id = :id",
resultSetMapping = "Person"
@@ -117,6 +118,7 @@ public static class Product {
private Integer id;
@Type( type = "bitset" )
+ @Column(name = "bitset_col")
private BitSet bitSet;
//Constructors, getters, and setters are omitted for brevity
diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobCharArrayTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobCharArrayTest.java
index feb6a1591ceb..c56769569d2b 100644
--- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobCharArrayTest.java
+++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobCharArrayTest.java
@@ -10,8 +10,10 @@
import javax.persistence.Id;
import javax.persistence.Lob;
+import org.hibernate.dialect.SybaseASE15Dialect;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
+import org.hibernate.testing.SkipForDialect;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
@@ -20,6 +22,7 @@
/**
* @author Vlad Mihalcea
*/
+@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement character stream handling")
public class ClobCharArrayTest extends BaseEntityManagerFunctionalTestCase {
@Override
diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobStringTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobStringTest.java
index 5df1221983db..0a216a292ce9 100644
--- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobStringTest.java
+++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobStringTest.java
@@ -10,8 +10,10 @@
import javax.persistence.Id;
import javax.persistence.Lob;
+import org.hibernate.dialect.SybaseASE15Dialect;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
+import org.hibernate.testing.SkipForDialect;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
@@ -20,6 +22,7 @@
/**
* @author Vlad Mihalcea
*/
+@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement character stream handling")
public class ClobStringTest extends BaseEntityManagerFunctionalTestCase {
@Override
diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobTest.java
index 503413c63659..f7edd1f10998 100644
--- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobTest.java
+++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobTest.java
@@ -16,9 +16,11 @@
import javax.persistence.Lob;
import org.hibernate.Session;
+import org.hibernate.dialect.SybaseASE15Dialect;
import org.hibernate.engine.jdbc.ClobProxy;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
+import org.hibernate.testing.SkipForDialect;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
@@ -28,6 +30,7 @@
/**
* @author Vlad Mihalcea
*/
+@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement character stream handling")
public class ClobTest extends BaseEntityManagerFunctionalTestCase {
@Override
diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobCharArrayTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobCharArrayTest.java
index 50fa6a64d2d1..f2caf437df51 100644
--- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobCharArrayTest.java
+++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobCharArrayTest.java
@@ -14,6 +14,7 @@
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.MySQL5Dialect;
import org.hibernate.dialect.PostgreSQL81Dialect;
+import org.hibernate.dialect.SybaseASE15Dialect;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.SkipForDialect;
@@ -33,6 +34,7 @@
},
comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and https://hibernate.atlassian.net/browse/HHH-10695 and https://hibernate.atlassian.net/browse/HHH-10473"
)
+@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement nationalized handling")
public class NClobCharArrayTest extends BaseEntityManagerFunctionalTestCase {
@Override
diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobStringTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobStringTest.java
index 03d20f9234bb..15c7b5e63120 100644
--- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobStringTest.java
+++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobStringTest.java
@@ -14,6 +14,7 @@
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.MySQL5Dialect;
import org.hibernate.dialect.PostgreSQL81Dialect;
+import org.hibernate.dialect.SybaseASE15Dialect;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.SkipForDialect;
@@ -33,6 +34,7 @@
},
comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and https://hibernate.atlassian.net/browse/HHH-10695 and https://hibernate.atlassian.net/browse/HHH-10473"
)
+@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement nationalized handling")
public class NClobStringTest extends BaseEntityManagerFunctionalTestCase {
@Override
diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobTest.java
index 8061589e4253..01eaae8b2035 100644
--- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobTest.java
+++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobTest.java
@@ -22,6 +22,7 @@
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.MySQL5Dialect;
import org.hibernate.dialect.PostgreSQL81Dialect;
+import org.hibernate.dialect.SybaseASE15Dialect;
import org.hibernate.engine.jdbc.NClobProxy;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
@@ -45,6 +46,7 @@
},
comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and https://hibernate.atlassian.net/browse/HHH-10695 and https://hibernate.atlassian.net/browse/HHH-10473"
)
+@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement nationalized handling")
public class NClobTest extends BaseEntityManagerFunctionalTestCase {
@Override
diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NationalizedTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NationalizedTest.java
index 22ba662aa263..c3257a988c1a 100644
--- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NationalizedTest.java
+++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NationalizedTest.java
@@ -12,6 +12,7 @@
import org.hibernate.annotations.Nationalized;
import org.hibernate.dialect.DerbyDialect;
import org.hibernate.dialect.PostgreSQL81Dialect;
+import org.hibernate.dialect.SybaseASE15Dialect;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.SkipForDialect;
@@ -30,6 +31,7 @@
},
comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and Derby doesn't support nationalized type"
)
+@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement nationalized handling")
public class NationalizedTest extends BaseEntityManagerFunctionalTestCase {
@Override
diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/SubselectTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/SubselectTest.java
index d64f14813fe6..438f72f7b954 100644
--- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/SubselectTest.java
+++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/SubselectTest.java
@@ -16,6 +16,7 @@
import org.hibernate.annotations.Subselect;
import org.hibernate.annotations.Synchronize;
import org.hibernate.dialect.DerbyDialect;
+import org.hibernate.dialect.SybaseASE15Dialect;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.SkipForDialect;
@@ -28,6 +29,7 @@
* @author Vlad Mihalcea
*/
@SkipForDialect(value = DerbyDialect.class, comment = "Derby doesn't support a CONCAT function")
+@SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase doesn't support a CONCAT function")
public class SubselectTest extends BaseEntityManagerFunctionalTestCase {
@Override
diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java
index 870997a9b4d3..fec3260c73f6 100644
--- a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java
+++ b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java
@@ -15,11 +15,13 @@
import javax.persistence.Id;
import org.hibernate.annotations.ValueGenerationType;
+import org.hibernate.dialect.SybaseASE15Dialect;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.tuple.AnnotationValueGeneration;
import org.hibernate.tuple.GenerationTiming;
import org.hibernate.tuple.ValueGenerator;
+import org.hibernate.testing.SkipForDialect;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
@@ -27,6 +29,7 @@
/**
* @author Vlad Mihalcea
*/
+@SkipForDialect(value = SybaseASE15Dialect.class, comment = "current_timestamp requires parenthesis which we don't render")
public class DatabaseValueGenerationTest extends BaseEntityManagerFunctionalTestCase {
@Override
diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java
index be78241ad8ed..5afa0bd0b976 100644
--- a/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java
+++ b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java
@@ -10,6 +10,8 @@
import org.hibernate.annotations.OnDeleteAction;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
+import org.hibernate.testing.DialectChecks;
+import org.hibernate.testing.RequiresDialectFeature;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
@@ -17,6 +19,7 @@
/**
* @author Vlad Mihalcea
*/
+@RequiresDialectFeature(DialectChecks.SupportsCascadeDeleteCheck.class)
public class CascadeOnDeleteTest extends BaseEntityManagerFunctionalTestCase {
@Override
diff --git a/documentation/src/test/java/org/hibernate/userguide/schema/UniqueConstraintTest.java b/documentation/src/test/java/org/hibernate/userguide/schema/UniqueConstraintTest.java
index ba6f042ff885..6651ccd70f30 100644
--- a/documentation/src/test/java/org/hibernate/userguide/schema/UniqueConstraintTest.java
+++ b/documentation/src/test/java/org/hibernate/userguide/schema/UniqueConstraintTest.java
@@ -19,9 +19,11 @@
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.dialect.SybaseASE15Dialect;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
+import org.hibernate.testing.SkipForDialect;
import org.hibernate.testing.util.ExceptionUtil;
import org.junit.Test;
@@ -44,6 +46,7 @@ protected Class>[] getAnnotatedClasses() {
}
@Test
+ @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor")
public void test() {
//tag::schema-generation-columns-unique-constraint-persist-example[]
Author _author = doInJPA( this::entityManagerFactory, entityManager -> {
diff --git a/documentation/src/test/resources/hibernate.properties b/documentation/src/test/resources/hibernate.properties
index 8b93710f2628..968847a8f3f7 100644
--- a/documentation/src/test/resources/hibernate.properties
+++ b/documentation/src/test/resources/hibernate.properties
@@ -10,6 +10,7 @@ hibernate.connection.driver_class @jdbc.driver@
hibernate.connection.url @jdbc.url@
hibernate.connection.username @jdbc.user@
hibernate.connection.password @jdbc.pass@
+hibernate.connection.init_sql @connection.init_sql@
hibernate.connection.pool_size 5
diff --git a/gradle/databases.gradle b/gradle/databases.gradle
index cf54fda5dd4d..bd2b65b16043 100644
--- a/gradle/databases.gradle
+++ b/gradle/databases.gradle
@@ -16,20 +16,23 @@ ext {
'jdbc.user' : 'sa',
'jdbc.pass' : '',
'jdbc.url' : 'jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;LOCK_TIMEOUT=10000',
+ 'connection.init_sql' : ''
],
hsqldb : [
'db.dialect' : 'org.hibernate.dialect.HSQLDialect',
'jdbc.driver': 'org.hsqldb.jdbc.JDBCDriver',
'jdbc.user' : 'sa',
'jdbc.pass' : '',
- 'jdbc.url' : 'jdbc:hsqldb:mem:test'
+ 'jdbc.url' : 'jdbc:hsqldb:mem:test',
+ 'connection.init_sql' : ''
],
derby : [
'db.dialect' : 'org.hibernate.dialect.DerbyTenSevenDialect',
'jdbc.driver': 'org.apache.derby.jdbc.EmbeddedDriver',
'jdbc.user' : 'hibernate_orm_test',
'jdbc.pass' : 'hibernate_orm_test',
- 'jdbc.url' : 'jdbc:derby:target/tmp/derby/hibernate_orm_test;databaseName=hibernate_orm_test;create=true'
+ 'jdbc.url' : 'jdbc:derby:target/tmp/derby/hibernate_orm_test;databaseName=hibernate_orm_test;create=true',
+ 'connection.init_sql' : ''
],
pgsql : [
'db.dialect' : 'org.hibernate.dialect.PostgreSQL95Dialect',
@@ -37,7 +40,8 @@ ext {
'jdbc.user' : 'hibernate_orm_test',
'jdbc.pass' : 'hibernate_orm_test',
// Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com
- 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0'
+ 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0',
+ 'connection.init_sql' : ''
],
pgsql_docker : [
'db.dialect' : 'org.hibernate.dialect.PostgreSQL10Dialect',
@@ -45,7 +49,8 @@ ext {
'jdbc.user' : 'hibernate_orm_test',
'jdbc.pass' : 'hibernate_orm_test',
// Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com
- 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0'
+ 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0',
+ 'connection.init_sql' : ''
],
pgsql_ci : [
'db.dialect' : 'org.hibernate.dialect.PostgreSQL95Dialect',
@@ -53,21 +58,41 @@ ext {
'jdbc.user' : 'hibernate_orm_test',
'jdbc.pass' : 'hibernate_orm_test',
// Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com
- 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0'
+ 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0',
+ 'connection.init_sql' : ''
+ ],
+ sybase_ci : [
+ 'db.dialect' : 'org.hibernate.dialect.SybaseASE157Dialect',
+ 'jdbc.driver': 'net.sourceforge.jtds.jdbc.Driver',
+ 'jdbc.user' : 'hibernate_orm_test',
+ 'jdbc.pass' : 'hibernate_orm_test',
+ // Disable prepared statement caching to avoid issues with changing schemas
+ 'jdbc.url' : 'jdbc:jtds:sybase://' + dbHost + ':5000/hibernate_orm_test;maxStatements=0;cacheMetaData=false',
+ 'connection.init_sql' : 'set ansinull on'
],
mysql : [
'db.dialect' : 'org.hibernate.dialect.MySQL57Dialect',
'jdbc.driver': 'com.mysql.jdbc.Driver',
'jdbc.user' : 'hibernateormtest',
'jdbc.pass' : 'hibernateormtest',
- 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test'
+ 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test',
+ 'connection.init_sql' : ''
],
mysql_docker : [
'db.dialect' : 'org.hibernate.dialect.MySQL57Dialect',
'jdbc.driver': 'com.mysql.jdbc.Driver',
'jdbc.user' : 'hibernate_orm_test',
'jdbc.pass' : 'hibernate_orm_test',
- 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test?useSSL=false'
+ 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test?useSSL=false',
+ 'connection.init_sql' : ''
+ ],
+ mysql_ci : [
+ 'db.dialect' : 'org.hibernate.dialect.MySQL8Dialect',
+ 'jdbc.driver': 'com.mysql.jdbc.Driver',
+ 'jdbc.user' : 'hibernate_orm_test',
+ 'jdbc.pass' : 'hibernate_orm_test',
+ 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test?allowPublicKeyRetrieval=true',
+ 'connection.init_sql' : ''
],
// uses docker mysql_8_0
mysql8_spatial_ci: [
@@ -75,28 +100,32 @@ ext {
'jdbc.driver': 'com.mysql.cj.jdbc.Driver',
'jdbc.user' : 'hibernate_orm_test',
'jdbc.pass' : 'hibernate_orm_test',
- 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test?allowPublicKeyRetrieval=true&useSSL=false'
+ 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test?allowPublicKeyRetrieval=true&useSSL=false',
+ 'connection.init_sql' : ''
],
mariadb : [
'db.dialect' : 'org.hibernate.dialect.MariaDB103Dialect',
'jdbc.driver': 'org.mariadb.jdbc.Driver',
'jdbc.user' : 'hibernate_orm_test',
'jdbc.pass' : 'hibernate_orm_test',
- 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test'
+ 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test',
+ 'connection.init_sql' : ''
],
mariadb_ci : [
'db.dialect' : 'org.hibernate.dialect.MariaDB103Dialect',
'jdbc.driver': 'org.mariadb.jdbc.Driver',
'jdbc.user' : 'root',
'jdbc.pass' : 'hibernate_orm_test',
- 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test'
+ 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test',
+ 'connection.init_sql' : ''
],
mariadb_spatial_ci : [
'db.dialect' : 'org.hibernate.spatial.dialect.mariadb.MariaDB103SpatialDialect',
'jdbc.driver': 'org.mariadb.jdbc.Driver',
'jdbc.user' : 'root',
'jdbc.pass' : 'hibernate_orm_test',
- 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test'
+ 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test',
+ 'connection.init_sql' : ''
],
postgis : [
'db.dialect' : 'org.hibernate.spatial.dialect.postgis.PostgisPG95Dialect',
@@ -104,14 +133,24 @@ ext {
'jdbc.user' : 'hibernate_orm_test',
'jdbc.pass' : 'hibernate_orm_test',
// Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com
- 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0'
+ 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0',
+ 'connection.init_sql' : ''
],
oracle : [
'db.dialect' : 'org.hibernate.dialect.Oracle10gDialect',
'jdbc.driver': 'oracle.jdbc.OracleDriver',
'jdbc.user' : 'hibernate_orm_test',
'jdbc.pass' : 'hibernate_orm_test',
- 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521/xe'
+ 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521/xe',
+ 'connection.init_sql' : ''
+ ],
+ oracle_jenkins : [
+ 'db.dialect' : 'org.hibernate.dialect.Oracle12cDialect',
+ 'jdbc.driver': 'oracle.jdbc.OracleDriver',
+ 'jdbc.user' : 'hibernate_orm_test',
+ 'jdbc.pass' : 'hibernate_orm_test',
+ 'jdbc.url' : 'jdbc:oracle:thin:@hibernate-testing-oracle-se.ccuzkqo3zqzq.us-east-1.rds.amazonaws.com:1521:ORCL',
+ 'connection.init_sql' : ''
],
// Use ./docker_db.sh oracle_ee to start the database
oracle_docker : [
@@ -119,70 +158,80 @@ ext {
'jdbc.driver': 'oracle.jdbc.OracleDriver',
'jdbc.user' : 'c##hibernate_orm_test',
'jdbc.pass' : 'hibernate_orm_test',
- 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521/ORCLPDB1.localdomain'
+ 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521/ORCLPDB1.localdomain',
+ 'connection.init_sql' : ''
],
oracle_ci : [
'db.dialect' : 'org.hibernate.dialect.Oracle12cDialect',
'jdbc.driver': 'oracle.jdbc.OracleDriver',
'jdbc.user' : 'SYSTEM',
'jdbc.pass' : 'Oracle18',
- 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521:XE'
+ 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521:XE',
+ 'connection.init_sql' : ''
],
oracle_spatial_ci : [
'db.dialect' : 'org.hibernate.spatial.dialect.oracle.OracleSpatial10gDialect',
'jdbc.driver': 'oracle.jdbc.OracleDriver',
'jdbc.user' : 'SYSTEM',
'jdbc.pass' : 'Oracle18',
- 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521:XE'
+ 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521:XE',
+ 'connection.init_sql' : ''
],
mssql : [
'db.dialect' : 'org.hibernate.dialect.SQLServer2012Dialect',
'jdbc.driver': 'com.microsoft.sqlserver.jdbc.SQLServerDriver',
'jdbc.user' : 'hibernate_orm_test',
'jdbc.pass' : 'hibernate_orm_test',
- 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';instance=SQLEXPRESS;databaseName=hibernate_orm_test'
+ 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';instance=SQLEXPRESS;databaseName=hibernate_orm_test',
+ 'connection.init_sql' : ''
],
mssql_ci : [
'db.dialect' : 'org.hibernate.dialect.SQLServer2012Dialect',
'jdbc.driver': 'com.microsoft.sqlserver.jdbc.SQLServerDriver',
'jdbc.user' : 'sa',
'jdbc.pass' : 'Hibernate_orm_test',
- 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';databaseName=hibernate_orm_test'
+ 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';databaseName=hibernate_orm_test;sendTimeAsDatetime=false',
+ 'connection.init_sql' : ''
],
mssql_spatial_ci : [
'db.dialect' : 'org.hibernate.spatial.dialect.sqlserver.SqlServer2012SpatialDialect',
'jdbc.driver': 'com.microsoft.sqlserver.jdbc.SQLServerDriver',
'jdbc.user' : 'sa',
'jdbc.pass' : 'Hibernate_orm_test',
- 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';databaseName=hibernate_orm_test'
+ 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';databaseName=hibernate_orm_test',
+ 'connection.init_sql' : ''
],
informix : [
'db.dialect' : 'org.hibernate.dialect.InformixDialect',
'jdbc.driver': 'com.informix.jdbc.IfxDriver',
'jdbc.user' : 'informix',
'jdbc.pass' : 'in4mix',
- 'jdbc.url' : 'jdbc:informix-sqli://' + dbHost + ':9088/sysuser:INFORMIXSERVER=dev;user=informix;password=in4mix'
+ 'jdbc.url' : 'jdbc:informix-sqli://' + dbHost + ':9088/sysuser:INFORMIXSERVER=dev;user=informix;password=in4mix',
+ 'connection.init_sql' : ''
],
db2 : [
'db.dialect' : 'org.hibernate.dialect.DB2Dialect',
'jdbc.driver': 'com.ibm.db2.jcc.DB2Driver',
'jdbc.user' : 'db2inst1',
'jdbc.pass' : 'db2inst1-pwd',
- 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/hibern8'
+ 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/hibern8',
+ 'connection.init_sql' : ''
],
db2_ci : [
'db.dialect' : 'org.hibernate.dialect.DB2Dialect',
'jdbc.driver': 'com.ibm.db2.jcc.DB2Driver',
'jdbc.user' : 'orm_test',
'jdbc.pass' : 'orm_test',
- 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/orm_test'
+ 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/orm_test',
+ 'connection.init_sql' : ''
],
db2_spatial_ci : [
'db.dialect' : 'org.hibernate.spatial.dialect.db2.DB2SpatialDialect',
'jdbc.driver': 'com.ibm.db2.jcc.DB2Driver',
'jdbc.user' : 'orm_test',
'jdbc.pass' : 'orm_test',
- 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/orm_test'
+ 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/orm_test',
+ 'connection.init_sql' : ''
],
hana : [
'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect',
@@ -190,7 +239,8 @@ ext {
'jdbc.user' : 'HIBERNATE_TEST',
'jdbc.pass' : 'H1bernate_test',
// Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html
- 'jdbc.url' : 'jdbc:sap://' + dbHost + ':30015/?statementCacheSize=0'
+ 'jdbc.url' : 'jdbc:sap://' + dbHost + ':30015/?statementCacheSize=0',
+ 'connection.init_sql' : ''
],
hana_cloud : [
'db.dialect' : 'org.hibernate.dialect.HANACloudColumnStoreDialect',
@@ -198,7 +248,17 @@ ext {
'jdbc.user' : 'HIBERNATE_TEST',
'jdbc.pass' : 'H1bernate_test',
// Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html
- 'jdbc.url' : 'jdbc:sap://' + dbHost + ':443/?encrypt=true&validateCertificate=false&statementCacheSize=0'
+ 'jdbc.url' : 'jdbc:sap://' + dbHost + ':443/?encrypt=true&validateCertificate=false&statementCacheSize=0',
+ 'connection.init_sql' : ''
+ ],
+ hana_jenkins : [
+ 'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect',
+ 'jdbc.driver': 'com.sap.db.jdbc.Driver',
+ 'jdbc.user' : 'HIBERNATE_TEST',
+ 'jdbc.pass' : 'H1bernate_test',
+ // Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html
+ 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39015/?statementCacheSize=0',
+ 'connection.init_sql' : ''
],
hana_vlad : [
'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect',
@@ -206,7 +266,8 @@ ext {
'jdbc.user' : 'VLAD',
'jdbc.pass' : 'V1ad_test',
// Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html
- 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39015/?statementCacheSize=0'
+ 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39015/?statementCacheSize=0',
+ 'connection.init_sql' : ''
],
hana_docker : [
'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect',
@@ -214,7 +275,8 @@ ext {
'jdbc.user' : 'SYSTEM',
'jdbc.pass' : 'H1bernate_test',
// Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html
- 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0'
+ 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0',
+ 'connection.init_sql' : ''
],
hana_ci : [
'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect',
@@ -222,7 +284,8 @@ ext {
'jdbc.user' : 'SYSTEM',
'jdbc.pass' : 'H1bernate_test',
// Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html
- 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0'
+ 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0',
+ 'connection.init_sql' : ''
],
hana_spatial_ci : [
'db.dialect' : 'org.hibernate.spatial.dialect.hana.HANASpatialDialect',
@@ -230,7 +293,8 @@ ext {
'jdbc.user' : 'SYSTEM',
'jdbc.pass' : 'H1bernate_test',
// Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html
- 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0'
+ 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0',
+ 'connection.init_sql' : ''
],
cockroachdb : [
'db.dialect' : 'org.hibernate.dialect.CockroachDB192Dialect',
@@ -239,7 +303,8 @@ ext {
'jdbc.user' : 'root',
'jdbc.pass' : '',
// Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com
- 'jdbc.url' : 'jdbc:postgresql://' + dbHost + ':26257/defaultdb?sslmode=disable&preparedStatementCacheQueries=0'
+ 'jdbc.url' : 'jdbc:postgresql://' + dbHost + ':26257/defaultdb?sslmode=disable&preparedStatementCacheQueries=0',
+ 'connection.init_sql' : ''
],
cockroachdb_spatial : [
'db.dialect' : 'org.hibernate.spatial.dialect.cockroachdb.CockroachDB202SpatialDialect',
@@ -248,7 +313,8 @@ ext {
'jdbc.user' : 'root',
'jdbc.pass' : '',
// Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com
- 'jdbc.url' : 'jdbc:postgresql://' + dbHost + ':26257/defaultdb?sslmode=disable&preparedStatementCacheQueries=0'
+ 'jdbc.url' : 'jdbc:postgresql://' + dbHost + ':26257/defaultdb?sslmode=disable&preparedStatementCacheQueries=0',
+ 'connection.init_sql' : ''
]
]
}
diff --git a/gradle/java-module.gradle b/gradle/java-module.gradle
index f4bb0dbfce63..ca8f48168fe1 100644
--- a/gradle/java-module.gradle
+++ b/gradle/java-module.gradle
@@ -79,17 +79,14 @@ dependencies {
testRuntime( libraries.derby )
testRuntime( libraries.hsqldb )
testRuntime( libraries.postgresql )
- testRuntime( libraries.mysql )
- testRuntime( libraries.mariadb )
testRuntime( libraries.mssql )
testRuntime( libraries.informix )
- testRuntime( libraries.hana )
testRuntime( libraries.cockroachdb )
+ testRuntime( libraries.oracle )
+ testRuntime( libraries.sybase )
asciidoclet 'org.asciidoctor:asciidoclet:1.+'
- testRuntime( libraries.oracle )
-
// Since both the DB2 driver and HANA have a package "net.jpountz" we have to add dependencies conditionally
// This is due to the "no split-packages" requirement of Java 9+
@@ -99,6 +96,12 @@ dependencies {
else if ( db.startsWith( 'hana' ) ) {
testRuntime( libraries.hana )
}
+ else if ( db.startsWith( 'mysql' ) ) {
+ testRuntimeOnly libraries.mysql
+ }
+ else if ( db.startsWith( 'mariadb' ) ) {
+ testRuntimeOnly libraries.mariadb
+ }
// Mac-specific
project.ext.toolsJar = file("${System.getProperty('java.home')}/../lib/tools.jar")
diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle
index ba9581f1d5ab..4c2b17a1b83c 100644
--- a/gradle/libraries.gradle
+++ b/gradle/libraries.gradle
@@ -8,10 +8,12 @@
// build a map of the dependency artifacts to use. Allows centralized definition of the version of artifacts to
// use. In that respect it serves a role similar to in Maven
ext {
+ junit5Version = '5.8.2'
+ junitVintageVersion = junit5Version
+ junit4Version = '4.13.2'
- junitVersion = '4.13.2'
h2Version = '1.4.197'
- bytemanVersion = '4.0.16' //Compatible with JDK 17
+ bytemanVersion = '4.0.20' //Compatible with JDK 20
jnpVersion = '5.0.6.CR1'
hibernateCommonsVersion = '5.1.2.Final'
@@ -24,7 +26,7 @@ ext {
weldVersion = '3.1.5.Final'
jakartaWeldVersion = '4.0.1.SP1'
- byteBuddyVersion = '1.12.7'
+ byteBuddyVersion = '1.12.18'
agroalVersion = '1.9'
@@ -46,7 +48,7 @@ ext {
//GraalVM
graalvmVersion = '21.3.0'
- micrometerVersion = '1.6.1'
+ micrometerVersion = '1.9.3'
libraries = [
// Ant
@@ -89,8 +91,8 @@ ext {
// logging
logging: 'org.jboss.logging:jboss-logging:3.4.3.Final',
- logging_annotations: 'org.jboss.logging:jboss-logging-annotations:2.1.0.Final',
- logging_processor: 'org.jboss.logging:jboss-logging-processor:2.1.0.Final',
+ logging_annotations: 'org.jboss.logging:jboss-logging-annotations:2.2.1.Final',
+ logging_processor: 'org.jboss.logging:jboss-logging-processor:2.2.1.Final',
// jaxb task
jaxb_api: "javax.xml.bind:jaxb-api:${jaxbApiVersion}",
@@ -116,23 +118,30 @@ ext {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~ testing
+ junit5_api: "org.junit.jupiter:junit-jupiter-api:${junit5Version}",
+ junit5_jupiter: "org.junit.jupiter:junit-jupiter-engine:${junit5Version}",
+ junit5_params : "org.junit.jupiter:junit-jupiter-params:${junit5Version}",
+ junit: "junit:junit:${junit4Version}",
+ junit5_vintage: "org.junit.vintage:junit-vintage-engine:${junitVintageVersion}",
+
log4j2: "org.apache.logging.log4j:log4j-core:2.17.1",
- junit: "junit:junit:${junitVersion}",
+
byteman: "org.jboss.byteman:byteman:${bytemanVersion}",
byteman_install: "org.jboss.byteman:byteman-install:${bytemanVersion}",
byteman_bmunit: "org.jboss.byteman:byteman-bmunit:${bytemanVersion}",
h2: "com.h2database:h2:${h2Version}",
hsqldb: "org.hsqldb:hsqldb:2.3.2",
derby: "org.apache.derby:derby:10.14.2.0",
- postgresql: 'org.postgresql:postgresql:42.2.16',
+ postgresql: 'org.postgresql:postgresql:42.5.0',
mysql: 'mysql:mysql-connector-java:8.0.27',
mariadb: 'org.mariadb.jdbc:mariadb-java-client:2.2.3',
- cockroachdb: 'org.postgresql:postgresql:42.2.8',
+ cockroachdb: 'org.postgresql:postgresql:42.5.0',
oracle: 'com.oracle.database.jdbc:ojdbc8:21.3.0.0',
mssql: 'com.microsoft.sqlserver:mssql-jdbc:7.2.1.jre8',
db2: 'com.ibm.db2:jcc:11.5.4.0',
hana: 'com.sap.cloud.db.jdbc:ngdbc:2.4.59',
+ sybase: 'net.sourceforge.jtds:jtds:1.3.1',
jodaTime: "joda-time:joda-time:${jodaTimeVersion}",
@@ -162,7 +171,7 @@ ext {
vibur: "org.vibur:vibur-dbcp:25.0",
agroal_api: "io.agroal:agroal-api:${agroalVersion}",
agroal_pool: "io.agroal:agroal-pool:${agroalVersion}",
- micrometer: "io.micrometer:micrometer-core:1.6.1",
+ micrometer: "io.micrometer:micrometer-core:${micrometerVersion}",
atomikos: "com.atomikos:transactions:4.0.6",
atomikos_jta: "com.atomikos:transactions-jta:4.0.6",
diff --git a/gradle/published-java-module.gradle b/gradle/published-java-module.gradle
index df0fd448b2c5..58201f9037bc 100644
--- a/gradle/published-java-module.gradle
+++ b/gradle/published-java-module.gradle
@@ -165,6 +165,8 @@ publishing {
task ciBuild( dependsOn: [test, publish] )
-task release( dependsOn: [test, publishToSonatype] )
-publishToSonatype.mustRunAfter test
+task releasePrepare( dependsOn: [publishAllPublicationsToStagingRepository] ) {
+ group = "Release"
+ description = "Performs a release: the hibernate version is set and the changelog.txt file updated, the changes are pushed to github, then the release is performed, tagged and the hibernate version is set to the development one."
+}
diff --git a/gradle/publishing-repos.gradle b/gradle/publishing-repos.gradle
index b417e1eba3ca..bc3c34a5857a 100644
--- a/gradle/publishing-repos.gradle
+++ b/gradle/publishing-repos.gradle
@@ -6,19 +6,22 @@
*/
apply plugin: 'maven-publish'
-if ( rootProject.ormVersion.isSnapshot ) {
- apply plugin: 'org.hibernate.build.maven-repo-auth'
-}
publishing {
publications {
publishedArtifacts( MavenPublication )
}
-
repositories {
maven {
- name 'jboss-snapshots-repository'
- url 'https://repository.jboss.org/nexus/content/repositories/snapshots'
+ name = "staging"
+ url = rootProject.layout.buildDirectory.dir("staging-deploy${File.separator}maven")
+ }
+ maven {
+ name = 'snapshots'
+ url = "https://central.sonatype.com/repository/maven-snapshots/"
+ // So that Gradle uses the `ORG_GRADLE_PROJECT_snapshotsPassword` / `ORG_GRADLE_PROJECT_snapshotsUsername`
+ // env variables to read the username/password for the `snapshots` repository publishing:
+ credentials(PasswordCredentials)
}
}
}
diff --git a/gradle/version.properties b/gradle/version.properties
index b2d8dafd4f07..0b454938b8b3 100644
--- a/gradle/version.properties
+++ b/gradle/version.properties
@@ -1 +1 @@
-hibernateVersion=5.6.5.Final
\ No newline at end of file
+hibernateVersion=5.6.16-SNAPSHOT
\ No newline at end of file
diff --git a/hibernate-agroal/src/test/resources/hibernate.properties b/hibernate-agroal/src/test/resources/hibernate.properties
index 6b80862911be..da8399b8675f 100644
--- a/hibernate-agroal/src/test/resources/hibernate.properties
+++ b/hibernate-agroal/src/test/resources/hibernate.properties
@@ -9,6 +9,7 @@ hibernate.connection.driver_class @jdbc.driver@
hibernate.connection.url @jdbc.url@
hibernate.connection.username @jdbc.user@
hibernate.connection.password @jdbc.pass@
+hibernate.connection.init_sql @connection.init_sql@
hibernate.jdbc.batch_size 10
hibernate.connection.provider_class AgroalConnectionProvider
diff --git a/hibernate-c3p0/src/test/resources/hibernate.properties b/hibernate-c3p0/src/test/resources/hibernate.properties
index 0d39da782e64..715af2a80a52 100644
--- a/hibernate-c3p0/src/test/resources/hibernate.properties
+++ b/hibernate-c3p0/src/test/resources/hibernate.properties
@@ -9,6 +9,7 @@ hibernate.connection.driver_class @jdbc.driver@
hibernate.connection.url @jdbc.url@
hibernate.connection.username @jdbc.user@
hibernate.connection.password @jdbc.pass@
+hibernate.connection.init_sql @connection.init_sql@
hibernate.connection.pool_size 5
hibernate.c3p0.min_size 50
diff --git a/hibernate-core-jakarta/hibernate-core-jakarta.gradle b/hibernate-core-jakarta/hibernate-core-jakarta.gradle
index 09e3e1bd5dc8..802fea183165 100644
--- a/hibernate-core-jakarta/hibernate-core-jakarta.gradle
+++ b/hibernate-core-jakarta/hibernate-core-jakarta.gradle
@@ -94,6 +94,8 @@ processResources.enabled false
compileTestJava.enabled false
processTestResources.enabled false
jar.enabled false
+javadocJar.enabled false
+sourcesJar.enabled false
ext {
transformedJarName = project(':hibernate-core').tasks.jar.archiveFileName.get().replaceAll( 'hibernate-core', 'hibernate-core-jakarta' )
@@ -123,6 +125,26 @@ task transformJar(type: JakartaJarTransformation) {
targetJar tasks.jar.archiveFile.get().asFile
}
+task transformSourcesJar(type: JakartaJarTransformation) {
+ description 'Transforms the hibernate-core sources jar using the JakartaTransformer tool'
+
+ dependsOn project(':hibernate-core').tasks.sourcesJar
+ mustRunAfter project(':hibernate-core').tasks.sourcesJar
+
+ sourceJar project(':hibernate-core').tasks.sourcesJar.archiveFile
+ targetJar tasks.sourcesJar.archiveFile.get().asFile
+}
+
+task transformJavadocJar(type: JakartaJarTransformation) {
+ description 'Transforms the hibernate-core javadoc jar using the JakartaTransformer tool'
+
+ dependsOn project(':hibernate-core').tasks.javadocJar
+ mustRunAfter project(':hibernate-core').tasks.javadocJar
+
+ sourceJar project(':hibernate-core').tasks.javadocJar.archiveFile
+ targetJar tasks.javadocJar.archiveFile.get().asFile
+}
+
configurations {
[apiElements, runtimeElements].each {
it.outgoing.artifacts.removeIf {
@@ -131,6 +153,12 @@ configurations {
it.outgoing.artifact(tasks.transformJar.targetJar) {
builtBy tasks.transformJar
}
+ it.outgoing.artifact(tasks.transformSourcesJar.targetJar) {
+ builtBy tasks.transformSourcesJar
+ }
+ it.outgoing.artifact(tasks.transformJavadocJar.targetJar) {
+ builtBy tasks.transformJavadocJar
+ }
}
}
diff --git a/hibernate-core/src/main/antlr/hql-sql.g b/hibernate-core/src/main/antlr/hql-sql.g
index 142348258022..c1ab78645d32 100644
--- a/hibernate-core/src/main/antlr/hql-sql.g
+++ b/hibernate-core/src/main/antlr/hql-sql.g
@@ -261,6 +261,15 @@ tokens
return dot;
}
+ protected AST lookupFkRefSource(AST path) throws SemanticException {
+ if ( path.getType() == DOT ) {
+ return lookupProperty( path, true, isInSelect() );
+ }
+ else {
+ return lookupNonQualifiedProperty( path );
+ }
+ }
+
protected boolean isNonQualifiedPropertyRef(AST ident) { return false; }
protected AST lookupNonQualifiedProperty(AST property) throws SemanticException { return property; }
@@ -746,12 +755,15 @@ identifier
;
addrExpr! [ boolean root ]
- : #(d:DOT lhs:addrExprLhs rhs:propertyName ) {
+ : #(d:DOT lhs:addrExprLhs rhs:propertyName ) {
// This gives lookupProperty() a chance to transform the tree
// to process collection properties (.elements, etc).
#addrExpr = #(#d, #lhs, #rhs);
#addrExpr = lookupProperty(#addrExpr,root,false);
}
+ | fk_ref:fkRef {
+ #addrExpr = #fk_ref;
+ }
| #(i:INDEX_OP lhs2:addrExprLhs rhs2:expr [ null ]) {
#addrExpr = #(#i, #lhs2, #rhs2);
processIndex(#addrExpr);
@@ -776,6 +788,12 @@ addrExpr! [ boolean root ]
}
;
+fkRef
+ : #( r:FK_REF p:propertyRef ) {
+ #p = lookupProperty( #p, false, isInSelect() );
+ }
+ ;
+
addrExprLhs
: addrExpr [ false ]
;
@@ -797,8 +815,7 @@ propertyRef!
#propertyRef = #(#d, #lhs, #rhs);
#propertyRef = lookupProperty(#propertyRef,false,true);
}
- |
- p:identifier {
+ | p:identifier {
// In many cases, things other than property-refs are recognized
// by this propertyRef rule. Some of those I have seen:
// 1) select-clause from-aliases
diff --git a/hibernate-core/src/main/antlr/hql.g b/hibernate-core/src/main/antlr/hql.g
index 3f2782263107..a8b32453c6e4 100644
--- a/hibernate-core/src/main/antlr/hql.g
+++ b/hibernate-core/src/main/antlr/hql.g
@@ -46,6 +46,7 @@ tokens
EXISTS="exists";
FALSE="false";
FETCH="fetch";
+ FK_REF;
FROM="from";
FULL="full";
GROUP="group";
@@ -722,7 +723,8 @@ atom
// level 0 - the basic element of an expression
primaryExpression
- : { validateSoftKeyword("function") && LA(2) == OPEN && LA(3) == QUOTED_STRING }? jpaFunctionSyntax
+ : { validateSoftKeyword("fk") && LA(2) == OPEN }? fkRefPath
+ | { validateSoftKeyword("function") && LA(2) == OPEN && LA(3) == QUOTED_STRING }? jpaFunctionSyntax
| { validateSoftKeyword("cast") && LA(2) == OPEN }? castFunction
| { validateSoftKeyword("size") && LA(2) == OPEN }? collectionSizeFunction
| identPrimary ( options {greedy=true;} : DOT^ "class" )?
@@ -731,6 +733,12 @@ primaryExpression
| OPEN! (expressionOrVector | subQuery) CLOSE!
;
+fkRefPath!
+ : "fk" OPEN p:identPrimary CLOSE {
+ #fkRefPath = #( [FK_REF], #p );
+ }
+ ;
+
jpaFunctionSyntax!
: i:IDENT OPEN n:QUOTED_STRING (COMMA a:exprList)? CLOSE {
final String functionName = unquote( #n.getText() );
@@ -824,6 +832,7 @@ identPrimary
#identPrimary = #( [ENTRY], path );
}
}
+ | (DOT^ FK_REF)
)?
// Also allow special 'aggregate functions' such as count(), avg(), etc.
| aggregate
diff --git a/hibernate-core/src/main/antlr/sql-gen.g b/hibernate-core/src/main/antlr/sql-gen.g
index b59667c79658..a5cfb13ee592 100644
--- a/hibernate-core/src/main/antlr/sql-gen.g
+++ b/hibernate-core/src/main/antlr/sql-gen.g
@@ -509,6 +509,7 @@ parameter
addrExpr
: #(r:DOT . .) { out(r); }
+ | #(fk:FK_REF .) { out(fk); }
| i:ALIAS_REF { out(i); }
| j:INDEX_OP { out(j); }
| v:RESULT_VARIABLE_REF { out(v); }
diff --git a/hibernate-core/src/main/java/org/hibernate/FetchNotFoundException.java b/hibernate-core/src/main/java/org/hibernate/FetchNotFoundException.java
new file mode 100644
index 000000000000..42f729c1f2ce
--- /dev/null
+++ b/hibernate-core/src/main/java/org/hibernate/FetchNotFoundException.java
@@ -0,0 +1,43 @@
+/*
+ * 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 .
+ */
+package org.hibernate;
+
+import java.util.Locale;
+import javax.persistence.EntityNotFoundException;
+
+/**
+ * Exception for {@link org.hibernate.annotations.NotFoundAction#EXCEPTION}
+ *
+ * @see org.hibernate.annotations.NotFound
+ *
+ * @author Steve Ebersole
+ */
+public class FetchNotFoundException extends EntityNotFoundException {
+ private final String entityName;
+ private final Object identifier;
+
+ public FetchNotFoundException(String entityName, Object identifier) {
+ super(
+ String.format(
+ Locale.ROOT,
+ "Entity `%s` with identifier value `%s` does not exist",
+ entityName,
+ identifier
+ )
+ );
+ this.entityName = entityName;
+ this.identifier = identifier;
+ }
+
+ public String getEntityName() {
+ return entityName;
+ }
+
+ public Object getIdentifier() {
+ return identifier;
+ }
+}
diff --git a/hibernate-core/src/main/java/org/hibernate/Hibernate.java b/hibernate-core/src/main/java/org/hibernate/Hibernate.java
index 9f4fea0184d5..22cd1e646f89 100644
--- a/hibernate-core/src/main/java/org/hibernate/Hibernate.java
+++ b/hibernate-core/src/main/java/org/hibernate/Hibernate.java
@@ -10,16 +10,18 @@
import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor;
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
-import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.HibernateIterator;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.jdbc.spi.JdbcServices;
-import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
+import org.hibernate.collection.spi.LazyInitializable;
+
+import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable;
+import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable;
/**
*
@@ -61,12 +63,11 @@ public static void initialize(Object proxy) throws HibernateException {
if ( proxy instanceof HibernateProxy ) {
( (HibernateProxy) proxy ).getHibernateLazyInitializer().initialize();
}
- else if ( proxy instanceof PersistentCollection ) {
- ( (PersistentCollection) proxy ).forceInitialization();
+ else if ( proxy instanceof LazyInitializable ) {
+ ( (LazyInitializable) proxy ).forceInitialization();
}
- else if ( proxy instanceof PersistentAttributeInterceptable ) {
- final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) proxy;
- final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor();
+ else if ( isPersistentAttributeInterceptable( proxy ) ) {
+ final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( proxy ).$$_hibernate_getInterceptor();
if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) {
( (EnhancementAsProxyLazinessInterceptor) interceptor ).forceInitialize( proxy, null );
}
@@ -84,15 +85,15 @@ public static boolean isInitialized(Object proxy) {
if ( proxy instanceof HibernateProxy ) {
return !( (HibernateProxy) proxy ).getHibernateLazyInitializer().isUninitialized();
}
- else if ( proxy instanceof PersistentAttributeInterceptable ) {
- final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) proxy ).$$_hibernate_getInterceptor();
+ else if ( isPersistentAttributeInterceptable( proxy ) ) {
+ final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( proxy ).$$_hibernate_getInterceptor();
if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) {
return false;
}
return true;
}
- else if ( proxy instanceof PersistentCollection ) {
- return ( (PersistentCollection) proxy ).wasInitialized();
+ else if ( proxy instanceof LazyInitializable ) {
+ return ( (LazyInitializable) proxy ).wasInitialized();
}
else {
return true;
@@ -200,8 +201,9 @@ public static boolean isPropertyInitialized(Object proxy, String propertyName) {
entity = proxy;
}
- if ( entity instanceof PersistentAttributeInterceptable ) {
- PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor();
+
+ if ( isPersistentAttributeInterceptable( entity ) ) {
+ PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( entity ).$$_hibernate_getInterceptor();
if ( interceptor instanceof BytecodeLazyAttributeInterceptor ) {
return ( (BytecodeLazyAttributeInterceptor) interceptor ).isAttributeLoaded( propertyName );
}
diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java
index c673743e22b2..9bb66e1c8cac 100644
--- a/hibernate-core/src/main/java/org/hibernate/Session.java
+++ b/hibernate-core/src/main/java/org/hibernate/Session.java
@@ -793,7 +793,22 @@ public interface Session extends SharedSessionContract, EntityManager, Hibernate
* @return the entity name
*/
String getEntityName(Object object);
-
+
+ /**
+ * Return a reference to the persistent instance with the same identity as the given
+ * instance, which might be detached, making the assumption that the instance is still
+ * persistent in the database. This method never results in access to the underlying
+ * data store, and thus might return a proxy that is initialized on-demand, when a
+ * non-identifier method is accessed.
+ *
+ * @param object a detached persistent instance
+ *
+ * @return the persistent instance or proxy
+ */
+ default T getReference(T object) {
+ throw new IllegalStateException( "getReference(Object) is not implemented in " + getClass() );
+ }
+
/**
* Create an {@link IdentifierLoadAccess} instance to retrieve the specified entity type by
* primary key.
@@ -1156,6 +1171,16 @@ interface LockRequest {
org.hibernate.query.Query createNamedQuery(String name, Class resultType);
+ /**
+ * Create a {@link NativeQuery} instance for the given SQL query string.
+ *
+ * @param queryString The SQL query
+ *
+ * @return The query instance for manipulation and execution
+ *
+ * @deprecated (since 5.2) use {@link #createNativeQuery(String)} instead
+ */
+ @Deprecated
@Override
NativeQuery createSQLQuery(String queryString);
}
diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityUpdateAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityUpdateAction.java
index 41bc9f9e653b..7dbed1ba0d37 100644
--- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityUpdateAction.java
+++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityUpdateAction.java
@@ -9,6 +9,7 @@
import java.io.Serializable;
import org.hibernate.AssertionFailure;
+import org.hibernate.CacheMode;
import org.hibernate.HibernateException;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.spi.access.EntityDataAccess;
@@ -32,6 +33,7 @@
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.stat.internal.StatsHelper;
import org.hibernate.stat.spi.StatisticsImplementor;
+import org.hibernate.tuple.entity.EntityMetamodel;
import org.hibernate.type.TypeHelper;
/**
@@ -239,9 +241,21 @@ public void execute() throws HibernateException {
entry.postUpdate( instance, state, nextVersion );
}
+ if ( entry.getStatus() == Status.DELETED ) {
+ final EntityMetamodel entityMetamodel = persister.getEntityMetamodel();
+ final boolean isImpliedOptimisticLocking = !entityMetamodel.isVersioned()
+ && entityMetamodel.getOptimisticLockStyle().isAllOrDirty();
+ if ( isImpliedOptimisticLocking && entry.getLoadedState() != null ) {
+ // The entity will be deleted and because we are going to create a delete statement that uses
+ // all the state values in the where clause, the entry state needs to be updated otherwise the statement execution will
+ // not delete any row (see HHH-15218).
+ entry.postUpdate( instance, state, nextVersion );
+ }
+ }
+
final StatisticsImplementor statistics = factory.getStatistics();
if ( persister.canWriteToCache() ) {
- if ( persister.isCacheInvalidationRequired() || entry.getStatus() != Status.MANAGED ) {
+ if ( isCacheInvalidationRequired( persister, session ) || entry.getStatus() != Status.MANAGED ) {
persister.getCacheAccessStrategy().remove( session, ck );
}
else if ( session.getCacheMode().isPutEnabled() ) {
@@ -275,6 +289,13 @@ else if ( session.getCacheMode().isPutEnabled() ) {
}
+ private static boolean isCacheInvalidationRequired(
+ EntityPersister persister,
+ SharedSessionContractImplementor session) {
+ // the cache has to be invalidated when CacheMode is equal to GET or IGNORE
+ return persister.isCacheInvalidationRequired() || session.getCacheMode() == CacheMode.GET || session.getCacheMode() == CacheMode.IGNORE;
+ }
+
protected boolean cacheUpdate(EntityPersister persister, Object previousVersion, Object ck) {
final SharedSessionContractImplementor session = getSession();
try {
diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java b/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java
index fa1e03a7645a..b9ca4abc83c2 100644
--- a/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java
+++ b/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java
@@ -7,8 +7,8 @@
package org.hibernate.annotations;
/**
- * Fetch options on associations. Defines more of the "how" of fetching, whereas JPA {@link javax.persistence.FetchType}
- * focuses on the "when".
+ * Defines how the association should be fetched, compared to
+ * {@link javax.persistence.FetchType} which defines when it should be fetched
*
* @author Emmanuel Bernard
*/
@@ -16,13 +16,27 @@ public enum FetchMode {
/**
* Use a secondary select for each individual entity, collection, or join load.
*/
- SELECT,
+ SELECT( org.hibernate.FetchMode.SELECT ),
/**
* Use an outer join to load the related entities, collections or joins.
*/
- JOIN,
+ JOIN( org.hibernate.FetchMode.JOIN ),
/**
- * Available for collections only.ââWhen accessing a non-initialized collection, this fetch mode will trigger loading all elements of all collections of the same role for all owners associated with the persistence context using a single secondary select.
+ * Available for collections only.
+ *
+ * When accessing a non-initialized collection, this fetch mode will trigger
+ * loading all elements of all collections of the same role for all owners
+ * associated with the persistence context using a single secondary select.
*/
- SUBSELECT
+ SUBSELECT( org.hibernate.FetchMode.SELECT );
+
+ private final org.hibernate.FetchMode hibernateFetchMode;
+
+ FetchMode(org.hibernate.FetchMode hibernateFetchMode) {
+ this.hibernateFetchMode = hibernateFetchMode;
+ }
+
+ public org.hibernate.FetchMode getHibernateFetchMode() {
+ return hibernateFetchMode;
+ }
}
diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/NotFoundAction.java b/hibernate-core/src/main/java/org/hibernate/annotations/NotFoundAction.java
index c6e4e9622d29..906d65024096 100644
--- a/hibernate-core/src/main/java/org/hibernate/annotations/NotFoundAction.java
+++ b/hibernate-core/src/main/java/org/hibernate/annotations/NotFoundAction.java
@@ -6,20 +6,36 @@
*/
package org.hibernate.annotations;
+import org.hibernate.FetchNotFoundException;
/**
- * Possible actions when an associated entity is not found in the database. Often seen with "legacy" foreign-key
- * schemes which do not use {@code NULL} to indicate a missing reference, instead using a "magic value".
+ * Possible actions when the database contains a non-null fk with no
+ * matching target. This also implies that there are no physical
+ * foreign-key constraints on the database.
*
+ * As an example, consider a typical Customer/Order model. These actions apply
+ * when a non-null `orders.customer_fk` value does not have a corresponding value
+ * in `customers.id`.
+ *
+ * Generally this will occur in 2 scenarios:
+ *
the associated data has been deleted
+ *
the model uses special "magic" values to indicate null
+ *
+ *
+ * @author Steve Ebersole
* @author Emmanuel Bernard
*/
public enum NotFoundAction {
/**
- * Raise an exception when an element is not found (default and recommended).
+ * Throw an exception when the association is not found (default and recommended).
+ *
+ * @see FetchNotFoundException
*/
EXCEPTION,
+
/**
- * Ignore the element when not found in database.
+ * Ignore the association when not found in database. Effectively treats the
+ * association as null, despite the non-null foreign-key value.
*/
IGNORE
}
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/spi/ClassFileArchiveEntryHandler.java b/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/spi/ClassFileArchiveEntryHandler.java
index 8679accff6c2..a4866f1a3ce0 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/spi/ClassFileArchiveEntryHandler.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/archive/scan/spi/ClassFileArchiveEntryHandler.java
@@ -61,8 +61,9 @@ public void handleEntry(ArchiveEntry entry, ArchiveContext context) {
private ClassDescriptor toClassDescriptor(ArchiveEntry entry) {
try (InputStream inputStream = entry.getStreamAccess().accessInputStream()) {
Indexer indexer = new Indexer();
- ClassInfo classInfo = indexer.index( inputStream );
+ indexer.index( inputStream );
Index index = indexer.complete();
+ ClassInfo classInfo = index.getKnownClasses().iterator().next();
return toClassDescriptor( classInfo, index, entry );
}
catch (IOException e) {
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java
index 89a749a2d154..a8b2f122a0ef 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java
@@ -67,19 +67,15 @@ else if ( CFG_XSD_MAPPING.matches( namespace ) ) {
);
return openUrlStream( HBM_DTD_MAPPING.getMappedLocalUrl() );
}
- else if ( LEGACY_HBM_DTD_MAPPING.matches( publicID, systemID ) ) {
- DeprecationLogger.DEPRECATION_LOGGER.recognizedObsoleteHibernateNamespace(
- LEGACY_HBM_DTD_MAPPING.getIdentifierBase(),
- HBM_DTD_MAPPING.getIdentifierBase()
- );
+ else if ( ALTERNATE_MAPPING_DTD.matches( publicID, systemID ) ) {
log.debug(
- "Recognized legacy hibernate-mapping identifier; attempting to resolve on classpath under org/hibernate/"
+ "Recognized alternate hibernate-mapping identifier; attempting to resolve on classpath under org/hibernate/"
);
- return openUrlStream( HBM_DTD_MAPPING.getMappedLocalUrl() );
+ return openUrlStream( ALTERNATE_MAPPING_DTD.getMappedLocalUrl() );
}
- else if ( LEGACY2_HBM_DTD_MAPPING.matches( publicID, systemID ) ) {
+ else if ( LEGACY_HBM_DTD_MAPPING.matches( publicID, systemID ) ) {
DeprecationLogger.DEPRECATION_LOGGER.recognizedObsoleteHibernateNamespace(
- LEGACY2_HBM_DTD_MAPPING.getIdentifierBase(),
+ LEGACY_HBM_DTD_MAPPING.getIdentifierBase(),
HBM_DTD_MAPPING.getIdentifierBase()
);
log.debug(
@@ -93,6 +89,12 @@ else if ( CFG_DTD_MAPPING.matches( publicID, systemID ) ) {
);
return openUrlStream( CFG_DTD_MAPPING.getMappedLocalUrl() );
}
+ else if ( ALTERNATE_CFG_DTD.matches( publicID, systemID ) ) {
+ log.debug(
+ "Recognized alternate hibernate-configuration identifier; attempting to resolve on classpath under org/hibernate/"
+ );
+ return openUrlStream( ALTERNATE_CFG_DTD.getMappedLocalUrl() );
+ }
else if ( LEGACY_CFG_DTD_MAPPING.matches( publicID, systemID ) ) {
DeprecationLogger.DEPRECATION_LOGGER.recognizedObsoleteHibernateNamespace(
LEGACY_CFG_DTD_MAPPING.getIdentifierBase(),
@@ -158,7 +160,7 @@ private InputStream resolveInLocalNamespace(String path) {
"http://xmlns.jcp.org/xml/ns/persistence/orm",
"org/hibernate/jpa/orm_2_1.xsd"
);
-
+
/**
* Maps the namespace for the orm.xml xsd for Jakarta Persistence 2.2
*/
@@ -174,7 +176,7 @@ private InputStream resolveInLocalNamespace(String path) {
"https://jakarta.ee/xml/ns/persistence/orm",
"org/hibernate/jpa/orm_3_0.xsd"
);
-
+
public static final NamespaceSchemaMapping HBM_XSD_MAPPING = new NamespaceSchemaMapping(
"http://www.hibernate.org/xsd/orm/hbm",
"org/hibernate/xsd/mapping/legacy-mapping-4.0.xsd"
@@ -191,27 +193,32 @@ private InputStream resolveInLocalNamespace(String path) {
);
public static final DtdMapping HBM_DTD_MAPPING = new DtdMapping(
- "http://www.hibernate.org/dtd/hibernate-mapping",
+ "www.hibernate.org/dtd/hibernate-mapping",
"org/hibernate/hibernate-mapping-3.0.dtd"
);
- public static final DtdMapping LEGACY_HBM_DTD_MAPPING = new DtdMapping(
- "http://www.hibernate.org/dtd/hibernate-mapping",
+ public static final DtdMapping ALTERNATE_MAPPING_DTD = new DtdMapping(
+ "hibernate.org/dtd/hibernate-mapping",
"org/hibernate/hibernate-mapping-3.0.dtd"
);
- public static final DtdMapping LEGACY2_HBM_DTD_MAPPING = new DtdMapping(
- "http://hibernate.sourceforge.net/hibernate-mapping",
+ public static final DtdMapping LEGACY_HBM_DTD_MAPPING = new DtdMapping(
+ "hibernate.sourceforge.net/hibernate-mapping",
"org/hibernate/hibernate-mapping-3.0.dtd"
);
public static final DtdMapping CFG_DTD_MAPPING = new DtdMapping(
- "http://www.hibernate.org/dtd/hibernate-configuration",
+ "www.hibernate.org/dtd/hibernate-configuration",
+ "org/hibernate/hibernate-configuration-3.0.dtd"
+ );
+
+ public static final DtdMapping ALTERNATE_CFG_DTD = new DtdMapping(
+ "hibernate.org/dtd/hibernate-configuration",
"org/hibernate/hibernate-configuration-3.0.dtd"
);
public static final DtdMapping LEGACY_CFG_DTD_MAPPING = new DtdMapping(
- "http://hibernate.sourceforge.net/hibernate-configuration",
+ "hibernate.sourceforge.net/hibernate-configuration",
"org/hibernate/hibernate-configuration-3.0.dtd"
);
@@ -235,27 +242,31 @@ public URL getMappedLocalUrl() {
}
public static class DtdMapping {
- private final String identifierBase;
+ private final String httpBase;
+ private final String httpsBase;
private final URL localSchemaUrl;
public DtdMapping(String identifierBase, String resourceName) {
- this.identifierBase = identifierBase;
+ this.httpBase = "http://" + identifierBase;
+ this.httpsBase = "https://" + identifierBase;
this.localSchemaUrl = LocalSchemaLocator.resolveLocalSchemaUrl( resourceName );
}
public String getIdentifierBase() {
- return identifierBase;
+ return httpBase;
}
public boolean matches(String publicId, String systemId) {
if ( publicId != null ) {
- if ( publicId.startsWith( identifierBase ) ) {
+ if ( publicId.startsWith( httpBase )
+ || publicId.startsWith( httpsBase ) ) {
return true;
}
}
if ( systemId != null ) {
- if ( systemId.startsWith( identifierBase ) ) {
+ if ( systemId.startsWith( httpBase )
+ || systemId.startsWith( httpsBase ) ) {
return true;
}
}
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java
index be697f9be446..667c189bf577 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java
@@ -75,6 +75,64 @@ public static Identifier toIdentifier(String text, boolean quote) {
}
}
+ /**
+ * Means to generate an {@link Identifier} instance from its simple text form.
+ *
+ * If passed text is {@code null}, {@code null} is returned.
+ *
+ * If passed text is surrounded in quote markers, the generated Identifier
+ * is considered quoted. Quote markers include back-ticks (`),
+ * double-quotes (") and brackets ([ and ]).
+ *
+ * @param text The text form
+ * @param quote Whether to quote unquoted text forms
+ * @param quoteOnNonIdentifierChar Controls whether to treat the result as quoted if text contains characters that are invalid for identifiers
+ *
+ * @return The identifier form, or {@code null} if text was {@code null}
+ */
+ public static Identifier toIdentifier(String text, boolean quote, boolean quoteOnNonIdentifierChar) {
+ if ( StringHelper.isEmpty( text ) ) {
+ return null;
+ }
+ int start = 0;
+ int end = text.length();
+ while ( start < end ) {
+ if ( !Character.isWhitespace( text.charAt( start ) ) ) {
+ break;
+ }
+ start++;
+ }
+ while ( start < end ) {
+ if ( !Character.isWhitespace( text.charAt( end - 1 ) ) ) {
+ break;
+ }
+ end--;
+ }
+ if ( isQuoted( text, start, end ) ) {
+ start++;
+ end--;
+ quote = true;
+ }
+ else if ( quoteOnNonIdentifierChar && !quote ) {
+ // Check the letters to determine if we must quote the text
+ char c = text.charAt( start );
+ if ( !Character.isLetter( c ) && c != '_' ) {
+ // SQL identifiers must begin with a letter or underscore
+ quote = true;
+ }
+ else {
+ for ( int i = start + 1; i < end; i++ ) {
+ c = text.charAt( i );
+ if ( !Character.isLetterOrDigit( c ) && c != '_' ) {
+ quote = true;
+ break;
+ }
+ }
+ }
+ }
+ return new Identifier( text.substring( start, end ), quote );
+ }
+
/**
* Is the given identifier text considered quoted. The following patterns are
* recognized as quoted:
@@ -96,6 +154,20 @@ public static boolean isQuoted(String name) {
|| ( name.startsWith( "\"" ) && name.endsWith( "\"" ) );
}
+ public static boolean isQuoted(String name, int start, int end) {
+ if ( start + 2 < end ) {
+ switch ( name.charAt( start ) ) {
+ case '`':
+ return name.charAt( end - 1 ) == '`';
+ case '[':
+ return name.charAt( end - 1 ) == ']';
+ case '"':
+ return name.charAt( end - 1 ) == '"';
+ }
+ }
+ return false;
+ }
+
/**
* Constructs an identifier instance.
*
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/AuxiliaryDatabaseObject.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/AuxiliaryDatabaseObject.java
index 60c54fcb334b..fe91498b11c0 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/AuxiliaryDatabaseObject.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/AuxiliaryDatabaseObject.java
@@ -56,7 +56,9 @@ default String[] sqlCreateStrings(SqlStringGenerationContext context) {
* @param dialect The dialect for which to generate the SQL creation strings
*
* @return the SQL strings for creating the database object.
- * @deprecated Implement {@link #sqlCreateStrings(SqlStringGenerationContext)} instead.
+ * @deprecated Hibernate ORM may never call this method,
+ * and implementations cannot properly handle default catalogs/schemas.
+ * Call/implement {@link #sqlCreateStrings(SqlStringGenerationContext)} instead.
*/
@Deprecated
default String[] sqlCreateStrings(Dialect dialect) {
@@ -80,7 +82,9 @@ default String[] sqlDropStrings(SqlStringGenerationContext context) {
* @param dialect The dialect for which to generate the SQL drop strings
*
* @return the SQL strings for dropping the database object.
- * @deprecated Implement {@link #sqlDropStrings(SqlStringGenerationContext)} instead.
+ * @deprecated Hibernate ORM may never call this method,
+ * and implementations cannot properly handle default catalogs/schemas.
+ * Call/implement {@link #sqlDropStrings(SqlStringGenerationContext)} instead.
*/
@Deprecated
default String[] sqlDropStrings(Dialect dialect) {
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java
index df59ef7466f7..618931f1d04b 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java
@@ -9,6 +9,7 @@
import java.util.Set;
import org.hibernate.boot.model.naming.Identifier;
+import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl;
import org.hibernate.dialect.Dialect;
import org.hibernate.internal.util.StringHelper;
@@ -76,19 +77,37 @@ public SimpleAuxiliaryDatabaseObject(
}
@Override
+ @Deprecated
public String[] sqlCreateStrings(Dialect dialect) {
+ // Implemented exclusively for backwards compatibility for callers other than Hibernate ORM.
+ // This is not called by Hibernate ORM and will not take into account
+ // default catalog/schema set through configuration properties.
+ return sqlCreateStrings( SqlStringGenerationContextImpl.forBackwardsCompatibility( dialect, null, null ) );
+ }
+
+ @Override
+ public String[] sqlCreateStrings(SqlStringGenerationContext context) {
final String[] copy = new String[createStrings.length];
for ( int i = 0, max =createStrings.length; i
* Note that the Identifiers returned from this helper already account for auto-quoting.
+ *
+ * @deprecated Use {@link #toIdentifier(String)} instead.
*/
+ @Deprecated
IdentifierHelper getIdentifierHelper();
+ /**
+ * Generate an Identifier instance from its simple name as obtained from mapping
+ * information.
+ *
+ * Note that Identifiers returned from here may be implicitly quoted based on
+ * 'globally quoted identifiers' or based on reserved words.
+ *
+ * @param text The text form of a name as obtained from mapping information.
+ *
+ * @return The identifier form of the name.
+ */
+ Identifier toIdentifier(String text);
+
/**
* @return The default catalog, used for table/sequence names that do not explicitly mention a catalog.
* May be {@code null}.
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java
index 1f6fa93d3a4c..e61331500201 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java
@@ -6,6 +6,7 @@
*/
package org.hibernate.boot.model.relational.internal;
+import java.sql.SQLException;
import java.util.Map;
import org.hibernate.boot.model.naming.Identifier;
@@ -17,13 +18,18 @@
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect;
+import org.hibernate.engine.jdbc.env.internal.QualifiedObjectNameFormatterStandardImpl;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
+import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
import org.hibernate.engine.jdbc.env.spi.QualifiedObjectNameFormatter;
+import org.jboss.logging.Logger;
+
public class SqlStringGenerationContextImpl
implements SqlStringGenerationContext {
+ private static final Logger log = Logger.getLogger( SqlStringGenerationContextImpl.class );
/**
* @param jdbcEnvironment The JDBC environment, to extract the dialect, identifier helper, etc.
@@ -67,6 +73,37 @@ public static SqlStringGenerationContext fromExplicit(JdbcEnvironment jdbcEnviro
return new SqlStringGenerationContextImpl( jdbcEnvironment, actualDefaultCatalog, actualDefaultSchema );
}
+ /**
+ * @param dialect The dialect to use.
+ * @param defaultCatalog The default catalog to use.
+ * @param defaultSchema The default schema to use.
+ * @return An {@link SqlStringGenerationContext}.
+ * @deprecated Only use for backwards compatibility in deprecated methods.
+ * New methods should take the {@link SqlStringGenerationContext} as an argument,
+ * and should not need to create their own context.
+ */
+ @Deprecated
+ public static SqlStringGenerationContext forBackwardsCompatibility(Dialect dialect, String defaultCatalog, String defaultSchema) {
+ NameQualifierSupport nameQualifierSupport = dialect.getNameQualifierSupport();
+ if ( nameQualifierSupport == null ) {
+ // assume both catalogs and schemas are supported
+ nameQualifierSupport = NameQualifierSupport.BOTH;
+ }
+ QualifiedObjectNameFormatter qualifiedObjectNameFormatter =
+ new QualifiedObjectNameFormatterStandardImpl( nameQualifierSupport );
+
+ Identifier actualDefaultCatalog = null;
+ if ( nameQualifierSupport.supportsCatalogs() ) {
+ actualDefaultCatalog = Identifier.toIdentifier( defaultCatalog );
+ }
+ Identifier actualDefaultSchema = null;
+ if ( nameQualifierSupport.supportsSchemas() ) {
+ actualDefaultSchema = Identifier.toIdentifier( defaultSchema );
+ }
+ return new SqlStringGenerationContextImpl( dialect, null, qualifiedObjectNameFormatter,
+ actualDefaultCatalog, actualDefaultSchema );
+ }
+
public static SqlStringGenerationContext forTests(JdbcEnvironment jdbcEnvironment) {
return forTests( jdbcEnvironment, null, null );
}
@@ -87,9 +124,17 @@ public static SqlStringGenerationContext forTests(JdbcEnvironment jdbcEnvironmen
@SuppressWarnings("deprecation")
private SqlStringGenerationContextImpl(JdbcEnvironment jdbcEnvironment,
Identifier defaultCatalog, Identifier defaultSchema) {
- this.dialect = jdbcEnvironment.getDialect();
- this.identifierHelper = jdbcEnvironment.getIdentifierHelper();
- this.qualifiedObjectNameFormatter = jdbcEnvironment.getQualifiedObjectNameFormatter();
+ this( jdbcEnvironment.getDialect(), jdbcEnvironment.getIdentifierHelper(),
+ jdbcEnvironment.getQualifiedObjectNameFormatter(),
+ defaultCatalog, defaultSchema );
+ }
+
+ private SqlStringGenerationContextImpl(Dialect dialect, IdentifierHelper identifierHelper,
+ QualifiedObjectNameFormatter qualifiedObjectNameFormatter,
+ Identifier defaultCatalog, Identifier defaultSchema) {
+ this.dialect = dialect;
+ this.identifierHelper = identifierHelper;
+ this.qualifiedObjectNameFormatter = qualifiedObjectNameFormatter;
this.defaultCatalog = defaultCatalog;
this.defaultSchema = defaultSchema;
}
@@ -104,6 +149,11 @@ public IdentifierHelper getIdentifierHelper() {
return identifierHelper;
}
+ @Override
+ public Identifier toIdentifier(String text) {
+ return identifierHelper != null ? identifierHelper.toIdentifier( text ) : Identifier.toIdentifier( text );
+ }
+
@Override
public Identifier getDefaultCatalog() {
return defaultCatalog;
diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java
index e0f7310847d1..3052ee32c0c9 100644
--- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java
+++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java
@@ -2948,7 +2948,7 @@ private Identifier determineCatalogName(TableSpecificationSource tableSpecSource
return database.toIdentifier( tableSpecSource.getExplicitCatalogName() );
}
else {
- return database.toIdentifier( metadataBuildingContext.getMappingDefaults().getImplicitCatalogName() );
+ return null;
}
}
diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java
index d142264bd9c7..849ebc93fd8c 100644
--- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java
@@ -34,6 +34,7 @@
import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState;
import org.hibernate.engine.spi.CompositeOwner;
import org.hibernate.engine.spi.CompositeTracker;
+import org.hibernate.engine.spi.EnhancedEntity;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker;
import org.hibernate.engine.spi.Managed;
@@ -68,6 +69,7 @@
public class EnhancerImpl implements Enhancer {
private static final CoreMessageLogger log = CoreLogging.messageLogger( Enhancer.class );
+ private static final AnnotationDescription TRANSIENT_ANNOTATION = AnnotationDescription.Builder.ofType( Transient.class ).build();
protected final ByteBuddyEnhancementContext enhancementContext;
private final ByteBuddyState byteBuddyState;
@@ -154,12 +156,14 @@ private DynamicType.Builder> doEnhance(DynamicType.Builder> builder, TypeDes
log.debugf( "Skipping enhancement of [%s]: already enhanced", managedCtClass.getName() );
return null;
}
+ final EnhancementStatus es = new EnhancementStatus( managedCtClass.getName() );
if ( enhancementContext.isEntityClass( managedCtClass ) ) {
log.debugf( "Enhancing [%s] as Entity", managedCtClass.getName() );
builder = builder.implement( ManagedEntity.class )
.defineMethod( EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME, Object.class, Visibility.PUBLIC )
.intercept( FixedValue.self() );
+ es.enabledInterfaceManagedEntity();
builder = addFieldWithGetterAndSetter(
builder,
@@ -183,7 +187,7 @@ private DynamicType.Builder> doEnhance(DynamicType.Builder> builder, TypeDes
EnhancerConstants.NEXT_SETTER_NAME
);
- builder = addInterceptorHandling( builder, managedCtClass );
+ builder = addInterceptorHandling( builder, managedCtClass, es );
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
List collectionFields = collectCollectionFields( managedCtClass );
@@ -191,7 +195,7 @@ private DynamicType.Builder> doEnhance(DynamicType.Builder> builder, TypeDes
if ( collectionFields.isEmpty() ) {
builder = builder.implement( SelfDirtinessTracker.class )
.defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE )
- .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
+ .annotateField( TRANSIENT_ANNOTATION )
.defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC )
.withParameters( String.class )
.intercept( implementationTrackChange )
@@ -206,13 +210,15 @@ private DynamicType.Builder> doEnhance(DynamicType.Builder> builder, TypeDes
.intercept( implementationSuspendDirtyTracking )
.defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC )
.intercept( implementationGetCollectionTrackerWithoutCollections );
+ es.enabledInterfaceSelfDirtinessTracker();
}
else {
+ //TODO es.enableInterfaceExtendedSelfDirtinessTracker ? Careful with consequences..
builder = builder.implement( ExtendedSelfDirtinessTracker.class )
.defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE )
- .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
+ .annotateField( TRANSIENT_ANNOTATION )
.defineField( EnhancerConstants.TRACKER_COLLECTION_NAME, CollectionTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE )
- .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
+ .annotateField( TRANSIENT_ANNOTATION )
.defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC )
.withParameters( String.class )
.intercept( implementationTrackChange )
@@ -302,13 +308,13 @@ private DynamicType.Builder> doEnhance(DynamicType.Builder> builder, TypeDes
}
}
- return createTransformer( managedCtClass ).applyTo( builder );
+ return createTransformer( managedCtClass ).applyTo( builder, es );
}
else if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
log.debugf( "Enhancing [%s] as Composite", managedCtClass.getName() );
builder = builder.implement( ManagedComposite.class );
- builder = addInterceptorHandling( builder, managedCtClass );
+ builder = addInterceptorHandling( builder, managedCtClass, es );
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
builder = builder.implement( CompositeTracker.class )
@@ -318,7 +324,7 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
FieldPersistence.TRANSIENT,
Visibility.PRIVATE
)
- .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
+ .annotateField( TRANSIENT_ANNOTATION )
.defineMethod(
EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER,
void.class,
@@ -335,17 +341,17 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
.intercept( implementationClearOwner );
}
- return createTransformer( managedCtClass ).applyTo( builder );
+ return createTransformer( managedCtClass ).applyTo( builder, es );
}
else if ( enhancementContext.isMappedSuperclassClass( managedCtClass ) ) {
log.debugf( "Enhancing [%s] as MappedSuperclass", managedCtClass.getName() );
builder = builder.implement( ManagedMappedSuperclass.class );
- return createTransformer( managedCtClass ).applyTo( builder );
+ return createTransformer( managedCtClass ).applyTo( builder, es );
}
else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) {
log.debugf( "Extended enhancement of [%s]", managedCtClass.getName() );
- return createTransformer( managedCtClass ).applyExtended( builder );
+ return createTransformer( managedCtClass ).applyExtended( builder, es );
}
else {
log.debugf( "Skipping enhancement of [%s]: not entity or composite", managedCtClass.getName() );
@@ -367,12 +373,13 @@ private boolean alreadyEnhanced(TypeDescription managedCtClass) {
return false;
}
- private DynamicType.Builder> addInterceptorHandling(DynamicType.Builder> builder, TypeDescription managedCtClass) {
+ private DynamicType.Builder> addInterceptorHandling(DynamicType.Builder> builder, TypeDescription managedCtClass, EnhancementStatus es) {
// interceptor handling is only needed if class has lazy-loadable attributes
if ( enhancementContext.hasLazyLoadableAttributes( managedCtClass ) ) {
log.debugf( "Weaving in PersistentAttributeInterceptable implementation on [%s]", managedCtClass.getName() );
builder = builder.implement( PersistentAttributeInterceptable.class );
+ es.enabledInterfacePersistentAttributeInterceptable();
builder = addFieldWithGetterAndSetter(
builder,
@@ -394,7 +401,7 @@ private static DynamicType.Builder> addFieldWithGetterAndSetter(
String setterName) {
return builder
.defineField( fieldName, type, Visibility.PRIVATE, FieldPersistence.TRANSIENT )
- .annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
+ .annotateField( TRANSIENT_ANNOTATION )
.defineMethod( getterName, type, Visibility.PUBLIC )
.intercept( FieldAccessor.ofField( fieldName ) )
.defineMethod( setterName, void.class, Visibility.PUBLIC )
@@ -592,4 +599,53 @@ void setClassNameAndBytes(String className, byte[] bytes) {
this.resolution = new Resolution.Explicit( bytes);
}
}
+
+ /**
+ * Attempt to keep track of which interfaces are being applied,
+ * so to attempt dodging the performance implications of for https://bugs.openjdk.org/browse/JDK-8180450
+ * We're optimising for the case in which entities are fully enhanced.
+ */
+ final static class EnhancementStatus {
+
+ private final String typeName;
+ private boolean managedEntity = false;
+ private boolean selfDirtynessTracker = false;
+ private boolean persistentAttributeInterceptable = false;
+ private boolean applied = false;
+
+ public EnhancementStatus(String typeName) {
+ this.typeName = typeName;
+ }
+
+ public void enabledInterfaceManagedEntity() {
+ this.managedEntity = true;
+ }
+
+ public void enabledInterfaceSelfDirtinessTracker() {
+ this.selfDirtynessTracker = true;
+ }
+
+ public void enabledInterfacePersistentAttributeInterceptable() {
+ this.persistentAttributeInterceptable = true;
+ }
+
+ public DynamicType.Builder> applySuperInterfaceOptimisations(DynamicType.Builder> builder) {
+ if ( applied ) {
+ throw new IllegalStateException("Should not apply super-interface optimisations twice");
+ }
+ else {
+ applied = true;
+ if ( managedEntity && persistentAttributeInterceptable && selfDirtynessTracker ) {
+ log.debugf( "Applying Enhancer optimisations for type [%s]; adding EnhancedEntity as additional marker.", typeName );
+ return builder.implement( EnhancedEntity.class );
+ }
+ else {
+ log.debugf( "Applying Enhancer optimisations for type [%s]; NOT enabling EnhancedEntity as additional marker.", typeName );
+ }
+ //TODO consider applying a marker for other combinations of interfaces as well?
+ }
+ return builder;
+ }
+ }
+
}
diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckerEqualsHelper.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckerEqualsHelper.java
new file mode 100644
index 000000000000..098db0b8051a
--- /dev/null
+++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckerEqualsHelper.java
@@ -0,0 +1,132 @@
+/*
+ * 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 .
+ */
+package org.hibernate.bytecode.enhance.internal.bytebuddy;
+
+import java.util.Objects;
+
+import org.hibernate.engine.spi.PersistentAttributeInterceptable;
+import org.hibernate.engine.spi.PersistentAttributeInterceptor;
+
+public final class InlineDirtyCheckerEqualsHelper {
+
+ public static boolean areEquals(
+ PersistentAttributeInterceptable persistentAttributeInterceptable,
+ String fieldName,
+ Object a,
+ Object b) {
+ final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor();
+ if ( persistentAttributeInterceptor != null
+ && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) {
+ return false;
+ }
+ return Objects.deepEquals( a, b );
+ }
+
+ public static boolean areEquals(
+ PersistentAttributeInterceptable persistentAttributeInterceptable,
+ String fieldName,
+ boolean a,
+ boolean b) {
+ final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor();
+ if ( persistentAttributeInterceptor != null
+ && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) {
+ return false;
+ }
+ return a == b;
+ }
+
+ public static boolean areEquals(
+ PersistentAttributeInterceptable persistentAttributeInterceptable,
+ String fieldName,
+ byte a,
+ byte b) {
+ final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor();
+ if ( persistentAttributeInterceptor != null
+ && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) {
+ return false;
+ }
+ return a == b;
+ }
+
+ public static boolean areEquals(
+ PersistentAttributeInterceptable persistentAttributeInterceptable,
+ String fieldName,
+ short a,
+ short b) {
+ final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor();
+ if ( persistentAttributeInterceptor != null
+ && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) {
+ return false;
+ }
+ return a == b;
+ }
+
+ public static boolean areEquals(
+ PersistentAttributeInterceptable persistentAttributeInterceptable,
+ String fieldName,
+ char a,
+ char b) {
+ final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor();
+ if ( persistentAttributeInterceptor != null
+ && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) {
+ return false;
+ }
+ return a == b;
+ }
+
+ public static boolean areEquals(
+ PersistentAttributeInterceptable persistentAttributeInterceptable,
+ String fieldName,
+ int a,
+ int b) {
+ final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor();
+ if ( persistentAttributeInterceptor != null
+ && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) {
+ return false;
+ }
+ return a == b;
+ }
+
+ public static boolean areEquals(
+ PersistentAttributeInterceptable persistentAttributeInterceptable,
+ String fieldName,
+ long a,
+ long b) {
+ final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor();
+ if ( persistentAttributeInterceptor != null
+ && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) {
+ return false;
+ }
+ return a == b;
+ }
+
+ public static boolean areEquals(
+ PersistentAttributeInterceptable persistentAttributeInterceptable,
+ String fieldName,
+ float a,
+ float b) {
+ final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor();
+ if ( persistentAttributeInterceptor != null
+ && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) {
+ return false;
+ }
+ return a == b;
+ }
+
+ public static boolean areEquals(
+ PersistentAttributeInterceptable persistentAttributeInterceptable,
+ String fieldName,
+ double a,
+ double b) {
+ final PersistentAttributeInterceptor persistentAttributeInterceptor = persistentAttributeInterceptable.$$_hibernate_getInterceptor();
+ if ( persistentAttributeInterceptor != null
+ && !persistentAttributeInterceptor.isAttributeLoaded( fieldName ) ) {
+ return false;
+ }
+ return a == b;
+ }
+}
diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java
index 1c61641c2940..6306e52436bb 100644
--- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java
+++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java
@@ -15,6 +15,7 @@
import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription;
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
+import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.asm.Advice;
@@ -31,16 +32,27 @@
final class InlineDirtyCheckingHandler implements Implementation, ByteCodeAppender {
+ private static final String HELPER_TYPE_NAME = Type.getInternalName( InlineDirtyCheckerEqualsHelper.class );
+ private static final Type PE_INTERCEPTABLE_TYPE = Type.getType( PersistentAttributeInterceptable.class );
+ private static final Type OBJECT_TYPE = Type.getType( Object.class );
+ private static final Type STRING_TYPE = Type.getType( String.class );
+
private final Implementation delegate;
private final TypeDescription managedCtClass;
private final FieldDescription.InDefinedShape persistentField;
+ private final boolean applyLazyCheck;
- private InlineDirtyCheckingHandler(Implementation delegate, TypeDescription managedCtClass, FieldDescription.InDefinedShape persistentField) {
+ private InlineDirtyCheckingHandler(
+ Implementation delegate,
+ TypeDescription managedCtClass,
+ FieldDescription.InDefinedShape persistentField,
+ boolean applyLazyCheck) {
this.delegate = delegate;
this.managedCtClass = managedCtClass;
this.persistentField = persistentField;
+ this.applyLazyCheck = applyLazyCheck;
}
static Implementation wrap(
@@ -57,8 +69,12 @@ else if ( !persistentField.hasAnnotation( Id.class )
&& !persistentField.hasAnnotation( EmbeddedId.class )
&& !( persistentField.getType().asErasure().isAssignableTo( Collection.class )
&& enhancementContext.isMappedCollection( persistentField ) ) ) {
- implementation = new InlineDirtyCheckingHandler( implementation, managedCtClass,
- persistentField.asDefined() );
+ implementation = new InlineDirtyCheckingHandler(
+ implementation,
+ managedCtClass,
+ persistentField.asDefined(),
+ enhancementContext.hasLazyLoadableAttributes( managedCtClass )
+ );
}
if ( enhancementContext.isCompositeClass( persistentField.getType().asErasure() )
@@ -97,6 +113,11 @@ public Size apply(
Context implementationContext,
MethodDescription instrumentedMethod) {
// if (arg != field) {
+
+ if ( applyLazyCheck ) {
+ methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
+ methodVisitor.visitLdcInsn( persistentField.getName() );
+ }
methodVisitor.visitVarInsn( Type.getType( persistentField.getType().asErasure().getDescriptor() ).getOpcode( Opcodes.ILOAD ), 1 );
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
if ( persistentField.getDeclaringType().asErasure().equals( managedCtClass ) ) {
@@ -117,30 +138,70 @@ public Size apply(
);
}
int branchCode;
- if ( persistentField.getType().isPrimitive() ) {
- if ( persistentField.getType().represents( long.class ) ) {
- methodVisitor.visitInsn( Opcodes.LCMP );
- }
- else if ( persistentField.getType().represents( float.class ) ) {
- methodVisitor.visitInsn( Opcodes.FCMPL );
- }
- else if ( persistentField.getType().represents( double.class ) ) {
- methodVisitor.visitInsn( Opcodes.DCMPL );
+ if ( applyLazyCheck ) {
+ if ( persistentField.getType().isPrimitive() ) {
+ final Type fieldType = Type.getType( persistentField.getDescriptor() );
+ methodVisitor.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ HELPER_TYPE_NAME,
+ "areEquals",
+ Type.getMethodDescriptor(
+ Type.BOOLEAN_TYPE,
+ PE_INTERCEPTABLE_TYPE,
+ STRING_TYPE,
+ fieldType,
+ fieldType
+ ),
+ false
+ );
}
else {
- methodVisitor.visitInsn( Opcodes.ISUB );
+ methodVisitor.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ HELPER_TYPE_NAME,
+ "areEquals",
+ Type.getMethodDescriptor(
+ Type.BOOLEAN_TYPE,
+ PE_INTERCEPTABLE_TYPE,
+ STRING_TYPE,
+ OBJECT_TYPE,
+ OBJECT_TYPE
+ ),
+ false
+ );
}
- branchCode = Opcodes.IFEQ;
+ branchCode = Opcodes.IFNE;
}
else {
- methodVisitor.visitMethodInsn(
- Opcodes.INVOKESTATIC,
- Type.getInternalName( Objects.class ),
- "deepEquals",
- Type.getMethodDescriptor( Type.getType( boolean.class ), Type.getType( Object.class ), Type.getType( Object.class ) ),
- false
- );
- branchCode = Opcodes.IFNE;
+ if ( persistentField.getType().isPrimitive() ) {
+ if ( persistentField.getType().represents( long.class ) ) {
+ methodVisitor.visitInsn( Opcodes.LCMP );
+ }
+ else if ( persistentField.getType().represents( float.class ) ) {
+ methodVisitor.visitInsn( Opcodes.FCMPL );
+ }
+ else if ( persistentField.getType().represents( double.class ) ) {
+ methodVisitor.visitInsn( Opcodes.DCMPL );
+ }
+ else {
+ methodVisitor.visitInsn( Opcodes.ISUB );
+ }
+ branchCode = Opcodes.IFEQ;
+ }
+ else {
+ methodVisitor.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ Type.getInternalName( Objects.class ),
+ "deepEquals",
+ Type.getMethodDescriptor(
+ Type.BOOLEAN_TYPE,
+ OBJECT_TYPE,
+ OBJECT_TYPE
+ ),
+ false
+ );
+ branchCode = Opcodes.IFNE;
+ }
}
Label skip = new Label();
methodVisitor.visitJumpInsn( branchCode, skip );
@@ -151,7 +212,7 @@ else if ( persistentField.getType().represents( double.class ) ) {
Opcodes.INVOKEVIRTUAL,
managedCtClass.getInternalName(),
EnhancerConstants.TRACKER_CHANGER_NAME,
- Type.getMethodDescriptor( Type.getType( void.class ), Type.getType( String.class ) ),
+ Type.getMethodDescriptor( Type.VOID_TYPE, STRING_TYPE ),
false
);
// }
@@ -159,7 +220,7 @@ else if ( persistentField.getType().represents( double.class ) ) {
if ( implementationContext.getClassFileVersion().isAtLeast( ClassFileVersion.JAVA_V6 ) ) {
methodVisitor.visitFrame( Opcodes.F_SAME, 0, null, 0, null );
}
- return new Size( 1 + 2 * persistentField.getType().asErasure().getStackSize().getSize(), instrumentedMethod.getStackSize() );
+ return new Size( 3 + 2 * persistentField.getType().asErasure().getStackSize().getSize(), instrumentedMethod.getStackSize() );
}
@Override
diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java
index 287ce6b793e4..94c2e65a36db 100644
--- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java
+++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java
@@ -91,6 +91,12 @@ public static PersistentAttributeTransformer collectPersistentFields(
ByteBuddyEnhancementContext enhancementContext,
TypePool classPool) {
List persistentFieldList = new ArrayList<>();
+ // HHH-10646 Add fields inherited from @MappedSuperclass
+ // HHH-10981 There is no need to do it for @MappedSuperclass
+ // HHH-15505 This needs to be done first so that fields with the same name in the mappedsuperclass and entity are handled correctly
+ if ( !enhancementContext.isMappedSuperclassClass( managedCtClass ) ) {
+ persistentFieldList.addAll( collectInheritPersistentFields( managedCtClass, enhancementContext ) );
+ }
for ( FieldDescription ctField : managedCtClass.getDeclaredFields() ) {
// skip static fields and skip fields added by enhancement and outer reference in inner classes
if ( ctField.getName().startsWith( "$$_hibernate_" ) || "this$0".equals( ctField.getName() ) ) {
@@ -101,11 +107,6 @@ public static PersistentAttributeTransformer collectPersistentFields(
persistentFieldList.add( annotatedField );
}
}
- // HHH-10646 Add fields inherited from @MappedSuperclass
- // HHH-10981 There is no need to do it for @MappedSuperclass
- if ( !enhancementContext.isMappedSuperclassClass( managedCtClass ) ) {
- persistentFieldList.addAll( collectInheritPersistentFields( managedCtClass, enhancementContext ) );
- }
AnnotatedFieldDescription[] orderedFields = enhancementContext.order( persistentFieldList.toArray( new AnnotatedFieldDescription[0] ) );
log.debugf( "Persistent fields for entity %s: %s", managedCtClass.getName(), Arrays.toString( orderedFields ) );
@@ -200,7 +201,8 @@ private AnnotatedFieldDescription getEnhancedField(String owner, String name, St
return null;
}
- DynamicType.Builder> applyTo(DynamicType.Builder> builder) {
+ DynamicType.Builder> applyTo(DynamicType.Builder> builder, EnhancerImpl.EnhancementStatus es) {
+ builder = es.applySuperInterfaceOptimisations(builder);
boolean compositeOwner = false;
builder = builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().invokable( NOT_HIBERNATE_GENERATED, this ) );
@@ -255,7 +257,7 @@ DynamicType.Builder> applyTo(DynamicType.Builder> builder) {
}
if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) {
- builder = applyExtended( builder );
+ builder = applyExtended( builder, es );
}
return builder;
@@ -304,7 +306,7 @@ private Implementation fieldWriterImplementation(AnnotatedFieldDescription enhan
}
}
- DynamicType.Builder> applyExtended(DynamicType.Builder> builder) {
+ DynamicType.Builder> applyExtended(DynamicType.Builder> builder, EnhancerImpl.EnhancementStatus es) {
AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper enhancer = new FieldAccessEnhancer( managedCtClass, enhancementContext, classPool );
return builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().invokable( NOT_HIBERNATE_GENERATED, enhancer ) );
}
diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/BytecodeLazyAttributeInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/BytecodeLazyAttributeInterceptor.java
index a4fd9a7593e0..5ff6b950c8c3 100644
--- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/BytecodeLazyAttributeInterceptor.java
+++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/BytecodeLazyAttributeInterceptor.java
@@ -36,6 +36,7 @@ public interface BytecodeLazyAttributeInterceptor extends SessionAssociableInter
*/
void attributeInitialized(String name);
+ @Override
boolean isAttributeLoaded(String fieldName);
boolean hasAnyUninitializedAttributes();
diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java
index e5a65e356579..005889668d2c 100644
--- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java
+++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java
@@ -72,8 +72,9 @@ public EnhancementAsProxyLazinessInterceptor(
this.inLineDirtyChecking = entityPersister.getEntityMode() == EntityMode.POJO
&& SelfDirtinessTracker.class.isAssignableFrom( entityPersister.getMappedClass() );
// if self-dirty tracking is enabled but DynamicUpdate is not enabled then we need to initialise the entity
- // because the pre-computed update statement contains even not dirty properties and so we need all the values
- initializeBeforeWrite = !( inLineDirtyChecking && entityPersister.getEntityMetamodel().isDynamicUpdate() );
+ // because the pre-computed update statement contains even not dirty properties and so we need all the values
+ // we have to initialise it even if it's versioned to fetch the current version
+ initializeBeforeWrite = !( inLineDirtyChecking && entityPersister.getEntityMetamodel().isDynamicUpdate() ) || entityPersister.isVersioned();
status = Status.UNINITIALIZED;
}
diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java
index 1b084cccd223..ecdbade9de96 100644
--- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java
+++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java
@@ -17,11 +17,15 @@
import org.hibernate.bytecode.enhance.spi.CollectionTracker;
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
import org.hibernate.collection.spi.PersistentCollection;
+import org.hibernate.engine.internal.ManagedTypeHelper;
import org.hibernate.engine.spi.SelfDirtinessTracker;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.persister.entity.EntityPersister;
+import static org.hibernate.engine.internal.ManagedTypeHelper.asSelfDirtinessTracker;
+import static org.hibernate.engine.internal.ManagedTypeHelper.isSelfDirtinessTracker;
+
/**
* Interceptor that loads attributes lazily
*
@@ -30,6 +34,8 @@
*/
public class LazyAttributeLoadingInterceptor extends AbstractLazyLoadInterceptor {
private final Object identifier;
+
+ //N.B. this Set needs to be treated as immutable
private final Set lazyFields;
private Set initializedLazyFields;
@@ -40,7 +46,8 @@ public LazyAttributeLoadingInterceptor(
SharedSessionContractImplementor session) {
super( entityName, session );
this.identifier = identifier;
- this.lazyFields = lazyFields;
+ //Important optimisation to not actually do a Map lookup for entities which don't have any lazy fields at all:
+ this.lazyFields = org.hibernate.internal.util.collections.CollectionHelper.toSmallSet( lazyFields );
}
@Override
@@ -121,7 +128,7 @@ public boolean isAttributeLoaded(String fieldName) {
}
private boolean isLazyAttribute(String fieldName) {
- return lazyFields == null || lazyFields.contains( fieldName );
+ return lazyFields.contains( fieldName );
}
private boolean isInitializedLazyField(String fieldName) {
@@ -129,7 +136,7 @@ private boolean isInitializedLazyField(String fieldName) {
}
public boolean hasAnyUninitializedAttributes() {
- if ( lazyFields == null || lazyFields.isEmpty() ) {
+ if ( lazyFields.isEmpty() ) {
return false;
}
@@ -152,13 +159,14 @@ public String toString() {
}
private void takeCollectionSizeSnapshot(Object target, String fieldName, Object value) {
- if ( value instanceof Collection && target instanceof SelfDirtinessTracker ) {
+ if ( value instanceof Collection && isSelfDirtinessTracker( target ) ) {
// This must be called first, so that we remember that there is a collection out there,
// even if we don't know its size (see below).
- CollectionTracker tracker = ( (SelfDirtinessTracker) target ).$$_hibernate_getCollectionTracker();
+ final SelfDirtinessTracker trackerAsSDT = asSelfDirtinessTracker( target );
+ CollectionTracker tracker = trackerAsSDT.$$_hibernate_getCollectionTracker();
if ( tracker == null ) {
- ( (SelfDirtinessTracker) target ).$$_hibernate_clearDirtyAttributes();
- tracker = ( (SelfDirtinessTracker) target ).$$_hibernate_getCollectionTracker();
+ trackerAsSDT.$$_hibernate_clearDirtyAttributes();
+ tracker = trackerAsSDT.$$_hibernate_getCollectionTracker();
}
if ( value instanceof PersistentCollection && !( (PersistentCollection) value ).wasInitialized() ) {
diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java
index 90c5b78a0890..593773e6fda2 100644
--- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java
+++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java
@@ -24,6 +24,7 @@
import java.util.function.Function;
import org.hibernate.HibernateException;
+import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
import org.hibernate.bytecode.spi.BasicProxyFactory;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.proxy.ProxyConfiguration;
@@ -188,6 +189,10 @@ public Unloaded> make(Function> makeProxyFun
return make( makeProxyFunction.apply( byteBuddy ) );
}
+ public Unloaded> make(TypePool typePool, Function> makeProxyFunction) {
+ return make( typePool, makeProxyFunction.apply( byteBuddy ) );
+ }
+
private Unloaded> make(DynamicType.Builder> builder) {
return make( null, builder );
}
@@ -248,7 +253,7 @@ public static class ProxyDefinitionHelpers {
private final ElementMatcher super MethodDescription> groovyGetMetaClassFilter;
private final ElementMatcher super MethodDescription> virtualNotFinalizerFilter;
- private final ElementMatcher super MethodDescription> hibernateGeneratedMethodFilter;
+ private final ElementMatcher super MethodDescription> proxyNonInterceptedMethodFilter;
private final MethodDelegation delegateToInterceptorDispatcherMethodDelegation;
private final FieldAccessor.PropertyConfigurable interceptorFieldAccessor;
@@ -256,7 +261,11 @@ private ProxyDefinitionHelpers() {
this.groovyGetMetaClassFilter = isSynthetic().and( named( "getMetaClass" )
.and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) );
this.virtualNotFinalizerFilter = isVirtual().and( not( isFinalizer() ) );
- this.hibernateGeneratedMethodFilter = nameStartsWith( "$$_hibernate_" ).and( isVirtual() );
+ this.proxyNonInterceptedMethodFilter = nameStartsWith( "$$_hibernate_" ).and( isVirtual() )
+ // HHH-15090: Don't apply extended enhancement reader/writer methods to the proxy;
+ // those need to be executed on the actual entity.
+ .and( not( nameStartsWith( EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX ) ) )
+ .and( not( nameStartsWith( EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX ) ) );
PrivilegedAction delegateToInterceptorDispatcherMethodDelegationPrivilegedAction =
new PrivilegedAction() {
@@ -294,8 +303,8 @@ public ElementMatcher super MethodDescription> getVirtualNotFinalizerFilter()
return virtualNotFinalizerFilter;
}
- public ElementMatcher super MethodDescription> getHibernateGeneratedMethodFilter() {
- return hibernateGeneratedMethodFilter;
+ public ElementMatcher super MethodDescription> getProxyNonInterceptedMethodFilter() {
+ return proxyNonInterceptedMethodFilter;
}
public MethodDelegation getDelegateToInterceptorDispatcherMethodDelegation() {
diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java
index 81793fb23d46..491af0356634 100644
--- a/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java
+++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java
@@ -178,13 +178,14 @@ private void evict(Serializable id, CollectionPersister collectionPersister, Eve
if ( LOG.isDebugEnabled() ) {
LOG.debug( "Evict CollectionRegion " + collectionPersister.getRole() + " for id " + id );
}
- AfterTransactionCompletionProcess afterTransactionProcess = new CollectionEvictCacheAction(
+ CollectionEvictCacheAction evictCacheAction = new CollectionEvictCacheAction(
collectionPersister,
null,
id,
session
- ).lockCache();
- session.getActionQueue().registerProcess( afterTransactionProcess );
+ );
+ evictCacheAction.execute();
+ session.getActionQueue().registerProcess( evictCacheAction.getAfterTransactionCompletionProcess() );
}
//execute the same process as invalidation with collection operations
@@ -199,13 +200,9 @@ protected CollectionEvictCacheAction(
@Override
public void execute() throws HibernateException {
- }
-
- public AfterTransactionCompletionProcess lockCache() {
beforeExecutions();
- return getAfterTransactionCompletionProcess();
+ evict();
}
-
}
}
diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/EnabledCaching.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/EnabledCaching.java
index 9ce34807a33b..ae218d636f83 100644
--- a/hibernate-core/src/main/java/org/hibernate/cache/internal/EnabledCaching.java
+++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/EnabledCaching.java
@@ -491,6 +491,10 @@ public QueryResultsCache getQueryResultsCacheStrictly(String regionName) {
return null;
}
+ if ( regionName == null || regionName.equals( getDefaultQueryResultsCache().getRegion().getName() ) ) {
+ return getDefaultQueryResultsCache();
+ }
+
return namedQueryResultsCacheMap.get( regionName );
}
diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/NoCachingTransactionSynchronizationImpl.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/NoCachingTransactionSynchronizationImpl.java
index b6914f3a484a..920e64158601 100644
--- a/hibernate-core/src/main/java/org/hibernate/cache/internal/NoCachingTransactionSynchronizationImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/NoCachingTransactionSynchronizationImpl.java
@@ -16,4 +16,9 @@ public class NoCachingTransactionSynchronizationImpl extends AbstractCacheTransa
public NoCachingTransactionSynchronizationImpl(RegionFactory regionFactory) {
super( regionFactory );
}
+
+ @Override
+ public long getCachingTimestamp() {
+ throw new UnsupportedOperationException( "Method not supported when 2LC is not enabled" );
+ }
}
diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheTransactionSynchronization.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheTransactionSynchronization.java
index 415abdc70c48..8d98aab96af2 100644
--- a/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheTransactionSynchronization.java
+++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheTransactionSynchronization.java
@@ -48,9 +48,29 @@ public interface CacheTransactionSynchronization {
*
* @implSpec This "timestamp" need not be related to timestamp in the Java
* Date/millisecond sense. It just needs to be an incrementing value.
+ *
+ * @deprecated Use {@link CacheTransactionSynchronization#getCachingTimestamp()} instead.
*/
+ @Deprecated
long getCurrentTransactionStartTimestamp();
+ /**
+ * What is the start time of this context object?
+ *
+ * @apiNote If not currently joined to a transaction, the timestamp from
+ * the last transaction is safe to use. If not ever/yet joined to a
+ * transaction, a timestamp at the time the Session/CacheTransactionSynchronization
+ * were created should be returned.
+ *
+ * @implSpec This "timestamp" need not be related to timestamp in the Java
+ * Date/millisecond sense. It just needs to be an incrementing value.
+ *
+ * An UnsupportedOperationException is thrown if the Second Level Cache has not been enabled
+ */
+ default long getCachingTimestamp(){
+ return getCurrentTransactionStartTimestamp();
+ }
+
/**
* Callback that owning Session has become joined to a resource transaction.
*
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java
index 3881e9912105..49a059e61628 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java
@@ -170,6 +170,7 @@
import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.UnionSubclass;
+import static org.hibernate.cfg.BinderHelper.isEmptyAnnotationValue;
import static org.hibernate.internal.CoreLogging.messageLogger;
/**
@@ -691,7 +692,7 @@ else if ( InheritanceType.JOINED.equals( inheritanceState.getType() ) ) {
SimpleValue key = new DependantValue( context, jsc.getTable(), jsc.getIdentifier() );
jsc.setKey( key );
ForeignKey fk = clazzToProcess.getAnnotation( ForeignKey.class );
- if ( fk != null && !BinderHelper.isEmptyAnnotationValue( fk.name() ) ) {
+ if ( fk != null && !isEmptyAnnotationValue( fk.name() ) ) {
key.setForeignKeyName( fk.name() );
}
else {
@@ -1371,7 +1372,7 @@ private static void bindTypeDef(TypeDef defAnn, MetadataBuildingContext context)
params.setProperty( param.name(), param.value() );
}
- if ( BinderHelper.isEmptyAnnotationValue( defAnn.name() ) && defAnn.defaultForType().equals( void.class ) ) {
+ if ( isEmptyAnnotationValue( defAnn.name() ) && defAnn.defaultForType().equals( void.class ) ) {
throw new AnnotationException(
"Either name or defaultForType (or both) attribute should be set in TypeDef having typeClass " +
defAnn.typeClass().getName()
@@ -1379,7 +1380,7 @@ private static void bindTypeDef(TypeDef defAnn, MetadataBuildingContext context)
}
final String typeBindMessageF = "Binding type definition: %s";
- if ( !BinderHelper.isEmptyAnnotationValue( defAnn.name() ) ) {
+ if ( !isEmptyAnnotationValue( defAnn.name() ) ) {
if ( LOG.isDebugEnabled() ) {
LOG.debugf( typeBindMessageF, defAnn.name() );
}
@@ -1794,10 +1795,12 @@ private static void processElementAnnotations(
);
}
+ final NotFound notFound = property.getAnnotation( NotFound.class );
+ final NotFoundAction notFoundAction = notFound == null ? null : notFound.action();
+ final boolean hasNotFound = notFoundAction != null;
+ checkFetchModeAgainstNotFound( propertyHolder.getEntityName(), property.getName(), hasNotFound, ann.fetch() );
+
Cascade hibernateCascade = property.getAnnotation( Cascade.class );
- NotFound notFound = property.getAnnotation( NotFound.class );
- boolean ignoreNotFound = notFound != null && notFound.action().equals( NotFoundAction.IGNORE );
- matchIgnoreNotFoundWithFetchType(propertyHolder.getEntityName(), property.getName(), ignoreNotFound, ann.fetch());
OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class );
boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() );
JoinTable assocTable = propertyHolder.getJoinTable( property );
@@ -1813,15 +1816,14 @@ private static void processElementAnnotations(
// is mandatory (even if the association has optional=true).
// If a @MapsId association has optional=true and is mapped with @NotFound(IGNORE) then
// the association is optional.
- final boolean mandatory =
- !ann.optional() ||
- property.isAnnotationPresent( Id.class ) ||
- ( property.isAnnotationPresent( MapsId.class ) && !ignoreNotFound );
+ final boolean mandatory = !ann.optional()
+ || property.isAnnotationPresent( Id.class )
+ || ( property.isAnnotationPresent( MapsId.class ) && !hasNotFound );
bindManyToOne(
getCascadeStrategy( ann.cascade(), hibernateCascade, false, forcePersist ),
joinColumns,
!mandatory,
- ignoreNotFound,
+ notFoundAction,
onDeleteCascade,
ToOneBinder.getTargetEntity( inferredData, context ),
propertyHolder,
@@ -1849,9 +1851,12 @@ else if ( property.isAnnotationPresent( OneToOne.class ) ) {
final boolean hasPkjc = property.isAnnotationPresent( PrimaryKeyJoinColumn.class )
|| property.isAnnotationPresent( PrimaryKeyJoinColumns.class );
boolean trueOneToOne = hasPkjc;
- Cascade hibernateCascade = property.getAnnotation( Cascade.class );
- NotFound notFound = property.getAnnotation( NotFound.class );
- boolean ignoreNotFound = notFound != null && notFound.action().equals( NotFoundAction.IGNORE );
+ final Cascade hibernateCascade = property.getAnnotation( Cascade.class );
+ final NotFound notFound = property.getAnnotation( NotFound.class );
+ final NotFoundAction notFoundAction = notFound == null ? null : notFound.action();
+ final boolean hasNotFound = notFoundAction != null;
+ checkFetchModeAgainstNotFound( propertyHolder.getEntityName(), property.getName(), hasNotFound, ann.fetch() );
+
// MapsId means the columns belong to the pk;
// A @MapsId association (obviously) must be non-null when the entity is first persisted.
// If a @MapsId association is not mapped with @NotFound(IGNORE), then the association
@@ -1859,14 +1864,14 @@ else if ( property.isAnnotationPresent( OneToOne.class ) ) {
// If a @MapsId association has optional=true and is mapped with @NotFound(IGNORE) then
// the association is optional.
// @OneToOne(optional = true) with @PKJC makes the association optional.
- final boolean mandatory =
- !ann.optional() ||
- property.isAnnotationPresent( Id.class ) ||
- ( property.isAnnotationPresent( MapsId.class ) && !ignoreNotFound );
- matchIgnoreNotFoundWithFetchType(propertyHolder.getEntityName(), property.getName(), ignoreNotFound, ann.fetch());
- OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class );
- boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() );
- JoinTable assocTable = propertyHolder.getJoinTable( property );
+ final boolean mandatory = !ann.optional()
+ || property.isAnnotationPresent( Id.class )
+ || ( property.isAnnotationPresent( MapsId.class ) && !hasNotFound );
+ checkFetchModeAgainstNotFound( propertyHolder.getEntityName(), property.getName(), hasNotFound, ann.fetch() );
+
+ final OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class );
+ final boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() );
+ final JoinTable assocTable = propertyHolder.getJoinTable( property );
if ( assocTable != null ) {
Join join = propertyHolder.addJoin( assocTable, false );
for ( Ejb3JoinColumn joinColumn : joinColumns ) {
@@ -1878,7 +1883,8 @@ else if ( property.isAnnotationPresent( OneToOne.class ) ) {
joinColumns,
!mandatory,
getFetchMode( ann.fetch() ),
- ignoreNotFound, onDeleteCascade,
+ notFoundAction,
+ onDeleteCascade,
ToOneBinder.getTargetEntity( inferredData, context ),
propertyHolder,
inferredData,
@@ -2572,13 +2578,13 @@ private static void bindJoinedTableAssociation(
if ( jpaIndexes != null && jpaIndexes.length > 0 ) {
associationTableBinder.setJpaIndex( jpaIndexes );
}
- if ( !BinderHelper.isEmptyAnnotationValue( schema ) ) {
+ if ( !isEmptyAnnotationValue( schema ) ) {
associationTableBinder.setSchema( schema );
}
- if ( !BinderHelper.isEmptyAnnotationValue( catalog ) ) {
+ if ( !isEmptyAnnotationValue( catalog ) ) {
associationTableBinder.setCatalog( catalog );
}
- if ( !BinderHelper.isEmptyAnnotationValue( tableName ) ) {
+ if ( !isEmptyAnnotationValue( tableName ) ) {
associationTableBinder.setName( tableName );
}
associationTableBinder.setUniqueConstraints( uniqueConstraints );
@@ -3040,7 +3046,7 @@ private static void bindManyToOne(
String cascadeStrategy,
Ejb3JoinColumn[] columns,
boolean optional,
- boolean ignoreNotFound,
+ NotFoundAction notFoundAction,
boolean cascadeOnDelete,
XClass targetEntity,
PropertyHolder propertyHolder,
@@ -3060,7 +3066,7 @@ private static void bindManyToOne(
final XProperty property = inferredData.getProperty();
defineFetchingStrategy( value, property );
//value.setFetchMode( fetchMode );
- value.setIgnoreNotFound( ignoreNotFound );
+ value.setNotFoundAction( notFoundAction );
value.setCascadeDeleteEnabled( cascadeOnDelete );
//value.setLazy( fetchMode != FetchMode.JOIN );
if ( !optional ) {
@@ -3090,7 +3096,7 @@ private static void bindManyToOne(
}
if ( property.isAnnotationPresent( ManyToOne.class ) && joinColumn != null
- && ! BinderHelper.isEmptyAnnotationValue( joinColumn.name() )
+ && ! isEmptyAnnotationValue( joinColumn.name() )
&& joinColumn.name().equals( columnName )
&& !property.isAnnotationPresent( MapsId.class ) ) {
hasSpecjManyToOne = true;
@@ -3153,11 +3159,24 @@ else if (hasSpecjManyToOne) {
propertyBinder.setXToMany( true );
final Property boundProperty = propertyBinder.makePropertyAndBind();
+ boundProperty.setOptional( optional && isNullable( joinColumns, joinColumn ) );
+ }
+
+ private static boolean isNullable(JoinColumns joinColumns, JoinColumn joinColumn) {
if ( joinColumn != null ) {
- boundProperty.setOptional( joinColumn.nullable() && optional );
+ return joinColumn.nullable();
+ }
+ else if ( joinColumns != null ) {
+ final JoinColumn[] col = joinColumns.value();
+ for ( int i = 0; i < col.length; i++ ) {
+ if ( joinColumns.value()[i].nullable() ) {
+ return true;
+ }
+ }
+ return false;
}
else {
- boundProperty.setOptional( optional );
+ return true;
}
}
@@ -3166,6 +3185,8 @@ protected static void defineFetchingStrategy(ToOne toOne, XProperty property) {
Fetch fetch = property.getAnnotation( Fetch.class );
ManyToOne manyToOne = property.getAnnotation( ManyToOne.class );
OneToOne oneToOne = property.getAnnotation( OneToOne.class );
+ NotFound notFound = property.getAnnotation( NotFound.class );
+
FetchType fetchType;
if ( manyToOne != null ) {
fetchType = manyToOne.fetch();
@@ -3178,7 +3199,12 @@ else if ( oneToOne != null ) {
"Define fetch strategy on a property not annotated with @OneToMany nor @OneToOne"
);
}
- if ( lazy != null ) {
+
+ if ( notFound != null ) {
+ toOne.setLazy( false );
+ toOne.setUnwrapProxy( true );
+ }
+ else if ( lazy != null ) {
toOne.setLazy( !( lazy.value() == LazyToOneOption.FALSE ) );
toOne.setUnwrapProxy( ( lazy.value() == LazyToOneOption.NO_PROXY ) );
}
@@ -3187,6 +3213,7 @@ else if ( oneToOne != null ) {
toOne.setUnwrapProxy( fetchType != FetchType.LAZY );
toOne.setUnwrapProxyImplicit( true );
}
+
if ( fetch != null ) {
if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) {
toOne.setFetchMode( FetchMode.JOIN );
@@ -3213,7 +3240,7 @@ private static void bindOneToOne(
Ejb3JoinColumn[] joinColumns,
boolean optional,
FetchMode fetchMode,
- boolean ignoreNotFound,
+ NotFoundAction notFoundAction,
boolean cascadeOnDelete,
XClass targetEntity,
PropertyHolder propertyHolder,
@@ -3257,7 +3284,7 @@ private static void bindOneToOne(
}
}
}
- if ( trueOneToOne || mapToPK || !BinderHelper.isEmptyAnnotationValue( mappedBy ) ) {
+ if ( trueOneToOne || mapToPK || !isEmptyAnnotationValue( mappedBy ) ) {
//is a true one-to-one
//FIXME referencedColumnName ignored => ordering may fail.
OneToOneSecondPass secondPass = new OneToOneSecondPass(
@@ -3267,7 +3294,7 @@ private static void bindOneToOne(
propertyHolder,
inferredData,
targetEntity,
- ignoreNotFound,
+ notFoundAction,
cascadeOnDelete,
optional,
cascadeStrategy,
@@ -3278,19 +3305,25 @@ private static void bindOneToOne(
secondPass.doSecondPass( context.getMetadataCollector().getEntityBindingMap() );
}
else {
- context.getMetadataCollector().addSecondPass(
- secondPass,
- BinderHelper.isEmptyAnnotationValue( mappedBy )
- );
+ context.getMetadataCollector().addSecondPass( secondPass, isEmptyAnnotationValue( mappedBy ) );
}
}
else {
//has a FK on the table
bindManyToOne(
- cascadeStrategy, joinColumns, optional, ignoreNotFound, cascadeOnDelete,
+ cascadeStrategy,
+ joinColumns,
+ optional,
+ notFoundAction,
+ cascadeOnDelete,
targetEntity,
- propertyHolder, inferredData, true, isIdentifierMapper, inSecondPass,
- propertyBinder, context
+ propertyHolder,
+ inferredData,
+ true,
+ isIdentifierMapper,
+ inSecondPass,
+ propertyBinder,
+ context
);
}
}
@@ -3462,10 +3495,20 @@ public static void bindForeignKeyNameAndDefinition(
JoinColumns joinColumns,
MetadataBuildingContext context) {
final boolean noConstraintByDefault = context.getBuildingOptions().isNoConstraintByDefault();
- if ( ( joinColumn != null && ( joinColumn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT
- || joinColumn.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) )
- || ( joinColumns != null && ( joinColumns.foreignKey().value() == ConstraintMode.NO_CONSTRAINT
- || joinColumns.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) ) ) {
+
+ final NotFound notFoundAnn= property.getAnnotation( NotFound.class );
+ if ( notFoundAnn != null ) {
+ // supersedes all others
+ value.setForeignKeyName( "none" );
+ }
+ else if ( joinColumn != null && (
+ joinColumn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT
+ || ( joinColumn.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) ) ) {
+ value.setForeignKeyName( "none" );
+ }
+ else if ( joinColumns != null && (
+ joinColumns.foreignKey().value() == ConstraintMode.NO_CONSTRAINT
+ || ( joinColumns.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) ) ) {
value.setForeignKeyName( "none" );
}
else {
@@ -3624,12 +3667,12 @@ private static boolean hasAnnotationsOnIdClass(XClass idClass) {
return false;
}
- private static void matchIgnoreNotFoundWithFetchType(
+ private static void checkFetchModeAgainstNotFound(
String entity,
String association,
- boolean ignoreNotFound,
+ boolean hasNotFound,
FetchType fetchType) {
- if ( ignoreNotFound && fetchType == FetchType.LAZY ) {
+ if ( hasNotFound && fetchType == FetchType.LAZY ) {
LOG.ignoreNotFoundWithFetchTypeLazy( entity, association );
}
}
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/CollectionSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/CollectionSecondPass.java
index 24256e2e37a0..4f4108fa9932 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/CollectionSecondPass.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/CollectionSecondPass.java
@@ -16,6 +16,7 @@
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.IndexedCollection;
import org.hibernate.mapping.OneToMany;
+import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.Value;
@@ -68,8 +69,7 @@ public void doSecondPass(java.util.Map persistentClasses)
}
}
- abstract public void secondPass(java.util.Map persistentClasses, java.util.Map inheritedMetas)
- throws MappingException;
+ abstract public void secondPass(java.util.Map persistentClasses, java.util.Map inheritedMetas);
private static String columns(Value val) {
StringBuilder columns = new StringBuilder();
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java
index 77be05530038..f9d7107fffea 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java
@@ -253,7 +253,7 @@ private static Ejb3JoinColumn buildJoinColumn(
String suffixForDefaultColumnName,
MetadataBuildingContext buildingContext) {
if ( ann != null ) {
- if ( BinderHelper.isEmptyAnnotationValue( mappedBy ) ) {
+ if ( !BinderHelper.isEmptyOrNullAnnotationValue( mappedBy ) ) {
throw new AnnotationException(
"Illegal attempt to define a @JoinColumn with a mappedBy association: "
+ BinderHelper.getRelativePath( propertyHolder, propertyName )
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java
index 7a376ee894b4..6da83d1da963 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java
@@ -8,14 +8,13 @@
import java.util.Iterator;
import java.util.Map;
-
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import org.hibernate.AnnotationException;
-import org.hibernate.FetchMode;
import org.hibernate.MappingException;
import org.hibernate.annotations.LazyGroup;
+import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.cfg.annotations.PropertyBinder;
@@ -41,7 +40,7 @@ public class OneToOneSecondPass implements SecondPass {
private String ownerEntity;
private String ownerProperty;
private PropertyHolder propertyHolder;
- private boolean ignoreNotFound;
+ private NotFoundAction notFoundAction;
private PropertyData inferredData;
private XClass targetEntity;
private boolean cascadeOnDelete;
@@ -57,7 +56,7 @@ public OneToOneSecondPass(
PropertyHolder propertyHolder,
PropertyData inferredData,
XClass targetEntity,
- boolean ignoreNotFound,
+ NotFoundAction notFoundAction,
boolean cascadeOnDelete,
boolean optional,
String cascadeStrategy,
@@ -68,7 +67,7 @@ public OneToOneSecondPass(
this.mappedBy = mappedBy;
this.propertyHolder = propertyHolder;
this.buildingContext = buildingContext;
- this.ignoreNotFound = ignoreNotFound;
+ this.notFoundAction = notFoundAction;
this.inferredData = inferredData;
this.targetEntity = targetEntity;
this.cascadeOnDelete = cascadeOnDelete;
@@ -109,6 +108,7 @@ public void doSecondPass(Map persistentClasses) throws MappingException {
PropertyBinder binder = new PropertyBinder();
binder.setName( propertyName );
+ binder.setProperty( inferredData.getProperty() );
binder.setValue( value );
binder.setCascade( cascadeStrategy );
binder.setAccessType( inferredData.getDefaultAccess() );
@@ -191,7 +191,7 @@ else if ( otherSideProperty.getValue() instanceof ManyToOne ) {
);
ManyToOne manyToOne = new ManyToOne( buildingContext, mappedByJoin.getTable() );
//FIXME use ignore not found here
- manyToOne.setIgnoreNotFound( ignoreNotFound );
+ manyToOne.setNotFoundAction( notFoundAction );
manyToOne.setCascadeDeleteEnabled( value.isCascadeDeleteEnabled() );
manyToOne.setFetchMode( value.getFetchMode() );
manyToOne.setLazy( value.isLazy() );
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java
index b04c27d16e18..f79c8df5dfe6 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java
@@ -106,7 +106,9 @@ public void doSecondPass(java.util.Map persistentClasses) throws MappingExceptio
/*
* HbmMetadataSourceProcessorImpl does this only when property-ref != null, but IMO, it makes sense event if it is null
*/
- if ( !manyToOne.isIgnoreNotFound() ) manyToOne.createPropertyRefConstraints( persistentClasses );
+ if ( manyToOne.getNotFoundAction() == null ) {
+ manyToOne.createPropertyRefConstraints( persistentClasses );
+ }
}
else if ( value instanceof OneToOne ) {
value.createForeignKey();
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java
index 9570c1df3d79..23a141065762 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java
@@ -50,6 +50,8 @@
import org.hibernate.annotations.LazyGroup;
import org.hibernate.annotations.Loader;
import org.hibernate.annotations.ManyToAny;
+import org.hibernate.annotations.NotFound;
+import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.hibernate.annotations.OptimisticLock;
@@ -156,7 +158,7 @@ public abstract class CollectionBinder {
private Ejb3Column[] elementColumns;
private boolean isEmbedded;
private XProperty property;
- private boolean ignoreNotFound;
+ private NotFoundAction notFoundAction;
private TableBinder tableBinder;
private Ejb3Column[] mapKeyColumns;
private Ejb3JoinColumn[] mapKeyManyToManyColumns;
@@ -572,7 +574,7 @@ public void bind() {
isEmbedded,
property,
collectionType,
- ignoreNotFound,
+ notFoundAction,
oneToMany,
tableBinder,
buildingContext
@@ -714,6 +716,8 @@ private void defineFetchingStrategy() {
ManyToMany manyToMany = property.getAnnotation( ManyToMany.class );
ElementCollection elementCollection = property.getAnnotation( ElementCollection.class );
ManyToAny manyToAny = property.getAnnotation( ManyToAny.class );
+ NotFound notFound = property.getAnnotation( NotFound.class );
+
FetchType fetchType;
if ( oneToMany != null ) {
fetchType = oneToMany.fetch();
@@ -732,34 +736,57 @@ else if ( manyToAny != null ) {
"Define fetch strategy on a property not annotated with @ManyToOne nor @OneToMany nor @CollectionOfElements"
);
}
- if ( lazy != null ) {
- collection.setLazy( !( lazy.value() == LazyCollectionOption.FALSE ) );
- collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA );
+
+ if ( notFound != null ) {
+ collection.setLazy( false );
+
+ if ( lazy != null ) {
+ collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA );
+ }
+
+ if ( fetch != null ) {
+ if ( fetch.value() != null ) {
+ collection.setFetchMode( fetch.value().getHibernateFetchMode() );
+ if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) {
+ collection.setSubselectLoadable( true );
+ collection.getOwner().setSubselectLoadableCollections( true );
+ }
+ }
+ }
+ else {
+ collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) );
+ }
}
else {
- collection.setLazy( fetchType == FetchType.LAZY );
- collection.setExtraLazy( false );
- }
- if ( fetch != null ) {
- if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) {
- collection.setFetchMode( FetchMode.JOIN );
- collection.setLazy( false );
+ if ( lazy != null ) {
+ collection.setLazy( !( lazy.value() == LazyCollectionOption.FALSE ) );
+ collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA );
}
- else if ( fetch.value() == org.hibernate.annotations.FetchMode.SELECT ) {
- collection.setFetchMode( FetchMode.SELECT );
+ else {
+ collection.setLazy( fetchType == FetchType.LAZY );
+ collection.setExtraLazy( false );
}
- else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) {
- collection.setFetchMode( FetchMode.SELECT );
- collection.setSubselectLoadable( true );
- collection.getOwner().setSubselectLoadableCollections( true );
+ if ( fetch != null ) {
+ if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) {
+ collection.setFetchMode( FetchMode.JOIN );
+ collection.setLazy( false );
+ }
+ else if ( fetch.value() == org.hibernate.annotations.FetchMode.SELECT ) {
+ collection.setFetchMode( FetchMode.SELECT );
+ }
+ else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) {
+ collection.setFetchMode( FetchMode.SELECT );
+ collection.setSubselectLoadable( true );
+ collection.getOwner().setSubselectLoadableCollections( true );
+ }
+ else {
+ throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() );
+ }
}
else {
- throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() );
+ collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) );
}
}
- else {
- collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) );
- }
}
private XClass getCollectionType() {
@@ -788,14 +815,14 @@ public SecondPass getSecondPass(
final boolean isEmbedded,
final XProperty property,
final XClass collType,
- final boolean ignoreNotFound,
+ final NotFoundAction notFoundAction,
final boolean unique,
final TableBinder assocTableBinder,
final MetadataBuildingContext buildingContext) {
return new CollectionSecondPass( buildingContext, collection ) {
@SuppressWarnings("rawtypes")
@Override
- public void secondPass(Map persistentClasses, Map inheritedMetas) throws MappingException {
+ public void secondPass(Map persistentClasses, Map inheritedMetas) {
bindStarToManySecondPass(
persistentClasses,
collType,
@@ -807,7 +834,7 @@ public void secondPass(Map persistentClasses, Map inheritedMetas) throws Mapping
property,
unique,
assocTableBinder,
- ignoreNotFound,
+ notFoundAction,
buildingContext
);
}
@@ -828,7 +855,7 @@ protected boolean bindStarToManySecondPass(
XProperty property,
boolean unique,
TableBinder associationTableBinder,
- boolean ignoreNotFound,
+ NotFoundAction notFoundAction,
MetadataBuildingContext buildingContext) {
PersistentClass persistentClass = persistentClasses.get( collType.getName() );
boolean reversePropertyInJoin = false;
@@ -863,7 +890,7 @@ protected boolean bindStarToManySecondPass(
fkJoinColumns,
collType,
cascadeDeleteEnabled,
- ignoreNotFound,
+ notFoundAction,
buildingContext,
inheritanceStatePerClass
);
@@ -878,7 +905,7 @@ protected boolean bindStarToManySecondPass(
inverseColumns,
elementColumns,
isEmbedded, collType,
- ignoreNotFound, unique,
+ notFoundAction, unique,
cascadeDeleteEnabled,
associationTableBinder,
property,
@@ -895,7 +922,7 @@ protected void bindOneToManySecondPass(
Ejb3JoinColumn[] fkJoinColumns,
XClass collectionType,
boolean cascadeDeleteEnabled,
- boolean ignoreNotFound,
+ NotFoundAction notFoundAction,
MetadataBuildingContext buildingContext,
Map inheritanceStatePerClass) {
@@ -910,7 +937,7 @@ protected void bindOneToManySecondPass(
org.hibernate.mapping.OneToMany oneToMany = new org.hibernate.mapping.OneToMany( buildingContext, collection.getOwner() );
collection.setElement( oneToMany );
oneToMany.setReferencedEntityName( collectionType.getName() );
- oneToMany.setIgnoreNotFound( ignoreNotFound );
+ oneToMany.setNotFoundAction( notFoundAction );
String assocClass = oneToMany.getReferencedEntityName();
PersistentClass associatedClass = persistentClasses.get( assocClass );
@@ -1314,7 +1341,7 @@ private void bindManyToManySecondPass(
Ejb3Column[] elementColumns,
boolean isEmbedded,
XClass collType,
- boolean ignoreNotFound, boolean unique,
+ NotFoundAction notFoundAction, boolean unique,
boolean cascadeDeleteEnabled,
TableBinder associationTableBinder,
XProperty property,
@@ -1457,7 +1484,7 @@ else if ( anyAnn != null ) {
//make the second join non lazy
element.setFetchMode( FetchMode.JOIN );
element.setLazy( false );
- element.setIgnoreNotFound( ignoreNotFound );
+ element.setNotFoundAction( notFoundAction );
// as per 11.1.38 of JPA 2.0 spec, default to primary key if no column is specified by @OrderBy.
if ( hqlOrderBy != null ) {
collValue.setManyToManyOrdering(
@@ -1824,8 +1851,21 @@ public void setProperty(XProperty property) {
this.property = property;
}
+ public NotFoundAction getNotFoundAction() {
+ return notFoundAction;
+ }
+
+ public void setNotFoundAction(NotFoundAction notFoundAction) {
+ this.notFoundAction = notFoundAction;
+ }
+
public void setIgnoreNotFound(boolean ignoreNotFound) {
- this.ignoreNotFound = ignoreNotFound;
+ if ( ignoreNotFound ) {
+ setNotFoundAction( NotFoundAction.IGNORE );
+ }
+ else {
+ setNotFoundAction( null );
+ }
}
public void setMapKeyColumns(Ejb3Column[] mapKeyColumns) {
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java
index 34ef384638e7..9bfb2b62f44d 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java
@@ -13,6 +13,7 @@
import org.hibernate.AnnotationException;
import org.hibernate.annotations.CollectionId;
+import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
@@ -52,11 +53,11 @@ protected boolean bindStarToManySecondPass(
XProperty property,
boolean unique,
TableBinder associationTableBinder,
- boolean ignoreNotFound,
+ NotFoundAction notFoundAction,
MetadataBuildingContext buildingContext) {
boolean result = super.bindStarToManySecondPass(
persistentClasses, collType, fkJoinColumns, keyColumns, inverseColumns, elementColumns, isEmbedded,
- property, unique, associationTableBinder, ignoreNotFound, getBuildingContext()
+ property, unique, associationTableBinder, notFoundAction, getBuildingContext()
);
CollectionId collectionIdAnn = property.getAnnotation( CollectionId.class );
if ( collectionIdAnn != null ) {
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java
index 69aa6c597699..5f09945873d8 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java
@@ -10,6 +10,7 @@
import org.hibernate.AnnotationException;
import org.hibernate.MappingException;
+import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.OrderBy;
import org.hibernate.annotations.Sort;
import org.hibernate.annotations.common.reflection.XClass;
@@ -76,14 +77,13 @@ public SecondPass getSecondPass(
final boolean isEmbedded,
final XProperty property,
final XClass collType,
- final boolean ignoreNotFound,
+ final NotFoundAction notFoundAction,
final boolean unique,
final TableBinder assocTableBinder,
final MetadataBuildingContext buildingContext) {
return new CollectionSecondPass( getBuildingContext(), ListBinder.this.collection ) {
@Override
- public void secondPass(Map persistentClasses, Map inheritedMetas)
- throws MappingException {
+ public void secondPass(Map persistentClasses, Map inheritedMetas) {
bindStarToManySecondPass(
persistentClasses,
collType,
@@ -95,7 +95,7 @@ public void secondPass(Map persistentClasses, Map inheritedMetas)
property,
unique,
assocTableBinder,
- ignoreNotFound,
+ notFoundAction,
buildingContext
);
bindIndex( buildingContext );
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java
index 4a4f83fe1c97..2d9920dacbf7 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java
@@ -23,13 +23,17 @@
import org.hibernate.AssertionFailure;
import org.hibernate.FetchMode;
import org.hibernate.MappingException;
+import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
+import org.hibernate.boot.model.relational.SqlStringGenerationContext;
+import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl;
import org.hibernate.boot.spi.BootstrapContext;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.cfg.AccessType;
import org.hibernate.cfg.AnnotatedClassType;
import org.hibernate.cfg.AnnotationBinder;
+import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.BinderHelper;
import org.hibernate.cfg.CollectionPropertyHolder;
import org.hibernate.cfg.CollectionSecondPass;
@@ -41,6 +45,8 @@
import org.hibernate.cfg.PropertyPreloadedData;
import org.hibernate.cfg.SecondPass;
import org.hibernate.dialect.HSQLDialect;
+import org.hibernate.engine.config.spi.ConfigurationService;
+import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Column;
@@ -56,6 +62,7 @@
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.Value;
+import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.Template;
/**
@@ -87,16 +94,15 @@ public SecondPass getSecondPass(
final boolean isEmbedded,
final XProperty property,
final XClass collType,
- final boolean ignoreNotFound,
+ final NotFoundAction notFoundAction,
final boolean unique,
final TableBinder assocTableBinder,
final MetadataBuildingContext buildingContext) {
return new CollectionSecondPass( buildingContext, MapBinder.this.collection ) {
- public void secondPass(Map persistentClasses, Map inheritedMetas)
- throws MappingException {
+ public void secondPass(Map persistentClasses, Map inheritedMetas) {
bindStarToManySecondPass(
persistentClasses, collType, fkJoinColumns, keyColumns, inverseColumns, elementColumns,
- isEmbedded, property, unique, assocTableBinder, ignoreNotFound, buildingContext
+ isEmbedded, property, unique, assocTableBinder, notFoundAction, buildingContext
);
bindKeyFromAssociationTable(
collType, persistentClasses, mapKeyPropertyName, property, isEmbedded, buildingContext,
@@ -412,6 +418,14 @@ protected Value createFormulatedValue(
MetadataBuildingContext buildingContext) {
Value element = collection.getElement();
String fromAndWhere = null;
+ final ServiceRegistry serviceRegistry = buildingContext.getBootstrapContext().getServiceRegistry();
+ final ConfigurationService configurationService = serviceRegistry.getService( ConfigurationService.class);
+ final SqlStringGenerationContext generationContext = SqlStringGenerationContextImpl.fromExplicit(
+ serviceRegistry.getService( JdbcServices.class).getJdbcEnvironment(),
+ buildingContext.getMetadataCollector().getDatabase(),
+ configurationService.getSetting(AvailableSettings.DEFAULT_CATALOG, String.class, null),
+ configurationService.getSetting( AvailableSettings.DEFAULT_SCHEMA, String.class, null)
+ );
if ( !( element instanceof OneToMany ) ) {
String referencedPropertyName = null;
if ( element instanceof ToOne ) {
@@ -435,7 +449,7 @@ else if ( element instanceof DependantValue ) {
referencedEntityColumns = referencedProperty.getColumnIterator();
}
fromAndWhere = getFromAndWhereFormula(
- associatedClass.getTable().getQualifiedTableName().toString(),
+ generationContext.format(associatedClass.getTable().getQualifiedTableName()),
element.getColumnIterator(),
referencedEntityColumns
);
@@ -444,9 +458,7 @@ else if ( element instanceof DependantValue ) {
// HHH-11005 - only if we are OneToMany and location of map key property is at a different level, need to add a select
if ( !associatedClass.equals( targetPropertyPersistentClass ) ) {
fromAndWhere = getFromAndWhereFormula(
- targetPropertyPersistentClass.getTable()
- .getQualifiedTableName()
- .toString(),
+ generationContext.format(targetPropertyPersistentClass.getTable().getQualifiedTableName()),
element.getColumnIterator(),
associatedClass.getIdentifier().getColumnIterator()
);
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java
index d3986b61cc92..7cb33fd33347 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java
@@ -273,7 +273,9 @@ public Property makeProperty() {
prop.setPropertyAccessorName( accessType.getType() );
if ( property != null ) {
- prop.setValueGenerationStrategy( determineValueGenerationStrategy( property ) );
+ if ( entityBinder != null ) {
+ prop.setValueGenerationStrategy( determineValueGenerationStrategy( property ) );
+ }
if ( property.isAnnotationPresent( AttributeAccessor.class ) ) {
final AttributeAccessor accessor = property.getAnnotation( AttributeAccessor.class );
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenAnnotationReader.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenAnnotationReader.java
index a020b8607da1..7f4553145901 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenAnnotationReader.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenAnnotationReader.java
@@ -179,7 +179,6 @@
import org.hibernate.boot.jaxb.mapping.spi.JaxbStoredProcedureParameter;
import org.hibernate.boot.jaxb.mapping.spi.JaxbTable;
import org.hibernate.boot.jaxb.mapping.spi.JaxbTableGenerator;
-import org.hibernate.boot.jaxb.mapping.spi.JaxbTransient;
import org.hibernate.boot.jaxb.mapping.spi.JaxbUniqueConstraint;
import org.hibernate.boot.jaxb.mapping.spi.JaxbVersion;
import org.hibernate.boot.jaxb.mapping.spi.LifecycleCallbackContainer;
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenMetadataProvider.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenMetadataProvider.java
index afd35add4866..04733894cc78 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenMetadataProvider.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenMetadataProvider.java
@@ -36,7 +36,7 @@
* @author Emmanuel Bernard
*/
@SuppressWarnings("unchecked")
-public final class JPAXMLOverriddenMetadataProvider implements MetadataProvider {
+public class JPAXMLOverriddenMetadataProvider implements MetadataProvider {
private static final MetadataProvider STATELESS_BASE_DELEGATE = new JavaMetadataProvider();
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/PropertyMappingElementCollector.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/PropertyMappingElementCollector.java
index 97bf6b89ea23..791fdc20371c 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/PropertyMappingElementCollector.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/PropertyMappingElementCollector.java
@@ -43,9 +43,9 @@
*
Only create lists if we actually have elements (most lists should be empty in most cases)
*
*/
-final class PropertyMappingElementCollector {
- static final Function PERSISTENT_ATTRIBUTE_NAME = PersistentAttribute::getName;
- static final Function JAXB_TRANSIENT_NAME = JaxbTransient::getName;
+public final class PropertyMappingElementCollector {
+ public static final Function PERSISTENT_ATTRIBUTE_NAME = PersistentAttribute::getName;
+ public static final Function JAXB_TRANSIENT_NAME = JaxbTransient::getName;
static final Function LIFECYCLE_CALLBACK_NAME = LifecycleCallback::getMethodName;
private final String propertyName;
@@ -70,7 +70,7 @@ final class PropertyMappingElementCollector {
private List postUpdate;
private List postLoad;
- PropertyMappingElementCollector(String propertyName) {
+ public PropertyMappingElementCollector(String propertyName) {
this.propertyName = propertyName;
}
diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/LazyInitializable.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/LazyInitializable.java
new file mode 100644
index 000000000000..38c8345181ec
--- /dev/null
+++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/LazyInitializable.java
@@ -0,0 +1,33 @@
+/*
+ * 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 .
+ */
+package org.hibernate.collection.spi;
+
+import org.hibernate.Incubating;
+
+/**
+ * Hibernate "wraps" a java collection in an instance of PersistentCollection. Envers uses custom collection
+ * wrappers (ListProxy, SetProxy, etc). All of them need to extend LazyInitializable, so the
+ * Hibernate.isInitialized method can check if the collection is initialized or not.
+ *
+ * @author Fabricio Gregorio
+ */
+@Incubating
+public interface LazyInitializable {
+
+ /**
+ * Is this instance initialized?
+ *
+ * @return Was this collection initialized? Or is its data still not (fully) loaded?
+ */
+ boolean wasInitialized();
+
+ /**
+ * To be called internally by the session, forcing immediate initialization.
+ */
+ void forceInitialization();
+
+}
diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java
index 0a98641c38b7..8fa8817d0d60 100644
--- a/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java
+++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java
@@ -45,7 +45,7 @@
*
* @author Gavin King
*/
-public interface PersistentCollection {
+public interface PersistentCollection extends LazyInitializable {
/**
* Get the owning entity. Note that the owner is only
* set during the flush cycle, and when a new collection
@@ -271,11 +271,6 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri
*/
Serializable getSnapshot(CollectionPersister persister);
- /**
- * To be called internally by the session, forcing immediate initialization.
- */
- void forceInitialization();
-
/**
* Does the given element/entry exist in the collection?
*
@@ -335,13 +330,6 @@ Object readFrom(ResultSet rs, CollectionPersister role, CollectionAliases descri
*/
boolean isWrapper(Object collection);
- /**
- * Is this instance initialized?
- *
- * @return Was this collection initialized? Or is its data still not (fully) loaded?
- */
- boolean wasInitialized();
-
/**
* Does this instance have any "queued" operations?
*
diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/CriteriaQuery.java b/hibernate-core/src/main/java/org/hibernate/criterion/CriteriaQuery.java
index 3dbebe2e9db4..d5b672687419 100755
--- a/hibernate-core/src/main/java/org/hibernate/criterion/CriteriaQuery.java
+++ b/hibernate-core/src/main/java/org/hibernate/criterion/CriteriaQuery.java
@@ -8,6 +8,7 @@
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
+import org.hibernate.cfg.NotYetImplementedException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.type.Type;
@@ -200,4 +201,16 @@ public interface CriteriaQuery {
* @return The generated alias
*/
public String generateSQLAlias();
+
+ default Type getForeignKeyType(Criteria criteria, String associationPropertyName){
+ throw new NotYetImplementedException("CriteriaQuery#getForeignKeyType() has not been yet implemented!");
+ }
+
+ default String[] getForeignKeyColumns(Criteria criteria, String associationPropertyName){
+ throw new NotYetImplementedException("CriteriaQuery#getForeignKeyColumns() has not been yet implemented!");
+ }
+
+ default TypedValue getForeignKeyTypeValue(Criteria criteria, String associationPropertyName, Object value){
+ throw new NotYetImplementedException("CriteriaQuery#getForeignKeyTypeValue() has not been yet implemented!");
+ }
}
diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/ForeignKeyExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/ForeignKeyExpression.java
new file mode 100644
index 000000000000..8fc343e69d66
--- /dev/null
+++ b/hibernate-core/src/main/java/org/hibernate/criterion/ForeignKeyExpression.java
@@ -0,0 +1,40 @@
+/*
+ * 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 .
+ */
+package org.hibernate.criterion;
+
+import org.hibernate.Criteria;
+import org.hibernate.engine.spi.TypedValue;
+import org.hibernate.internal.util.StringHelper;
+
+public class ForeignKeyExpression implements Criterion {
+ private final String associationPropertyName;
+ private final Object value;
+ private final String operator;
+
+ public ForeignKeyExpression(String associationPropertyName, Object value, String operator) {
+ this.associationPropertyName = associationPropertyName;
+ this.value = value;
+ this.operator = operator;
+ }
+
+ @Override
+ public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) {
+ final String[] columns = criteriaQuery.getForeignKeyColumns( criteria, associationPropertyName );
+
+ String result = String.join( " and ", StringHelper.suffix( columns, operator + " ?" ) );
+ if ( columns.length > 1 ) {
+ result = '(' + result + ')';
+ }
+ return result;
+ }
+
+ @Override
+ public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery) {
+ return new TypedValue[] { criteriaQuery.getForeignKeyTypeValue( criteria, associationPropertyName, value ) };
+ }
+
+}
diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/ForeignKeyNullExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/ForeignKeyNullExpression.java
new file mode 100644
index 000000000000..fa81e944f369
--- /dev/null
+++ b/hibernate-core/src/main/java/org/hibernate/criterion/ForeignKeyNullExpression.java
@@ -0,0 +1,52 @@
+/*
+ * 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 .
+ */
+package org.hibernate.criterion;
+
+import org.hibernate.Criteria;
+import org.hibernate.engine.spi.TypedValue;
+import org.hibernate.internal.util.StringHelper;
+
+public class ForeignKeyNullExpression implements Criterion {
+ private static final TypedValue[] NO_VALUES = new TypedValue[0];
+
+ private final String associationPropertyName;
+ private final boolean negated;
+
+ public ForeignKeyNullExpression(String associationPropertyName) {
+ this.associationPropertyName = associationPropertyName;
+ this.negated = false;
+ }
+
+ public ForeignKeyNullExpression(String associationPropertyName, boolean negated) {
+ this.associationPropertyName = associationPropertyName;
+ this.negated = negated;
+ }
+
+ @Override
+ public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) {
+ final String[] columns = criteriaQuery.getForeignKeyColumns( criteria, associationPropertyName );
+
+ String result = String.join( " and ", StringHelper.suffix( columns, getSuffix() ) );
+ if ( columns.length > 1 ) {
+ result = '(' + result + ')';
+ }
+ return result;
+ }
+
+ private String getSuffix() {
+ if ( negated ) {
+ return " is not null";
+ }
+ return " is null";
+ }
+
+ @Override
+ public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery) {
+ return NO_VALUES;
+ }
+
+}
diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/ForeingKeyProjection.java b/hibernate-core/src/main/java/org/hibernate/criterion/ForeingKeyProjection.java
new file mode 100644
index 000000000000..6df5fb1cc749
--- /dev/null
+++ b/hibernate-core/src/main/java/org/hibernate/criterion/ForeingKeyProjection.java
@@ -0,0 +1,55 @@
+/*
+ * 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 .
+ */
+package org.hibernate.criterion;
+
+import org.hibernate.Criteria;
+import org.hibernate.type.Type;
+
+public class ForeingKeyProjection extends SimpleProjection {
+ private String associationPropertyName;
+
+ protected ForeingKeyProjection(String associationPropertyName) {
+ this.associationPropertyName = associationPropertyName;
+ }
+
+ @Override
+ public Type[] getTypes(Criteria criteria, CriteriaQuery criteriaQuery) {
+ return new Type[] { criteriaQuery.getForeignKeyType( criteria, associationPropertyName ) };
+ }
+
+ @Override
+ public String toSqlString(Criteria criteria, int position, CriteriaQuery criteriaQuery) {
+ final StringBuilder buf = new StringBuilder();
+ final String[] cols = criteriaQuery.getForeignKeyColumns( criteria, associationPropertyName );
+ for ( int i = 0; i < cols.length; i++ ) {
+ buf.append( cols[i] )
+ .append( " as y" )
+ .append( position + i )
+ .append( '_' );
+ if ( i < cols.length - 1 ) {
+ buf.append( ", " );
+ }
+ }
+ return buf.toString();
+ }
+
+ @Override
+ public boolean isGrouped() {
+ return false;
+ }
+
+ @Override
+ public String toGroupSqlString(Criteria criteria, CriteriaQuery criteriaQuery) {
+ return super.toGroupSqlString( criteria, criteriaQuery );
+ }
+
+ @Override
+ public String toString() {
+ return "fk";
+ }
+
+}
diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/LikeExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/LikeExpression.java
index e266a70d784d..b846cacc80d6 100644
--- a/hibernate-core/src/main/java/org/hibernate/criterion/LikeExpression.java
+++ b/hibernate-core/src/main/java/org/hibernate/criterion/LikeExpression.java
@@ -78,7 +78,7 @@ public String toSqlString(Criteria criteria,CriteriaQuery criteriaQuery) {
@Override
public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery) {
- final String matchValue = ignoreCase ? value.toString().toLowerCase(Locale.ROOT) : value.toString();
+ final String matchValue = ignoreCase ? value.toString().toLowerCase() : value.toString();
return new TypedValue[] { criteriaQuery.getTypedValue( criteria, propertyName, matchValue ) };
}
diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/Projections.java b/hibernate-core/src/main/java/org/hibernate/criterion/Projections.java
index e2826eb7930d..dbb310622d75 100755
--- a/hibernate-core/src/main/java/org/hibernate/criterion/Projections.java
+++ b/hibernate-core/src/main/java/org/hibernate/criterion/Projections.java
@@ -61,6 +61,16 @@ public static IdentifierProjection id() {
return new IdentifierProjection();
}
+ /*
+ * An foreign key value projection.
+ *
+ * @return The foreign key projection
+ *
+ */
+ public static ForeingKeyProjection fk(String associationPropertyName) {
+ return new ForeingKeyProjection(associationPropertyName);
+ }
+
/**
* Create a distinct projection from a projection.
*
diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/Restrictions.java b/hibernate-core/src/main/java/org/hibernate/criterion/Restrictions.java
index 548ed9e09b51..64993d4ad3c9 100755
--- a/hibernate-core/src/main/java/org/hibernate/criterion/Restrictions.java
+++ b/hibernate-core/src/main/java/org/hibernate/criterion/Restrictions.java
@@ -36,6 +36,22 @@ public class Restrictions {
public static Criterion idEq(Object value) {
return new IdentifierEqExpression( value );
}
+
+ public static Criterion fkEq(String associationPropertyName, Object value) {
+ return new ForeignKeyExpression( associationPropertyName, value, "=" );
+ }
+
+ public static Criterion fkNe(String associationPropertyName, Object value) {
+ return new ForeignKeyExpression( associationPropertyName, value, "<>" );
+ }
+
+ public static Criterion fkIsNotNull(String associationPropertyName) {
+ return new ForeignKeyNullExpression( associationPropertyName, true);
+ }
+
+ public static Criterion fkIsNull(String associationPropertyName) {
+ return new ForeignKeyNullExpression( associationPropertyName );
+ }
/**
* Apply an "equal" constraint to the named property
*
diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/SimpleExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/SimpleExpression.java
index 5d377d78de7b..caee68875543 100644
--- a/hibernate-core/src/main/java/org/hibernate/criterion/SimpleExpression.java
+++ b/hibernate-core/src/main/java/org/hibernate/criterion/SimpleExpression.java
@@ -39,7 +39,7 @@ protected SimpleExpression(String propertyName, Object value, String op, boolean
this.op = op;
}
- protected final String getOp() {
+ public final String getOp() {
return op;
}
diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java
index b69f036ff332..5b1dff81e203 100644
--- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java
+++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java
@@ -20,6 +20,7 @@
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.dialect.function.VarArgsSQLFunction;
import org.hibernate.dialect.hint.IndexQueryHintHandler;
+import org.hibernate.dialect.identity.H2FinalTableIdentityColumnSupport;
import org.hibernate.dialect.identity.H2IdentityColumnSupport;
import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.pagination.AbstractLimitHandler;
@@ -505,7 +506,7 @@ public boolean supportsIfExistsBeforeTableName() {
@Override
public IdentityColumnSupport getIdentityColumnSupport() {
- return new H2IdentityColumnSupport();
+ return isVersion2 ? H2FinalTableIdentityColumnSupport.INSTANCE : H2IdentityColumnSupport.INSTANCE;
}
@Override
diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java
index d6e7186b5f65..e28b5371a035 100644
--- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java
+++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java
@@ -50,6 +50,12 @@ public String getNullColumnString() {
return " null";
}
+ @Override
+ public boolean canCreateSchema() {
+ // As far as I can tell, it does not
+ return false;
+ }
+
@Override
public String getCurrentSchemaCommand() {
return "select db_name()";
diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLFunctionRegistry.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLFunctionRegistry.java
index 48d413fa6b71..3c8d95bd7a68 100644
--- a/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLFunctionRegistry.java
+++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLFunctionRegistry.java
@@ -39,9 +39,12 @@ public SQLFunctionRegistry(Dialect dialect, Map userFunctio
*
* @param functionName The name of the function to locate
*
- * @return The located function, maye return {@code null}
+ * @return The located function, may return {@code null}
*/
- public SQLFunction findSQLFunction(String functionName) {
+ public SQLFunction findSQLFunction(final String functionName) {
+ if ( functionName == null ) {
+ return null;
+ }
return functionMap.get( functionName );
}
diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2FinalTableIdentityColumnSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2FinalTableIdentityColumnSupport.java
new file mode 100644
index 000000000000..3f93ae73868c
--- /dev/null
+++ b/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2FinalTableIdentityColumnSupport.java
@@ -0,0 +1,29 @@
+/*
+ * 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 http://www.gnu.org/licenses/lgpl-2.1.html
+ */
+package org.hibernate.dialect.identity;
+
+/**
+ * Identity column support for H2 2+ versions
+ * @author Jan Schatteman
+ */
+public class H2FinalTableIdentityColumnSupport extends H2IdentityColumnSupport {
+
+ public static final H2FinalTableIdentityColumnSupport INSTANCE = new H2FinalTableIdentityColumnSupport();
+
+ private H2FinalTableIdentityColumnSupport() {
+ }
+
+ @Override
+ public boolean supportsInsertSelectIdentity() {
+ return true;
+ }
+
+ @Override
+ public String appendIdentitySelectToInsert(String identityColumnName, String insertString) {
+ return "select " + identityColumnName + " from final table ( " + insertString + " )";
+ }
+}
diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2IdentityColumnSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2IdentityColumnSupport.java
index 8ea8827a6c1f..35bcf5d32b59 100644
--- a/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2IdentityColumnSupport.java
+++ b/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2IdentityColumnSupport.java
@@ -10,6 +10,12 @@
* @author Andrea Boriero
*/
public class H2IdentityColumnSupport extends IdentityColumnSupportImpl {
+
+ public static final H2IdentityColumnSupport INSTANCE = new H2IdentityColumnSupport();
+
+ protected H2IdentityColumnSupport() {
+ }
+
@Override
public boolean supportsIdentityColumns() {
return true;
diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/identity/IdentityColumnSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/identity/IdentityColumnSupport.java
index e63592fbe556..e000d8357f32 100644
--- a/hibernate-core/src/main/java/org/hibernate/dialect/identity/IdentityColumnSupport.java
+++ b/hibernate-core/src/main/java/org/hibernate/dialect/identity/IdentityColumnSupport.java
@@ -56,6 +56,23 @@ public interface IdentityColumnSupport {
*/
String appendIdentitySelectToInsert(String insertString);
+ /**
+ * Provided we {@link #supportsInsertSelectIdentity}, then attach the
+ * "select identity" clause to the insert statement.
+ *
+ * Note, if {@link #supportsInsertSelectIdentity} == false then
+ * the insert-string should be returned without modification.
+ *
+ * @param identityColumnName The name of the identity column
+ * @param insertString The insert command
+ *
+ * @return The insert command with any necessary identity select
+ * clause attached.
+ */
+ default String appendIdentitySelectToInsert(String identityColumnName, String insertString) {
+ return appendIdentitySelectToInsert( insertString );
+ }
+
/**
* Get the select command to use to retrieve the last generated IDENTITY
* value for a particular table
diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/unique/UniqueDelegate.java b/hibernate-core/src/main/java/org/hibernate/dialect/unique/UniqueDelegate.java
index 6fdc173af796..a00dff0f7e05 100644
--- a/hibernate-core/src/main/java/org/hibernate/dialect/unique/UniqueDelegate.java
+++ b/hibernate-core/src/main/java/org/hibernate/dialect/unique/UniqueDelegate.java
@@ -35,6 +35,21 @@
* @author Brett Meyer
*/
public interface UniqueDelegate {
+ /**
+ * Get the fragment that can be used to make a column unique as part of its column definition.
+ *
+ * This is intended for dialects which do not support unique constraints
+ *
+ * @param column The column to which to apply the unique
+ * @return The fragment (usually "unique"), empty string indicates the uniqueness will be indicated using a
+ * different approach
+ * @deprecated Implement {@link #getColumnDefinitionUniquenessFragment(Column, SqlStringGenerationContext)} instead.
+ */
+ @Deprecated
+ default String getColumnDefinitionUniquenessFragment(Column column) {
+ throw new IllegalStateException("getColumnDefinitionUniquenessFragment(...) was not implemented!");
+ }
+
/**
* Get the fragment that can be used to make a column unique as part of its column definition.
*
@@ -45,7 +60,27 @@ public interface UniqueDelegate {
* @return The fragment (usually "unique"), empty string indicates the uniqueness will be indicated using a
* different approach
*/
- public String getColumnDefinitionUniquenessFragment(Column column, SqlStringGenerationContext context);
+ default String getColumnDefinitionUniquenessFragment(Column column, SqlStringGenerationContext context) {
+ return getColumnDefinitionUniquenessFragment( column );
+ }
+
+ /**
+ * Get the fragment that can be used to apply unique constraints as part of table creation. The implementation
+ * should iterate over the {@link org.hibernate.mapping.UniqueKey} instances for the given table (see
+ * {@link org.hibernate.mapping.Table#getUniqueKeyIterator()} and generate the whole fragment for all
+ * unique keys
+ *
+ * Intended for Dialects which support unique constraint definitions, but just not in separate ALTER statements.
+ *
+ * @param table The table for which to generate the unique constraints fragment
+ * @return The fragment, typically in the form {@code ", unique(col1, col2), unique( col20)"}. NOTE: The leading
+ * comma is important!
+ * @deprecated Implement {@link #getTableCreationUniqueConstraintsFragment(Table, SqlStringGenerationContext)} instead.
+ */
+ @Deprecated
+ default String getTableCreationUniqueConstraintsFragment(Table table) {
+ throw new IllegalStateException("getTableCreationUniqueConstraintsFragment(...) was not implemented!");
+ }
/**
* Get the fragment that can be used to apply unique constraints as part of table creation. The implementation
@@ -60,7 +95,22 @@ public interface UniqueDelegate {
* @return The fragment, typically in the form {@code ", unique(col1, col2), unique( col20)"}. NOTE: The leading
* comma is important!
*/
- public String getTableCreationUniqueConstraintsFragment(Table table, SqlStringGenerationContext context);
+ default String getTableCreationUniqueConstraintsFragment(Table table, SqlStringGenerationContext context) {
+ return getTableCreationUniqueConstraintsFragment( table );
+ }
+
+ /**
+ * Get the SQL ALTER TABLE command to be used to create the given UniqueKey.
+ *
+ * @param uniqueKey The UniqueKey instance. Contains all information about the columns
+ * @param metadata Access to the bootstrap mapping information
+ * @return The ALTER TABLE command
+ * @deprecated Implement {@link #getAlterTableToAddUniqueKeyCommand(UniqueKey, Metadata, SqlStringGenerationContext)} instead.
+ */
+ @Deprecated
+ default String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata) {
+ throw new IllegalStateException("getAlterTableToAddUniqueKeyCommand(...) was not implemented!");
+ }
/**
* Get the SQL ALTER TABLE command to be used to create the given UniqueKey.
@@ -70,8 +120,23 @@ public interface UniqueDelegate {
* @param context A context for SQL string generation
* @return The ALTER TABLE command
*/
- public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata,
- SqlStringGenerationContext context);
+ default String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata,
+ SqlStringGenerationContext context) {
+ return getAlterTableToAddUniqueKeyCommand( uniqueKey, metadata );
+ }
+
+ /**
+ * Get the SQL ALTER TABLE command to be used to drop the given UniqueKey.
+ *
+ * @param uniqueKey The UniqueKey instance. Contains all information about the columns
+ * @param metadata Access to the bootstrap mapping information
+ * @return The ALTER TABLE command
+ * @deprecated Implement {@link #getAlterTableToDropUniqueKeyCommand(UniqueKey, Metadata, SqlStringGenerationContext)} instead.
+ */
+ @Deprecated
+ default String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata) {
+ throw new IllegalStateException("getAlterTableToDropUniqueKeyCommand(...) was not implemented!");
+ }
/**
* Get the SQL ALTER TABLE command to be used to drop the given UniqueKey.
@@ -81,7 +146,9 @@ public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata m
* @param context A context for SQL string generation
* @return The ALTER TABLE command
*/
- public String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata,
- SqlStringGenerationContext context);
+ default String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata,
+ SqlStringGenerationContext context) {
+ return getAlterTableToDropUniqueKeyCommand( uniqueKey, metadata );
+ }
}
diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java
index cfa74558cead..abb003c63290 100644
--- a/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java
+++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java
@@ -22,6 +22,7 @@
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityEntryExtraState;
import org.hibernate.engine.spi.EntityKey;
+import org.hibernate.engine.spi.Managed;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
@@ -279,9 +280,7 @@ public void postUpdate(Object entity, Object[] updatedState, Object nextVersion)
getPersister().setPropertyValue( entity, getPersister().getVersionProperty(), nextVersion );
}
- if( entity instanceof SelfDirtinessTracker ) {
- ( (SelfDirtinessTracker) entity ).$$_hibernate_clearDirtyAttributes();
- }
+ ManagedTypeHelper.processIfSelfDirtinessTracker( entity, AbstractEntityEntry::clearDirtyAttributes );
getPersistenceContext().getSession()
.getFactory()
@@ -289,6 +288,10 @@ public void postUpdate(Object entity, Object[] updatedState, Object nextVersion)
.resetDirty( entity, getPersister(), (Session) getPersistenceContext().getSession() );
}
+ private static void clearDirtyAttributes(final SelfDirtinessTracker entity) {
+ entity.$$_hibernate_clearDirtyAttributes();
+ }
+
@Override
public void postDelete() {
setCompressedValue( EnumState.PREVIOUS_STATUS, getStatus() );
@@ -345,10 +348,10 @@ public boolean requiresDirtyCheck(Object entity) {
@SuppressWarnings( {"SimplifiableIfStatement"})
private boolean isUnequivocallyNonDirty(Object entity) {
- if ( entity instanceof SelfDirtinessTracker ) {
+ if ( ManagedTypeHelper.isSelfDirtinessTracker( entity ) ) {
boolean uninitializedProxy = false;
- if ( entity instanceof PersistentAttributeInterceptable ) {
- final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) entity;
+ if ( ManagedTypeHelper.isPersistentAttributeInterceptable( entity ) ) {
+ final PersistentAttributeInterceptable interceptable = ManagedTypeHelper.asPersistentAttributeInterceptable( entity );
final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor();
if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) {
EnhancementAsProxyLazinessInterceptor enhancementAsProxyLazinessInterceptor = (EnhancementAsProxyLazinessInterceptor) interceptor;
@@ -365,11 +368,11 @@ else if ( entity instanceof HibernateProxy ) {
// we never have to check an uninitialized proxy
return uninitializedProxy || !persister.hasCollections()
&& !persister.hasMutableProperties()
- && !( (SelfDirtinessTracker) entity ).$$_hibernate_hasDirtyAttributes();
+ && !ManagedTypeHelper.asSelfDirtinessTracker( entity ).$$_hibernate_hasDirtyAttributes();
}
- if ( entity instanceof PersistentAttributeInterceptable ) {
- final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) entity;
+ if ( ManagedTypeHelper.isPersistentAttributeInterceptable( entity ) ) {
+ final PersistentAttributeInterceptable interceptable = ManagedTypeHelper.asPersistentAttributeInterceptable( entity );
final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor();
if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) {
// we never have to check an uninitialized proxy
diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java
index 97f20b183509..b67e2292695e 100644
--- a/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java
+++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java
@@ -9,6 +9,7 @@
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
+import org.hibernate.engine.internal.ManagedTypeHelper;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.PersistenceContext;
@@ -91,21 +92,22 @@ public void addEntityEntry(Object entity, EntityEntry entityEntry) {
ManagedEntity managedEntity = getAssociatedManagedEntity( entity );
final boolean alreadyAssociated = managedEntity != null;
if ( !alreadyAssociated ) {
- if ( ManagedEntity.class.isInstance( entity ) ) {
+ if ( ManagedTypeHelper.isManagedEntity( entity ) ) {
+ final ManagedEntity managed = ManagedTypeHelper.asManagedEntity( entity );
if ( entityEntry.getPersister().isMutable() ) {
- managedEntity = (ManagedEntity) entity;
+ managedEntity = managed;
// We know that managedEntity is not associated with the same PersistenceContext.
// Check if managedEntity is associated with a different PersistenceContext.
checkNotAssociatedWithOtherPersistenceContextIfMutable( managedEntity );
}
else {
// Create a holder for PersistenceContext-related data.
- managedEntity = new ImmutableManagedEntityHolder( (ManagedEntity) entity );
+ managedEntity = new ImmutableManagedEntityHolder( managed );
if ( immutableManagedEntityXref == null ) {
immutableManagedEntityXref = new IdentityHashMap();
}
immutableManagedEntityXref.put(
- (ManagedEntity) entity,
+ managed,
(ImmutableManagedEntityHolder) managedEntity
);
}
@@ -150,8 +152,8 @@ public void addEntityEntry(Object entity, EntityEntry entityEntry) {
}
private ManagedEntity getAssociatedManagedEntity(Object entity) {
- if ( ManagedEntity.class.isInstance( entity ) ) {
- final ManagedEntity managedEntity = (ManagedEntity) entity;
+ if ( ManagedTypeHelper.isManagedEntity( entity ) ) {
+ final ManagedEntity managedEntity = ManagedTypeHelper.asManagedEntity( entity );
if ( managedEntity.$$_hibernate_getEntityEntry() == null ) {
// it is not associated
return null;
@@ -368,7 +370,10 @@ public void downgradeLocks() {
ManagedEntity node = head;
while ( node != null ) {
- node.$$_hibernate_getEntityEntry().setLockMode( LockMode.NONE );
+ EntityEntry entityEntry = node.$$_hibernate_getEntityEntry();
+ if ( entityEntry != null ) {
+ entityEntry.setLockMode( LockMode.NONE );
+ }
node = node.$$_hibernate_getNextManagedEntity();
}
diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/ManagedTypeHelper.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/ManagedTypeHelper.java
new file mode 100644
index 000000000000..284660f56b59
--- /dev/null
+++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/ManagedTypeHelper.java
@@ -0,0 +1,188 @@
+/*
+ * 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 .
+ */
+package org.hibernate.engine.internal;
+
+import org.hibernate.engine.spi.EnhancedEntity;
+import org.hibernate.engine.spi.Managed;
+import org.hibernate.engine.spi.ManagedEntity;
+import org.hibernate.engine.spi.PersistentAttributeInterceptable;
+import org.hibernate.engine.spi.SelfDirtinessTracker;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * This is a helper to encapsulate an optimal strategy to execute type checks
+ * for interfaces which attempts to avoid the performance issues tracked
+ * as https://bugs.openjdk.org/browse/JDK-8180450 ;
+ * the problem is complex and best understood by reading the OpenJDK tracker;
+ * we'll focus on a possible solution here.
+ *
+ * To avoid polluting the secondary super-type cache, the important aspect is to
+ * not switch types repeatedly for the same concrete object; using a Java
+ * agent which was developed for this purpose (https://github.com/franz1981/type-pollution-agent)
+ * we identified a strong case with Hibernate ORM is triggered when the entities are
+ * using bytecode enhancement, as they are being checked by a set of interfaces:
+ * {@see org.hibernate.engine.spi.PersistentAttributeInterceptable}
+ * {@see org.hibernate.engine.spi.ManagedEntity}
+ * {@see org.hibernate.engine.spi.SelfDirtinessTracker}
+ * {@see org.hibernate.engine.spi.Managed}
+ * With our domain knowledge, we bet on the assumption that either enhancement isn't being
+ * used at all, OR that when enhancement is being used, there is a strong likelyhood for
+ * all of these supertypes to be have been injected into the managed objected of the domain
+ * model (this isn't a certainty as otherwise we'd not have multiple interfaces to separate
+ * these), but we're working based on the assumption so to at least optimise for what
+ * we expect being a very common configuration.
+ * (At this time we won't optimise also embeddables and other corner cases, which will
+ * need to be looked at separately).
+ * We therefore introduce a new marker interface {@see EnhancedEntity}, which extends
+ * all these other contracts, and have the enhancer tool apply it when all other interfaces
+ * have been applied.
+ * This then allows to check always and consistently for this type only; as fallback
+ * path, we perform the "traditional" operation as it would have been before this patch.
+ * @author Sanne Grinovero
+ */
+public final class ManagedTypeHelper {
+
+ /**
+ * @param type
+ * @return true if and only if the type is assignable to a {@see Managed} type.
+ */
+ public static boolean isManagedType(final Class type) {
+ return EnhancedEntity.class.isAssignableFrom( type ) || Managed.class.isAssignableFrom( type );
+ }
+
+ /**
+ * @param entity
+ * @return true if and only if the entity implements {@see Managed}
+ */
+ public static boolean isManaged(final Object entity) {
+ return entity instanceof EnhancedEntity || entity instanceof Managed;
+ }
+
+ /**
+ * @param entity
+ * @return true if and only if the entity implements {@see ManagedEntity}
+ */
+ public static boolean isManagedEntity(Object entity) {
+ return entity instanceof EnhancedEntity || entity instanceof ManagedEntity;
+ }
+
+ /**
+ * @param type
+ * @return true if and only if the type is assignable to a {@see PersistentAttributeInterceptable} type.
+ */
+ public static boolean isPersistentAttributeInterceptableType(final Class type) {
+ return EnhancedEntity.class.isAssignableFrom( type ) || PersistentAttributeInterceptable.class.isAssignableFrom( type );
+ }
+
+ /**
+ * @param entity
+ * @return true if and only if the entity implements {@see PersistentAttributeInterceptable}
+ */
+ public static boolean isPersistentAttributeInterceptable(final Object entity) {
+ return entity instanceof EnhancedEntity || entity instanceof PersistentAttributeInterceptable;
+ }
+
+ /**
+ * @param entity
+ * @return true if and only if the entity implements {@see SelfDirtinessTracker}
+ */
+ public static boolean isSelfDirtinessTracker(final Object entity) {
+ return entity instanceof EnhancedEntity || entity instanceof SelfDirtinessTracker;
+ }
+
+ /**
+ * Helper to execute an action on an entity, but exclusively if it's implementing the {@see PersistentAttributeInterceptable}
+ * interface. Otherwise no action is performed.
+ *
+ * @param entity
+ * @param action The action to be performed; it should take the entity as first parameter, and an additional parameter T as second parameter.
+ * @param optionalParam a parameter which can be passed to the action
+ * @param the type of the additional parameter.
+ */
+ public static void processIfPersistentAttributeInterceptable(
+ final Object entity,
+ final BiConsumer action,
+ final T optionalParam) {
+ if ( entity instanceof EnhancedEntity ) {
+ EnhancedEntity e = (EnhancedEntity) entity;
+ action.accept( e, optionalParam );
+ }
+ else if ( entity instanceof PersistentAttributeInterceptable ) {
+ PersistentAttributeInterceptable e = (PersistentAttributeInterceptable) entity;
+ action.accept( e, optionalParam );
+ }
+ }
+
+ /**
+ * If the entity is implementing SelfDirtinessTracker, apply some action to it.
+ * It is first cast to SelfDirtinessTracker using an optimal strategy.
+ * If the entity does not implement SelfDirtinessTracker, no operation is performed.
+ * @param entity
+ * @param action
+ */
+ public static void processIfSelfDirtinessTracker(final Object entity, final Consumer action) {
+ if ( entity instanceof EnhancedEntity ) {
+ EnhancedEntity e = (EnhancedEntity) entity;
+ action.accept( e );
+ }
+ else if ( entity instanceof SelfDirtinessTracker ) {
+ SelfDirtinessTracker e = (SelfDirtinessTracker) entity;
+ action.accept( e );
+ }
+ }
+
+ /**
+ * Cast the object to PersistentAttributeInterceptable
+ * (using this is highly preferrable over a direct cast)
+ * @param entity the entity to cast
+ * @return the same instance after casting
+ * @throws ClassCastException if it's not of the right type
+ */
+ public static PersistentAttributeInterceptable asPersistentAttributeInterceptable(final Object entity) {
+ if ( entity instanceof EnhancedEntity ) {
+ return (EnhancedEntity) entity;
+ }
+ else {
+ return (PersistentAttributeInterceptable) entity;
+ }
+ }
+
+ /**
+ * Cast the object to ManagedEntity
+ * (using this is highly preferrable over a direct cast)
+ * @param entity the entity to cast
+ * @return the same instance after casting
+ * @throws ClassCastException if it's not of the right type
+ */
+ public static ManagedEntity asManagedEntity(final Object entity) {
+ if ( entity instanceof EnhancedEntity ) {
+ return (EnhancedEntity) entity;
+ }
+ else {
+ return (ManagedEntity) entity;
+ }
+ }
+
+ /**
+ * Cast the object to SelfDirtinessTracker
+ * (using this is highly preferrable over a direct cast)
+ * @param entity the entity to cast
+ * @return the same instance after casting
+ * @throws ClassCastException if it's not of the right type
+ */
+ public static SelfDirtinessTracker asSelfDirtinessTracker(final Object entity) {
+ if ( entity instanceof EnhancedEntity ) {
+ return (EnhancedEntity) entity;
+ }
+ else {
+ return (SelfDirtinessTracker) entity;
+ }
+ }
+
+}
diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdXrefDelegate.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdXrefDelegate.java
index 71233928bfb3..ed464b0ab1ee 100644
--- a/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdXrefDelegate.java
+++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdXrefDelegate.java
@@ -87,13 +87,14 @@ public boolean cacheNaturalIdCrossReference(EntityPersister persister, Serializa
/**
* Handle removing cross reference entries for the given natural-id/pk combo
*
- * @param persister The persister representing the entity type.
- * @param pk The primary key value
+ * @param persister The persister representing the entity type.
+ * @param pk The primary key value
* @param naturalIdValues The natural id value(s)
- *
+ * @param removeOnNaturalIdCache remove the entry on shared cache too
+ *
* @return The cached values, if any. May be different from incoming values.
*/
- public Object[] removeNaturalIdCrossReference(EntityPersister persister, Serializable pk, Object[] naturalIdValues) {
+ public Object[] removeNaturalIdCrossReference(EntityPersister persister, Serializable pk, Object[] naturalIdValues, boolean removeOnNaturalIdCache) {
persister = locatePersisterForKey( persister );
validateNaturalId( persister, naturalIdValues );
@@ -108,7 +109,7 @@ public Object[] removeNaturalIdCrossReference(EntityPersister persister, Seriali
}
}
- if ( persister.hasNaturalIdCache() ) {
+ if ( removeOnNaturalIdCache && persister.hasNaturalIdCache() ) {
final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister
.getNaturalIdCacheAccessStrategy();
final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister, session() );
diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/NonNullableTransientDependencies.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/NonNullableTransientDependencies.java
index 4d064c73a194..36df2d7afae9 100644
--- a/hibernate-core/src/main/java/org/hibernate/engine/internal/NonNullableTransientDependencies.java
+++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/NonNullableTransientDependencies.java
@@ -24,7 +24,7 @@ public final class NonNullableTransientDependencies {
// for the map value.
private Map