"""
+ }
+
+ 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.
-[![Build Status](https://ci.hibernate.org/job/hibernate-orm-main-h2-main/badge/icon)](https://ci.hibernate.org/job/hibernate-orm-main-h2-main/)
-[![Language grade: Java](https://img.shields.io/lgtm/grade/java/g/hibernate/hibernate-orm.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/hibernate/hibernate-orm/context:java)
+[![Build Status](https://ci.hibernate.org/job/hibernate-orm-pipeline/job/5.6/badge/icon)](https://ci.hibernate.org/job/hibernate-orm-pipeline/job/5.6/)
Building from sources
=========
diff --git a/build.gradle b/build.gradle
index 15ab92bf1c80..0a85bee8bad8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,7 +14,7 @@ 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'
}
}
@@ -22,7 +22,7 @@ buildscript {
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 'nu.studer.credentials' version '2.2'
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
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/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile
new file mode 100644
index 000000000000..9883c59457eb
--- /dev/null
+++ b/ci/snapshot-publish.Jenkinsfile
@@ -0,0 +1,51 @@
+/*
+ * 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
+}
+
+pipeline {
+ agent {
+ label 'Fedora'
+ }
+ 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 {
+ configFileProvider([
+ configFile(fileId: 'ci-hibernate.deploy.settings.maven', variable: 'MAVEN_SETTINGS_PATH')
+ ]) {
+ sh '''./gradlew clean publishPublishedArtifactsPublicationToJboss-snapshots-repositoryRepository \
+ -Dmaven.settings=$MAVEN_SETTINGS_PATH \
+ --no-scan
+ '''
+ }
+ }
+ }
+ }
+ post {
+ always {
+ configFileProvider([configFile(fileId: 'job-configuration.yaml', variable: 'JOB_CONFIGURATION_FILE')]) {
+ notifyBuildResult maintainers: (String) readYaml(file: env.JOB_CONFIGURATION_FILE).notification?.email?.recipients
+ }
+ }
+ }
+}
\ No newline at end of file
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/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