diff --git a/ci/jpa-3.1-tck.Jenkinsfile b/ci/jpa-3.1-tck.Jenkinsfile
index b7977967fb09..2e65d894773b 100644
--- a/ci/jpa-3.1-tck.Jenkinsfile
+++ b/ci/jpa-3.1-tck.Jenkinsfile
@@ -28,6 +28,7 @@ pipeline {
choice(name: 'IMAGE_JDK', choices: ['jdk11'], description: 'The JDK base image version to use for the TCK image.')
string(name: 'TCK_VERSION', defaultValue: '3.1.2', description: 'The version of the Jakarta JPA TCK i.e. `2.2.0` or `3.0.1`')
string(name: 'TCK_SHA', defaultValue: '618a9fcdb0f897cda71227ed57d035ae1dc40fc392318809a734ffc6968e43ff', description: 'The SHA256 of the Jakarta JPA TCK that is distributed under https://download.eclipse.org/jakartaee/persistence/3.1/jakarta-persistence-tck-${TCK_VERSION}.zip.sha256')
+ string(name: 'TCK_URL', defaultValue: '', description: 'The URL from which to download the TCK ZIP file. Only needed for testing staged builds. Ensure the TCK_VERSION variable matches the ZIP file name suffix.')
booleanParam(name: 'NO_SLEEP', defaultValue: true, description: 'Whether the NO_SLEEP patch should be applied to speed up the TCK execution')
}
stages {
@@ -50,9 +51,14 @@ pipeline {
}
dir('tck') {
checkout changelog: false, poll: false, scm: [$class: 'GitSCM', branches: [[name: '*/main']], extensions: [], userRemoteConfigs: [[url: 'https://github.com/hibernate/jakarta-tck-runner.git']]]
- sh """ \
- cd jpa-3.1; docker build -f Dockerfile.${params.IMAGE_JDK} -t jakarta-tck-runner --build-arg TCK_VERSION=${params.TCK_VERSION} --build-arg TCK_SHA=${params.TCK_SHA} .
- """
+ script {
+ if ( params.TCK_URL == null || params.TCK_URL.isEmpty() ) {
+ sh "cd jpa-3.1; docker build -f Dockerfile.${params.IMAGE_JDK} -t jakarta-tck-runner --build-arg TCK_VERSION=${params.TCK_VERSION} --build-arg TCK_SHA=${params.TCK_SHA} ."
+ }
+ else {
+ sh "cd jpa-3.1; docker build -f Dockerfile.${params.IMAGE_JDK} -t jakarta-tck-runner --build-arg TCK_VERSION=${params.TCK_VERSION} --build-arg TCK_SHA=${params.TCK_SHA} --build-arg TCK_URL=${params.TCK_URL} ."
+ }
+ }
}
}
}
diff --git a/ci/quarkus.Jenkinsfile b/ci/quarkus.Jenkinsfile
index 75e6955c236c..9988bcf06218 100644
--- a/ci/quarkus.Jenkinsfile
+++ b/ci/quarkus.Jenkinsfile
@@ -39,13 +39,13 @@ pipeline {
sh "git clone -b 3.8 --single-branch https://github.com/quarkusio/quarkus.git . || git reset --hard && git clean -fx && git pull"
sh "sed -i 's@.*@${env.HIBERNATE_VERSION}@' bom/application/pom.xml"
// Need to override the default maven configuration this way, because there is no other way to do it
- sh "sed -i 's/-Xmx5g/-Xmx1920m/' ./.mvn/jvm.config"
+ sh "sed -i 's/-Xmx5g/-Xmx2048m/' ./.mvn/jvm.config"
sh "echo -e '\\n-XX:MaxMetaspaceSize=768m'>>./.mvn/jvm.config"
sh "./mvnw -pl !docs -Dquickly install"
// Need to kill the gradle daemons started during the Maven install run
sh "sudo pkill -f '.*GradleDaemon.*' || true"
// Need to override the default maven configuration this way, because there is no other way to do it
- sh "sed -i 's/-Xmx1920m/-Xmx1340m/' ./.mvn/jvm.config"
+ sh "sed -i 's/-Xmx2048m/-Xmx1340m/' ./.mvn/jvm.config"
sh "sed -i 's/MaxMetaspaceSize=768m/MaxMetaspaceSize=512m/' ./.mvn/jvm.config"
def excludes = "'!integration-tests/kafka-oauth-keycloak,!integration-tests/kafka-sasl-elytron,!integration-tests/hibernate-search-orm-opensearch,!integration-tests/maven,!integration-tests/quartz,!integration-tests/reactive-messaging-kafka,!integration-tests/resteasy-reactive-kotlin/standard,!integration-tests/opentelemetry-reactive-messaging,!integration-tests/virtual-threads/kafka-virtual-threads,!integration-tests/smallrye-jwt-oidc-webapp,!docs'"
sh "TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED=true ./mvnw -pl :quarkus-hibernate-orm -amd -pl ${excludes} verify -Dstart-containers -Dtest-containers -Dskip.gradle.build"
diff --git a/docker_db.sh b/docker_db.sh
index 3078435d456a..dcf5f84fd25f 100755
--- a/docker_db.sh
+++ b/docker_db.sh
@@ -21,7 +21,7 @@ mysql() {
mysql_8_0() {
$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.31 --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_as_cs --skip-character-set-client-handshake --log-bin-trust-function-creators=1
+ $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.31 --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_as_cs --skip-character-set-client-handshake --log-bin-trust-function-creators=1 --lower_case_table_names=2
# Give the container some time to start
OUTPUT=
n=0
@@ -45,7 +45,7 @@ mysql_8_0() {
mysql_8_1() {
$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.1.0 --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_as_cs --skip-character-set-client-handshake --log-bin-trust-function-creators=1
+ $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.1.0 --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_as_cs --skip-character-set-client-handshake --log-bin-trust-function-creators=1 --lower_case_table_names=2
# Give the container some time to start
OUTPUT=
n=0
@@ -69,7 +69,7 @@ mysql_8_1() {
mysql_8_2() {
$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.2.0 --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_as_cs --skip-character-set-client-handshake --log-bin-trust-function-creators=1
+ $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.2.0 --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_as_cs --skip-character-set-client-handshake --log-bin-trust-function-creators=1 --lower_case_table_names=2
# Give the container some time to start
OUTPUT=
n=0
@@ -97,7 +97,7 @@ mariadb() {
mariadb_10_4() {
$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.4.31 --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-character-set-client-handshake
+ $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.4.31 --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-character-set-client-handshake --lower_case_table_names=2
OUTPUT=
n=0
until [ "$n" -ge 5 ]
@@ -120,7 +120,7 @@ mariadb_10_4() {
mariadb_10_9() {
$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.9.3 --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-character-set-client-handshake
+ $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.9.3 --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-character-set-client-handshake --lower_case_table_names=2
OUTPUT=
n=0
until [ "$n" -ge 5 ]
@@ -143,7 +143,7 @@ mariadb_10_9() {
mariadb_11_1() {
$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:11.1.2 --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-character-set-client-handshake
+ $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:11.1.2 --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-character-set-client-handshake --lower_case_table_names=2
OUTPUT=
n=0
until [ "$n" -ge 5 ]
@@ -166,7 +166,7 @@ mariadb_11_1() {
mariadb_11_3() {
$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:11.3.1-rc --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-character-set-client-handshake
+ $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:11.3.1-rc --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-character-set-client-handshake --lower_case_table_names=2
OUTPUT=
n=0
until [ "$n" -ge 5 ]
diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityDeleteAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityDeleteAction.java
index e231c4d34e55..f28f79c66a82 100644
--- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityDeleteAction.java
+++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityDeleteAction.java
@@ -174,7 +174,7 @@ protected void postDeleteLoaded(
final EntityKey key = entry.getEntityKey();
persistenceContext.removeEntityHolder( key );
removeCacheItem( ck );
- persistenceContext.getNaturalIdResolutions().removeSharedResolution( id, naturalIdValues, persister );
+ persistenceContext.getNaturalIdResolutions().removeSharedResolution( id, naturalIdValues, persister, true);
postDelete();
}
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 e21b524406ce..ed522c9f2064 100644
--- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java
+++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java
@@ -684,6 +684,11 @@ public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy(
);
}
+ @Override
+ public String getTemporaryTableCreateOptions() {
+ return "TRANSACTIONAL";
+ }
+
@Override
public TemporaryTableKind getSupportedTemporaryTableKind() {
return TemporaryTableKind.LOCAL;
diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdResolutionsImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdResolutionsImpl.java
index 4960e9dd3a9f..e0eccd2d8c84 100644
--- a/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdResolutionsImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdResolutionsImpl.java
@@ -433,6 +433,11 @@ private void manageSharedResolution(
@Override
public void removeSharedResolution(Object id, Object naturalId, EntityMappingType entityDescriptor) {
+ removeSharedResolution( id, naturalId, entityDescriptor, false );
+ }
+
+ @Override
+ public void removeSharedResolution(Object id, Object naturalId, EntityMappingType entityDescriptor, boolean delayToAfterTransactionCompletion) {
final NaturalIdMapping naturalIdMapping = entityDescriptor.getNaturalIdMapping();
if ( naturalIdMapping == null ) {
// nothing to do
@@ -453,7 +458,18 @@ public void removeSharedResolution(Object id, Object naturalId, EntityMappingTyp
final EntityPersister persister = locatePersisterForKey( entityDescriptor.getEntityPersister() );
final Object naturalIdCacheKey = cacheAccess.generateCacheKey( naturalId, persister, session() );
- cacheAccess.evict( naturalIdCacheKey );
+ if ( delayToAfterTransactionCompletion ) {
+ session().asEventSource().getActionQueue().registerProcess(
+ (success, session) -> {
+ if ( success ) {
+ cacheAccess.evict( naturalIdCacheKey );
+ }
+ }
+ );
+ }
+ else {
+ cacheAccess.evict( naturalIdCacheKey );
+ }
// if ( sessionCachedNaturalIdValues != null
// && !Arrays.equals( sessionCachedNaturalIdValues, deletedNaturalIdValues ) ) {
@@ -479,7 +495,7 @@ public void handleSynchronization(Object pk, Object entity, EntityMappingType en
cacheResolution( pk, naturalIdValuesFromCurrentObjectState, persister );
stashInvalidNaturalIdReference( persister, cachedNaturalIdValues );
- removeSharedResolution( pk, cachedNaturalIdValues, persister );
+ removeSharedResolution( pk, cachedNaturalIdValues, persister, false );
}
}
diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/NaturalIdResolutions.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/NaturalIdResolutions.java
index ee4727d23f08..19c945fa6c39 100644
--- a/hibernate-core/src/main/java/org/hibernate/engine/spi/NaturalIdResolutions.java
+++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/NaturalIdResolutions.java
@@ -68,8 +68,12 @@ void manageSharedResolution(
/**
* Removes any cross-reference from the L2 cache
*/
- void removeSharedResolution(Object id, Object naturalId, EntityMappingType entityDescriptor);
-
+ void removeSharedResolution(Object id, Object naturalId, EntityMappingType entityDescriptor, boolean delayToAfterTransactionCompletion);
+
+ default void removeSharedResolution(Object id, Object naturalId, EntityMappingType entityDescriptor) {
+ removeSharedResolution( id, naturalId, entityDescriptor, false );
+ }
+
/**
* Find the cached natural-id for the given identifier
*
diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java
index e1106e9af453..e9f837b9d4b3 100644
--- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java
+++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java
@@ -26,6 +26,7 @@
import org.hibernate.engine.spi.Status;
import org.hibernate.event.spi.EventSource;
import org.hibernate.id.Assigned;
+import org.hibernate.id.CompositeNestedGeneratedValueGenerator;
import org.hibernate.id.IdentifierGenerationException;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
@@ -197,7 +198,8 @@ protected Object performSave(
processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes );
- if ( persister.getGenerator() instanceof Assigned ) {
+ final Generator generator = persister.getGenerator();
+ if ( generator instanceof Assigned || generator instanceof CompositeNestedGeneratedValueGenerator ) {
id = persister.getIdentifier( entity, source );
if ( id == null ) {
throw new IdentifierGenerationException(
diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java
index ac07226a4122..d998941b369c 100644
--- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java
+++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java
@@ -276,6 +276,7 @@ private static boolean hasCustomEventListeners(EventSource source) {
// Bean Validation adds a PRE_DELETE listener
// and Envers adds a POST_DELETE listener
return fss.eventListenerGroup_PRE_DELETE.count() > 0
+ || fss.eventListenerGroup_POST_COMMIT_DELETE.count() > 0
|| fss.eventListenerGroup_POST_DELETE.count() > 1
|| fss.eventListenerGroup_POST_DELETE.count() == 1
&& !(fss.eventListenerGroup_POST_DELETE.listeners().iterator().next()
diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/BasicValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/BasicValuedModelPart.java
index a98d27a41885..babd435f58e1 100644
--- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/BasicValuedModelPart.java
+++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/BasicValuedModelPart.java
@@ -59,4 +59,9 @@ default int forEachSelectable(SelectableConsumer consumer) {
default boolean hasPartitionedSelectionMapping() {
return isPartitioned();
}
+
+ @Override
+ default BasicValuedModelPart asBasicValuedModelPart() {
+ return this;
+ }
}
diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityAssociationMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityAssociationMapping.java
index e84b8143c07b..a9ef7203acef 100644
--- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityAssociationMapping.java
+++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityAssociationMapping.java
@@ -6,6 +6,8 @@
*/
package org.hibernate.metamodel.mapping;
+import java.util.Set;
+
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
/**
@@ -21,6 +23,8 @@ default String getFetchableName() {
EntityMappingType getAssociatedEntityMappingType();
+ Set getTargetKeyPropertyNames();
+
/**
* The model sub-part relative to the associated entity type that is the target
* of this association's foreign-key
diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ModelPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ModelPart.java
index 176c4ab8d172..b8e12bbe9cfe 100644
--- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ModelPart.java
+++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ModelPart.java
@@ -148,6 +148,11 @@ default EntityMappingType asEntityMappingType(){
return null;
}
+ @Nullable
+ default BasicValuedModelPart asBasicValuedModelPart() {
+ return null;
+ }
+
/**
* A short hand form of {@link #breakDownJdbcValues(Object, int, Object, Object, JdbcValueBiConsumer, SharedSessionContractImplementor)},
* that passes 0 as offset and null for the two values {@code X} and {@code Y}.
diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEntityCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEntityCollectionPart.java
index 01e6de36b8b4..27c3fc890c5a 100644
--- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEntityCollectionPart.java
+++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEntityCollectionPart.java
@@ -60,7 +60,7 @@ public abstract class AbstractEntityCollectionPart implements EntityCollectionPa
private final EntityMappingType associatedEntityTypeDescriptor;
private final NotFoundAction notFoundAction;
- private final Set targetKeyPropertyNames;
+ protected final Set targetKeyPropertyNames;
public AbstractEntityCollectionPart(
Nature nature,
@@ -110,10 +110,6 @@ public EntityMappingType getMappedType() {
return getAssociatedEntityMappingType();
}
- protected Set getTargetKeyPropertyNames() {
- return targetKeyPropertyNames;
- }
-
@Override
public NavigableRole getNavigableRole() {
return navigableRole;
diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java
index d197318c022f..ff4ecd2c5fd1 100644
--- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java
+++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java
@@ -7,6 +7,7 @@
package org.hibernate.metamodel.mapping.internal;
import java.util.Locale;
+import java.util.Set;
import java.util.function.Consumer;
import org.hibernate.annotations.NotFoundAction;
@@ -135,6 +136,11 @@ public ModelPart findSubPart(String name, EntityMappingType targetType) {
return super.findSubPart( name, targetType );
}
+ @Override
+ public Set getTargetKeyPropertyNames() {
+ return targetKeyPropertyNames;
+ }
+
@Override
public int breakDownJdbcValues(
Object domainValue,
diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java
index 00aedb2f71b4..4687df0c388b 100644
--- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java
+++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java
@@ -891,6 +891,7 @@ public String getTargetKeyPropertyName() {
return targetKeyPropertyName;
}
+ @Override
public Set getTargetKeyPropertyNames() {
return targetKeyPropertyNames;
}
diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java
index 39bcabe75ea9..a646b2e2d37e 100644
--- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java
+++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java
@@ -4115,7 +4115,7 @@ private void handleNaturalIdReattachment(Object entity, SharedSessionContractImp
? null
: naturalIdMapping.extractNaturalIdFromEntityState( entitySnapshot );
- naturalIdResolutions.removeSharedResolution( id, naturalIdSnapshot, this );
+ naturalIdResolutions.removeSharedResolution( id, naturalIdSnapshot, this, false );
final Object naturalId = naturalIdMapping.extractNaturalIdFromEntity( entity );
naturalIdResolutions.manageLocalResolution( id, naturalId, this, CachedNaturalIdValueSource.UPDATE );
}
diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractSelectionQuery.java b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractSelectionQuery.java
index 9953a29bc339..6d717ad8b1b2 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractSelectionQuery.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractSelectionQuery.java
@@ -315,7 +315,7 @@ protected static void checkQueryReturnType(
}
if ( !sessionFactory.getSessionFactoryOptions().getJpaCompliance().isJpaQueryComplianceEnabled() ) {
- verifyResultType( expectedResultClass, sqmSelection.getNodeType() );
+ verifyResultType( expectedResultClass, sqmSelection.getExpressible() );
}
}
// else, let's assume we can instantiate it!
diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java
index 85b0ba11bb0d..d463dbb1bb0c 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java
@@ -701,7 +701,12 @@ protected void verifyUpdate() {
}
protected int doExecuteUpdate() {
- return resolveNonSelectQueryPlan().executeUpdate( this );
+ try {
+ return resolveNonSelectQueryPlan().executeUpdate( this );
+ }
+ finally {
+ domainParameterXref.clearExpansions();
+ }
}
private NonSelectQueryPlan resolveNonSelectQueryPlan() {
diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmPathVisitor.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmPathVisitor.java
new file mode 100644
index 000000000000..c91f48ffd9e8
--- /dev/null
+++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmPathVisitor.java
@@ -0,0 +1,90 @@
+/*
+ * 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.query.sqm.internal;
+
+import java.util.function.Consumer;
+
+import org.hibernate.metamodel.model.domain.DiscriminatorSqmPath;
+import org.hibernate.metamodel.model.domain.internal.EntityDiscriminatorSqmPath;
+import org.hibernate.query.sqm.spi.BaseSemanticQueryWalker;
+import org.hibernate.query.sqm.tree.domain.NonAggregatedCompositeSimplePath;
+import org.hibernate.query.sqm.tree.domain.SqmAnyValuedSimplePath;
+import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
+import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
+import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
+import org.hibernate.query.sqm.tree.domain.SqmPath;
+import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath;
+import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
+import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
+
+/**
+ * Generic {@link org.hibernate.query.sqm.SemanticQueryWalker} that applies the provided
+ * {@link Consumer} to all {@link SqmPath paths} encountered during visitation.
+ *
+ * @author Marco Belladelli
+ */
+public class SqmPathVisitor extends BaseSemanticQueryWalker {
+ private final Consumer> pathConsumer;
+
+ public SqmPathVisitor(Consumer> pathConsumer) {
+ this.pathConsumer = pathConsumer;
+ }
+
+ @Override
+ public Object visitBasicValuedPath(SqmBasicValuedSimplePath> path) {
+ pathConsumer.accept( path );
+ return path;
+ }
+
+ @Override
+ public Object visitEmbeddableValuedPath(SqmEmbeddedValuedSimplePath> path) {
+ pathConsumer.accept( path );
+ return path;
+ }
+
+ @Override
+ public Object visitEntityValuedPath(SqmEntityValuedSimplePath> path) {
+ pathConsumer.accept( path );
+ return path;
+ }
+
+ @Override
+ public Object visitAnyValuedValuedPath(SqmAnyValuedSimplePath> path) {
+ pathConsumer.accept( path );
+ return path;
+ }
+
+ @Override
+ public Object visitQualifiedAttributeJoin(SqmAttributeJoin, ?> path) {
+ pathConsumer.accept( path );
+ return path;
+ }
+
+ @Override
+ public Object visitTreatedPath(SqmTreatedPath, ?> path) {
+ pathConsumer.accept( path );
+ return path;
+ }
+
+ @Override
+ public Object visitDiscriminatorPath(EntityDiscriminatorSqmPath path) {
+ pathConsumer.accept( path );
+ return path;
+ }
+
+ @Override
+ public Object visitPluralValuedPath(SqmPluralValuedSimplePath> path) {
+ pathConsumer.accept( path );
+ return path;
+ }
+
+ @Override
+ public Object visitNonAggregatedCompositeValuedPath(NonAggregatedCompositeSimplePath> path) {
+ pathConsumer.accept( path );
+ return path;
+ }
+}
diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java
index b293e1936203..3808c56f7272 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java
@@ -24,13 +24,14 @@
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.Bindable;
+import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
-import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
+import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.query.IllegalQueryOperationException;
@@ -44,22 +45,28 @@
import org.hibernate.query.sqm.SqmQuerySource;
import org.hibernate.query.sqm.spi.JdbcParameterBySqmParameterAccess;
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
+import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmDmlStatement;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef;
+import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmJoin;
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
import org.hibernate.query.sqm.tree.from.SqmRoot;
+import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
+import org.hibernate.query.sqm.tree.select.SqmQueryPart;
+import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
import org.hibernate.query.sqm.tree.select.SqmSortSpecification;
import org.hibernate.spi.NavigablePath;
+import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlTreeCreationException;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.from.TableGroup;
@@ -73,6 +80,7 @@
import org.hibernate.type.internal.ConvertedBasicTypeImpl;
import org.hibernate.type.spi.TypeConfiguration;
+import static org.hibernate.internal.util.NullnessUtil.castNonNull;
import static org.hibernate.query.sqm.tree.jpa.ParameterCollector.collectParameters;
/**
@@ -126,13 +134,57 @@ public static IllegalQueryOperationException expectingNonSelect(SqmStatement>
}
/**
- * Utility that returns {@code true} if the specified {@link SqmPath sqmPath} should be
- * dereferenced using the target table mapping, i.e. when the path's lhs is an explicit join.
+ * Utility that returns the entity association target's mapping type if the specified {@code sqmPath} should
+ * be dereferenced using the target table, i.e. when the path's lhs is an explicit join that is used in the
+ * group by clause, or defaults to the provided {@code modelPartContainer} otherwise.
*/
- public static boolean needsTargetTableMapping(SqmPath> sqmPath, ModelPartContainer modelPartContainer) {
- return modelPartContainer.getPartMappingType() != modelPartContainer
- && sqmPath.getLhs() instanceof SqmFrom, ?>
- && modelPartContainer.getPartMappingType() instanceof ManagedMappingType;
+ public static ModelPartContainer getTargetMappingIfNeeded(
+ SqmPath> sqmPath,
+ ModelPartContainer modelPartContainer,
+ SqmToSqlAstConverter sqlAstCreationState) {
+ final SqmQueryPart> queryPart = sqlAstCreationState.getCurrentSqmQueryPart();
+ if ( queryPart != null ) {
+ // We only need to do this for queries
+ final Clause clause = sqlAstCreationState.getCurrentClauseStack().getCurrent();
+ if ( clause != Clause.FROM && modelPartContainer.getPartMappingType() != modelPartContainer && sqmPath.getLhs() instanceof SqmFrom, ?> ) {
+ final ModelPart modelPart;
+ if ( modelPartContainer instanceof PluralAttributeMapping ) {
+ modelPart = getCollectionPart(
+ (PluralAttributeMapping) modelPartContainer,
+ castNonNull( sqmPath.getNavigablePath().getParent() )
+ );
+ }
+ else {
+ modelPart = modelPartContainer;
+ }
+ if ( modelPart instanceof EntityAssociationMapping ) {
+ final EntityAssociationMapping association = (EntityAssociationMapping) modelPart;
+ // If the path is one of the association's target key properties,
+ // we need to render the target side if in group/order by
+ if ( association.getTargetKeyPropertyNames().contains( sqmPath.getReferencedPathSource().getPathName() )
+ && ( clause == Clause.GROUP || clause == Clause.ORDER
+ || !isFkOptimizationAllowed( sqmPath.getLhs() )
+ || queryPart.getFirstQuerySpec().groupByClauseContains( sqmPath.getNavigablePath(), sqlAstCreationState )
+ || queryPart.getFirstQuerySpec().orderByClauseContains( sqmPath.getNavigablePath(), sqlAstCreationState ) ) ) {
+ return association.getAssociatedEntityMappingType();
+ }
+ }
+ }
+ }
+ return modelPartContainer;
+ }
+
+ private static CollectionPart getCollectionPart(PluralAttributeMapping attribute, NavigablePath path) {
+ final CollectionPart.Nature nature = CollectionPart.Nature.fromNameExact( path.getLocalName() );
+ if ( nature != null ) {
+ switch ( nature ) {
+ case ELEMENT:
+ return attribute.getElementDescriptor();
+ case INDEX:
+ return attribute.getIndexDescriptor();
+ }
+ }
+ return null;
}
/**
@@ -156,6 +208,35 @@ public static boolean isFkOptimizationAllowed(SqmPath> sqmPath) {
return false;
}
+ public static List getGroupByNavigablePaths(SqmQuerySpec> querySpec) {
+ final List> expressions = querySpec.getGroupByClauseExpressions();
+ if ( expressions.isEmpty() ) {
+ return Collections.emptyList();
+ }
+
+ final List navigablePaths = new ArrayList<>( expressions.size() );
+ final SqmPathVisitor pathVisitor = new SqmPathVisitor( path -> navigablePaths.add( path.getNavigablePath() ) );
+ for ( SqmExpression> expression : expressions ) {
+ expression.accept( pathVisitor );
+ }
+ return navigablePaths;
+ }
+
+ public static List getOrderByNavigablePaths(SqmQuerySpec> querySpec) {
+ final SqmOrderByClause order = querySpec.getOrderByClause();
+ if ( order == null || order.getSortSpecifications().isEmpty() ) {
+ return Collections.emptyList();
+ }
+
+ final List sortSpecifications = order.getSortSpecifications();
+ final List navigablePaths = new ArrayList<>( sortSpecifications.size() );
+ final SqmPathVisitor pathVisitor = new SqmPathVisitor( path -> navigablePaths.add( path.getNavigablePath() ) );
+ for ( SqmSortSpecification sortSpec : sortSpecifications ) {
+ sortSpec.getSortExpression().accept( pathVisitor );
+ }
+ return navigablePaths;
+ }
+
public static Map, Map, List>> generateJdbcParamsXref(
DomainParameterXref domainParameterXref,
JdbcParameterBySqmParameterAccess jdbcParameterBySqmParameterAccess) {
diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableStrategy.java
index 62e1e682c013..62621e63661a 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableStrategy.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableStrategy.java
@@ -22,6 +22,9 @@
public class LocalTemporaryTableStrategy {
public static final String SHORT_NAME = "local_temporary";
+ /**
+ * For H2 dialect avoid setting the drop strategy to true because H2 forces a commit when dropping a temporary table
+ */
public static final String DROP_ID_TABLES = "hibernate.query.mutation_strategy.local_temporary.drop_tables";
private final TemporaryTable temporaryTable;
diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/SqmCreationHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/SqmCreationHelper.java
index 09f187f73dd9..1c43c1ae9b34 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/SqmCreationHelper.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/SqmCreationHelper.java
@@ -11,6 +11,8 @@
import org.hibernate.spi.NavigablePath;
import org.hibernate.query.sqm.tree.domain.SqmPath;
+import java.util.concurrent.atomic.AtomicLong;
+
/**
* @author Steve Ebersole
*/
@@ -27,6 +29,8 @@ public class SqmCreationHelper {
*/
public static final String IMPLICIT_ALIAS = "{implicit}";
+ private static final AtomicLong UNIQUE_ID_COUNTER = new AtomicLong();
+
public static NavigablePath buildRootNavigablePath(String base, String alias) {
return new NavigablePath( base, determineAlias( alias ) );
}
@@ -35,10 +39,14 @@ public static NavigablePath buildSubNavigablePath(NavigablePath lhs, String base
return lhs.append( base, determineAlias( alias ) );
}
+ public static String acquireUniqueAlias() {
+ return Long.toString(UNIQUE_ID_COUNTER.incrementAndGet());
+ }
+
public static String determineAlias(String alias) {
// Make sure we always create a unique alias, otherwise we might use a wrong table group for the same join
if ( alias == null ) {
- return Long.toString( System.nanoTime() );
+ return acquireUniqueAlias();
}
else if ( alias == IMPLICIT_ALIAS ) {
return null;
diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java
index 8e9e2536e635..55472bebe572 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java
@@ -514,6 +514,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base
private boolean negativeAdjustment;
private final Set visitedAssociationKeys = new HashSet<>();
+ private final HashMap, Object> metadata = new HashMap<>();
private final MappingMetamodel domainModel;
public BaseSqmToSqlAstConverter(
@@ -8389,6 +8390,42 @@ private void applyOrdering(TableGroup tableGroup, OrderByFragment orderByFragmen
orderByFragments.add( new AbstractMap.SimpleEntry<>( orderByFragment, tableGroup ) );
}
+ @Override
+ public M resolveMetadata(S source, Function producer ) {
+ //noinspection unchecked
+ return (M) metadata.computeIfAbsent( new MetadataKey<>( source, producer ), k -> producer.apply( source ) );
+ }
+
+ static class MetadataKey {
+ private final S source;
+ private final Function producer;
+
+ public MetadataKey(S source, Function producer) {
+ this.source = source;
+ this.producer = producer;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( this == o ) {
+ return true;
+ }
+ if ( o == null || getClass() != o.getClass() ) {
+ return false;
+ }
+
+ final MetadataKey, ?> that = (MetadataKey, ?>) o;
+ return source.equals( that.source ) && producer.equals( that.producer );
+ }
+
+ @Override
+ public int hashCode() {
+ int result = source.hashCode();
+ result = 31 * result + producer.hashCode();
+ return result;
+ }
+ }
+
@Override
public boolean isResolvingCircularFetch() {
return resolvingCircularFetch;
diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/FakeSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/FakeSqmToSqlAstConverter.java
index 7e8f09985aa2..36d367a119b4 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/FakeSqmToSqlAstConverter.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/FakeSqmToSqlAstConverter.java
@@ -7,6 +7,7 @@
package org.hibernate.query.sqm.sql;
import java.util.List;
+import java.util.function.Function;
import java.util.function.Supplier;
import org.hibernate.LockMode;
diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmToSqlAstConverter.java
index 378fef24bee4..b19ad9e79678 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmToSqlAstConverter.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmToSqlAstConverter.java
@@ -7,6 +7,7 @@
package org.hibernate.query.sqm.sql;
import java.util.List;
+import java.util.function.Function;
import java.util.function.Supplier;
import org.hibernate.internal.util.collections.Stack;
@@ -23,6 +24,8 @@
import org.hibernate.sql.ast.tree.expression.QueryTransformer;
import org.hibernate.sql.ast.tree.predicate.Predicate;
+import jakarta.annotation.Nullable;
+
/**
* Specialized SemanticQueryWalker (SQM visitor) for producing SQL AST.
*
@@ -52,4 +55,10 @@ public interface SqmToSqlAstConverter extends SemanticQueryWalker