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, SqlAs Predicate visitNestedTopLevelPredicate(SqmPredicate predicate); + /** + * Resolve a generic metadata object from the provided source, using the specified producer. + */ + default M resolveMetadata(S source, Function producer) { + return producer.apply( source ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java index 92c21e8d95e3..7253c3743ce5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java @@ -13,7 +13,6 @@ import org.hibernate.metamodel.MappingMetamodel; import org.hibernate.metamodel.mapping.BasicValuedModelPart; import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPartContainer; @@ -34,7 +33,8 @@ import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.update.Assignable; -import static org.hibernate.query.sqm.internal.SqmUtil.needsTargetTableMapping; +import static org.hibernate.internal.util.NullnessUtil.castNonNull; +import static org.hibernate.query.sqm.internal.SqmUtil.getTargetMappingIfNeeded; /** * @author Steve Ebersole @@ -81,22 +81,14 @@ public static BasicValuedPathInterpretation from( } } - final BasicValuedModelPart mapping; - if ( needsTargetTableMapping( sqmPath, modelPartContainer ) ) { - // We have to make sure we render the column of the target table - mapping = (BasicValuedModelPart) ( (ManagedMappingType) modelPartContainer.getPartMappingType() ).findSubPart( - sqmPath.getReferencedPathSource().getPathName(), - treatTarget - ); - } - else { - mapping = (BasicValuedModelPart) modelPartContainer.findSubPart( - sqmPath.getReferencedPathSource().getPathName(), - treatTarget - ); - } + // Use the target type to find the sub part if needed, otherwise just use the container + final ModelPart modelPart = getTargetMappingIfNeeded( + sqmPath, + modelPartContainer, + sqlAstCreationState + ).findSubPart( sqmPath.getReferencedPathSource().getPathName(), treatTarget ); - if ( mapping == null ) { + if ( modelPart == null ) { if ( jpaQueryComplianceEnabled ) { // to get the better error, see if we got nothing because of treat handling final ModelPart subPart = tableGroup.getModelPart().findSubPart( @@ -111,6 +103,7 @@ public static BasicValuedPathInterpretation from( throw new UnknownPathException( "Path '" + sqmPath.getNavigablePath() + "' did not reference a known model part" ); } + final BasicValuedModelPart mapping = castNonNull( modelPart.asBasicValuedModelPart() ); final TableReference tableReference = tableGroup.resolveTableReference( sqmPath.getNavigablePath(), mapping, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedExpression.java index d54b8c3deb2f..d477397522da 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedExpression.java @@ -15,6 +15,7 @@ import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping; +import org.hibernate.query.sqm.spi.SqmCreationHelper; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.SqlAstWalker; import org.hibernate.sql.ast.spi.SqlAstCreationState; @@ -46,7 +47,7 @@ public EmbeddableValuedExpression( assert mapping != null; assert sqlExpression != null; assert mapping.getEmbeddableTypeDescriptor().getNumberOfAttributeMappings() == sqlExpression.getExpressions().size(); - this.navigablePath = baseNavigablePath.append( mapping.getPartName(), Long.toString( System.nanoTime() ) ); + this.navigablePath = baseNavigablePath.append( mapping.getPartName(), SqmCreationHelper.acquireUniqueAlias()); this.mapping = mapping; this.sqlExpression = sqlExpression; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedPathInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedPathInterpretation.java index 28fc5875b9f6..af23ff2c41a0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedPathInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedPathInterpretation.java @@ -13,7 +13,6 @@ import org.hibernate.metamodel.MappingMetamodel; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; @@ -29,7 +28,7 @@ import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.update.Assignable; -import static org.hibernate.query.sqm.internal.SqmUtil.needsTargetTableMapping; +import static org.hibernate.query.sqm.internal.SqmUtil.getTargetMappingIfNeeded; /** * @author Steve Ebersole @@ -65,20 +64,12 @@ else if ( lhs.getNodeType() instanceof EntityDomainType ) { } final ModelPartContainer modelPartContainer = tableGroup.getModelPart(); - final EmbeddableValuedModelPart mapping; - if ( needsTargetTableMapping( sqmPath, modelPartContainer ) ) { - // We have to make sure we render the column of the target table - mapping = (EmbeddableValuedModelPart) ( (ManagedMappingType) modelPartContainer.getPartMappingType() ).findSubPart( - sqmPath.getReferencedPathSource().getPathName(), - treatTarget - ); - } - else { - mapping = (EmbeddableValuedModelPart) modelPartContainer.findSubPart( - sqmPath.getReferencedPathSource().getPathName(), - treatTarget - ); - } + // Use the target type to find the sub part if needed, otherwise just use the container + final EmbeddableValuedModelPart mapping = (EmbeddableValuedModelPart) getTargetMappingIfNeeded( + sqmPath, + modelPartContainer, + sqlAstCreationState + ).findSubPart( sqmPath.getReferencedPathSource().getPathName(), treatTarget ); return new EmbeddableValuedPathInterpretation<>( mapping.toSqlExpression( diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java index 24d5185afb3a..b66b0732bce6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java @@ -162,7 +162,9 @@ private static EntityValuedPathInterpretation from( // we try to make use of it and the FK model part if possible based on the inferred mapping if ( mapping instanceof EntityAssociationMapping ) { final EntityAssociationMapping associationMapping = (EntityAssociationMapping) mapping; - final ModelPart keyTargetMatchPart = associationMapping.getKeyTargetMatchPart(); + final ModelPart keyTargetMatchPart = associationMapping.getForeignKeyDescriptor().getPart( + associationMapping.getSideNature() + ); if ( associationMapping.isFkOptimizationAllowed() ) { final boolean forceUsingForeignKeyAssociationSidePart; @@ -265,7 +267,7 @@ public static EntityValuedPathInterpretation from( if ( currentClause == Clause.GROUP || currentClause == Clause.ORDER ) { assert sqlAstCreationState.getCurrentSqmQueryPart().isSimpleQueryPart(); final SqmQuerySpec querySpec = sqlAstCreationState.getCurrentSqmQueryPart().getFirstQuerySpec(); - if ( currentClause == Clause.ORDER && !querySpec.groupByClauseContains( navigablePath ) ) { + if ( currentClause == Clause.ORDER && !querySpec.groupByClauseContains( navigablePath, sqlAstCreationState ) ) { // We must ensure that the order by expression be expanded but only if the group by // contained the same expression, and that was expanded as well expandToAllColumns = false; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmDmlStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmDmlStatement.java index bbd93e034b0c..d662b36ce699 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmDmlStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmDmlStatement.java @@ -16,6 +16,7 @@ import org.hibernate.query.criteria.JpaRoot; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmQuerySource; +import org.hibernate.query.sqm.spi.SqmCreationHelper; import org.hibernate.query.sqm.tree.cte.SqmCteStatement; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.from.SqmRoot; @@ -84,21 +85,21 @@ public JpaCteCriteria getCteCriteria(String cteName) { @Override public JpaCteCriteria with(AbstractQuery criteria) { - return withInternal( Long.toString( System.nanoTime() ), criteria ); + return withInternal( SqmCreationHelper.acquireUniqueAlias(), criteria ); } @Override public JpaCteCriteria withRecursiveUnionAll( AbstractQuery baseCriteria, Function, AbstractQuery> recursiveCriteriaProducer) { - return withInternal( Long.toString( System.nanoTime() ), baseCriteria, false, recursiveCriteriaProducer ); + return withInternal( SqmCreationHelper.acquireUniqueAlias(), baseCriteria, false, recursiveCriteriaProducer ); } @Override public JpaCteCriteria withRecursiveUnionDistinct( AbstractQuery baseCriteria, Function, AbstractQuery> recursiveCriteriaProducer) { - return withInternal( Long.toString( System.nanoTime() ), baseCriteria, true, recursiveCriteriaProducer ); + return withInternal( SqmCreationHelper.acquireUniqueAlias(), baseCriteria, true, recursiveCriteriaProducer ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java index 5e07bb77fde4..deb419c6b8f4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java @@ -122,7 +122,7 @@ public Class getJavaType() { @Override public JavaType getExpressibleJavaType() { - return getJavaTypeDescriptor(); + return super.getExpressible().getExpressibleJavaType(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java index cce753c1ceaa..1a6c7d534017 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java @@ -109,7 +109,7 @@ public SqmTreatedPath treatAs(EntityDomainType treatTarge @Override public JavaType getExpressibleJavaType() { - return getJavaTypeDescriptor(); + return super.getExpressible().getExpressibleJavaType(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java index d17299ec57d0..afe59643d7e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java @@ -19,6 +19,7 @@ import org.hibernate.query.criteria.JpaRoot; import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.spi.SqmCreationHelper; import org.hibernate.query.sqm.tree.AbstractSqmNode; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.cte.SqmCteStatement; @@ -112,21 +113,21 @@ public JpaCteCriteria getCteCriteria(String cteName) { @Override public JpaCteCriteria with(AbstractQuery criteria) { - return withInternal( Long.toString( System.nanoTime() ), criteria ); + return withInternal( SqmCreationHelper.acquireUniqueAlias(), criteria ); } @Override public JpaCteCriteria withRecursiveUnionAll( AbstractQuery baseCriteria, Function, AbstractQuery> recursiveCriteriaProducer) { - return withInternal( Long.toString( System.nanoTime() ), baseCriteria, false, recursiveCriteriaProducer ); + return withInternal( SqmCreationHelper.acquireUniqueAlias(), baseCriteria, false, recursiveCriteriaProducer ); } @Override public JpaCteCriteria withRecursiveUnionDistinct( AbstractQuery baseCriteria, Function, AbstractQuery> recursiveCriteriaProducer) { - return withInternal( Long.toString( System.nanoTime() ), baseCriteria, true, recursiveCriteriaProducer ); + return withInternal( SqmCreationHelper.acquireUniqueAlias(), baseCriteria, true, recursiveCriteriaProducer ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmAliasedNode.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmAliasedNode.java index 5ec4a50cca54..e164b3c53561 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmAliasedNode.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmAliasedNode.java @@ -30,4 +30,9 @@ public interface SqmAliasedNode extends SqmTypedNode { default SqmExpressible getNodeType() { return getSelectableNode().getNodeType(); } + + @Override + default SqmExpressible getExpressible() { + return getSelectableNode().getExpressible(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java index 6ad3b64c3dae..d0ed480aae32 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Set; +import org.hibernate.Internal; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.model.domain.EmbeddableDomainType; @@ -25,11 +26,12 @@ import org.hibernate.query.sqm.FetchClauseType; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.internal.SqmUtil; +import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmNode; 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.SqmTreatedPath; import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef; import org.hibernate.query.sqm.tree.expression.SqmExpression; @@ -729,9 +731,32 @@ private void appendTreatJoins(SqmFrom sqmFrom, StringBuilder sb) { } } - public boolean groupByClauseContains(NavigablePath path) { - for ( SqmExpression expression : groupByClauseExpressions ) { - if ( expression instanceof SqmPath && ( (SqmPath) expression ).getNavigablePath() == path ) { + @Internal + public boolean groupByClauseContains(NavigablePath navigablePath, SqmToSqlAstConverter sqlAstConverter) { + if ( groupByClauseExpressions.isEmpty() ) { + return false; + } + return navigablePathsContain( sqlAstConverter.resolveMetadata( + this, + SqmUtil::getGroupByNavigablePaths + ), navigablePath ); + } + + @Internal + public boolean orderByClauseContains(NavigablePath navigablePath, SqmToSqlAstConverter sqlAstConverter) { + final SqmOrderByClause orderByClause = getOrderByClause(); + if ( orderByClause == null || orderByClause.getSortSpecifications().isEmpty() ) { + return false; + } + return navigablePathsContain( sqlAstConverter.resolveMetadata( + this, + SqmUtil::getOrderByNavigablePaths + ), navigablePath ); + } + + private boolean navigablePathsContain(List navigablePaths, NavigablePath navigablePath) { + for ( NavigablePath path : navigablePaths ) { + if ( path.isParentOrEqual( navigablePath ) ) { return true; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateJavaType.java index 3ddf89bec7a8..a10b208106af 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateJavaType.java @@ -145,7 +145,7 @@ public Object unwrap(Date value, Class type, WrapperOptions options) { } if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) { - return new java.sql.Timestamp( value.getTime() ); + return new java.sql.Timestamp( unwrapDateEpoch( value ) ); } if ( java.sql.Time.class.isAssignableFrom( type ) ) { @@ -158,18 +158,31 @@ public Object unwrap(Date value, Class type, WrapperOptions options) { private LocalDate unwrapLocalDate(Date value) { return value instanceof java.sql.Date ? ( (java.sql.Date) value ).toLocalDate() - : new java.sql.Date( value.getTime() ).toLocalDate(); + : new java.sql.Date( unwrapDateEpoch( value ) ).toLocalDate(); } private java.sql.Date unwrapSqlDate(Date value) { - return value instanceof java.sql.Date - ? (java.sql.Date) value - : new java.sql.Date( value.getTime() ); + if ( value instanceof java.sql.Date ) { + final java.sql.Date sqlDate = (java.sql.Date) value; + final long dateEpoch = toDateEpoch( sqlDate.getTime() ); + return dateEpoch == sqlDate.getTime() ? sqlDate : new java.sql.Date( dateEpoch ); + } + return new java.sql.Date( unwrapDateEpoch( value ) ); } private static long unwrapDateEpoch(Date value) { - return value.getTime(); + return toDateEpoch( value.getTime() ); + } + + private static long toDateEpoch(long value) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis( value ); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.clear(Calendar.MINUTE); + calendar.clear(Calendar.SECOND); + calendar.clear(Calendar.MILLISECOND); + return calendar.getTimeInMillis(); } @Override @@ -183,15 +196,15 @@ public Date wrap(Object value, WrapperOptions options) { } if ( value instanceof Long ) { - return new java.sql.Date( (Long) value ); + return new java.sql.Date( toDateEpoch( (Long) value ) ); } if ( value instanceof Calendar ) { - return new java.sql.Date( ( (Calendar) value ).getTimeInMillis() ); + return new java.sql.Date( toDateEpoch( ( (Calendar) value ).getTimeInMillis() ) ); } if ( value instanceof Date ) { - return new java.sql.Date( ( (Date) value ).getTime() ); + return unwrapSqlDate( (Date) value ); } if ( value instanceof LocalDate ) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/DialectTempTableNoCommitTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/DialectTempTableNoCommitTest.java new file mode 100644 index 000000000000..c448d9e3032e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/DialectTempTableNoCommitTest.java @@ -0,0 +1,194 @@ +/* + * 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.orm.test.dialect.unit; + +import org.hibernate.dialect.H2Dialect; +import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableStrategy; + +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.hibernate.testing.orm.junit.SkipForDialect; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * @author Jan Schatteman + */ +@DomainModel( + annotatedClasses = { + DialectTempTableNoCommitTest.Person.class, + DialectTempTableNoCommitTest.Engineer.class, + DialectTempTableNoCommitTest.Doctor.class + } +) +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsTemporaryTable.class) +@JiraKey("HHH-17943") +public class DialectTempTableNoCommitTest { + + private static final Long ID = 1l; + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + session -> + session.createMutationQuery( "delete from Doctor" ).executeUpdate() + ); + } + + @Test + @ServiceRegistry( + settings = @Setting(name = LocalTemporaryTableStrategy.DROP_ID_TABLES, value = "true") + ) + @SkipForDialect(dialectClass = H2Dialect.class) + @SessionFactory + public void noCommitAfterTempTableCreationAndDropTempTableTest(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + // the following shouldn't commit anything to the DB + Doctor d = new Doctor(); + d.setId( ID ); + session.persist( d ); + session.flush(); + session.createMutationQuery( "update Engineer set fellow = false where fellow = true" ) + .executeUpdate(); + session.getTransaction().markRollbackOnly(); + } + ); + scope.inTransaction( + session -> { + assertNull( session.find( Doctor.class, ID ) ); + } + ); + + scope.inTransaction( + session -> { + // the following shouldn't commit anything to the DB + Doctor d = new Doctor(); + d.setId( ID ); + session.persist( d ); + session.flush(); + session.createMutationQuery( "update Engineer set fellow = false where fellow = true" ) + .executeUpdate(); + } + ); + scope.inTransaction( + session -> { + assertNotNull( session.find( Doctor.class, ID ) ); + } + ); + } + + @Test + @ServiceRegistry( + settings = @Setting(name = LocalTemporaryTableStrategy.DROP_ID_TABLES, value = "false") + ) + @SessionFactory + public void noCommitAfterTempTableCreationAndNoDropTempTableTest2(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + // the following shouldn't commit anything to the DB + Doctor d = new Doctor(); + d.setId( ID ); + session.persist( d ); + session.flush(); + session.createMutationQuery( "update Engineer set fellow = false where fellow = true" ) + .executeUpdate(); + session.getTransaction().markRollbackOnly(); + } + ); + scope.inTransaction( + session -> { + assertNull( session.find( Doctor.class, ID ) ); + } + ); + + scope.inTransaction( + session -> { + // the following shouldn't commit anything to the DB + Doctor d = new Doctor(); + d.setId( ID ); + session.persist( d ); + session.flush(); + session.createMutationQuery( "update Engineer set fellow = false where fellow = true" ) + .executeUpdate(); + } + ); + scope.inTransaction( + session -> { + assertNotNull( session.find( Doctor.class, ID ) ); + } + ); + } + + @Entity(name = "Person") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Person { + + @Id + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + } + + @Entity(name = "Engineer") + public static class Engineer extends Person { + + private boolean fellow; + + public boolean isFellow() { + return fellow; + } + + public void setFellow(boolean fellow) { + this.fellow = fellow; + } + } + + @Entity(name = "Doctor") + public static class Doctor { + @Id + private Long id; + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/callbacks/PrePersistAndCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/callbacks/PrePersistAndCompositeIdTest.java new file mode 100644 index 000000000000..81172919364b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/callbacks/PrePersistAndCompositeIdTest.java @@ -0,0 +1,234 @@ +package org.hibernate.orm.test.jpa.callbacks; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; + +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinColumns; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.PrePersist; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + + +@Jpa( + annotatedClasses = { + PrePersistAndCompositeIdTest.Parent.class, + PrePersistAndCompositeIdTest.Child.class, + } +) +@JiraKey("HHH-18032") +public class PrePersistAndCompositeIdTest { + + private static final Long PARENT_ID1 = 1L; + private static final Long PARENT_ID2 = 2L; + private static final Long PARENT_ID_2_2 = 3L; + + + private static final Long CHILD_ID1 = 3L; + private static final Long CHILD_ID2 = 4L; + private static final Long CHILD_ID_2_2 = 5L; + + @AfterEach + public void tearDown(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + entityManager.createQuery( "delete from Child" ).executeUpdate(); + entityManager.createQuery( "delete from Parent" ).executeUpdate(); + } + ); + } + + @Test + public void testMerge(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + Child child = new Child( CHILD_ID1, null, "child" ); + Child merged = entityManager.merge( child ); + Parent parent = new Parent( PARENT_ID1, null, "parent", merged ); + entityManager.merge( parent ); + } + ); + + scope.inTransaction( + session -> { + Parent parent = session.find( Parent.class, new CompositeId( PARENT_ID1, PARENT_ID2 ) ); + assertThat( parent ).isNotNull(); + assertThat( parent.getChildren().size() ).isEqualTo( 1 ); + } + ); + } + + @Test + public void testPersist(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + Child child = new Child( CHILD_ID1, null, "child" ); + entityManager.persist( child ); + Parent parent = new Parent( PARENT_ID1, null, "parent", child ); + entityManager.persist( parent ); + } + ); + + scope.inTransaction( + session -> { + Parent parent = session.find( Parent.class, new CompositeId( PARENT_ID1, PARENT_ID2 ) ); + assertThat( parent ).isNotNull(); + assertThat( parent.getChildren().size() ).isEqualTo( 1 ); + } + ); + } + + @Test + public void testMergeAssignBothId(EntityManagerFactoryScope scope) { + + scope.inTransaction( + entityManager -> { + Child child = new Child( CHILD_ID1, CHILD_ID_2_2, "child" ); + Child merged = entityManager.merge( child ); + Parent parent = new Parent( PARENT_ID1, PARENT_ID_2_2, "parent", merged ); + entityManager.merge( parent ); + } + ); + scope.inTransaction( + session -> { + Parent parent = session.find( Parent.class, new CompositeId( PARENT_ID1, PARENT_ID_2_2 ) ); + assertThat( parent ).isNotNull(); + assertThat( parent.getChildren().size() ).isEqualTo( 1 ); + } + ); + } + + @Test + public void testPersistAssignBothId(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + Child child = new Child( CHILD_ID1, CHILD_ID_2_2, "child" ); + entityManager.persist( child ); + Parent parent = new Parent( PARENT_ID1, PARENT_ID_2_2, "parent", child ); + entityManager.persist( parent ); + } + ); + scope.inTransaction( + session -> { + Parent parent = session.find( Parent.class, new CompositeId( PARENT_ID1, PARENT_ID_2_2 ) ); + assertThat( parent ).isNotNull(); + assertThat( parent.getChildren().size() ).isEqualTo( 1 ); + } + ); + } + + @Entity(name = "Parent") + @IdClass(CompositeId.class) + public static class Parent { + @Id + private Long id1; + + @Id + private Long id2; + + private String name; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true) + @Fetch(FetchMode.SELECT) + List children = new ArrayList<>(); + + public Parent() { + } + + public Parent(Long id1, Long id2, String name, Child child) { + this.id1 = id1; + this.id2 = id2; + this.name = name; + this.children.add( child ); + child.parent = this; + } + + @PrePersist + public void prePersist() { + if ( id2 == null ) { + id2 = PARENT_ID2; + } + } + + public Long getId1() { + return id1; + } + + public Long getId2() { + return id2; + } + + public String getName() { + return name; + } + + public List getChildren() { + return children; + } + } + + @Entity(name = "Child") + @IdClass(CompositeId.class) + public static class Child { + @Id + private Long id1; + + @Id + private Long id2; + + private String name; + + @ManyToOne + @JoinColumns({ + @JoinColumn(name = "parent_id1", referencedColumnName = "id1"), + @JoinColumn(name = "parent_id2", referencedColumnName = "id2") + }) + private Parent parent; + + public Child() { + } + + public Child(Long id1, Long id2, String name) { + this.id1 = id1; + this.id2 = id2; + this.name = name; + } + + @PrePersist + public void prePersist() { + if ( id2 == null ) { + id2 = CHILD_ID2; + } + } + } + + public static class CompositeId { + private Long id1; + + private Long id2; + + public CompositeId() { + } + + public CompositeId(Long id1, Long id2) { + this.id1 = id1; + this.id2 = id2; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ql/MapIssueTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ql/MapIssueTest.java index 5f58d9dcb506..c735fa79af3d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ql/MapIssueTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ql/MapIssueTest.java @@ -52,15 +52,15 @@ public void testOnlyCollectionTableJoined(SessionFactoryScope scope) { } @Test - public void testMapKeyJoinIsIncluded(SessionFactoryScope scope) { + public void testMapKeyJoinIsOmitted(SessionFactoryScope scope) { SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); scope.inTransaction( s -> { s.createQuery( "select c from MapOwner as o join o.contents c join c.relationship r where r.id is not null" ).list(); statementInspector.assertExecutedCount( 1 ); - // Assert 3 joins, collection table, collection element and relationship - statementInspector.assertNumberOfJoins( 0, 3 ); + // Assert 2 joins, collection table and collection element. No need to join the relationship because it is not nullable + statementInspector.assertNumberOfJoins( 0, 2 ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/ReuseQueryWithExpandedParameterTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/ReuseQueryWithExpandedParameterTest.java new file mode 100644 index 000000000000..7d61b39be316 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/ReuseQueryWithExpandedParameterTest.java @@ -0,0 +1,79 @@ +package org.hibernate.orm.test.jpa.query; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Query; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Yanming Zhou + */ + +@Jpa(annotatedClasses = ReuseQueryWithExpandedParameterTest.Person.class) +@JiraKey("HHH-18027") +public class ReuseQueryWithExpandedParameterTest { + + @Test + public void reuseQueryWithExpandedParameter(EntityManagerFactoryScope scope) { + scope.inTransaction( em -> { + Person a = new Person( "a" ); + em.persist( a ); + Person b = new Person( "b" ); + em.persist( b ); + Person c = new Person( "c" ); + em.persist( c ); + Person d = new Person( "d" ); + em.persist( d ); + + Query q = em.createQuery( "from Person where name in (:names)" ); + assertEquals( 2, q.setParameter( "names", Set.of( "a", "b" ) ).getResultList().size() ); + assertEquals( 2, q.setParameter( "names", Set.of( "c", "d" ) ).getResultList().size() ); + + q = em.createQuery( "delete from Person where name in (:names)" ); + assertEquals( 2, q.setParameter( "names", Set.of( "a", "b" ) ).executeUpdate() ); + assertEquals( 2, q.setParameter( "names", Set.of( "c", "d" ) ).executeUpdate() ); + } ); + } + + @Entity(name = "Person") + public static class Person { + @Id + @GeneratedValue + private Long id; + + private String name; + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +} + diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/limit/Oracle12LimitTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/limit/Oracle12LimitTest.java index 82b1720434b3..f19128709be0 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/limit/Oracle12LimitTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/limit/Oracle12LimitTest.java @@ -19,6 +19,7 @@ import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Path; import jakarta.persistence.criteria.Root; @RequiresDialect(value = OracleDialect.class, majorVersion = 12) @@ -42,11 +43,12 @@ public void testLimit(SessionFactoryScope scope) { final Root personRoot = criteriaquery.from( Person.class ); final Join travels = personRoot.join( "travels", JoinType.LEFT ); - criteriaquery.select( personRoot ). + final Path destination = travels.get( "destination" ); + criteriaquery.multiselect( personRoot, destination ). where( criteriabuilder.or( criteriabuilder.equal( personRoot.get( "name" ), "A" ) ) ) .distinct( true ); - criteriaquery.orderBy( criteriabuilder.desc( criteriabuilder.upper( travels.get( "destination" ) ) ) ); + criteriaquery.orderBy( criteriabuilder.desc( criteriabuilder.upper( destination ) ) ); final TypedQuery createQuery = session.createQuery( criteriaquery ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/MappedSuperclassWithGenericsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/MappedSuperclassWithGenericsTest.java index 8a7087a5096f..9ebed2c128e7 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/MappedSuperclassWithGenericsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/MappedSuperclassWithGenericsTest.java @@ -7,44 +7,90 @@ package org.hibernate.orm.test.mapping; import java.io.Serializable; +import java.util.List; import java.util.Objects; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Root; -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.orm.junit.DomainModel; -import org.hibernate.testing.orm.junit.SessionFactory; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; -@TestForIssue(jiraKey = "HHH-14499") @DomainModel( annotatedClasses = { MappedSuperclassWithGenericsTest.IntermediateAbstractMapped.class, MappedSuperclassWithGenericsTest.BaseEntity.class, MappedSuperclassWithGenericsTest.AbstractGenericMappedSuperType.class, + MappedSuperclassWithGenericsTest.SimpleEntity.class, + MappedSuperclassWithGenericsTest.GenericIdBaseEntity.class } ) @SessionFactory public class MappedSuperclassWithGenericsTest { - @Test + @Jira( "https://hibernate.atlassian.net/browse/HHH-14499" ) public void testIt() { } + @Test + @Jira( "https://hibernate.atlassian.net/browse/HHH-18007" ) + void testSelectCriteriaGenericId(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder(); + final CriteriaQuery criteriaQuery = criteriaBuilder.createQuery( Long.class ); + final Root root = criteriaQuery.from( SimpleEntity.class ); + final Path idPath = root.get( "id" ); + criteriaQuery.select( idPath ); + final List resultList = session.createQuery( criteriaQuery ).getResultList(); + assertThat( resultList ).hasSize( 1 ).containsOnly( 1L ); + } ); + } + + @Test + @Jira( "https://hibernate.atlassian.net/browse/HHH-18007" ) + void testSelectGenericId(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final List resultList = session.createQuery( + "select e.id from SimpleEntity e", + Long.class + ).getResultList(); + assertThat( resultList ).hasSize( 1 ).containsOnly( 1L ); + } ); + } + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> session.persist( new SimpleEntity( 1L, "simple_1" ) ) ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createMutationQuery( "delete from SimpleEntity" ).executeUpdate() ); + } + @MappedSuperclass public static abstract class AbstractGenericMappedSuperType { - private T whateverType; - } @MappedSuperclass - @IdClass(PK.class) + @IdClass( PK.class ) public static abstract class IntermediateAbstractMapped extends AbstractGenericMappedSuperType { - @Id private String keyOne; @Id @@ -53,9 +99,8 @@ public static abstract class IntermediateAbstractMapped extends AbstractGener private String keyThree; } - @SuppressWarnings("UnusedDeclaration") + @SuppressWarnings( "UnusedDeclaration" ) public static class PK implements Serializable { - private String keyOne; private String keyTwo; private String keyThree; @@ -80,11 +125,37 @@ public int hashCode() { } } - @Entity(name = "BaseEntity") + @Entity( name = "BaseEntity" ) public static class BaseEntity extends IntermediateAbstractMapped { - String aString; + } + + @MappedSuperclass + public static class GenericIdBaseEntity { + @Id + private T id; + + protected GenericIdBaseEntity(T id) { + this.id = id; + } + public T getId() { + return id; + } } + @Entity( name = "SimpleEntity" ) + public static class SimpleEntity extends GenericIdBaseEntity { + @Column + private String string; + + public SimpleEntity() { + super( null ); + } + + protected SimpleEntity(Long id, String string) { + super( id ); + this.string = string; + } + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/mutable/cached/CachedMutableNaturalIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/mutable/cached/CachedMutableNaturalIdTest.java index 999165354043..afded9f7ee96 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/mutable/cached/CachedMutableNaturalIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/mutable/cached/CachedMutableNaturalIdTest.java @@ -179,6 +179,40 @@ public void testBySimpleNaturalIdResolveEntityFrom2LCacheSubClass(SessionFactory } ); } + + @Test + @TestForIssue( jiraKey = "HHH-16557" ) + public void testCreateDeleteRecreate(SessionFactoryScope scope) { + + final Integer id = scope.fromTransaction( + (session) -> { + AllCached it = new AllCached( "it" ); + session.persist(it); + session.remove(it); + // insert-remove might happen in an app driven by users GUI interactions + return it.getId(); + } + ); + + // now recreate with same naturalId value + scope.inTransaction( + (session) -> { + AllCached it = new AllCached( "it" ); + session.persist(it); + // resolving from first level cache + assertNotNull(session.bySimpleNaturalId( AllCached.class ).load( "it" )); + } + ); + + scope.inTransaction( + (session) -> { + // should resolve from second level cache + final AllCached shouldBeThere = session.bySimpleNaturalId( AllCached.class ).load( "it" ); + assertNotNull( shouldBeThere ); + assert(id.compareTo(shouldBeThere.getId()) != 0); + } + ); + } @Test @JiraKey("HHH-16558") diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/ManyToManyGroupByOrderByTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/ManyToManyGroupByOrderByTest.java new file mode 100644 index 000000000000..674371ddbf62 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/ManyToManyGroupByOrderByTest.java @@ -0,0 +1,210 @@ +/* + * 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.orm.test.query; + +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.Tuple; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@DomainModel( annotatedClasses = { + ManyToManyGroupByOrderByTest.Person.class, + ManyToManyGroupByOrderByTest.Cat.class, +} ) +@SessionFactory +@Jira( "https://hibernate.atlassian.net/browse/HHH-17837" ) +@Jira( "https://hibernate.atlassian.net/browse/HHH-18202" ) +public class ManyToManyGroupByOrderByTest { + @Test + public void testSelectEntity(SessionFactoryScope scope) { + // explicit join group by + scope.inTransaction( session -> { + final Person result = session.createQuery( + "select owner from Cat cat join cat.owners owner group by owner", + Person.class + ).getSingleResult(); + assertThat( result.getId() ).isEqualTo( 1L ); + } ); + // explicit join group by + order by + scope.inTransaction( session -> { + final Person result = session.createQuery( + "select owner from Cat cat join cat.owners owner group by owner order by owner", + Person.class + ).getSingleResult(); + assertThat( result.getId() ).isEqualTo( 1L ); + } ); + // implicit join group by + scope.inTransaction( session -> { + final Person result = session.createQuery( + "select element(cat.owners) from Cat cat group by element(cat.owners)", + Person.class + ).getSingleResult(); + assertThat( result.getId() ).isEqualTo( 1L ); + } ); + // implicit join group by + order by + scope.inTransaction( session -> { + final Person result = session.createQuery( + "select element(cat.owners) from Cat cat group by element(cat.owners) order by element(cat.owners)", + Person.class + ).getSingleResult(); + assertThat( result.getId() ).isEqualTo( 1L ); + } ); + } + + @Test + public void testSelectAssociationId(SessionFactoryScope scope) { + // explicit join group by + scope.inTransaction( session -> { + final Tuple result = session.createQuery( + "select owner.id, owner.name from Cat cat join cat.owners owner group by owner", + Tuple.class + ).getSingleResult(); + assertThat( result.get( 0, Long.class ) ).isEqualTo( 1L ); + assertThat( result.get( 1, String.class ) ).isEqualTo( "Marco" ); + } ); + // explicit join group by + order by + scope.inTransaction( session -> { + final Tuple result = session.createQuery( + "select owner.id, owner.name from Cat cat join cat.owners owner group by owner order by owner", + Tuple.class + ).getSingleResult(); + assertThat( result.get( 0, Long.class ) ).isEqualTo( 1L ); + assertThat( result.get( 1, String.class ) ).isEqualTo( "Marco" ); + } ); + // implicit join group by + scope.inTransaction( session -> { + final Tuple result = session.createQuery( + "select element(cat.owners).id from Cat cat group by element(cat.owners)", + Tuple.class + ).getSingleResult(); + assertThat( result.get( 0, Long.class ) ).isEqualTo( 1L ); + } ); + // implicit join group by + order by + scope.inTransaction( session -> { + final Tuple result = session.createQuery( + "select element(cat.owners).id from Cat cat group by element(cat.owners) order by element(cat.owners)", + Tuple.class + ).getSingleResult(); + assertThat( result.get( 0, Long.class ) ).isEqualTo( 1L ); + } ); + } + + @Test + public void testDistinctAndAggregates(SessionFactoryScope scope) { + // explicit join distinct + scope.inTransaction( session -> { + final Tuple result = session.createQuery( + "select distinct owner.id from Cat cat join cat.owners owner group by owner.id order by owner.id", + Tuple.class + ).getSingleResult(); + assertThat( result.get( 0, Long.class ) ).isEqualTo( 1L ); + } ); + // explicit join distinct + aggregate + scope.inTransaction( session -> { + final Tuple result = session.createQuery( + "select distinct min(owner.id), cat.id from Cat cat join cat.owners owner group by cat.id order by min(owner.id), cat.id", + Tuple.class + ).getSingleResult(); + assertThat( result.get( 0, Long.class ) ).isEqualTo( 1L ); + } ); + // implicit join distinct + scope.inTransaction( session -> { + final Tuple result = session.createQuery( + "select distinct element(cat.owners).id from Cat cat group by element(cat.owners).id order by element(cat.owners).id", + Tuple.class + ).getSingleResult(); + assertThat( result.get( 0, Long.class ) ).isEqualTo( 1L ); + } ); + // implicit join distinct + aggregate + scope.inTransaction( session -> { + final Tuple result = session.createQuery( + "select distinct min(element(cat.owners).id), cat.id from Cat cat group by cat.id order by min(element(cat.owners).id), cat.id", + Tuple.class + ).getSingleResult(); + assertThat( result.get( 0, Long.class ) ).isEqualTo( 1L ); + } ); + } + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Cat cat = new Cat(); + final Person owner = new Person( 1L, "Marco" ); + cat.addToOwners( owner ); + session.persist( owner ); + session.persist( cat ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from Cat" ).executeUpdate(); + session.createMutationQuery( "delete from Person" ).executeUpdate(); + } ); + } + + @Entity( name = "Person" ) + static class Person { + @Id + private Long id; + + private String name; + + public Person() { + } + + public Person(Long id, String name) { + this.id = id; + this.name = name; + } + + @ManyToMany( mappedBy = "owners" ) + protected Set ownedCats = new HashSet<>(); + + public Long getId() { + return id; + } + } + + @Entity( name = "Cat" ) + static class Cat { + @Id + @GeneratedValue + protected Long id; + + @JoinTable + @ManyToMany + private Set owners = new HashSet(); + + public Set getOwners() { + return this.owners; + } + + public void addToOwners(Person person) { + owners.add( person ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/onetoone/bidirectional/EntityWithBidirectionalAssociationsOneOfWhichIsAJoinTableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/onetoone/bidirectional/EntityWithBidirectionalAssociationsOneOfWhichIsAJoinTableTest.java index 0a780c481392..56f23af3ccba 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/onetoone/bidirectional/EntityWithBidirectionalAssociationsOneOfWhichIsAJoinTableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/onetoone/bidirectional/EntityWithBidirectionalAssociationsOneOfWhichIsAJoinTableTest.java @@ -152,8 +152,9 @@ public void testHqlSelectSon(SessionFactoryScope scope) { .getSingleResult(); statementInspector.assertExecutedCount( 2 ); - // The join to the target table PARENT for Male#parent is added since it's explicitly joined in HQL - statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 2 ); + // The join to the target table PARENT for Male#parent is avoided, + // because the FK in the collection table is not-null and data from the target table is not needed + statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 1 ); statementInspector.assertNumberOfOccurrenceInQuery( 1, "join", 3 ); assertThat( son.getParent(), CoreMatchers.notNullValue() ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/type/descriptor/java/JdbcDateJavaTypeDescriptorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/type/descriptor/java/JdbcDateJavaTypeDescriptorTest.java index 5045f408d002..de72436d39ae 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/type/descriptor/java/JdbcDateJavaTypeDescriptorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/type/descriptor/java/JdbcDateJavaTypeDescriptorTest.java @@ -1,10 +1,13 @@ package org.hibernate.orm.test.type.descriptor.java; import java.sql.Date; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import org.hibernate.type.descriptor.java.JdbcDateJavaType; import org.hibernate.testing.orm.junit.BaseUnitTest; +import org.hibernate.testing.orm.junit.JiraKey; import org.junit.jupiter.api.Test; import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; @@ -32,6 +35,7 @@ public void testIsInstance() { } @Test + @JiraKey("HHH-18036") public void testWrap() { final JdbcDateJavaType javaType = JdbcDateJavaType.INSTANCE; @@ -42,5 +46,18 @@ public void testWrap() { final java.util.Date utilDate = new java.util.Date( 0 ); final java.util.Date wrappedUtilDate = javaType.wrap( utilDate, null ); assertThat( wrappedUtilDate ).isInstanceOf( java.sql.Date.class ); + + final java.util.Date utilDateWithTime = java.util.Date.from( ZonedDateTime.of( + 2000, + 1, + 1, + 12, + 0, + 0, + 0, + ZoneOffset.UTC + ).toInstant() ); + final java.util.Date wrappedUtilDateWithTime = javaType.wrap( utilDateWithTime, null ); + assertThat( wrappedUtilDateWithTime ).isEqualTo( java.sql.Date.valueOf( "2000-01-01" ) ); } }