From 0f0ed4fc6e5c16de89a4b216a3d2e89e26dd1fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 26 May 2025 15:54:47 +0200 Subject: [PATCH 001/168] Clearer stage names in release process --- ci/release/Jenkinsfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index 91fb48d0060d..f878147809c2 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -97,7 +97,7 @@ pipeline { ) } stages { - stage('Release check') { + stage('Check') { steps { script { print "INFO: params.RELEASE_VERSION = ${params.RELEASE_VERSION}" @@ -176,7 +176,7 @@ pipeline { } } } - stage('Release prepare') { + stage('Prepare') { steps { script { checkoutReleaseScripts() @@ -202,7 +202,7 @@ pipeline { } } } - stage('Publish release') { + stage('Publish') { steps { script { checkoutReleaseScripts() @@ -242,7 +242,7 @@ pipeline { } } } - stage('Website release') { + stage('Update website') { steps { script { checkoutReleaseScripts() @@ -269,7 +269,7 @@ pipeline { } } } - stage('GitHub release') { + stage('Create GitHub release') { steps { script { checkoutReleaseScripts() From 88730ef22f2ff8c03c113c0a3858a67ef1ea58b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 26 May 2025 17:45:11 +0200 Subject: [PATCH 002/168] Use a script to release on Jira --- ci/release/Jenkinsfile | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index f878147809c2..78a14cd1e640 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -242,6 +242,17 @@ pipeline { } } } + stage('Release on Jira') { + steps { + script { + checkoutReleaseScripts() + + withCredentials([string(credentialsId: 'release-webhook.hibernate.atlassian.net', variable: 'JIRA_WEBHOOK_SECRET')]) { + sh ".release/scripts/jira-release.sh ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION}" + } + } + } + } stage('Update website') { steps { script { From 6a186214c98d953fa1146116c11b1501622c4761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 28 May 2025 12:19:18 +0200 Subject: [PATCH 003/168] Avoid auto-release when there are no "releasable" commits The determination of "releasable" is in the release scripts, but currently it boils down to having a Jira key in the commit message. --- ci/release/Jenkinsfile | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index 78a14cd1e640..0b1dad76412d 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -115,13 +115,6 @@ pipeline { def releaseVersion def developmentVersion - def lastCommitter = sh(script: 'git show -s --format=\'%an\'', returnStdout: true).trim() - def secondLastCommitter = sh(script: 'git show -s --format=\'%an\' HEAD~1', returnStdout: true).trim() - def isCiLastCommiter = lastCommitter == 'Hibernate-CI' && secondLastCommitter == 'Hibernate-CI' - - echo "Last two commits were performed by '${lastCommitter}'/'${secondLastCommitter}'." - echo "Is 'Hibernate-CI' the last commiter: '${isCiLastCommiter}'." - if ( manualRelease ) { echo "Release was requested manually" @@ -139,10 +132,13 @@ pipeline { else { echo "Release was triggered automatically" - // Avoid doing an automatic release for commits from a release - - if (isCiLastCommiter) { - print "INFO: Automatic release skipped because last commits were for the previous release" + // Avoid doing an automatic release if there are no "releasable" commits since the last release (see release scripts for determination) + def releasableCommitCount = sh( + script: ".release/scripts/count-releasable-commits.sh ${env.PROJECT}", + returnStdout: true + ).trim().toInteger() + if ( releasableCommitCount <= 0 ) { + print "INFO: Automatic release skipped because no releasable commits were pushed since the previous release" currentBuild.getRawBuild().getExecutor().interrupt(Result.NOT_BUILT) sleep(1) // Interrupt is not blocking and does not take effect immediately. return From 796d6dd95fe35bc37537edfbd2791ca07fff9ddd Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Wed, 4 Jun 2025 09:23:45 +0200 Subject: [PATCH 004/168] Enable GH actions on 7.0 branch --- .github/workflows/ci.yml | 4 ++-- .github/workflows/codeql.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 762469a85469..bc4c4726c661 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,10 +3,10 @@ name: GH Actions CI on: push: branches: - - 'main' + - '7.0' pull_request: branches: - - 'main' + - '7.0' permissions: { } # none diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 964aeafe7eeb..38da27a214e0 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,10 +2,10 @@ name: "CodeQL" on: push: - branches: [ 'main' ] + branches: [ '7.0' ] pull_request: # The branches below must be a subset of the branches above - branches: [ 'main' ] + branches: [ '7.0' ] schedule: - cron: '34 11 * * 4' From c028d0d5327df0c2546fa99c9929f79fa5be6917 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Mon, 19 May 2025 23:25:59 +0000 Subject: [PATCH 005/168] Post-steps for release : `7.0.0.Final` (cherry picked from commit 6ebfb9f69ed3a6ceb71f4465ed3cd9753b4882f1) --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 4c241f1ad969..36545aa38b6d 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=7.0.0.Final \ No newline at end of file +hibernateVersion=7.0.1-SNAPSHOT \ No newline at end of file From 82334edd980b6ee30b74af250641d78ae39c3f96 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Tue, 20 May 2025 08:46:16 +0200 Subject: [PATCH 006/168] Update ORM version in the changelog (cherry picked from commit 28bcb12e5de97bab441d7494bf443a1d62d4b520) --- changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 24a7e4e2f678..b47823ee7033 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,4 @@ -Hibernate 6 Changelog +Hibernate 7 Changelog ======================= Note: Please refer to JIRA to learn more about each issue. From 46456181ef0732f7e7443682b07b663a578f5844 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Thu, 29 May 2025 00:42:00 +0200 Subject: [PATCH 007/168] HHH-18252 fix handling of @IdClass in repository methods --- .../test/data/basic/BookAuthorRepository.java | 2 +- .../data/idclass/CompositeIdClassTest.java | 3 + .../test/data/idclass/YourRepository.java | 22 ++++++ .../test/data/superdao/generic/SuperRepo.java | 6 +- .../annotation/AbstractAnnotatedMethod.java | 11 ++- .../annotation/AbstractCriteriaMethod.java | 28 +++++--- .../annotation/AbstractFinderMethod.java | 28 +++++--- .../annotation/AbstractQueryMethod.java | 7 +- .../annotation/AnnotationMetaEntity.java | 68 +++++++++++++------ .../processor/annotation/IdFinderMethod.java | 6 +- 10 files changed, 130 insertions(+), 51 deletions(-) create mode 100644 tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/idclass/YourRepository.java diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/basic/BookAuthorRepository.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/basic/BookAuthorRepository.java index 0ae5c6b19eb7..13df42f16602 100644 --- a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/basic/BookAuthorRepository.java +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/basic/BookAuthorRepository.java @@ -56,7 +56,7 @@ public interface BookAuthorRepository { Book book(String isbn); @Find - Optional bookMaybe(@By("#id") String id); + Optional bookMaybe(@By("id(this)") String id); @Find Book[] books(@By("isbn") String[] isbns); diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/idclass/CompositeIdClassTest.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/idclass/CompositeIdClassTest.java index 1b9f7f1da548..9d1d7f42a355 100644 --- a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/idclass/CompositeIdClassTest.java +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/idclass/CompositeIdClassTest.java @@ -17,15 +17,18 @@ public class CompositeIdClassTest extends CompilationTest { @Test @WithClasses({ MyRepository.class, + YourRepository.class, MyEntity.class, }) public void test() { System.out.println( getMetaModelSourceAsString( MyEntity.class ) ); System.out.println( getMetaModelSourceAsString( MyEntity.class, true ) ); System.out.println( getMetaModelSourceAsString( MyRepository.class ) ); + System.out.println( getMetaModelSourceAsString( YourRepository.class ) ); assertMetamodelClassGeneratedFor( MyEntity.class ); assertMetamodelClassGeneratedFor( MyEntity.class, true ); assertMetamodelClassGeneratedFor( MyRepository.class ); + assertMetamodelClassGeneratedFor( YourRepository.class ); assertPresenceOfMethodInMetamodelFor( MyRepository.class, "findById", diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/idclass/YourRepository.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/idclass/YourRepository.java new file mode 100644 index 000000000000..14a4a2a2bce9 --- /dev/null +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/idclass/YourRepository.java @@ -0,0 +1,22 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.processor.test.data.idclass; + +import jakarta.data.repository.BasicRepository; +import jakarta.data.repository.By; +import jakarta.data.repository.Find; +import jakarta.data.repository.Repository; + +import java.util.List; + +@Repository +public interface YourRepository + extends BasicRepository { + @Find + List findThem(@By("id(this)") MyEntity.MyEntityId id); + @Find + MyEntity findIt(@By("id(this)") MyEntity.MyEntityId id); + +} diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/superdao/generic/SuperRepo.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/superdao/generic/SuperRepo.java index 765008be8513..7fbae5bdb3b6 100644 --- a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/superdao/generic/SuperRepo.java +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/superdao/generic/SuperRepo.java @@ -26,7 +26,7 @@ public interface SuperRepo { List saveAll(List entities); @Find - Optional findById(@By("#id") K id); + Optional findById(@By("id(this)") K id); @Find Optional findById2(@By("id(this)") K id); @@ -37,8 +37,8 @@ public interface SuperRepo { @Find Page findAll(PageRequest pageRequest, Order order); -// @Delete -// void deleteById(@By("#id") K id); +// @Delete +// void deleteById(@By("id(this)") K id); @Delete void delete(T entity); diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractAnnotatedMethod.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractAnnotatedMethod.java index 2ce1e5d1f146..943a710f5127 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractAnnotatedMethod.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractAnnotatedMethod.java @@ -13,6 +13,7 @@ import static java.util.Collections.emptyList; import static java.util.stream.Collectors.toList; +import static org.hibernate.metamodel.mapping.EntityIdentifierMapping.ID_ROLE_NAME; import static org.hibernate.processor.util.Constants.ENTITY_MANAGER; import static org.hibernate.processor.util.Constants.OBJECTS; import static org.hibernate.processor.util.TypeUtils.hasAnnotation; @@ -80,12 +81,18 @@ void nullCheck(StringBuilder declaration, String paramName) { .append('\t') .append(annotationMetaEntity.staticImport(OBJECTS, "requireNonNull")) .append('(') - .append(paramName.replace('.', '$')) + .append(parameterName(paramName)) .append(", \"Null ") - .append(paramName) + .append(ID_ROLE_NAME.equals(paramName) ? "id" : paramName) .append("\");\n"); } + static String parameterName(String name) { + return name.equals(ID_ROLE_NAME) + ? "id" + : name.replace('.', '$'); + } + protected void handle(StringBuilder declaration, String handled, String rethrown) { if ( isReactive() ) { declaration.append( "\n\t\t\t.onFailure(" ) diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractCriteriaMethod.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractCriteriaMethod.java index 9e89859b1188..0ec5ee60ba3b 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractCriteriaMethod.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractCriteriaMethod.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.StringTokenizer; +import static org.hibernate.metamodel.mapping.EntityIdentifierMapping.ID_ROLE_NAME; import static org.hibernate.processor.util.TypeUtils.getGeneratedClassFullyQualifiedName; import static org.hibernate.processor.util.TypeUtils.isPrimitive; @@ -179,7 +180,7 @@ void where(StringBuilder declaration, List paramTypes) { private void condition(StringBuilder declaration, int i, String paramName, String paramType) { declaration .append("\n\t\t\t"); - final String parameterName = paramName.replace('.', '$'); + final String parameterName = parameterName(paramName); if ( isNullable(i) && !isPrimitive(paramType) ) { declaration .append(parameterName) @@ -226,15 +227,26 @@ private void path(StringBuilder declaration, String paramName) { final StringTokenizer tokens = new StringTokenizer(paramName, "."); String typeName = entity; while ( typeName != null && tokens.hasMoreTokens() ) { - final TypeElement typeElement = annotationMetaEntity.getContext().getElementUtils() - .getTypeElement( typeName ); + final TypeElement typeElement = + annotationMetaEntity.getContext().getElementUtils() + .getTypeElement( typeName ); final String memberName = tokens.nextToken(); declaration - .append(".get(") - .append( annotationMetaEntity.importType( getGeneratedClassFullyQualifiedName( typeElement, false ) ) ) - .append('.') - .append(memberName) - .append(')'); + .append( ".get(" ); + if ( ID_ROLE_NAME.equals(memberName) ) { + declaration + .append( '"' ) + .append( memberName ) + .append( '"' ); + } + else { + declaration + .append( annotationMetaEntity.importType( + getGeneratedClassFullyQualifiedName( typeElement, false ) ) ) + .append( '.' ) + .append( memberName ); + } + declaration.append( ')' ); typeName = annotationMetaEntity.getMemberType(typeName, memberName); } } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractFinderMethod.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractFinderMethod.java index 27df3324c7e6..78839411351f 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractFinderMethod.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractFinderMethod.java @@ -9,6 +9,7 @@ import javax.lang.model.element.ExecutableElement; import java.util.List; +import static org.hibernate.metamodel.mapping.EntityIdentifierMapping.ID_ROLE_NAME; import static org.hibernate.processor.util.Constants.HIB_SESSION; /** @@ -73,8 +74,9 @@ public String getAttributeNameDeclarationString() { void comment(StringBuilder declaration) { declaration .append("\n/**") - .append("\n * Find ") - .append("{@link ") + .append("\n * ") + .append(this instanceof CriteriaDeleteMethod ? "Delete" : "Find") + .append(" {@link ") .append(annotationMetaEntity.importType(entity)) .append("}"); long paramCount = paramTypes.stream() @@ -99,14 +101,20 @@ void comment(StringBuilder declaration) { } count++; final String path = paramNames.get(i); - declaration - .append("{@link ") - .append(annotationMetaEntity.importType(entity)) - .append('#') - .append(qualifier(path)) - .append(' ') - .append(path) - .append("}"); + if ( ID_ROLE_NAME.equals(path) ) { + declaration + .append("identifier"); + } + else { + declaration + .append("{@link ") + .append(annotationMetaEntity.importType(entity)) + .append('#') + .append(qualifier(path)) + .append(' ') + .append(path) + .append("}"); + } } } } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractQueryMethod.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractQueryMethod.java index 7ba9afd3ba40..54b438071e99 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractQueryMethod.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractQueryMethod.java @@ -170,7 +170,7 @@ void parameters(List paramTypes, StringBuilder declaration) { declaration .append(annotationMetaEntity.importType(paramType)) .append(" ") - .append(paramNames.get(i).replace('.', '$')); + .append(parameterName(paramNames.get(i))); } declaration .append(")"); @@ -317,8 +317,9 @@ void handleRestrictionParameters( } } else if ( isRangeParam(paramType) && returnTypeName!= null ) { - final TypeElement entityElement = annotationMetaEntity.getContext().getElementUtils() - .getTypeElement( returnTypeName ); + final TypeElement entityElement = + annotationMetaEntity.getContext().getElementUtils() + .getTypeElement( returnTypeName ); declaration .append("\t_spec.restrict(") .append(annotationMetaEntity.importType(HIB_RESTRICTION)) diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java index 2534e3df0b9a..1a8767dd6cd9 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java @@ -77,6 +77,7 @@ import static org.hibernate.grammars.hql.HqlLexer.WHERE; import static org.hibernate.internal.util.StringHelper.qualify; import static org.hibernate.internal.util.StringHelper.unqualify; +import static org.hibernate.metamodel.mapping.EntityIdentifierMapping.ID_ROLE_NAME; import static org.hibernate.processor.annotation.AbstractQueryMethod.isRangeParam; import static org.hibernate.processor.annotation.AbstractQueryMethod.isRestrictionParam; import static org.hibernate.processor.annotation.AbstractQueryMethod.isSessionParameter; @@ -2247,7 +2248,9 @@ enum FieldType { } private @Nullable FieldType validateFinderParameter(TypeElement entityType, VariableElement param) { - final Element member = memberMatchingPath( entityType, parameterName( param ) ); + final String path = parameterName( param ); + final boolean idClassRef = isIdRef( path ) && hasAnnotation( entityType, ID_CLASS ); + final Element member = idClassRef ? null : memberMatchingPath( entityType, path ); if ( member != null ) { if ( containsAnnotation( member, MANY_TO_MANY, ONE_TO_MANY, ELEMENT_COLLECTION ) ) { message( param, @@ -2278,25 +2281,47 @@ else if ( containsAnnotation( member, NATURAL_ID ) ) { return FieldType.BASIC; } } - else { + else if ( idClassRef ) { final AnnotationMirror idClass = getAnnotationMirror( entityType, ID_CLASS ); - if ( idClass != null ) { + if ( idClass == null ) { + return null; // cannot happen! + } + else { final AnnotationValue value = getAnnotationValue( idClass ); - if ( value != null ) { - if ( isSameType( param.asType(), (TypeMirror) value.getValue() ) ) { - return FieldType.ID; - } + if ( value != null + && isSameType( actualParameterType( param ), + (TypeMirror) value.getValue() ) ) { + return FieldType.ID; + } + else { + message( param, + "does not match id class of entity class '" + entityType + "'", + Diagnostic.Kind.ERROR ); + return null; } } - + } + else { message( param, - "no matching field named '" + parameterName( param ) - + "' in entity class '" + entityType + "'", + "no matching field named '" + path + + "' in entity class '" + entityType + "'", Diagnostic.Kind.ERROR ); return null; } } + private TypeMirror actualParameterType(VariableElement param) { + final ExecutableElement method = + (ExecutableElement) + param.getEnclosingElement(); + final ExecutableType methodType = + (ExecutableType) + context.getTypeUtils() + .asMemberOf( (DeclaredType) element.asType(), method ); + return methodType.getParameterTypes() + .get( method.getParameters().indexOf( param ) ); + } + /** * Check the type of a parameter of a {@code @Find} method against the field type * in the entity class. @@ -2423,8 +2448,7 @@ private static TypeMirror memberType(Element member) { } private static boolean isIdRef(String token) { - return "#id".equals(token) // for Jakarta Data M4 release - || "id(this)".equalsIgnoreCase(token); // post M4 + return "id(this)".equalsIgnoreCase(token); // post M4 } private @Nullable Element memberMatchingPath( @@ -3141,27 +3165,29 @@ private ExecutableType memberMethodType(ExecutableElement method) { private static List parameterPatterns(ExecutableElement method) { return method.getParameters().stream() .map(param -> hasAnnotation(param, PATTERN)) - .collect(toList()); + .toList(); } private List parameterNames(ExecutableElement method, TypeElement entity) { final String idName = - // account for special @By("#id") hack in Jakarta Data - entity.getEnclosedElements().stream() - .filter(member -> hasAnnotation(member, ID)) - .map(TypeUtils::propertyName) - .findFirst() - .orElse("id"); + hasAnnotation( entity, ID_CLASS ) + ? ID_ROLE_NAME + // account for special @By("id(this)") hack in Jakarta Data + : entity.getEnclosedElements().stream() + .filter(member -> hasAnnotation(member, ID)) + .map(TypeUtils::propertyName) + .findFirst() + .orElse(ID_ROLE_NAME); return method.getParameters().stream() .map(AnnotationMetaEntity::parameterName) .map(name -> isIdRef(name) ? idName : name) - .collect(toList()); + .toList(); } private static List parameterNames(ExecutableElement method) { return method.getParameters().stream() .map(AnnotationMetaEntity::parameterName) - .collect(toList()); + .toList(); } private static String parameterName(VariableElement parameter) { diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/IdFinderMethod.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/IdFinderMethod.java index a78c69fdd01b..4b347fcccb1d 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/IdFinderMethod.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/IdFinderMethod.java @@ -138,11 +138,11 @@ private void throwEmptyResult(StringBuilder declaration) { .append( "(\"No '" ) .append( annotationMetaEntity.importType( entity ) ) .append( "' for given id [\" + " ) - .append( paramName ) + .append( parameterName(paramName) ) .append( " + \"]\",\n\t\t\t\t\tnew " ) .append( annotationMetaEntity.importType( "org.hibernate.ObjectNotFoundException" ) ) .append( "((Object) " ) - .append( paramName ) + .append( parameterName(paramName) ) .append( ", \"" ) .append( entity ) .append( "\"))"); @@ -207,7 +207,7 @@ private void findWithNoFetchProfiles(StringBuilder declaration) { .append(isUsingStatelessSession() ? ".get(" : ".find(") .append(annotationMetaEntity.importType(entity)) .append(".class, ") - .append(paramName); + .append(parameterName(paramName)); if ( isReactiveSessionAccess() ) { declaration .append(')'); From c4fb731778aecb1c9011c5f58d50913973caaee9 Mon Sep 17 00:00:00 2001 From: Peter Bambazek Date: Sat, 17 May 2025 19:28:54 +0200 Subject: [PATCH 008/168] HHH-18813: Fix of generated Insert-Query in CteUpdateHandler --- .../internal/cte/CteUpdateHandler.java | 6 +- .../orm/test/secondarytable/HHH18813Test.java | 120 ++++++++++++++++++ 2 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/secondarytable/HHH18813Test.java diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java index 28c82a7b5384..749706d28ec4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java @@ -153,8 +153,8 @@ protected void addDmlCtes( tableExpression, true ); - final List assignmentList = assignmentsByTable.get( updatingTableReference ); - if ( assignmentList == null ) { + final List assignmentsForInsert = assignmentsByTable.get( updatingTableReference ); + if ( assignmentsForInsert == null ) { continue; } final String insertCteTableName = getInsertCteTableName( tableExpression ); @@ -229,7 +229,7 @@ protected void addDmlCtes( // Collect the target column references from the key expressions final List targetColumnReferences = new ArrayList<>( existsKeyColumns ); // And transform assignments to target column references and selections - for ( Assignment assignment : assignments ) { + for ( Assignment assignment : assignmentsForInsert ) { targetColumnReferences.addAll( assignment.getAssignable().getColumnReferences() ); querySpec.getSelectClause().addSqlSelection( new SqlSelectionImpl( diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/secondarytable/HHH18813Test.java b/hibernate-core/src/test/java/org/hibernate/orm/test/secondarytable/HHH18813Test.java new file mode 100644 index 000000000000..741bd8bf3199 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/secondarytable/HHH18813Test.java @@ -0,0 +1,120 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.secondarytable; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.SecondaryTable; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + + +/** + * Test for a Bugfix described in HHH-18813. + * The CteUpdateHandler generated an Insert-Query, + * which contained columns that do not exist in the target table. + * + * @author Peter Bambazek + */ +@JiraKey(value = "HHH-18813") +@DomainModel( + annotatedClasses = {HHH18813Test.SecondaryTableEntitySub.class, HHH18813Test.SecondaryTableEntityBase.class}) +@SessionFactory +class HHH18813Test { + + @Test + void hhh18813Test(SessionFactoryScope scope) { + + // prepare + scope.inTransaction( session -> { + SecondaryTableEntitySub entitySub = new SecondaryTableEntitySub(); + entitySub.setB( 111L ); + entitySub.setC( 222L ); + session.persist( entitySub ); + } ); + + // asset before + scope.inTransaction( session -> { + SecondaryTableEntitySub entitySub = session.createQuery( + "select s from SecondaryTableEntitySub s", + SecondaryTableEntitySub.class ).getSingleResult(); + assertNotNull( entitySub ); + assertEquals( 111L, entitySub.getB() ); + assertEquals( 222L, entitySub.getC() ); + } ); + + // update + scope.inTransaction( session -> { + session.createMutationQuery( "update SecondaryTableEntitySub e set e.b=:b, e.c=:c" ) + .setParameter( "b", 333L ) + .setParameter( "c", 444L ) + .executeUpdate(); + } ); + + // asset after + scope.inTransaction( session -> { + SecondaryTableEntitySub entitySub = session.createQuery( "select s from SecondaryTableEntitySub s", + SecondaryTableEntitySub.class ).getSingleResult(); + assertNotNull( entitySub ); + assertEquals( 333L, entitySub.getB() ); + assertEquals( 444L, entitySub.getC() ); + } ); + } + + @Entity(name = "SecondaryTableEntitySub") + @Inheritance(strategy = InheritanceType.JOINED) + @SecondaryTable(name = "test") + public static class SecondaryTableEntitySub extends SecondaryTableEntityBase { + + @Column + private Long b; + + @Column(table = "test") + private Long c; + + public Long getB() { + return b; + } + + public void setB(Long b) { + this.b = b; + } + + public Long getC() { + return c; + } + + public void setC(Long c) { + this.c = c; + } + } + + @Entity + @Inheritance(strategy = InheritanceType.JOINED) + public static class SecondaryTableEntityBase { + + @Id + @GeneratedValue + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + } +} From cf41508439e27b5afb7285bde5e85c889939473b Mon Sep 17 00:00:00 2001 From: Peter Bambazek Date: Sun, 1 Jun 2025 11:03:43 +0200 Subject: [PATCH 009/168] HHH-18876 ArrayIndexOutOfBoundsException in ArrayInitializer with ListIndexBase --- .../collection/internal/ArrayInitializer.java | 5 +- ...erColumnListIndexArrayInitializerTest.java | 110 ++++++++++++++++++ ...nListIndexHHH18771ListInitializerTest.java | 9 +- 3 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/OrderColumnListIndexArrayInitializerTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/ArrayInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/ArrayInitializer.java index 35841a504658..15ea69534c33 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/ArrayInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/ArrayInitializer.java @@ -130,10 +130,13 @@ protected void resolveInstanceSubInitializers(ImmediateCollectionInitializerData final Initializer initializer = elementAssembler.getInitializer(); if ( initializer != null ) { final RowProcessingState rowProcessingState = data.getRowProcessingState(); - final Integer index = listIndexAssembler.assemble( rowProcessingState ); + Integer index = listIndexAssembler.assemble( rowProcessingState ); if ( index != null ) { final PersistentArrayHolder arrayHolder = getCollectionInstance( data ); assert arrayHolder != null; + if ( indexBase != 0 ) { + index -= indexBase; + } initializer.resolveInstance( Array.get( arrayHolder.getArray(), index ), rowProcessingState ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/OrderColumnListIndexArrayInitializerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/OrderColumnListIndexArrayInitializerTest.java new file mode 100644 index 000000000000..c35b57f615aa --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/OrderColumnListIndexArrayInitializerTest.java @@ -0,0 +1,110 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.mapping.collections; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OrderColumn; +import org.hibernate.annotations.ListIndexBase; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@JiraKey("HHH-18876") +@DomainModel( + annotatedClasses = { + OrderColumnListIndexArrayInitializerTest.Person.class, + OrderColumnListIndexArrayInitializerTest.Phone.class, + } +) +@SessionFactory +class OrderColumnListIndexArrayInitializerTest { + + @Test + void hhh18876Test(SessionFactoryScope scope) { + + // prepare data + scope.inTransaction( session -> { + + // person + Person person = new Person(); + person.id = 1L; + session.persist( person ); + + // add phone + Phone phone = new Phone(); + phone.id = 1L; + phone.person = person; + + person.phones = new Phone[1]; + person.phones[0] = phone; + + // add children + Person children = new Person(); + children.id = 2L; + children.mother = person; + + person.children = new Person[1]; + person.children[0] = children; + } ); + + // load and assert + scope.inTransaction( session -> { + Person person = session.createSelectionQuery( "select p from Person p where id=1", Person.class ) + .getSingleResult(); + assertNotNull( person ); + assertEquals( 1, person.phones.length ); + assertNotNull( person.phones[0] ); + assertEquals( 1, person.children.length ); + assertEquals( person, person.children[0].mother ); + } ); + } + + @Entity(name = "Person") + public static class Person { + + @Id + Long id; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "person", cascade = CascadeType.ALL) + @OrderColumn(name = "order_id") + @ListIndexBase(100) + Phone[] phones; + + @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @JoinTable(name = "parent_child_relationships", joinColumns = @JoinColumn(name = "parent_id"), + inverseJoinColumns = @JoinColumn(name = "child_id")) + @OrderColumn(name = "pos") + @ListIndexBase(200) + Person[] children; + + @ManyToOne + @JoinColumn(name = "mother_id") + Person mother; + } + + @Entity(name = "Phone") + public static class Phone { + + @Id + Long id; + + @ManyToOne + Person person; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/OrderColumnListIndexHHH18771ListInitializerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/OrderColumnListIndexHHH18771ListInitializerTest.java index 41643297c747..bfadaf20077f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/OrderColumnListIndexHHH18771ListInitializerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/OrderColumnListIndexHHH18771ListInitializerTest.java @@ -26,6 +26,8 @@ import jakarta.persistence.OrderColumn; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * @author Selaron @@ -50,7 +52,12 @@ public void testLifecycle() { person.getChildren().get( 0 ).setMother( person ); } ); doInJPA( this::entityManagerFactory, entityManager -> { - entityManager.find( Person.class, 1L ); + Person person = entityManager.find( Person.class, 1L ); + assertNotNull( person ); + assertEquals( 1, person.getPhones().size() ); + assertNotNull( person.getPhones().get( 0 ) ); + assertEquals( 1, person.getChildren().size() ); + assertEquals( person, person.getChildren().get( 0 ).getMother() ); } ); } From 508302628ba6d61b02975735e65cc7ec1eb2c118 Mon Sep 17 00:00:00 2001 From: Peter Bambazek Date: Tue, 27 May 2025 17:00:49 +0200 Subject: [PATCH 010/168] HHH-18891 fix of an AssertionError when using a NotFound annotation --- .../internal/EmbeddableAssembler.java | 3 + .../CompositeForeignKeyNotFoundTest.java | 144 ++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/notfound/CompositeForeignKeyNotFoundTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableAssembler.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableAssembler.java index 104f2892a053..b3859c877b29 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableAssembler.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableAssembler.java @@ -32,6 +32,9 @@ public JavaType getAssembledJavaType() { public Object assemble(RowProcessingState rowProcessingState) { final InitializerData data = initializer.getData( rowProcessingState ); final Initializer.State state = data.getState(); + if ( state == Initializer.State.UNINITIALIZED ) { + initializer.resolveKey( data ); + } if ( state == Initializer.State.KEY_RESOLVED ) { initializer.resolveInstance( data ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/CompositeForeignKeyNotFoundTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/CompositeForeignKeyNotFoundTest.java new file mode 100644 index 000000000000..27fe7a028481 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/CompositeForeignKeyNotFoundTest.java @@ -0,0 +1,144 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.notfound; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import org.hibernate.FetchNotFoundException; +import org.hibernate.annotations.JoinColumnOrFormula; +import org.hibernate.annotations.JoinFormula; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.query.Query; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@JiraKey(value = "HHH-18891") +@DomainModel( + annotatedClasses = {CompositeForeignKeyNotFoundTest.Document.class, CompositeForeignKeyNotFoundTest.DocumentIgnore.class, + CompositeForeignKeyNotFoundTest.DocumentException.class, CompositeForeignKeyNotFoundTest.Person.class}) +@SessionFactory +public class CompositeForeignKeyNotFoundTest { + + @Test + void hhh18891TestWithNotFoundIgnore(SessionFactoryScope scope) { + + // prepare document + scope.inTransaction( session -> { + Query nativeQuery = session.createNativeQuery( + "insert into DocumentIgnore (id,owner) values (123,42)" ); + nativeQuery.executeUpdate(); + } ); + + // assert document + scope.inTransaction( session -> { + final DocumentIgnore document = session.find( DocumentIgnore.class, 123 ); + assertNotNull( document ); + assertEquals( 123, document.id ); + assertNull( document.owner ); + } ); + } + + @Test + void hhh18891TestWithNotFoundException(SessionFactoryScope scope) { + + // prepare document + scope.inTransaction( session -> { + Query nativeQuery = session.createNativeQuery( + "insert into DocumentException (id,owner) values (123,42)" ); + nativeQuery.executeUpdate(); + } ); + + // assert document + scope.inTransaction( session -> { + assertThrows( FetchNotFoundException.class, () -> + session.find( DocumentException.class, 123 ) ); + } ); + } + + @Test + void hhh18891TestWithoutNotFoundAnnotation(SessionFactoryScope scope) { + + // prepare document + scope.inTransaction( session -> { + Query nativeQuery = session.createNativeQuery( + "insert into Document (id,owner) values (123,42)" ); + nativeQuery.executeUpdate(); + } ); + + // assert document + scope.inTransaction( session -> { + final Document document = session.find( Document.class, 123 ); + assertNotNull( document ); + assertEquals( 123, document.id ); + assertNull( document.owner ); + } ); + } + + @Entity(name = "DocumentIgnore") + public static class DocumentIgnore { + + @Id + @GeneratedValue + Long id; + + @ManyToOne + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumnOrFormula(column = @JoinColumn(name = "owner", referencedColumnName = "id", insertable = false, + updatable = false)) + @JoinColumnOrFormula(formula = @JoinFormula(value = "'fubar'", referencedColumnName = "name")) + Person owner; + } + + @Entity(name = "DocumentException") + public static class DocumentException { + + @Id + @GeneratedValue + Long id; + + @ManyToOne + @NotFound(action = NotFoundAction.EXCEPTION) + @JoinColumnOrFormula(column = @JoinColumn(name = "owner", referencedColumnName = "id", insertable = false, + updatable = false)) + @JoinColumnOrFormula(formula = @JoinFormula(value = "'fubar'", referencedColumnName = "name")) + Person owner; + } + + @Entity(name = "Document") + public static class Document { + + @Id + @GeneratedValue + Long id; + + @ManyToOne + @JoinColumnOrFormula(column = @JoinColumn(name = "owner", referencedColumnName = "id", insertable = false, + updatable = false)) + @JoinColumnOrFormula(formula = @JoinFormula(value = "'fubar'", referencedColumnName = "name")) + Person owner; + } + + @Entity(name = "Person") + public static class Person { + + @Id + Long id; + + String name; + } + +} From 20bb837615eeef322377f67ff892ff66770a52ba Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Wed, 21 May 2025 12:02:52 +0200 Subject: [PATCH 011/168] HHH-19369 Test and fix CCE of AccessOptimizer foreign package bridge (cherry picked from commit 223610353e95d85c0d54295502b71b253032fb6e) --- .../bytebuddy/BytecodeProviderImpl.java | 3 +- .../ForeignPackageSuperclassAccessorTest.java | 29 ++++++++++++++++ .../orm/test/bytecode/SuperclassEntity.java | 34 +++++++++++++++++++ .../foreignpackage/ConcreteEntity.java | 33 ++++++++++++++++++ 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/ForeignPackageSuperclassAccessorTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/SuperclassEntity.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/foreignpackage/ConcreteEntity.java diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java index 6fc6de8e3af5..bfc7d9ac92b8 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java @@ -75,6 +75,7 @@ public class BytecodeProviderImpl implements BytecodeProvider { private static final String INSTANTIATOR_PROXY_NAMING_SUFFIX = "HibernateInstantiator"; private static final String OPTIMIZER_PROXY_NAMING_SUFFIX = "HibernateAccessOptimizer"; + private static final String OPTIMIZER_PROXY_BRIDGE_NAMING_SUFFIX = "HibernateAccessOptimizerBridge"; private static final ElementMatcher.Junction newInstanceMethodName = ElementMatchers.named( "newInstance" ); private static final ElementMatcher.Junction getPropertyValuesMethodName = ElementMatchers.named( @@ -328,7 +329,7 @@ private Class determineAccessOptimizerSuperClass(Class clazz, String[] pro final ForeignPackageClassInfo foreignPackageClassInfo = foreignPackageClassInfos.get( i ); final Class newSuperClass = superClass; - final String className = foreignPackageClassInfo.clazz.getName() + "$" + OPTIMIZER_PROXY_NAMING_SUFFIX + encodeName( foreignPackageClassInfo.propertyNames, foreignPackageClassInfo.getters, foreignPackageClassInfo.setters ); + final String className = foreignPackageClassInfo.clazz.getName() + "$" + OPTIMIZER_PROXY_BRIDGE_NAMING_SUFFIX + encodeName( foreignPackageClassInfo.propertyNames, foreignPackageClassInfo.getters, foreignPackageClassInfo.setters ); superClass = byteBuddyState.load( foreignPackageClassInfo.clazz, className, diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/ForeignPackageSuperclassAccessorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/ForeignPackageSuperclassAccessorTest.java new file mode 100644 index 000000000000..a2fae4213692 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/ForeignPackageSuperclassAccessorTest.java @@ -0,0 +1,29 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode; + +import org.hibernate.orm.test.bytecode.foreignpackage.ConcreteEntity; + +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.Test; + +@SessionFactory +@DomainModel(annotatedClasses = { + ConcreteEntity.class, + SuperclassEntity.class +}) +@Jira("https://hibernate.atlassian.net/browse/HHH-19369") +public class ForeignPackageSuperclassAccessorTest { + + @Test + public void test(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.find( SuperclassEntity.class, 1L ); + } ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/SuperclassEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/SuperclassEntity.java new file mode 100644 index 000000000000..c77a09e92b46 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/SuperclassEntity.java @@ -0,0 +1,34 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; + +@Entity +@Inheritance(strategy = InheritanceType.JOINED) +public class SuperclassEntity { + @Id + protected long id; + protected 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/bytecode/foreignpackage/ConcreteEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/foreignpackage/ConcreteEntity.java new file mode 100644 index 000000000000..977c9b4529df --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/foreignpackage/ConcreteEntity.java @@ -0,0 +1,33 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.foreignpackage; + +import org.hibernate.orm.test.bytecode.SuperclassEntity; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity +public class ConcreteEntity extends SuperclassEntity { + @Id + protected long id; + protected String bname; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getBname() { + return bname; + } + + public void setBname(String bname) { + this.bname = bname; + } +} From 6454eb2feb7d48d4b2c0f9fdde398089726a857f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kuruc?= Date: Sat, 19 Apr 2025 16:58:09 +0200 Subject: [PATCH 012/168] HHH-19372: Reproducing test (cherry picked from commit 62ca0fcb771c3f9c7b7c0784ed54750cb891b365) --- .../HierarchyBytecodeOptimizerTest.java | 49 +++++++++++++++++++ .../enhancement/optimizer/ParentEntity.java | 35 +++++++++++++ .../optimizer/child/ChildEntity.java | 22 +++++++++ 3 files changed, 106 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/HierarchyBytecodeOptimizerTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ParentEntity.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/child/ChildEntity.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/HierarchyBytecodeOptimizerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/HierarchyBytecodeOptimizerTest.java new file mode 100644 index 000000000000..65b8f1ce8a9b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/HierarchyBytecodeOptimizerTest.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.optimizer; + +import org.hibernate.orm.test.bytecode.enhancement.optimizer.child.ChildEntity; +import org.hibernate.query.Query; + +import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced; +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.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@DomainModel(annotatedClasses = { + ParentEntity.class, + ChildEntity.class, +}) +@SessionFactory +@Jira( "https://hibernate.atlassian.net/browse/HHH-19059" ) +@BytecodeEnhanced +public class HierarchyBytecodeOptimizerTest { + + @Test + public void testOptimizerSetPropertyValues(SessionFactoryScope scope) { + ChildEntity childEntity = new ChildEntity(); + childEntity.setId( 1L ); + childEntity.setField( "field" ); + childEntity.setChieldField( "childField" ); + + scope.inTransaction( session -> session.persist( childEntity ) ); + + scope.inTransaction( session -> { + Query query = session.createQuery( "select c from ChildEntity c where c.field = :field", ChildEntity.class); + query.setParameter( "field", "field" ); + assertThat( query.uniqueResult() ).isNotNull(); + } ); + } + + @AfterAll + public void cleanup(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncateMappedObjects(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ParentEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ParentEntity.java new file mode 100644 index 000000000000..84a7f72690f3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ParentEntity.java @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.optimizer; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; + +@Entity(name = "ParentEntity") +@Inheritance(strategy = InheritanceType.JOINED) +public class ParentEntity { + @Id + private Long id; + + private String field; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getField() { + return field; + } + + public void setField(String field) { + this.field = field; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/child/ChildEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/child/ChildEntity.java new file mode 100644 index 000000000000..d189fad30c9b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/child/ChildEntity.java @@ -0,0 +1,22 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.optimizer.child; + +import org.hibernate.orm.test.bytecode.enhancement.optimizer.ParentEntity; + +import jakarta.persistence.Entity; + +@Entity(name = "ChildEntity") +public class ChildEntity extends ParentEntity { + private String childField; + + public String getChildField() { + return childField; + } + + public void setChieldField(String childField) { + this.childField = childField; + } +} From 17210f26c1bc54c077da55cea26062be84153032 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 23 Apr 2025 15:12:19 +0200 Subject: [PATCH 013/168] HHH-19369 Add test for issue (cherry picked from commit 4d75316d00537ea7b647484850d0b1332b6e1120) --- .../enhancement/optimizer/AncestorEntity.java | 35 ++++++ .../enhancement/optimizer/ChildEntity3.java | 20 +++ .../enhancement/optimizer/ChildEntity4.java | 20 +++ .../enhancement/optimizer/ChildEntity5.java | 20 +++ .../enhancement/optimizer/ChildEntity6.java | 20 +++ .../enhancement/optimizer/ChildEntity7.java | 20 +++ .../enhancement/optimizer/ChildEntity8.java | 20 +++ .../enhancement/optimizer/ChildEntity9.java | 20 +++ ...BytecodeOptimizerMethodVisibilityTest.java | 68 +++++++++++ ...ierarchyBytecodeOptimizerOrderingTest.java | 115 ++++++++++++++++++ .../HierarchyBytecodeOptimizerTest.java | 11 +- .../enhancement/optimizer/ParentEntity.java | 3 +- .../optimizer/child/ChildEntity10.java | 21 ++++ .../optimizer/parent/Ancestor.java | 35 ++++++ .../optimizer/parent/ChildEntity2.java | 21 ++++ 15 files changed, 444 insertions(+), 5 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/AncestorEntity.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity3.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity4.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity5.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity6.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity7.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity8.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity9.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/HierarchyBytecodeOptimizerMethodVisibilityTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/HierarchyBytecodeOptimizerOrderingTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/child/ChildEntity10.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/parent/Ancestor.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/parent/ChildEntity2.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/AncestorEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/AncestorEntity.java new file mode 100644 index 000000000000..ab7cdffb8a4d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/AncestorEntity.java @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.optimizer; + +import jakarta.persistence.Entity; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import org.hibernate.orm.test.bytecode.enhancement.optimizer.parent.Ancestor; + +@Entity(name = "AncestorEntity") +@Inheritance(strategy = InheritanceType.JOINED) +public class AncestorEntity extends Ancestor { + + private Long id; + + private String field; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getField() { + return field; + } + + public void setField(String field) { + this.field = field; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity3.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity3.java new file mode 100644 index 000000000000..100191b43b98 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity3.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.optimizer; + +import jakarta.persistence.Entity; + +@Entity(name = "ChildEntity3") +public class ChildEntity3 extends AncestorEntity { + private String childField; + + public String getChildField() { + return childField; + } + + public void setChieldField(String childField) { + this.childField = childField; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity4.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity4.java new file mode 100644 index 000000000000..a8ed90f3ef5b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity4.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.optimizer; + +import jakarta.persistence.Entity; + +@Entity(name = "ChildEntity4") +public class ChildEntity4 extends AncestorEntity { + private String childField; + + public String getChildField() { + return childField; + } + + public void setChieldField(String childField) { + this.childField = childField; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity5.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity5.java new file mode 100644 index 000000000000..ba7540fec1fa --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity5.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.optimizer; + +import jakarta.persistence.Entity; + +@Entity(name = "ChildEntity5") +public class ChildEntity5 extends AncestorEntity { + private String childField; + + public String getChildField() { + return childField; + } + + public void setChieldField(String childField) { + this.childField = childField; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity6.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity6.java new file mode 100644 index 000000000000..a996eb9089ed --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity6.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.optimizer; + +import jakarta.persistence.Entity; + +@Entity(name = "ChildEntity6") +public class ChildEntity6 extends AncestorEntity { + private String childField; + + public String getChildField() { + return childField; + } + + public void setChieldField(String childField) { + this.childField = childField; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity7.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity7.java new file mode 100644 index 000000000000..9a9775a53cd3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity7.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.optimizer; + +import jakarta.persistence.Entity; + +@Entity(name = "ChildEntity7") +public class ChildEntity7 extends AncestorEntity { + private String childField; + + public String getChildField() { + return childField; + } + + public void setChildField(String childField) { + this.childField = childField; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity8.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity8.java new file mode 100644 index 000000000000..f6bdd29a2a29 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity8.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.optimizer; + +import jakarta.persistence.Entity; + +@Entity(name = "ChildEntity8") +public class ChildEntity8 extends AncestorEntity { + private String childField; + + public String getChildField() { + return childField; + } + + public void setChildField(String childField) { + this.childField = childField; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity9.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity9.java new file mode 100644 index 000000000000..b734abdd91ee --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ChildEntity9.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.optimizer; + +import jakarta.persistence.Entity; + +@Entity(name = "ChildEntity9") +public class ChildEntity9 extends AncestorEntity { + private String childField; + + public String getChildField() { + return childField; + } + + public void setChildField(String childField) { + this.childField = childField; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/HierarchyBytecodeOptimizerMethodVisibilityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/HierarchyBytecodeOptimizerMethodVisibilityTest.java new file mode 100644 index 000000000000..e6e9dd57eed4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/HierarchyBytecodeOptimizerMethodVisibilityTest.java @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.optimizer; + +import org.hibernate.orm.test.bytecode.enhancement.optimizer.child.ChildEntity10; +import org.hibernate.orm.test.bytecode.enhancement.optimizer.parent.Ancestor; +import org.hibernate.orm.test.bytecode.enhancement.optimizer.parent.ChildEntity2; +import org.hibernate.query.Query; +import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced; +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.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@DomainModel(annotatedClasses = { + Ancestor.class, + AncestorEntity.class, + ChildEntity2.class, + ChildEntity10.class +}) +@SessionFactory +@Jira("https://hibernate.atlassian.net/browse/HHH-19372") +@BytecodeEnhanced +public class HierarchyBytecodeOptimizerMethodVisibilityTest { + + @Test + public void testOptimizerSetPropertyValues(SessionFactoryScope scope) { + ChildEntity2 childEntity2 = new ChildEntity2(); + childEntity2.setId( 1L ); + childEntity2.setField( "field" ); + childEntity2.setChieldField( "childField" ); + + ChildEntity10 childEntity10 = new ChildEntity10(); + childEntity10.setId( 3L ); + childEntity10.setField( "field10" ); + childEntity10.setChieldField( "childField3" ); + + scope.inTransaction( session -> { + session.persist( childEntity2 ); + session.persist( childEntity10 ); + } ); + + scope.inTransaction( session -> { + Query query = session.createQuery( "select c from ChildEntity2 c where c.field = :field", + ChildEntity2.class ); + query.setParameter( "field", "field" ); + assertThat( query.uniqueResult() ).isNotNull(); + } ); + + scope.inTransaction( session -> { + Query query = session.createQuery( "select c from ChildEntity10 c where c.field = :field", + ChildEntity10.class ); + query.setParameter( "field", "field10" ); + assertThat( query.uniqueResult() ).isNotNull(); + } ); + } + + @AfterAll + public void cleanup(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncateMappedObjects(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/HierarchyBytecodeOptimizerOrderingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/HierarchyBytecodeOptimizerOrderingTest.java new file mode 100644 index 000000000000..1493c992c53e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/HierarchyBytecodeOptimizerOrderingTest.java @@ -0,0 +1,115 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.optimizer; + +import org.hibernate.orm.test.bytecode.enhancement.optimizer.parent.Ancestor; +import org.hibernate.query.Query; +import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced; +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.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@DomainModel(annotatedClasses = { + Ancestor.class, + ParentEntity.class, + ChildEntity3.class, + ChildEntity4.class, + ChildEntity5.class, + ChildEntity6.class, + ChildEntity7.class, + ChildEntity8.class, + ChildEntity9.class +}) +@SessionFactory +@Jira("https://hibernate.atlassian.net/browse/HHH-19369") +@BytecodeEnhanced +public class HierarchyBytecodeOptimizerOrderingTest { + + @Test + public void testOptimizerSetPropertyValues(SessionFactoryScope scope) { + ChildEntity3 childEntity3 = new ChildEntity3(); + childEntity3.setId( 3L ); + childEntity3.setName( "child3" ); + childEntity3.setField( "field3" ); + childEntity3.setChieldField( "childField3" ); + + ChildEntity4 childEntity4 = new ChildEntity4(); + childEntity4.setId( 4L ); + childEntity4.setName( "child4" ); + childEntity4.setField( "field4" ); + childEntity4.setChieldField( "childField4" ); + + ChildEntity5 childEntity5 = new ChildEntity5(); + childEntity5.setId( 5L ); + childEntity5.setName( "child5" ); + childEntity5.setField( "field5" ); + childEntity5.setChieldField( "childField5" ); + + ChildEntity6 childEntity6 = new ChildEntity6(); + childEntity6.setId( 6L ); + childEntity6.setName( "child6" ); + childEntity6.setField( "field6" ); + childEntity6.setChieldField( "childField6" ); + + ChildEntity7 childEntity7 = new ChildEntity7(); + childEntity7.setId( 7L ); + childEntity7.setName( "child7" ); + childEntity7.setField( "field7" ); + childEntity7.setChildField( "childField7" ); + + scope.inTransaction( session -> { + session.persist( childEntity3 ); + session.persist( childEntity4 ); + session.persist( childEntity5 ); + session.persist( childEntity6 ); + session.persist( childEntity7 ); + } ); + + scope.inTransaction( session -> { + Query query = session.createQuery( "select c from ChildEntity3 c where c.field = :field", + ChildEntity3.class ); + query.setParameter( "field", "field3" ); + assertThat( query.uniqueResult() ).isNotNull(); + } ); + + scope.inTransaction( session -> { + Query query = session.createQuery( "select c from ChildEntity4 c where c.field = :field", + ChildEntity4.class ); + query.setParameter( "field", "field4" ); + assertThat( query.uniqueResult() ).isNotNull(); + } ); + + scope.inTransaction( session -> { + Query query = session.createQuery( "select c from ChildEntity5 c where c.field = :field", + ChildEntity5.class ); + query.setParameter( "field", "field5" ); + assertThat( query.uniqueResult() ).isNotNull(); + } ); + + scope.inTransaction( session -> { + Query query = session.createQuery( "select c from ChildEntity6 c where c.field = :field", + ChildEntity6.class ); + query.setParameter( "field", "field6" ); + assertThat( query.uniqueResult() ).isNotNull(); + } ); + + scope.inTransaction( session -> { + Query query = session.createQuery( + "select c from ChildEntity7 c where c.field = :field", ChildEntity7.class ); + query.setParameter( "field", "field7" ); + assertThat( query.uniqueResult() ).isNotNull(); + } ); + } + + @AfterAll + public void cleanup(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncateMappedObjects(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/HierarchyBytecodeOptimizerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/HierarchyBytecodeOptimizerTest.java index 65b8f1ce8a9b..a90b07190ef3 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/HierarchyBytecodeOptimizerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/HierarchyBytecodeOptimizerTest.java @@ -6,7 +6,6 @@ import org.hibernate.orm.test.bytecode.enhancement.optimizer.child.ChildEntity; import org.hibernate.query.Query; - import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.Jira; @@ -22,7 +21,7 @@ ChildEntity.class, }) @SessionFactory -@Jira( "https://hibernate.atlassian.net/browse/HHH-19059" ) +@Jira("https://hibernate.atlassian.net/browse/HHH-19372") @BytecodeEnhanced public class HierarchyBytecodeOptimizerTest { @@ -33,13 +32,17 @@ public void testOptimizerSetPropertyValues(SessionFactoryScope scope) { childEntity.setField( "field" ); childEntity.setChieldField( "childField" ); - scope.inTransaction( session -> session.persist( childEntity ) ); + scope.inTransaction( session -> { + session.persist( childEntity ); + } ); scope.inTransaction( session -> { - Query query = session.createQuery( "select c from ChildEntity c where c.field = :field", ChildEntity.class); + Query query = session.createQuery( "select c from ChildEntity c where c.field = :field", + ChildEntity.class ); query.setParameter( "field", "field" ); assertThat( query.uniqueResult() ).isNotNull(); } ); + } @AfterAll diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ParentEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ParentEntity.java index 84a7f72690f3..ee933b160ed0 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ParentEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/ParentEntity.java @@ -11,7 +11,8 @@ @Entity(name = "ParentEntity") @Inheritance(strategy = InheritanceType.JOINED) -public class ParentEntity { +public class ParentEntity { + @Id private Long id; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/child/ChildEntity10.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/child/ChildEntity10.java new file mode 100644 index 000000000000..f1a6756a8c20 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/child/ChildEntity10.java @@ -0,0 +1,21 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.optimizer.child; + +import jakarta.persistence.Entity; +import org.hibernate.orm.test.bytecode.enhancement.optimizer.AncestorEntity; + +@Entity(name = "ChildEntity10") +public class ChildEntity10 extends AncestorEntity { + private String childField; + + public String getChildField() { + return childField; + } + + public void setChieldField(String childField) { + this.childField = childField; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/parent/Ancestor.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/parent/Ancestor.java new file mode 100644 index 000000000000..15389f9b22fb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/parent/Ancestor.java @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.optimizer.parent; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; + +@Entity(name = "Parent") +@Inheritance(strategy = InheritanceType.JOINED) +public class Ancestor { + @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/bytecode/enhancement/optimizer/parent/ChildEntity2.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/parent/ChildEntity2.java new file mode 100644 index 000000000000..05e4917d8c9e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/optimizer/parent/ChildEntity2.java @@ -0,0 +1,21 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.optimizer.parent; + +import jakarta.persistence.Entity; +import org.hibernate.orm.test.bytecode.enhancement.optimizer.AncestorEntity; + +@Entity(name = "ChildEntity2") +public class ChildEntity2 extends AncestorEntity { + private String childField; + + public String getChildField() { + return childField; + } + + public void setChieldField(String childField) { + this.childField = childField; + } +} From f86ed147e6bf63f6eddb0bc166bbccde911f3295 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Wed, 21 May 2025 13:57:47 +0200 Subject: [PATCH 014/168] HHH-19372 Test and fix IAE for AccessOptimizer bridge superclasses (cherry picked from commit 09b6bf866a1fe1b5cf385f36fc72d27a3d6c1bab) --- .../bytebuddy/BytecodeProviderImpl.java | 162 +++++++++--------- 1 file changed, 79 insertions(+), 83 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java index bfc7d9ac92b8..00b381bf9056 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java @@ -14,7 +14,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; @@ -281,13 +280,13 @@ public ReflectionOptimizer getReflectionOptimizer( } } - private static class ForeignPackageClassInfo { + private static class BridgeMembersClassInfo { final Class clazz; final List propertyNames = new ArrayList<>(); final List getters = new ArrayList<>(); final List setters = new ArrayList<>(); - public ForeignPackageClassInfo(Class clazz) { + public BridgeMembersClassInfo(Class clazz) { this.clazz = clazz; } } @@ -296,99 +295,81 @@ private Class determineAccessOptimizerSuperClass(Class clazz, String[] pro if ( clazz.isInterface() ) { return Object.class; } - // generate access optimizer super classes for foreign package super classes that declare fields + // generate access optimizer super classes for super classes that declare members requiring bridge methods // each should declare protected static methods get_FIELDNAME(OWNER)/set_FIELDNAME(OWNER, TYPE) // which should be called then from within GetPropertyValues/SetPropertyValues // Since these super classes will be in the correct package, the package-private entity field access is fine - final List foreignPackageClassInfos = createForeignPackageClassInfos( clazz ); - for ( Iterator iterator = foreignPackageClassInfos.iterator(); iterator.hasNext(); ) { - final ForeignPackageClassInfo foreignPackageClassInfo = iterator.next(); - for ( int i = 0; i < getters.length; i++ ) { - final Member getter = getters[i]; - final Member setter = setters[i]; - boolean found = false; - if ( getter.getDeclaringClass() == foreignPackageClassInfo.clazz && !Modifier.isPublic( getter.getModifiers() ) ) { - foreignPackageClassInfo.getters.add( getter ); - found = true; - } - if ( setter.getDeclaringClass() == foreignPackageClassInfo.clazz && !Modifier.isPublic( setter.getModifiers() ) ) { - foreignPackageClassInfo.setters.add( setter ); - found = true; - } - if ( found ) { - foreignPackageClassInfo.propertyNames.add( propertyNames[i] ); - } - } - if ( foreignPackageClassInfo.getters.isEmpty() && foreignPackageClassInfo.setters.isEmpty() ) { - iterator.remove(); - } - } + final List bridgeMembersClassInfos = createBridgeMembersClassInfos( clazz, getters, setters, propertyNames ); Class superClass = Object.class; - for ( int i = foreignPackageClassInfos.size() - 1; i >= 0; i-- ) { - final ForeignPackageClassInfo foreignPackageClassInfo = foreignPackageClassInfos.get( i ); + for ( int i = bridgeMembersClassInfos.size() - 1; i >= 0; i-- ) { + final BridgeMembersClassInfo bridgeMembersClassInfo = bridgeMembersClassInfos.get( i ); final Class newSuperClass = superClass; - final String className = foreignPackageClassInfo.clazz.getName() + "$" + OPTIMIZER_PROXY_BRIDGE_NAMING_SUFFIX + encodeName( foreignPackageClassInfo.propertyNames, foreignPackageClassInfo.getters, foreignPackageClassInfo.setters ); + final String className = bridgeMembersClassInfo.clazz.getName() + "$" + OPTIMIZER_PROXY_BRIDGE_NAMING_SUFFIX + encodeName( bridgeMembersClassInfo.propertyNames, bridgeMembersClassInfo.getters, bridgeMembersClassInfo.setters ); superClass = byteBuddyState.load( - foreignPackageClassInfo.clazz, + bridgeMembersClassInfo.clazz, className, (byteBuddy, namingStrategy) -> { DynamicType.Builder builder = byteBuddy.with( namingStrategy ).subclass( newSuperClass ); - for ( Member getter : foreignPackageClassInfo.getters ) { - final Class getterType; - if ( getter instanceof Field field ) { - getterType = field.getType(); - } - else if ( getter instanceof Method method ) { - getterType = method.getReturnType(); - } + for ( Member getter : bridgeMembersClassInfo.getters ) { + if ( !Modifier.isPublic( getter.getModifiers() ) ) { + final Class getterType; + if ( getter instanceof Field field ) { + getterType = field.getType(); + } + else if ( getter instanceof Method method ) { + getterType = method.getReturnType(); + } else { throw new AssertionFailure( "Unexpected member" + getter ); } - builder = builder.defineMethod( - "get_" + getter.getName(), - TypeDescription.Generic.OfNonGenericType.ForLoadedType.of( - getterType - ), - Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC - ) - .withParameter( foreignPackageClassInfo.clazz ) - .intercept( - new Implementation.Simple( - new GetFieldOnArgument( - getter - ) - ) - ); - } - for ( Member setter : foreignPackageClassInfo.setters ) { - final Class setterType; - if ( setter instanceof Field field ) { - setterType = field.getType(); - } - else if ( setter instanceof Method method ) { - setterType = method.getParameterTypes()[0]; + builder = builder.defineMethod( + "get_" + getter.getName(), + TypeDescription.Generic.OfNonGenericType.ForLoadedType.of( + getterType + ), + Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC + ) + .withParameter( bridgeMembersClassInfo.clazz ) + .intercept( + new Implementation.Simple( + new GetFieldOnArgument( + getter + ) + ) + ); } + } + for ( Member setter : bridgeMembersClassInfo.setters ) { + if ( !Modifier.isPublic( setter.getModifiers() ) ) { + final Class setterType; + if ( setter instanceof Field field ) { + setterType = field.getType(); + } + else if ( setter instanceof Method method ) { + setterType = method.getParameterTypes()[0]; + } else { throw new AssertionFailure( "Unexpected member" + setter ); } - builder = builder.defineMethod( - "set_" + setter.getName(), - TypeDescription.Generic.VOID, - Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC - ) - .withParameter( foreignPackageClassInfo.clazz ) - .withParameter( setterType ) - .intercept( - new Implementation.Simple( - new SetFieldOnArgument( - setter - ) - ) - ); + builder = builder.defineMethod( + "set_" + setter.getName(), + TypeDescription.Generic.VOID, + Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC + ) + .withParameter( bridgeMembersClassInfo.clazz ) + .withParameter( setterType ) + .intercept( + new Implementation.Simple( + new SetFieldOnArgument( + setter + ) + ) + ); + } } return builder; @@ -398,10 +379,10 @@ else if ( setter instanceof Method method ) { for ( int j = 0; j < getters.length; j++ ) { final Member getter = getters[j]; final Member setter = setters[j]; - if ( foreignPackageClassInfo.getters.contains( getter ) ) { + if ( bridgeMembersClassInfo.getters.contains( getter ) && !Modifier.isPublic( getter.getModifiers() ) ) { getters[j] = new ForeignPackageMember( superClass, getter ); } - if ( foreignPackageClassInfo.setters.contains( setter ) ) { + if ( bridgeMembersClassInfo.setters.contains( setter ) && !Modifier.isPublic( setter.getModifiers() ) ) { setters[j] = new ForeignPackageMember( superClass, setter ); } } @@ -623,16 +604,31 @@ private boolean is64BitType(Class type) { } } - private List createForeignPackageClassInfos(Class clazz) { - final List foreignPackageClassInfos = new ArrayList<>(); + private List createBridgeMembersClassInfos( + Class clazz, + Member[] getters, + Member[] setters, + String[] propertyNames) { + final List bridgeMembersClassInfos = new ArrayList<>(); Class c = clazz.getSuperclass(); while (c != Object.class) { - if ( !c.getPackageName().equals( clazz.getPackageName() ) ) { - foreignPackageClassInfos.add( new ForeignPackageClassInfo( c ) ); + final BridgeMembersClassInfo bridgeMemberClassInfo = new BridgeMembersClassInfo( c ); + for ( int i = 0; i < getters.length; i++ ) { + final Member getter = getters[i]; + final Member setter = setters[i]; + if ( getter.getDeclaringClass() == c && !Modifier.isPublic( getter.getModifiers() ) + || setter.getDeclaringClass() == c && !Modifier.isPublic( setter.getModifiers() ) ) { + bridgeMemberClassInfo.getters.add( getter ); + bridgeMemberClassInfo.setters.add( setter ); + bridgeMemberClassInfo.propertyNames.add( propertyNames[i] ); + } + } + if ( !bridgeMemberClassInfo.propertyNames.isEmpty() ) { + bridgeMembersClassInfos.add( bridgeMemberClassInfo ); } c = c.getSuperclass(); } - return foreignPackageClassInfos; + return bridgeMembersClassInfos; } public ByteBuddyProxyHelper getByteBuddyProxyHelper() { From 48dbca0e7a80452147b36efda2e9519c09fe1a3b Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Fri, 25 Apr 2025 22:24:28 +0200 Subject: [PATCH 015/168] HHH-19383 - validation of NativeQuery result mappings Signed-off-by: Jan Schatteman (cherry picked from commit a9461d1474bb18eb36d268ccf820aa6930b1870b) --- .../query/sql/internal/NativeQueryImpl.java | 60 ++++++++++++++----- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java index 0c7ab0380387..f93f74dbdd56 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java @@ -5,6 +5,7 @@ package org.hibernate.query.sql.internal; import java.io.Serializable; +import java.lang.reflect.Constructor; import java.time.Instant; import java.util.Calendar; import java.util.Collection; @@ -125,6 +126,7 @@ import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty; import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpty; import static org.hibernate.internal.util.collections.CollectionHelper.makeCopy; +import static org.hibernate.internal.util.type.PrimitiveWrapperHelper.getDescriptorByPrimitiveType; import static org.hibernate.jpa.HibernateHints.HINT_NATIVE_LOCK_MODE; import static org.hibernate.query.results.internal.Builders.resultClassBuilder; import static org.hibernate.query.results.ResultSetMapping.resolveResultSetMapping; @@ -330,25 +332,54 @@ private void handleExplicitResultSetMapping() { setTupleTransformerForResultType( resultType ); } else { - checkResultType( resultType ); + checkResultType( resultType, resultSetMapping ); } } } - private void checkResultType(Class resultType) { - switch ( resultSetMapping.getNumberOfResultBuilders() ) { - case 0: - throw new IllegalArgumentException( "Named query exists, but did not specify a resultClass" ); - case 1: - final Class actualResultJavaType = - resultSetMapping.getResultBuilders().get( 0 ).getJavaType(); - if ( actualResultJavaType != null && !resultType.isAssignableFrom( actualResultJavaType ) ) { - throw buildIncompatibleException( resultType, actualResultJavaType ); - } - break; - default: - throw new IllegalArgumentException( "Cannot create TypedQuery for query with more than one return" ); + private void checkResultType(Class resultType, ResultSetMapping resultSetMapping) { + // resultType can be null if any of the deprecated methods were used to create the query + if ( resultType != null && !isResultTypeAlwaysAllowed( resultType )) { + switch ( resultSetMapping.getNumberOfResultBuilders() ) { + case 0: + if ( !resultSetMapping.isDynamic() ) { + throw new IllegalArgumentException( "Named query exists, but did not specify a resultClass" ); + } + break; + case 1: + final Class actualResultJavaType = + resultSetMapping.getResultBuilders().get( 0 ).getJavaType(); + if ( actualResultJavaType != null && !resultType.isAssignableFrom( actualResultJavaType ) ) { + throw buildIncompatibleException( resultType, actualResultJavaType ); + } + break; + default: + // The return type has to be a class with an appropriate constructor, i.e. one whose parameter types match + // the types of the result builders. If none such constructor is found, throw an IAE + if ( !validConstructorFoundForResultType( resultType, resultSetMapping ) ) { + throw new IllegalArgumentException( "The declared return type for a multi-valued result set mapping should be Object[], Map, List, or Tuple" ); + } + } + } + } + + private boolean validConstructorFoundForResultType(Class resultType, ResultSetMapping resultSetMapping) { + // Only 1 constructor with the right number of parameters is allowed (see NativeQueryConstructorTransformer) + Constructor constructor = resultType.getConstructors()[0]; + if ( constructor.getParameterCount() != resultSetMapping.getNumberOfResultBuilders() ) { + return false; + } + final List resultBuilders = resultSetMapping.getResultBuilders(); + Class[] paramTypes = constructor.getParameterTypes(); + for ( int i = 0; i < resultBuilders.size(); i++ ) { + if ( + resultBuilders.get( i ).getJavaType() != ( paramTypes[i].isPrimitive() ? + getDescriptorByPrimitiveType(paramTypes[i] ).getWrapperClass() : + paramTypes[i]) ) { + return false; + } } + return true; } protected void setTupleTransformerForResultType(Class resultClass) { @@ -740,6 +771,7 @@ else if ( !isResultTypeAlwaysAllowed( resultType ) else { mapping = resultSetMapping; } + checkResultType( resultType, mapping ); return isCacheableQuery() ? getInterpretationCache() .resolveSelectQueryPlan( selectInterpretationsKey( mapping ), () -> createQueryPlan( mapping ) ) From ee0884f1190f6180c17fd0b9d5f057996dff9029 Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Fri, 16 May 2025 00:15:52 +0200 Subject: [PATCH 016/168] HHH-19383 - Add a few more tests and put them together Signed-off-by: Jan Schatteman (cherry picked from commit 9a1bf3bbdec1c94d6e095b281a57dd09bfa3a7dd) --- .../sql/NativeQueryResultCheckingTests.java | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultCheckingTests.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultCheckingTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultCheckingTests.java new file mode 100644 index 000000000000..7367776939a7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultCheckingTests.java @@ -0,0 +1,198 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.query.sql; + +import java.util.List; +import java.util.Map; + +import jakarta.persistence.Tuple; +import org.hibernate.orm.test.sql.hand.Person; +import org.hibernate.query.NativeQuery; +import org.hibernate.testing.orm.domain.StandardDomainModel; +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.hibernate.testing.orm.domain.library.Book; +import org.hibernate.type.StandardBasicTypes; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * @author Jan Schatteman + */ +@DomainModel( + standardModels = StandardDomainModel.LIBRARY +) +@SessionFactory +@Jira("https://hibernate.atlassian.net/browse/HHH-19383") +public class NativeQueryResultCheckingTests { + + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19376") + public void testForHHH19376(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + String sql = "SELECT p.*, COUNT(*) OVER() AS total_count " + + "FROM Person p " + + "WHERE p.name ILIKE :name " + + "ORDER BY p.id"; + // Declare Person as result type + NativeQuery query = session.createNativeQuery(sql, Person.class); + query.setParameter("name", "Ga%"); + query.setMaxResults(2); + query.setFirstResult(0); + // Now mutate the result set mapping and verify an Exception is thrown + assertThrows( IllegalArgumentException.class, + () -> query.addScalar( "total_count", StandardBasicTypes.LONG).list() ); + } + ); + } + + @Test + public void testOkMutateResultSetMappingWithString(SessionFactoryScope scope) { + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> session.createNativeQuery( "select isbn from Book", String.class ) + .addScalar( "isbn", String.class ) + .getResultList() + ) + ); + } + + @Test + public void testNokMutateResultSetMappingWithString(SessionFactoryScope scope) { + scope.inTransaction( + session -> + assertThrows( IllegalArgumentException.class, + () -> session.createNativeQuery( "select title, isbn from Book", String.class ) + .addScalar( "title", String.class ) + .addScalar( "isbn", String.class ) + .getResultList() + ) + ); + } + + @Test + public void testOkMutateResultSetMappingWithBook(SessionFactoryScope scope) { + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> session.createNativeQuery( "select id, name from Book", Book.class ) + .addScalar( "id", Integer.class ) + .addScalar( "name", String.class ) + .getResultList() + ) + ); + } + + @Test + public void testNokMutateResultSetMappingWithBook(SessionFactoryScope scope) { + scope.inTransaction( + session -> + assertThrows( IllegalArgumentException.class, + () -> session.createNativeQuery( "select title, isbn from Book", Book.class ) + // this mapping doesn't have an appropriate constructor in Book, should throw error + .addScalar( "title", String.class ) + .addScalar( "isbn", String.class ) + .getResultList() + ) + ); + } + + @Test + public void testMutateResultSetMappingWithObjectArray(SessionFactoryScope scope) { + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> { + session.createNativeQuery( "select * from Book", Object[].class ) + .addScalar( "id", Integer.class ) + .addScalar( "name", String.class ) + .getResultList(); + } + ) + ); + } + + @Test + public void testMutateResultSetMappingWithTuple(SessionFactoryScope scope) { + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> { + session.createNativeQuery( "select * from Book", Tuple.class ) + .addScalar( "id", Integer.class ) + .addScalar( "name", String.class ) + .getResultList(); + } + ) + ); + } + + @Test + public void testMutateResultSetMappingWithMap(SessionFactoryScope scope) { + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> { + session.createNativeQuery( "select * from Book", Map.class ) + .addScalar( "id", Integer.class ) + .addScalar( "name", String.class ) + .getResultList(); + } + ) + ); + } + + @Test + public void testMutateResultSetMappingWithList(SessionFactoryScope scope) { + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> { + session.createNativeQuery( "select * from Book", List.class ) + .addScalar( "id", Integer.class ) + .addScalar( "name", String.class ) + .getResultList(); + } + ) + ); + } + + @Test + public void testRecordWithPrimitiveField(SessionFactoryScope scope) { + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> session.createNativeQuery( "select id, name from Person", Record.class) + // map a Long onto a primitive long, shouldn't throw an exception + .addScalar("id", Long.class) + .addScalar("name", String.class) + .getResultList() + ) + ); + } + + static class Record { + long id; + String name; + public Record(long id, String name) { + this.id = id; + this.name = name; + } + long id() { + return id; + } + String name() { + return name; + } + } + +} From 93a1350407e65f2efd774921674190e07b2e1ea1 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 21 May 2025 23:36:12 +0200 Subject: [PATCH 017/168] HHH-19383 add more tests (cherry picked from commit fe7a4d730407cf20c2c5b1bb75518754676554bc) --- .../sql/NativeQueryResultCheckingTests.java | 166 +++++++++++++++--- 1 file changed, 137 insertions(+), 29 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultCheckingTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultCheckingTests.java index 7367776939a7..fba4161d3101 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultCheckingTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultCheckingTests.java @@ -55,7 +55,7 @@ public void testForHHH19376(SessionFactoryScope scope) { } @Test - public void testOkMutateResultSetMappingWithString(SessionFactoryScope scope) { + public void testOneColumn(SessionFactoryScope scope) { scope.inTransaction( session -> assertDoesNotThrow( @@ -64,15 +64,50 @@ public void testOkMutateResultSetMappingWithString(SessionFactoryScope scope) { .getResultList() ) ); - } - - @Test - public void testNokMutateResultSetMappingWithString(SessionFactoryScope scope) { scope.inTransaction( session -> assertThrows( IllegalArgumentException.class, - () -> session.createNativeQuery( "select title, isbn from Book", String.class ) - .addScalar( "title", String.class ) + () -> session.createNativeQuery( "select isbn from Book", Integer.class ) + .addScalar( "isbn", String.class ) + .getResultList() + ) + ); + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> session.createNativeQuery( "select isbn from Book", Object[].class ) + .addScalar( "isbn", String.class ) + .getResultList() + ) + ); + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> session.createNativeQuery( "select isbn from Book", Tuple.class ) + .addScalar( "isbn", String.class ) + .getResultList() + ) + ); + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> session.createNativeQuery( "select isbn from Book", Map.class ) + .addScalar( "isbn", String.class ) + .getResultList() + ) + ); + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> session.createNativeQuery( "select isbn from Book", Object.class ) + .addScalar( "isbn", String.class ) + .getResultList() + ) + ); + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> session.createNativeQuery( "select isbn from Book" ) .addScalar( "isbn", String.class ) .getResultList() ) @@ -80,26 +115,67 @@ public void testNokMutateResultSetMappingWithString(SessionFactoryScope scope) { } @Test - public void testOkMutateResultSetMappingWithBook(SessionFactoryScope scope) { + public void testTwoStringColumns(SessionFactoryScope scope) { + scope.inTransaction( + session -> + assertThrows( IllegalArgumentException.class, + () -> session.createNativeQuery( "select name, isbn from Book", String.class ) + .addScalar( "name", String.class ) + .addScalar( "isbn", String.class ) + .getResultList() + ) + ); scope.inTransaction( session -> assertDoesNotThrow( - () -> session.createNativeQuery( "select id, name from Book", Book.class ) - .addScalar( "id", Integer.class ) + () -> session.createNativeQuery( "select name, isbn from Book", Object[].class ) .addScalar( "name", String.class ) + .addScalar( "isbn", String.class ) + .getResultList() + ) + ); + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> session.createNativeQuery( "select name, isbn from Book", Tuple.class ) + .addScalar( "name", String.class ) + .addScalar( "isbn", String.class ) + .getResultList() + ) + ); + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> session.createNativeQuery( "select name, isbn from Book", Map.class ) + .addScalar( "name", String.class ) + .addScalar( "isbn", String.class ) + .getResultList() + ) + ); + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> session.createNativeQuery( "select name, isbn from Book", Object.class ) + .addScalar( "name", String.class ) + .addScalar( "isbn", String.class ) + .getResultList() + ) + ); + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> session.createNativeQuery( "select name, isbn from Book" ) + .addScalar( "name", String.class ) + .addScalar( "isbn", String.class ) .getResultList() ) ); - } - - @Test - public void testNokMutateResultSetMappingWithBook(SessionFactoryScope scope) { scope.inTransaction( session -> assertThrows( IllegalArgumentException.class, - () -> session.createNativeQuery( "select title, isbn from Book", Book.class ) + () -> session.createNativeQuery( "select name, isbn from Book", Book.class ) // this mapping doesn't have an appropriate constructor in Book, should throw error - .addScalar( "title", String.class ) + .addScalar( "name", String.class ) .addScalar( "isbn", String.class ) .getResultList() ) @@ -107,7 +183,20 @@ public void testNokMutateResultSetMappingWithBook(SessionFactoryScope scope) { } @Test - public void testMutateResultSetMappingWithObjectArray(SessionFactoryScope scope) { + public void testOkMutateResultSetMappingWithBook(SessionFactoryScope scope) { + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> session.createNativeQuery( "select id, name from Book", Book.class ) + .addScalar( "id", Integer.class ) + .addScalar( "name", String.class ) + .getResultList() + ) + ); + } + + @Test + public void testAllColumns(SessionFactoryScope scope) { scope.inTransaction( session -> assertDoesNotThrow( @@ -119,10 +208,6 @@ public void testMutateResultSetMappingWithObjectArray(SessionFactoryScope scope) } ) ); - } - - @Test - public void testMutateResultSetMappingWithTuple(SessionFactoryScope scope) { scope.inTransaction( session -> assertDoesNotThrow( @@ -134,10 +219,6 @@ public void testMutateResultSetMappingWithTuple(SessionFactoryScope scope) { } ) ); - } - - @Test - public void testMutateResultSetMappingWithMap(SessionFactoryScope scope) { scope.inTransaction( session -> assertDoesNotThrow( @@ -149,10 +230,28 @@ public void testMutateResultSetMappingWithMap(SessionFactoryScope scope) { } ) ); - } - - @Test - public void testMutateResultSetMappingWithList(SessionFactoryScope scope) { + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> { + session.createNativeQuery( "select * from Book", Object.class ) + .addScalar( "id", Integer.class ) + .addScalar( "name", String.class ) + .getResultList(); + } + ) + ); + scope.inTransaction( + session -> + assertDoesNotThrow( + () -> { + session.createNativeQuery( "select * from Book", Book.class ) + .addScalar( "id", Integer.class ) + .addScalar( "name", String.class ) + .getResultList(); + } + ) + ); scope.inTransaction( session -> assertDoesNotThrow( @@ -164,6 +263,15 @@ public void testMutateResultSetMappingWithList(SessionFactoryScope scope) { } ) ); + scope.inTransaction( + session -> + assertThrows( IllegalArgumentException.class, + () -> session.createNativeQuery( "select * from Book", Book.class ) + .addScalar( "isbn", String.class ) + .addScalar( "name", String.class ) + .getResultList() + ) + ); } @Test From 291b35f6cfe2aa7bd5682ff4799a50339c134f15 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 21 May 2025 23:42:36 +0200 Subject: [PATCH 018/168] HHH-19383 partial fix to checking of constructor signature NativeQueryConstructorTransformer does not prevent the result type from having multiple constructors, it just rejects a result type with multiple constructors with matching arity (cherry picked from commit 3b3ee645eed07cf9eb0e16b626c74b1a93e1302f) --- .../query/sql/internal/NativeQueryImpl.java | 46 +++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java index f93f74dbdd56..a8679fad7a54 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java @@ -5,7 +5,6 @@ package org.hibernate.query.sql.internal; import java.io.Serializable; -import java.lang.reflect.Constructor; import java.time.Instant; import java.util.Calendar; import java.util.Collection; @@ -354,32 +353,43 @@ private void checkResultType(Class resultType, ResultSetMapping resultSetMapp } break; default: - // The return type has to be a class with an appropriate constructor, i.e. one whose parameter types match - // the types of the result builders. If none such constructor is found, throw an IAE + // The return type has to be a class with an appropriate constructor, + // i.e. one whose parameter types match the types of the result builders. + // If no such constructor is found, throw an IAE if ( !validConstructorFoundForResultType( resultType, resultSetMapping ) ) { - throw new IllegalArgumentException( "The declared return type for a multi-valued result set mapping should be Object[], Map, List, or Tuple" ); + throw new IllegalArgumentException( + "The return type for a multivalued result set mapping should be Object[], Map, List, or Tuple" + + " or it must have an appropriate constructor" + ); } } } } private boolean validConstructorFoundForResultType(Class resultType, ResultSetMapping resultSetMapping) { - // Only 1 constructor with the right number of parameters is allowed (see NativeQueryConstructorTransformer) - Constructor constructor = resultType.getConstructors()[0]; - if ( constructor.getParameterCount() != resultSetMapping.getNumberOfResultBuilders() ) { - return false; - } - final List resultBuilders = resultSetMapping.getResultBuilders(); - Class[] paramTypes = constructor.getParameterTypes(); - for ( int i = 0; i < resultBuilders.size(); i++ ) { - if ( - resultBuilders.get( i ).getJavaType() != ( paramTypes[i].isPrimitive() ? - getDescriptorByPrimitiveType(paramTypes[i] ).getWrapperClass() : - paramTypes[i]) ) { - return false; + // TODO: Only one constructor with the right number of parameters is allowed + // (see NativeQueryConstructorTransformer) so we should validate that + outer: for ( var constructor : resultType.getConstructors() ) { + if ( constructor.getParameterCount() == resultSetMapping.getNumberOfResultBuilders() ) { + final var resultBuilders = resultSetMapping.getResultBuilders(); + final var paramTypes = constructor.getParameterTypes(); + for ( int i = 0; i < resultBuilders.size(); i++ ) { + if ( !constructorParameterMatches( resultBuilders.get( i ), paramTypes[i] ) ) { + continue outer; + } + } + return true; } } - return true; + return false; + } + + private static boolean constructorParameterMatches(ResultBuilder resultBuilder, Class paramType) { + final Class parameterClass = + paramType.isPrimitive() + ? getDescriptorByPrimitiveType( paramType ).getWrapperClass() + : paramType; + return resultBuilder.getJavaType() == parameterClass; } protected void setTupleTransformerForResultType(Class resultClass) { From 0f0107e5a55b22ef4f7251f55be4c8bce9fb3f9f Mon Sep 17 00:00:00 2001 From: Miroslav Silhavy Date: Mon, 12 May 2025 21:13:29 +0200 Subject: [PATCH 019/168] HHH-19387 - Fix concrete descriptor when left join returns null in a cached query (cherry picked from commit 2950a5e84dc3cce54fe54c02f4613b6986cdf82a) --- .../internal/EntityInitializerImpl.java | 4 + ...hCollectionReloadCacheInheritanceTest.java | 178 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/querycache/EntityWithCollectionReloadCacheInheritanceTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java index 00a1c02b1339..e6af2fc6b027 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java @@ -1651,6 +1651,10 @@ public void resolveState(EntityInitializerData data) { castNonNull( discriminatorAssembler ), entityDescriptor ); + if (data.concreteDescriptor == null) { + // Return because the value is null + return; + } } } resolveEntityState( data ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/querycache/EntityWithCollectionReloadCacheInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/querycache/EntityWithCollectionReloadCacheInheritanceTest.java new file mode 100644 index 000000000000..5493cbf3aef3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/querycache/EntityWithCollectionReloadCacheInheritanceTest.java @@ -0,0 +1,178 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.querycache; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +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.Access; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; + +import static jakarta.persistence.AccessType.FIELD; +import static jakarta.persistence.EnumType.STRING; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author miroslav silhavy + */ +@DomainModel(annotatedClasses = { + EntityWithCollectionReloadCacheInheritanceTest.HighSchoolStudent.class, + EntityWithCollectionReloadCacheInheritanceTest.DefaultSubject.class, + EntityWithCollectionReloadCacheInheritanceTest.EnglishSubject.class +}) +@SessionFactory +@Jira("https://hibernate.atlassian.net/browse/HHH-19387") +public class EntityWithCollectionReloadCacheInheritanceTest { + + @Test + public void test(SessionFactoryScope scope) { + scope.inTransaction( session -> { + List list = session.createQuery( + "select s" + + " from HighSchoolStudent s left join fetch s.subjects m" + + " where s.name in :names", HighSchoolStudent.class + ) + .setParameter( "names", Arrays.asList( "Brian" ) ) + .setCacheable( true ) + .list(); + + assertThat( list ).hasSize( 1 ); + + list = session.createQuery( + "select s" + + " from HighSchoolStudent s left join fetch s.subjects m" + + " where s.name in :names", HighSchoolStudent.class + ) + .setParameter( "names", Arrays.asList( "Andrew", "Brian" ) ) + .setCacheable( true ) + .list(); + + assertThat( list ).hasSize( 2 ); + } ); + } + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + HighSchoolStudent s1 = new HighSchoolStudent(); + s1.setName( "Andrew" ); + session.persist( s1 ); + + HighSchoolStudent s2 = new HighSchoolStudent(); + s2.setName( "Brian" ); + session.persist( s2 ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncateMappedObjects(); + } + + @Entity(name = "HighSchoolStudent") + @Access(FIELD) + static class HighSchoolStudent { + + @Id + @GeneratedValue + @Column(name = "id") + private Long id; + + @Column(name = "name") + private String name; + + @ManyToMany(targetEntity = DefaultSubject.class, fetch = FetchType.LAZY) + @JoinTable(name = "STUDENT_SUBJECT", + joinColumns = { @JoinColumn(name = "student_id") }, + inverseJoinColumns = { @JoinColumn(name = "subject_id") } + ) + private Set subjects; + + 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; + } + + public Set getSubjects() { + return subjects; + } + + public void setMajors(Set subjects) { + this.subjects = subjects; + } + + } + + @Entity(name = "DefaultSubject") + @DiscriminatorValue("DEFAULT") + @DiscriminatorColumn(name = "TYPE", length = 20) + @Access(FIELD) + static class DefaultSubject { + + enum SubjectType { + DEFAULT, + ENGLISH + } + + @Column(name = "TYPE", nullable = false, length = 20, insertable = false, updatable = false) + @Enumerated(STRING) + @JdbcTypeCode(SqlTypes.VARCHAR) + private SubjectType type; + + @Id + @GeneratedValue + @Column(name = "id") + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + } + + @Entity(name = "EnglishSubject") + @DiscriminatorValue("ENGLISH") + @Access(FIELD) + static class EnglishSubject extends DefaultSubject { + } + +} From 14aa5bdc8780e5b2bf8a243b31d49278629fac52 Mon Sep 17 00:00:00 2001 From: Miroslav Silhavy Date: Thu, 15 May 2025 22:59:37 +0200 Subject: [PATCH 020/168] HHH-19387 - Reformat and reword test entities (cherry picked from commit 4abca25ae1eeb372abf1bd97554ea03ce34167fa) --- .../internal/EntityInitializerImpl.java | 4 +- ...hCollectionReloadCacheInheritanceTest.java | 80 +++++++++---------- 2 files changed, 40 insertions(+), 44 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java index e6af2fc6b027..6ed4affa6a2d 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java @@ -1651,8 +1651,8 @@ public void resolveState(EntityInitializerData data) { castNonNull( discriminatorAssembler ), entityDescriptor ); - if (data.concreteDescriptor == null) { - // Return because the value is null + if ( data.concreteDescriptor == null ) { + // this should imply the entity is missing return; } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/querycache/EntityWithCollectionReloadCacheInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/querycache/EntityWithCollectionReloadCacheInheritanceTest.java index 5493cbf3aef3..03977b12df7f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/querycache/EntityWithCollectionReloadCacheInheritanceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/querycache/EntityWithCollectionReloadCacheInheritanceTest.java @@ -4,22 +4,6 @@ */ package org.hibernate.orm.test.querycache; -import java.util.Arrays; -import java.util.List; -import java.util.Set; - -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; - -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.Access; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorColumn; import jakarta.persistence.DiscriminatorValue; @@ -28,20 +12,34 @@ import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinTable; import jakarta.persistence.ManyToMany; +import org.hibernate.annotations.JdbcTypeCode; +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.hibernate.type.SqlTypes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; -import static jakarta.persistence.AccessType.FIELD; import static jakarta.persistence.EnumType.STRING; import static org.assertj.core.api.Assertions.assertThat; /** - * @author miroslav silhavy + * @author Miroslav Silhavy */ @DomainModel(annotatedClasses = { EntityWithCollectionReloadCacheInheritanceTest.HighSchoolStudent.class, - EntityWithCollectionReloadCacheInheritanceTest.DefaultSubject.class, + EntityWithCollectionReloadCacheInheritanceTest.Subject.class, EntityWithCollectionReloadCacheInheritanceTest.EnglishSubject.class }) @SessionFactory @@ -51,7 +49,7 @@ public class EntityWithCollectionReloadCacheInheritanceTest { @Test public void test(SessionFactoryScope scope) { scope.inTransaction( session -> { - List list = session.createQuery( + List highSchoolStudents = session.createQuery( "select s" + " from HighSchoolStudent s left join fetch s.subjects m" + " where s.name in :names", HighSchoolStudent.class @@ -60,9 +58,9 @@ public void test(SessionFactoryScope scope) { .setCacheable( true ) .list(); - assertThat( list ).hasSize( 1 ); + assertThat( highSchoolStudents ).hasSize( 1 ); - list = session.createQuery( + highSchoolStudents = session.createQuery( "select s" + " from HighSchoolStudent s left join fetch s.subjects m" + " where s.name in :names", HighSchoolStudent.class @@ -71,7 +69,7 @@ public void test(SessionFactoryScope scope) { .setCacheable( true ) .list(); - assertThat( list ).hasSize( 2 ); + assertThat( highSchoolStudents ).hasSize( 2 ); } ); } @@ -94,7 +92,6 @@ public void tearDown(SessionFactoryScope scope) { } @Entity(name = "HighSchoolStudent") - @Access(FIELD) static class HighSchoolStudent { @Id @@ -105,12 +102,12 @@ static class HighSchoolStudent { @Column(name = "name") private String name; - @ManyToMany(targetEntity = DefaultSubject.class, fetch = FetchType.LAZY) + @ManyToMany(targetEntity = Subject.class, fetch = FetchType.LAZY) @JoinTable(name = "STUDENT_SUBJECT", joinColumns = { @JoinColumn(name = "student_id") }, inverseJoinColumns = { @JoinColumn(name = "subject_id") } ) - private Set subjects; + private Set subjects; public Long getId() { return id; @@ -128,37 +125,32 @@ public void setName(String name) { this.name = name; } - public Set getSubjects() { + public Set getSubjects() { return subjects; } - public void setMajors(Set subjects) { + public void setSubjects(Set subjects) { this.subjects = subjects; } } - @Entity(name = "DefaultSubject") + @Entity(name = "Subject") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorValue("DEFAULT") @DiscriminatorColumn(name = "TYPE", length = 20) - @Access(FIELD) - static class DefaultSubject { + static class Subject { - enum SubjectType { - DEFAULT, - ENGLISH - } + @Id + @GeneratedValue + @Column(name = "id") + private Long id; @Column(name = "TYPE", nullable = false, length = 20, insertable = false, updatable = false) @Enumerated(STRING) @JdbcTypeCode(SqlTypes.VARCHAR) private SubjectType type; - @Id - @GeneratedValue - @Column(name = "id") - private Long id; - public Long getId() { return id; } @@ -171,8 +163,12 @@ public void setId(Long id) { @Entity(name = "EnglishSubject") @DiscriminatorValue("ENGLISH") - @Access(FIELD) - static class EnglishSubject extends DefaultSubject { + static class EnglishSubject extends Subject { + } + + enum SubjectType { + DEFAULT, + ENGLISH } } From 1ec2a41a1022cb296a1f374b237eff0e2b5a53f1 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 14 May 2025 17:49:25 +0200 Subject: [PATCH 021/168] HHH-19463 when we have @Repository(provider="Hibernate") don't skip repo in this case it can't be intended for another provider (cherry picked from commit 05e9f1f44e37428cd4ee48e0cf398ed5409deb41) --- .../annotation/AnnotationMetaEntity.java | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java index 1a8767dd6cd9..eca690965215 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java @@ -418,8 +418,9 @@ else if ( method.getEnclosingElement().getKind().isInterface() } primaryEntity = primaryEntity( lifecycleMethods ); - if ( primaryEntity != null && !hasAnnotation(primaryEntity, ENTITY) - || !checkEntities(lifecycleMethods)) { + final boolean hibernateRepo = isExplicitlyHibernateRepository(); + if ( !checkEntity( primaryEntity, hibernateRepo ) + || !checkEntities( lifecycleMethods, hibernateRepo ) ) { // NOTE EARLY EXIT with initialized = false return; } @@ -469,6 +470,29 @@ && containsAnnotation( method, HQL, SQL, FIND ) ) { initialized = true; } + private boolean checkEntity(@Nullable TypeElement entity, boolean hibernateRepo) { + if ( entity != null && !hasAnnotation( entity, ENTITY ) ) { + if ( hibernateRepo ) { + context.message( element, + "unrecognized primary entity type: " + entity.getQualifiedName(), + Diagnostic.Kind.ERROR ); + } + return false; + } + return true; + } + + private boolean isExplicitlyHibernateRepository() { + final AnnotationMirror repository = getAnnotationMirror( element, JD_REPOSITORY ); + if ( repository != null ) { + final AnnotationValue provider = getAnnotationValue( repository, "provider" ); + return provider != null && provider.getValue().toString().equalsIgnoreCase( "hibernate" ); + } + else { + return false; + } + } + /** * Creates a generated id class named {@code Entity_.Id} if the * entity has multiple {@code @Id} fields, but no {@code @IdClass} @@ -613,7 +637,7 @@ private boolean isEquivalentPrimitiveType(TypeMirror type, TypeMirror match) { && isSameType( context.getTypeUtils().boxedClass( ((PrimitiveType) type) ).asType(), match ); } - private boolean checkEntities(List lifecycleMethods) { + private boolean checkEntities(List lifecycleMethods, boolean hibernateRepo) { boolean foundPersistenceEntity = false; VariableElement nonPersistenceParameter = null; for (ExecutableElement lifecycleMethod : lifecycleMethods) { @@ -638,7 +662,7 @@ else if ( declaredType == parameterType message(nonPersistenceParameter, "parameter type '" + nonPersistenceParameter.asType() + "' is not a Jakarta Persistence entity class (skipping entire repository)", - Diagnostic.Kind.WARNING); + hibernateRepo ? Diagnostic.Kind.ERROR : Diagnostic.Kind.WARNING); } return nonPersistenceParameter == null; } From 1308a8ba8c5c39ae3d5dd3539f53bb38f36fde5d Mon Sep 17 00:00:00 2001 From: Gavin King Date: Mon, 26 May 2025 10:24:54 +0200 Subject: [PATCH 022/168] HHH-19471 use custom implementation of MD5 because the JDK version is not available in FIPS mode (cherry picked from commit f97aa743bcd81b438f603c3e55147f5f2943bc38) --- .../boot/model/naming/NamingHelper.java | 124 +++++++++++++++--- 1 file changed, 107 insertions(+), 17 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/NamingHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/NamingHelper.java index e1e515d89f6c..a46bf2aa992f 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/NamingHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/NamingHelper.java @@ -6,13 +6,9 @@ import java.io.UnsupportedEncodingException; import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.List; -import org.hibernate.HibernateException; - import static java.util.Comparator.comparing; /** @@ -125,27 +121,121 @@ public String generateHashedConstraintName( /** * Hash a constraint name using MD5. Convert the MD5 digest to base 35 - * (full alphanumeric), guaranteeing - * that the length of the name will always be smaller than the 30 - * character identifier restriction enforced by a few dialects. + * (full alphanumeric), guaranteeing that the length of the name will + * always be smaller than the 30 character identifier restriction + * enforced by some dialects. * * @param name The name to be hashed. * * @return String The hashed name. */ public String hashedName(String name) { + final byte[] bytes; try { - final MessageDigest md5 = MessageDigest.getInstance( "MD5" ); - md5.reset(); - md5.update( charset != null ? name.getBytes( charset ) : name.getBytes() ); - final BigInteger bigInt = new BigInteger( 1, md5.digest() ); - // By converting to base 35 (full alphanumeric), we guarantee - // that the length of the name will always be smaller than the 30 - // character identifier restriction enforced by a few dialects. - return bigInt.toString( 35 ); + bytes = charset == null + ? name.getBytes() + : name.getBytes( charset ); + } + catch (UnsupportedEncodingException uee) { + throw new IllegalArgumentException(uee); + } + final byte[] digest = hash( pad( bytes ) ); + return new BigInteger( 1, digest ).toString( 35 ); + } + + // Constants for MD5 + private static final int[] S = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 + }; + + private static final int[] K = new int[64]; + static { + for ( int i = 0; i < 64; i++ ) { + K[i] = (int)(long) ( (1L << 32) * Math.abs( Math.sin( i + 1 ) ) ); + } + } + + public static byte[] hash(byte[] message) { + int a0 = 0x67452301; + int b0 = 0xefcdab89; + int c0 = 0x98badcfe; + int d0 = 0x10325476; + + for ( int i = 0; i < message.length / 64; i++ ) { + final int[] M = new int[16]; + for (int j = 0; j < 16; j++) { + M[j] = ((message[i * 64 + j * 4] & 0xFF)) + | ((message[i * 64 + j * 4 + 1] & 0xFF) << 8) + | ((message[i * 64 + j * 4 + 2] & 0xFF) << 16) + | ((message[i * 64 + j * 4 + 3] & 0xFF) << 24); + } + + int A = a0, B = b0, C = c0, D = d0; + + for (int j = 0; j < 64; j++) { + final int F, g; + if (j < 16) { + F = (B & C) | (~B & D); + g = j; + } + else if (j < 32) { + F = (D & B) | (~D & C); + g = (5 * j + 1) % 16; + } + else if (j < 48) { + F = B ^ C ^ D; + g = (3 * j + 5) % 16; + } + else { + F = C ^ (B | ~D); + g = (7 * j) % 16; + } + + final int temp = D; + D = C; + C = B; + B = B + Integer.rotateLeft( A + F + K[j] + M[g], S[j] ); + A = temp; + } + + a0 += A; + b0 += B; + c0 += C; + d0 += D; } - catch ( NoSuchAlgorithmException | UnsupportedEncodingException e ) { - throw new HibernateException( "Unable to generate a hashed name", e ); + + // Convert final state to byte array (little-endian) + final byte[] digest = new byte[16]; + encodeInt( digest, 0, a0 ); + encodeInt( digest, 4, b0 ); + encodeInt( digest, 8, c0 ); + encodeInt( digest, 12, d0 ); + return digest; + } + + private static void encodeInt(byte[] output, int offset, int value) { + output[offset] = (byte) (value & 0xFF); + output[offset + 1] = (byte) ((value >>> 8) & 0xFF); + output[offset + 2] = (byte) ((value >>> 16) & 0xFF); + output[offset + 3] = (byte) ((value >>> 24) & 0xFF); + } + + private static byte[] pad(byte[] input) { + final int originalLength = input.length; + final int numPaddingBytes = ( 56 - (originalLength + 1) % 64 + 64 ) % 64; + + final byte[] padded = new byte[originalLength + 1 + numPaddingBytes + 8]; + System.arraycopy( input, 0, padded, 0, originalLength ); + padded[originalLength] = (byte) 0x80; + + long bitLength = (long) originalLength * 8; + for ( int i = 0; i < 8; i++ ) { + padded[padded.length - 8 + i] = (byte) ( ( bitLength >>> (8 * i) ) & 0xFF ); } + + return padded; } } From 394c7409c48456eababef564b8135c36dcd3df78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20NO=C3=8BL?= Date: Mon, 19 May 2025 10:33:47 +0200 Subject: [PATCH 023/168] HHH-19472: native queries can return Object[] (cherry picked from commit 4122964722521d3fb0b7748e1103465dd4dbd3af) --- .../jpa/spi/NativeQueryArrayTransformer.java | 22 +++ .../query/sql/internal/NativeQueryImpl.java | 6 +- .../hql/SingleSelectionArrayResultTest.java | 173 ++++++++++++------ 3 files changed, 145 insertions(+), 56 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryArrayTransformer.java diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryArrayTransformer.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryArrayTransformer.java new file mode 100644 index 000000000000..461c100d6ff0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryArrayTransformer.java @@ -0,0 +1,22 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.jpa.spi; + +import org.hibernate.query.TupleTransformer; + +/** + * A {@link TupleTransformer} for handling {@code Object[]} results from native queries. + * + * @since 7.0 + */ +public class NativeQueryArrayTransformer implements TupleTransformer { + + public static final NativeQueryArrayTransformer INSTANCE = new NativeQueryArrayTransformer(); + + @Override + public Object[] transformTuple(Object[] tuple, String[] aliases) { + return tuple; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java index a8679fad7a54..7bc1b24efa3a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java @@ -25,6 +25,7 @@ import org.hibernate.FlushMode; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jpa.spi.NativeQueryArrayTransformer; import org.hibernate.jpa.spi.NativeQueryConstructorTransformer; import org.hibernate.jpa.spi.NativeQueryListTransformer; import org.hibernate.jpa.spi.NativeQueryMapTransformer; @@ -418,7 +419,10 @@ else if ( Map.class.equals( resultClass ) ) { else if ( List.class.equals( resultClass ) ) { return NativeQueryListTransformer.INSTANCE; } - else if ( resultClass != Object.class && resultClass != Object[].class ) { + else if ( Object[].class.equals( resultClass )) { + return NativeQueryArrayTransformer.INSTANCE; + } + else if ( resultClass != Object.class ) { // TODO: this is extremely fragile and probably a bug if ( isClass( resultClass ) && !hasJavaTypeDescriptor( resultClass ) ) { // not a basic type, so something we can attempt diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/SingleSelectionArrayResultTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/SingleSelectionArrayResultTest.java index 0a3577b076e1..e99c9b54d8b6 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/SingleSelectionArrayResultTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/SingleSelectionArrayResultTest.java @@ -4,81 +4,144 @@ */ package org.hibernate.orm.test.hql; +import java.util.stream.Stream; + +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.query.NativeQuery; +import org.hibernate.query.Query; +import org.hibernate.query.SelectionQuery; + import org.hibernate.testing.orm.domain.gambit.BasicEntity; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.RequiresDialects; 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 org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; import static org.assertj.core.api.Assertions.assertThat; /** * @author Marco Belladelli */ -@DomainModel( annotatedClasses = BasicEntity.class ) +@DomainModel(annotatedClasses = BasicEntity.class) @SessionFactory -@Jira( "https://hibernate.atlassian.net/browse/HHH-18450" ) +@Jira("https://hibernate.atlassian.net/browse/HHH-18450") +@Jira("https://hibernate.atlassian.net/browse/HHH-19472") +@RequiresDialects({@RequiresDialect(H2Dialect.class), @RequiresDialect(PostgreSQLDialect.class)}) public class SingleSelectionArrayResultTest { - @Test - public void testArrayResult(SessionFactoryScope scope) { + + static class TestArguments implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext extensionContext) { + return Stream.of( + Arguments.of( "select 1", null, null ), + Arguments.of( "select cast(1 as integer)", null, null ), + Arguments.of( "select id from BasicEntity", null, null ), + Arguments.of( "select cast(id as integer) from BasicEntity", null, null ), + Arguments.of( "select ?1", 1, 1 ), + Arguments.of( "select :p1", "p1", 1 ), + Arguments.of( "select cast(?1 as integer)", 1, 1 ), + Arguments.of( "select cast(:p1 as integer)", "p1", 1 ) + ); + } + } + + @ParameterizedTest + @ArgumentsSource(TestArguments.class) + public void testQueryObjectResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) { + scope.inTransaction( session -> { + Query query = session.createQuery( ql, Object.class ); + if ( arg1 instanceof Integer ) { + query.setParameter( (Integer) arg1, arg2 ); + } + if ( arg1 instanceof String ) { + query.setParameter( (String) arg1, arg2 ); + } + assertThat( query.getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); + } ); + } + + @ParameterizedTest + @ArgumentsSource(TestArguments.class) + public void testNativeQueryObjectResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) { + scope.inTransaction( session -> { + NativeQuery query = session.createNativeQuery( ql, Object.class ); + if ( arg1 instanceof Integer ) { + query.setParameter( (Integer) arg1, arg2 ); + } + if ( arg1 instanceof String ) { + query.setParameter( (String) arg1, arg2 ); + } + assertThat( query.getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); + } ); + } + + @ParameterizedTest + @ArgumentsSource(TestArguments.class) + public void testSelectionQueryObjectResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) { + scope.inTransaction( session -> { + SelectionQuery query = session.createSelectionQuery( ql, Object.class ); + if ( arg1 instanceof Integer ) { + query.setParameter( (Integer) arg1, arg2 ); + } + if ( arg1 instanceof String ) { + query.setParameter( (String) arg1, arg2 ); + } + assertThat( query.getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); + } ); + } + + @ParameterizedTest + @ArgumentsSource(TestArguments.class) + public void testQueryArrayResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) { + scope.inTransaction( session -> { + Query query = session.createQuery( ql, Object[].class ); + if ( arg1 instanceof Integer ) { + query.setParameter( (Integer) arg1, arg2 ); + } + if ( arg1 instanceof String ) { + query.setParameter( (String) arg1, arg2 ); + } + assertThat( query.getSingleResult() ).containsExactly( 1 ); + } ); + } + + @ParameterizedTest + @ArgumentsSource(TestArguments.class) + public void testNativeQueryArrayResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) { scope.inTransaction( session -> { - assertThat( session.createQuery( - "select 1", - Object[].class - ).getSingleResult() ).containsExactly( 1 ); - assertThat( session.createQuery( - "select cast(1 as integer)", - Object[].class - ).getSingleResult() ).containsExactly( 1 ); - assertThat( session.createSelectionQuery( - "select id from BasicEntity", - Object[].class - ).getSingleResult() ).containsExactly( 1 ); - assertThat( session.createSelectionQuery( - "select cast(id as integer) from BasicEntity", - Object[].class - ).getSingleResult() ).containsExactly( 1 ); - assertThat( session.createSelectionQuery( - "select ?1", - Object[].class - ).setParameter( 1, 1 ).getSingleResult() ).containsExactly( 1 ); - assertThat( session.createQuery( - "select cast(:p1 as integer)", - Object[].class - ).setParameter( "p1", 1 ).getSingleResult() ).containsExactly( 1 ); + NativeQuery query = session.createNativeQuery( ql, Object[].class ); + if ( arg1 instanceof Integer ) { + query.setParameter( (Integer) arg1, arg2 ); + } + if ( arg1 instanceof String ) { + query.setParameter( (String) arg1, arg2 ); + } + assertThat( query.getSingleResult() ).containsExactly( 1 ); } ); } - @Test - public void testNormalResult(SessionFactoryScope scope) { + @ParameterizedTest + @ArgumentsSource(TestArguments.class) + public void testSelectionQueryArrayResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) { scope.inTransaction( session -> { - assertThat( session.createQuery( - "select 1", - Object.class - ).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); - assertThat( session.createQuery( - "select cast(1 as integer)", - Object.class - ).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); - assertThat( session.createSelectionQuery( - "select id from BasicEntity", - Object.class - ).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); - assertThat( session.createSelectionQuery( - "select cast(id as integer) from BasicEntity", - Object.class - ).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); - assertThat( session.createSelectionQuery( - "select ?1", - Object.class - ).setParameter( 1, 1 ).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); - assertThat( session.createQuery( - "select cast(:p1 as integer)", - Object.class - ).setParameter( "p1", 1 ).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); + SelectionQuery query = session.createSelectionQuery( ql, Object[].class ); + if ( arg1 instanceof Integer ) { + query.setParameter( (Integer) arg1, arg2 ); + } + if ( arg1 instanceof String ) { + query.setParameter( (String) arg1, arg2 ); + } + assertThat( query.getSingleResult() ).containsExactly( 1 ); } ); } From 4924ec986b0d6ff96fccdcf920e6095721074007 Mon Sep 17 00:00:00 2001 From: Steve Abbott Date: Fri, 16 May 2025 19:10:37 -0400 Subject: [PATCH 024/168] HHH-19473: Use asDefined type when enhancing BiDirectional Associations to resolve issue with mismatched generated method signatures. (cherry picked from commit 5c1edfa862a2a60f93a15a5d39815433431d3cde) --- .../bytebuddy/BiDirectionalAssociationHandler.java | 1 + .../lazy/LazyLoadingAndParameterizedInheritanceTest.java | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java index ad52a7e64373..30417eba8bf2 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java @@ -78,6 +78,7 @@ static Implementation wrap( TypeDescription targetType = FieldLocator.ForClassHierarchy.Factory.INSTANCE.make( targetEntity ) .locate( bidirectionalAttributeName ) .getField() + .asDefined() .getType() .asErasure(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/LazyLoadingAndParameterizedInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/LazyLoadingAndParameterizedInheritanceTest.java index e474a1d8b92a..2015c6974961 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/LazyLoadingAndParameterizedInheritanceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/LazyLoadingAndParameterizedInheritanceTest.java @@ -30,6 +30,7 @@ import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; @DomainModel( annotatedClasses = { @@ -101,6 +102,14 @@ public void test(SessionFactoryScope scope) { } ); } + @Test + public void testCollectionWrite() { + Three three = new Three(); + Two two = new Two(); + assertThatNoException().isThrownBy(() -> three.setTwos(Set.of(two))); + assertThat(two.getThree()).isSameAs(three); + } + @Entity(name = "One") public static class One extends AbsOne { } From e6b01f8f50581e58ef87ce3918c6adb167f46b2c Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 22 May 2025 12:59:42 +0200 Subject: [PATCH 025/168] HHH-19476 Add test for issue (cherry picked from commit 7b0ad1931388db3f2a5318cf9bc82eb5a08766a5) --- .../jpa/JpaProxyComplianceEnabledTest.java | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/JpaProxyComplianceEnabledTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/JpaProxyComplianceEnabledTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/JpaProxyComplianceEnabledTest.java new file mode 100644 index 000000000000..fb7fb9050e88 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/JpaProxyComplianceEnabledTest.java @@ -0,0 +1,124 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +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.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@Jpa( + annotatedClasses = { + JpaProxyComplianceEnabledTest.Provider.class, + JpaProxyComplianceEnabledTest.TelephoneNumber.class, + }, + proxyComplianceEnabled = true +) +@JiraKey("HHH-19476") +public class JpaProxyComplianceEnabledTest { + + private static final Integer PROVIDER_ID = 1; + private static final Integer TELEPHONE_NUMBER_ID = 2; + + @BeforeAll + public static void init(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { + Provider provider = new Provider( PROVIDER_ID, "A Provider" ); + entityManager.persist( provider ); + + TelephoneNumber telephoneNumber1 = new TelephoneNumber( + TELEPHONE_NUMBER_ID, + "123-456-7890", + provider + ); + entityManager.persist( telephoneNumber1 ); + } ); + } + + @Test + public void testJoinFetchAfterFind(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { + TelephoneNumber telephoneNumber = entityManager.find( TelephoneNumber.class, TELEPHONE_NUMBER_ID ); + List telephoneNumbers = entityManager.createQuery( + "from TelephoneNumber t join fetch t.provider", + TelephoneNumber.class ) + .getResultList(); + assertThat( telephoneNumbers.size() ).isEqualTo( 1 ); + } + ); + } + + @Entity(name = "TelephoneNumber") + public static class TelephoneNumber { + @Id + private Integer id; + + @Column(name = "phone_number") + private String number; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "provider", nullable = false) + private Provider provider; + + public TelephoneNumber() { + } + + public TelephoneNumber(Integer id, String number, Provider provider) { + this.id = id; + this.number = number; + this.provider = provider; + } + + public Integer getId() { + return id; + } + + public String getNumber() { + return number; + } + + public Provider getProvider() { + return provider; + } + } + + @Entity(name = "Provider") + public static class Provider { + + @Id + private Integer id; + + private String name; + + public Provider() { + } + + public Provider(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + } + +} From dd5321b3c22ae019da0421854b68edd879ee0a97 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 22 May 2025 17:19:08 +0200 Subject: [PATCH 026/168] HHH-19476 claimEntityHolderIfPossible Assertion Error (cherry picked from commit aed0a93b73571e86d944dd397601a7b899ecda2e) --- .../results/graph/entity/internal/EntityInitializerImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java index 6ed4affa6a2d..290ef46ea22a 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java @@ -974,7 +974,7 @@ else if ( lazyInitializer.isUninitialized() ) { ? entityDescriptor : determineConcreteEntityDescriptor( rowProcessingState, discriminatorAssembler, entityDescriptor ); assert data.concreteDescriptor != null; - resolveEntityKey( data, lazyInitializer.getIdentifier() ); + resolveEntityKey( data, lazyInitializer.getInternalIdentifier() ); data.entityHolder = persistenceContext.claimEntityHolderIfPossible( data.entityKey, null, @@ -989,7 +989,7 @@ else if ( lazyInitializer.isUninitialized() ) { else { data.entityInstanceForNotify = lazyInitializer.getImplementation(); data.concreteDescriptor = session.getEntityPersister( null, data.entityInstanceForNotify ); - resolveEntityKey( data, lazyInitializer.getIdentifier() ); + resolveEntityKey( data, lazyInitializer.getInternalIdentifier() ); data.entityHolder = persistenceContext.getEntityHolder( data.entityKey ); // Even though the lazyInitializer reports it is initialized, check if the entity holder reports initialized, // because in a nested initialization scenario, this nested initializer must initialize the entity From 4a94b38ae6512eddace6b0ef3295d7b0c49cfbd9 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Thu, 22 May 2025 12:13:47 +0200 Subject: [PATCH 027/168] HHH-19477 Test aggressive release mode for HQL queries (cherry picked from commit 8133698f7e134bcc61dd099436deade153d94c44) --- .../jdbc/internal/AggressiveReleaseTest.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/internal/AggressiveReleaseTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/internal/AggressiveReleaseTest.java index 6fe9d3de7588..2ec6f226f402 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/internal/AggressiveReleaseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/internal/AggressiveReleaseTest.java @@ -18,6 +18,7 @@ import org.hibernate.testing.boot.BasicTestingJdbcServiceImpl; import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest; +import org.hibernate.testing.orm.junit.Jira; import org.hibernate.testing.orm.junit.RequiresDialect; import org.hibernate.testing.orm.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.jupiter.api.AfterEach; @@ -116,9 +117,9 @@ protected void cleanupTest() throws Exception { @Test public void testBasicRelease() { - connectionProvider.clear(); ResourceRegistry registry = sessionFactoryScope().fromSession( session -> { + connectionProvider.clear(); JdbcCoordinatorImpl jdbcCoord = (JdbcCoordinatorImpl) session.getJdbcCoordinator(); ResourceRegistry resourceRegistry = jdbcCoord.getLogicalConnection().getResourceRegistry(); try { @@ -153,9 +154,9 @@ public void testBasicRelease() { @Test public void testReleaseCircumventedByHeldResources() { - connectionProvider.clear(); ResourceRegistry registry = sessionFactoryScope().fromSession( session -> { + connectionProvider.clear(); JdbcCoordinatorImpl jdbcCoord = (JdbcCoordinatorImpl) session.getJdbcCoordinator(); ResourceRegistry resourceRegistry = jdbcCoord.getLogicalConnection().getResourceRegistry(); @@ -217,10 +218,9 @@ public void testReleaseCircumventedByHeldResources() { @Test public void testReleaseCircumventedManually() { - connectionProvider.clear(); - connectionProvider.clear(); ResourceRegistry registry = sessionFactoryScope().fromSession( session -> { + connectionProvider.clear(); JdbcCoordinatorImpl jdbcCoord = (JdbcCoordinatorImpl) session.getJdbcCoordinator(); ResourceRegistry resourceRegistry = jdbcCoord.getLogicalConnection().getResourceRegistry(); @@ -273,4 +273,20 @@ public void testReleaseCircumventedManually() { assertEquals( 0, connectionProvider.getAcquiredConnections().size() ); assertEquals( 2, connectionProvider.getReleasedConnections().size() ); } + + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19477") + public void testHql() { + sessionFactoryScope().inTransaction( session -> { + connectionProvider.clear(); + JdbcCoordinatorImpl jdbcCoord = (JdbcCoordinatorImpl) session.getJdbcCoordinator(); + ResourceRegistry resourceRegistry = jdbcCoord.getLogicalConnection().getResourceRegistry(); + + session.createSelectionQuery( "select 1" ).uniqueResult(); + + assertFalse( resourceRegistry.hasRegisteredResources() ); + assertEquals( 0, connectionProvider.getAcquiredConnections().size() ); + assertEquals( 1, connectionProvider.getReleasedConnections().size() ); + } ); + } } From 505e10ddc5cab950e4ccd40e70d9e1bc92329c28 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 20 May 2025 16:37:00 +0200 Subject: [PATCH 028/168] HHH-19477 Add missing JdbcCoordinator#afterStatement calls to enable aggressive connection release (cherry picked from commit 83a88f6f27c9d018dc86f540775dfc245a782798) --- .../temptable/TemporaryTableHelper.java | 9 ++-- .../PreparedStatementDetailsStandard.java | 5 +- .../id/insert/AbstractSelectingDelegate.java | 1 - .../id/insert/GetGeneratedKeysDelegate.java | 1 - .../mutation/AbstractDeleteCoordinator.java | 25 +++++----- .../procedure/internal/ProcedureCallImpl.java | 1 + .../internal/ProcedureOutputsImpl.java | 6 --- .../result/internal/OutputsImpl.java | 5 +- .../model/jdbc/DeleteOrUpsertOperation.java | 47 ++++++++++++------- .../jdbc/OptionalTableUpdateOperation.java | 29 ++++++++---- .../internal/DeferredResultSetAccess.java | 9 ++-- 11 files changed, 83 insertions(+), 55 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTableHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTableHelper.java index 21d6a17a5226..fa7f9b0cca39 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTableHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTableHelper.java @@ -12,6 +12,7 @@ import java.util.function.Function; import org.hibernate.engine.jdbc.internal.FormatStyle; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; @@ -147,15 +148,16 @@ public static void cleanTemporaryTableRows( TemporaryTableExporter exporter, Function sessionUidAccess, SharedSessionContractImplementor session) { + final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); PreparedStatement preparedStatement = null; try { final String sql = exporter.getSqlTruncateCommand( temporaryTable, sessionUidAccess, session ); - preparedStatement = session.getJdbcCoordinator().getStatementPreparer().prepareStatement( sql ); + preparedStatement = jdbcCoordinator.getStatementPreparer().prepareStatement( sql ); if ( temporaryTable.getSessionUidColumn() != null ) { final String sessionUid = sessionUidAccess.apply( session ); preparedStatement.setString( 1, sessionUid ); } - session.getJdbcCoordinator().getResultSetReturn().executeUpdate( preparedStatement, sql ); + jdbcCoordinator.getResultSetReturn().executeUpdate( preparedStatement, sql ); } catch( Throwable t ) { log.unableToCleanupTemporaryIdTable(t); @@ -163,11 +165,12 @@ public static void cleanTemporaryTableRows( finally { if ( preparedStatement != null ) { try { - session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( preparedStatement ); + jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( preparedStatement ); } catch( Throwable ignore ) { // ignore } + jdbcCoordinator.afterStatementExecution(); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/mutation/internal/PreparedStatementDetailsStandard.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/mutation/internal/PreparedStatementDetailsStandard.java index 765c5c8cae70..e4616aaffd2b 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/mutation/internal/PreparedStatementDetailsStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/mutation/internal/PreparedStatementDetailsStandard.java @@ -10,6 +10,7 @@ import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; import org.hibernate.engine.jdbc.mutation.group.PreparedStatementGroup; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.jdbc.Expectation; @@ -66,9 +67,11 @@ public TableMapping getMutatingTableDetails() { @Override public void releaseStatement(SharedSessionContractImplementor session) { if ( statement != null ) { - session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( statement ); + final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); + jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( statement ); statement = null; toRelease = false; + jdbcCoordinator.afterStatementExecution(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractSelectingDelegate.java b/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractSelectingDelegate.java index f771354afe4a..c76aa5601bbd 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractSelectingDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractSelectingDelegate.java @@ -86,7 +86,6 @@ public GeneratedValues performMutation( statementDetails.releaseStatement( session ); } jdbcValueBindings.afterStatement( statementDetails.getMutatingTableDetails() ); - session.getJdbcCoordinator().afterStatementExecution(); } // the insert is complete, select the generated id... diff --git a/hibernate-core/src/main/java/org/hibernate/id/insert/GetGeneratedKeysDelegate.java b/hibernate-core/src/main/java/org/hibernate/id/insert/GetGeneratedKeysDelegate.java index 3f54dae743e0..6ba7d3c69861 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/insert/GetGeneratedKeysDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/id/insert/GetGeneratedKeysDelegate.java @@ -141,7 +141,6 @@ public GeneratedValues performMutation( statementDetails.releaseStatement( session ); } jdbcValueBindings.afterStatement( statementDetails.getMutatingTableDetails() ); - jdbcCoordinator.afterStatementExecution(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/AbstractDeleteCoordinator.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/AbstractDeleteCoordinator.java index 861e20ec01d6..954dee81df7a 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/AbstractDeleteCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/AbstractDeleteCoordinator.java @@ -273,17 +273,20 @@ protected void doStaticDelete( session ); - mutationExecutor.execute( - entity, - null, - null, - (statementDetails, affectedRowCount, batchPosition) -> - resultCheck( id, statementDetails, affectedRowCount, batchPosition ), - session, - staleStateException -> staleObjectState( id, staleStateException ) - ); - - mutationExecutor.release(); + try { + mutationExecutor.execute( + entity, + null, + null, + (statementDetails, affectedRowCount, batchPosition) -> + resultCheck( id, statementDetails, affectedRowCount, batchPosition ), + session, + staleStateException -> staleObjectState( id, staleStateException ) + ); + } + finally { + mutationExecutor.release(); + } } private StaleObjectStateException staleObjectState(Object id, StaleStateException staleStateException) { diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java index 571e9eb9855b..03573f9fa780 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java @@ -672,6 +672,7 @@ private ProcedureOutputsImpl buildOutputs() { } catch (SQLException e) { jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( statement ); + getSession().getJdbcCoordinator().afterStatementExecution(); throw jdbcServices.getSqlExceptionHelper().convert( e, "Error registering CallableStatement parameters", diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java index 5a952ce4692f..d4686e03774f 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java @@ -145,10 +145,4 @@ protected Output buildFunctionReturn() { return buildResultSetOutput( () -> results ); } } - - @Override - public void release() { - super.release(); - getResultContext().getSession().getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( callableStatement ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/result/internal/OutputsImpl.java b/hibernate-core/src/main/java/org/hibernate/result/internal/OutputsImpl.java index cd5ab76833ab..4a46a3999bc8 100644 --- a/hibernate-core/src/main/java/org/hibernate/result/internal/OutputsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/result/internal/OutputsImpl.java @@ -14,6 +14,7 @@ import java.util.function.Supplier; import org.hibernate.JDBCException; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.event.monitor.spi.EventMonitor; @@ -135,7 +136,9 @@ public boolean goToNext() { @Override public void release() { - context.getSession().getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( jdbcStatement ); + final JdbcCoordinator jdbcCoordinator = context.getSession().getJdbcCoordinator(); + jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( jdbcStatement ); + jdbcCoordinator.afterStatementExecution(); } private List extractCurrentResults() { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/DeleteOrUpsertOperation.java b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/DeleteOrUpsertOperation.java index 9d99e6135878..6af41093defa 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/DeleteOrUpsertOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/DeleteOrUpsertOperation.java @@ -122,19 +122,25 @@ private void performDelete(JdbcValueBindings jdbcValueBindings, SharedSessionCon final PreparedStatementGroupSingleTable statementGroup = new PreparedStatementGroupSingleTable( upsertDelete, session ); final PreparedStatementDetails statementDetails = statementGroup.resolvePreparedStatementDetails( tableMapping.getTableName() ); - final PreparedStatement upsertDeleteStatement = statementDetails.resolveStatement(); - session.getJdbcServices().getSqlStatementLogger().logStatement( statementDetails.getSqlString() ); - - bindDeleteKeyValues( - jdbcValueBindings, - optionalTableUpdate.getParameters(), - statementDetails, - session - ); - final int rowCount = session.getJdbcCoordinator().getResultSetReturn() - .executeUpdate( upsertDeleteStatement, statementDetails.getSqlString() ); - MODEL_MUTATION_LOGGER.tracef( "`%s` rows upsert-deleted from `%s`", rowCount, tableMapping.getTableName() ); + try { + final PreparedStatement upsertDeleteStatement = statementDetails.resolveStatement(); + session.getJdbcServices().getSqlStatementLogger().logStatement( statementDetails.getSqlString() ); + + bindDeleteKeyValues( + jdbcValueBindings, + optionalTableUpdate.getParameters(), + statementDetails, + session + ); + + final int rowCount = session.getJdbcCoordinator().getResultSetReturn() + .executeUpdate( upsertDeleteStatement, statementDetails.getSqlString() ); + MODEL_MUTATION_LOGGER.tracef( "`%s` rows upsert-deleted from `%s`", rowCount, tableMapping.getTableName() ); + } + finally { + statementDetails.releaseStatement( session ); + } } private void bindDeleteKeyValues( @@ -196,15 +202,20 @@ private void performUpsert(JdbcValueBindings jdbcValueBindings, SharedSessionCon final PreparedStatementGroupSingleTable statementGroup = new PreparedStatementGroupSingleTable( upsertOperation, session ); final PreparedStatementDetails statementDetails = statementGroup.resolvePreparedStatementDetails( tableMapping.getTableName() ); - final PreparedStatement updateStatement = statementDetails.resolveStatement(); - session.getJdbcServices().getSqlStatementLogger().logStatement( statementDetails.getSqlString() ); + try { + final PreparedStatement updateStatement = statementDetails.resolveStatement(); + session.getJdbcServices().getSqlStatementLogger().logStatement( statementDetails.getSqlString() ); - jdbcValueBindings.beforeStatement( statementDetails ); + jdbcValueBindings.beforeStatement( statementDetails ); - final int rowCount = session.getJdbcCoordinator().getResultSetReturn() - .executeUpdate( updateStatement, statementDetails.getSqlString() ); + final int rowCount = session.getJdbcCoordinator().getResultSetReturn() + .executeUpdate( updateStatement, statementDetails.getSqlString() ); - MODEL_MUTATION_LOGGER.tracef( "`%s` rows upserted into `%s`", rowCount, tableMapping.getTableName() ); + MODEL_MUTATION_LOGGER.tracef( "`%s` rows upserted into `%s`", rowCount, tableMapping.getTableName() ); + } + finally { + statementDetails.releaseStatement( session ); + } } /* diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java index c4b5e312a7bd..e34a5fbf339a 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java @@ -170,13 +170,20 @@ public void performMutation( private void performDelete(JdbcValueBindings jdbcValueBindings, SharedSessionContractImplementor session) { final JdbcDeleteMutation jdbcDelete = createJdbcDelete( session ); - final PreparedStatement deleteStatement = createStatementDetails( jdbcDelete, session ); + final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); + final PreparedStatement deleteStatement = createStatementDetails( jdbcDelete, jdbcCoordinator ); session.getJdbcServices().getSqlStatementLogger().logStatement( jdbcDelete.getSqlString() ); bindKeyValues( jdbcValueBindings, deleteStatement, jdbcDelete, session ); - session.getJdbcCoordinator().getResultSetReturn() - .executeUpdate( deleteStatement, jdbcDelete.getSqlString() ); + try { + session.getJdbcCoordinator().getResultSetReturn() + .executeUpdate( deleteStatement, jdbcDelete.getSqlString() ); + } + finally { + jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( deleteStatement ); + jdbcCoordinator.afterStatementExecution(); + } } private void bindKeyValues( @@ -343,6 +350,9 @@ private boolean performUpdate( statementDetails.getSqlString() ); } + finally { + statementDetails.releaseStatement( session ); + } } /* @@ -386,7 +396,8 @@ protected JdbcMutationOperation createJdbcUpdate(SharedSessionContractImplemento private void performInsert(JdbcValueBindings jdbcValueBindings, SharedSessionContractImplementor session) { final JdbcInsertMutation jdbcInsert = createJdbcInsert( session ); - final PreparedStatement insertStatement = createStatementDetails( jdbcInsert, session ); + final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); + final PreparedStatement insertStatement = createStatementDetails( jdbcInsert, jdbcCoordinator ); try { session.getJdbcServices().getSqlStatementLogger().logStatement( jdbcInsert.getSqlString() ); @@ -415,11 +426,12 @@ private void performInsert(JdbcValueBindings jdbcValueBindings, SharedSessionCon } ); } - session.getJdbcCoordinator().getResultSetReturn() + jdbcCoordinator.getResultSetReturn() .executeUpdate( insertStatement, jdbcInsert.getSqlString() ); } finally { - session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( insertStatement ); + jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( insertStatement ); + jdbcCoordinator.afterStatementExecution(); } } @@ -463,11 +475,10 @@ protected JdbcInsertMutation createJdbcInsert(SharedSessionContractImplementor s private static PreparedStatement createStatementDetails( PreparableMutationOperation operation, - SharedSessionContractImplementor session) { - final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); + JdbcCoordinator jdbcCoordinator) { final MutationStatementPreparer statementPreparer = jdbcCoordinator.getMutationStatementPreparer(); final PreparedStatement statement = statementPreparer.prepareStatement( operation.getSqlString(), false ); - session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().register( null, statement ); + jdbcCoordinator.getLogicalConnection().getResourceRegistry().register( null, statement ); return statement; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java index 92e7dba62a25..9f8dfb979251 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java @@ -14,6 +14,7 @@ import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.NoopLimitHandler; import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.engine.spi.SessionEventListenerManager; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -333,8 +334,9 @@ protected LockMode determineFollowOnLockMode(LockOptions lockOptions) { @Override public void release() { - final LogicalConnectionImplementor logicalConnection = - getPersistenceContext().getJdbcCoordinator().getLogicalConnection(); + final JdbcCoordinator jdbcCoordinator = + getPersistenceContext().getJdbcCoordinator(); + final LogicalConnectionImplementor logicalConnection = jdbcCoordinator.getLogicalConnection(); if ( resultSet != null ) { logicalConnection.getResourceRegistry().release( resultSet, preparedStatement ); resultSet = null; @@ -343,9 +345,8 @@ public void release() { if ( preparedStatement != null ) { logicalConnection.getResourceRegistry().release( preparedStatement ); preparedStatement = null; + jdbcCoordinator.afterStatementExecution(); } - - logicalConnection.afterStatement(); } @Override From 34748ede907a8ffc0b86621ba195ed8cc8815160 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 23 May 2025 09:03:55 +0200 Subject: [PATCH 029/168] HHH-19479 automatically make referenced column unique this was happening for multi-column references, but not for single columns (cherry picked from commit b6e5d6fc4b56c49300264bca52d5e5a6234356f7) --- .../boot/model/internal/BinderHelper.java | 11 ++- .../boot/model/internal/IndexBinder.java | 8 +- .../boot/model/internal/TableBinder.java | 31 +++++++- .../org/hibernate/mapping/SimpleValue.java | 2 +- .../java/org/hibernate/mapping/Table.java | 74 ++++++++----------- 5 files changed, 79 insertions(+), 47 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java index 3454689f186c..6ff03bdcc189 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java @@ -224,6 +224,12 @@ private static void checkColumnInSameTable( * considered the target of the association. This method adds * the property holding the synthetic component to the target * entity {@link PersistentClass} by side effect. + *

+ * This method automatically marks the reference column unique, + * or creates a unique key on the referenced columns. It's not + * really clear that we should do this. Perhaps we should just + * validate that they are unique and error if not, like in + * {@code TableBinder.checkReferenceToUniqueKey()}. */ private static Property referencedProperty( PersistentClass ownerEntity, @@ -238,7 +244,10 @@ private static Property referencedProperty( && ownerEntity == columnOwner && !( properties.get(0).getValue() instanceof ToOne ) ) { // no need to make a synthetic property - return properties.get(0); + final Property property = properties.get( 0 ); + // mark it unique + property.getValue().createUniqueKey( context ); + return property; } else { // Create a synthetic Property whose Value is a synthetic diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IndexBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IndexBinder.java index ab867157fc71..19e54e7a8fb6 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IndexBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IndexBinder.java @@ -10,6 +10,7 @@ import java.util.StringTokenizer; import org.hibernate.AnnotationException; +import org.hibernate.AssertionFailure; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.ImplicitIndexNameSource; import org.hibernate.boot.model.naming.ImplicitNamingStrategy; @@ -175,7 +176,12 @@ private void createIndexOrUniqueKey( uniqueKey.setNameExplicit( nameExplicit ); uniqueKey.setOptions( options ); for ( int i = 0; i < columns.length; i++ ) { - uniqueKey.addColumn( (Column) columns[i], orderings != null ? orderings[i] : null ); + if ( columns[i] instanceof Column column) { + uniqueKey.addColumn( column, orderings != null ? orderings[i] : null ); + } + else { + throw new AssertionFailure( "Not a column" ); + } } } else { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java index 9c54b2022cfe..e2e0aafd328e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java @@ -34,6 +34,7 @@ import org.hibernate.mapping.SortableValue; import org.hibernate.mapping.Table; import org.hibernate.mapping.ToOne; +import org.hibernate.mapping.UniqueKey; import org.hibernate.mapping.Value; import jakarta.persistence.Index; @@ -752,7 +753,35 @@ else if ( value instanceof DependantValue ) { + referencedEntity.getEntityName() + "." + referencedPropertyName ); } linkJoinColumnWithValueOverridingNameIfImplicit( referencedEntity, synthProp.getValue(), joinColumns, value ); - ( (SortableValue) value).sortProperties(); + checkReferenceToUniqueKey( value, synthProp ); + ( (SortableValue) value ).sortProperties(); + } + + // This code is good but unnecessary, because BinderHelper.referencedProperty() + // automatically marks the referenced property unique (but is this actually good?) + private static void checkReferenceToUniqueKey(SimpleValue value, Property synthProp) { + final Table table = synthProp.getValue().getTable(); + final List columns = synthProp.getValue().getConstraintColumns(); + if ( columns.size() == 1 ) { + final Column column = columns.get( 0 ); + assert column.isUnique(); +// if ( !column.isUnique() ) { +// throw new MappingException( "Referenced column '" + column.getName() +// + "' in table '" + table.getName() + "' is not unique" +// + " ('@JoinColumn' must reference a unique key)" ); +// } + } + else { + final UniqueKey uniqueKey = new UniqueKey( table ); + for ( Column column : columns ) { + uniqueKey.addColumn( column ); + } + assert table.isRedundantUniqueKey( uniqueKey ); +// if ( !table.isRedundantUniqueKey( uniqueKey ) ) { +// throw new MappingException( "Referenced columns in table '" + table.getName() + "' are not unique" +// + " ('@JoinColumn's must reference a unique key" ); +// } + } } private static void bindImplicitColumns( diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java index 2eeae2602a7c..6ec39cabe7ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -354,7 +354,7 @@ public ForeignKey createForeignKeyOfEntity(String entityName) { @Override public void createUniqueKey(MetadataBuildingContext context) { if ( hasFormula() ) { - throw new MappingException( "unique key constraint involves formulas" ); + throw new MappingException( "Unique key constraint involves formulas" ); } getTable().createUniqueKey( getConstraintColumns(), context ); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java index a7417a2ae785..9ea8b5f47b91 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java @@ -297,7 +297,7 @@ public void addColumn(Column column) { @Internal public void columnRenamed(Column column) { - for ( Map.Entry entry : columns.entrySet() ) { + for ( var entry : columns.entrySet() ) { if ( entry.getValue() == column ) { columns.remove( entry.getKey() ); columns.put( column.getCanonicalName(), column ); @@ -330,12 +330,10 @@ public Map getUniqueKeys() { private int sizeOfUniqueKeyMapOnLastCleanse; private void cleanseUniqueKeyMapIfNeeded() { - if ( uniqueKeys.size() == sizeOfUniqueKeyMapOnLastCleanse ) { - // nothing to do - return; + if ( uniqueKeys.size() != sizeOfUniqueKeyMapOnLastCleanse ) { + cleanseUniqueKeyMap(); + sizeOfUniqueKeyMapOnLastCleanse = uniqueKeys.size(); } - cleanseUniqueKeyMap(); - sizeOfUniqueKeyMapOnLastCleanse = uniqueKeys.size(); } private void cleanseUniqueKeyMap() { @@ -351,57 +349,47 @@ private void cleanseUniqueKeyMap() { if ( !uniqueKeys.isEmpty() ) { if ( uniqueKeys.size() == 1 ) { // we have to worry about condition 2 above, but not condition 1 - final Map.Entry uniqueKeyEntry = uniqueKeys.entrySet().iterator().next(); + final var uniqueKeyEntry = uniqueKeys.entrySet().iterator().next(); if ( isSameAsPrimaryKeyColumns( uniqueKeyEntry.getValue() ) ) { uniqueKeys.remove( uniqueKeyEntry.getKey() ); } } else { // we have to check both conditions 1 and 2 - final Iterator> uniqueKeyEntries = uniqueKeys.entrySet().iterator(); - while ( uniqueKeyEntries.hasNext() ) { - final Map.Entry uniqueKeyEntry = uniqueKeyEntries.next(); - final UniqueKey uniqueKey = uniqueKeyEntry.getValue(); - boolean removeIt = false; - - // Never remove explicit unique keys based on column matching - if ( !uniqueKey.isExplicit() ) { - // condition 1 : check against other unique keys - for ( UniqueKey otherUniqueKey : uniqueKeys.values() ) { - // make sure it's not the same unique key - if ( uniqueKeyEntry.getValue() == otherUniqueKey ) { - continue; - } - if ( otherUniqueKey.getColumns().containsAll( uniqueKey.getColumns() ) - && uniqueKey.getColumns().containsAll( otherUniqueKey.getColumns() ) ) { - removeIt = true; - break; - } - } - } + //uniqueKeys.remove( uniqueKeyEntry.getKey() ); + uniqueKeys.entrySet().removeIf( entry -> isRedundantUniqueKey( entry.getValue() ) ); + } + } + } - // condition 2 : check against pk - if ( !removeIt && isSameAsPrimaryKeyColumns( uniqueKeyEntry.getValue() ) ) { - primaryKey.setOrderingUniqueKey(uniqueKeyEntry.getValue()); - removeIt = true; - } + public boolean isRedundantUniqueKey(UniqueKey uniqueKey) { - if ( removeIt ) { - //uniqueKeys.remove( uniqueKeyEntry.getKey() ); - uniqueKeyEntries.remove(); - } + // Never remove explicit unique keys based on column matching + if ( !uniqueKey.isExplicit() ) { + // condition 1 : check against other unique keys + for ( UniqueKey otherUniqueKey : uniqueKeys.values() ) { + // make sure it's not the same unique key + if ( uniqueKey != otherUniqueKey + && otherUniqueKey.getColumns().containsAll( uniqueKey.getColumns() ) + && uniqueKey.getColumns().containsAll( otherUniqueKey.getColumns() ) ) { + return true; } } } + + // condition 2 : check against pk + if ( isSameAsPrimaryKeyColumns( uniqueKey ) ) { + primaryKey.setOrderingUniqueKey( uniqueKey ); + return true; + } + + return false; } private boolean isSameAsPrimaryKeyColumns(UniqueKey uniqueKey) { - if ( primaryKey == null || primaryKey.getColumns().isEmpty() ) { - // happens for many-to-many tables - return false; - } - return primaryKey.getColumns().containsAll( uniqueKey.getColumns() ) - && primaryKey.getColumns().size() == uniqueKey.getColumns().size(); + return primaryKey != null && !primaryKey.getColumns().isEmpty() // happens for many-to-many tables + && primaryKey.getColumns().size() == uniqueKey.getColumns().size() + && primaryKey.getColumns().containsAll( uniqueKey.getColumns() ); } @Override From 1af2f9824a4da0c1f656038bb42b75c5fed41f12 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 23 May 2025 12:01:57 +0200 Subject: [PATCH 030/168] HHH-19479 add test for issue (cherry picked from commit de5c9cd2e91572e2519caa67935b2650a4167709) --- .../ManyToOneRefColumnNameTest.java | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/annotations/refcolnames/constraints/ManyToOneRefColumnNameTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/refcolnames/constraints/ManyToOneRefColumnNameTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/refcolnames/constraints/ManyToOneRefColumnNameTest.java new file mode 100644 index 000000000000..ba70ddac7afe --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/refcolnames/constraints/ManyToOneRefColumnNameTest.java @@ -0,0 +1,129 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.annotations.refcolnames.constraints; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.RollbackException; +import org.hibernate.exception.ConstraintViolationException; +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 static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.fail; + +@Jpa(annotatedClasses = {ManyToOneRefColumnNameTest.This.class, ManyToOneRefColumnNameTest.That.class}) +@JiraKey("HHH-19479") +class ManyToOneRefColumnNameTest { + + @Test void test(EntityManagerFactoryScope scope) { + scope.getEntityManagerFactory().getSchemaManager().truncate(); + This thisThing = new This(); + That thatThing = new That(); + thatThing.compositeKeyOne = 1; + thatThing.compositeKeyTwo = 2; + thatThing.singleKey = "hello"; + thisThing.thatBySingleKey = thatThing; + thisThing.thatByCompositeKey = thatThing; + scope.inTransaction( em -> { + em.persist( thatThing ); + em.persist( thisThing ); + } ); + That thing = new That(); + thing.singleKey = "goodbye"; + thing.compositeKeyOne = 5; + thing.compositeKeyTwo = 3; + scope.inTransaction( em -> { + em.persist( thing ); + } ); + } + + @Test void testUnique(EntityManagerFactoryScope scope) { + scope.getEntityManagerFactory().getSchemaManager().truncate(); + This thisThing = new This(); + That thatThing = new That(); + thatThing.compositeKeyOne = 1; + thatThing.compositeKeyTwo = 2; + thatThing.singleKey = "hello"; + thisThing.thatBySingleKey = thatThing; + thisThing.thatByCompositeKey = thatThing; + scope.inTransaction( em -> { + em.persist( thatThing ); + em.persist( thisThing ); + } ); + That thing = new That(); + try { + thing.singleKey = "hello"; + scope.inTransaction( em -> { + em.persist( thing ); + } ); + fail(); + } + catch (RollbackException re) { + assertInstanceOf( ConstraintViolationException.class, re.getCause() ); + } + } + + @Test void testUniqueKey(EntityManagerFactoryScope scope) { + scope.getEntityManagerFactory().getSchemaManager().truncate(); + This thisThing = new This(); + That thatThing = new That(); + thatThing.compositeKeyOne = 1; + thatThing.compositeKeyTwo = 2; + thatThing.singleKey = "hello"; + thisThing.thatBySingleKey = thatThing; + thisThing.thatByCompositeKey = thatThing; + scope.inTransaction( em -> { + em.persist( thatThing ); + em.persist( thisThing ); + } ); + That thing = new That(); + thing.singleKey = "goodbye"; + thing.compositeKeyOne = 1; + thing.compositeKeyTwo = 2; + try { + scope.inTransaction( em -> { + em.persist( thing ); + } ); + fail(); + } + catch (RollbackException re) { + assertInstanceOf( ConstraintViolationException.class, re.getCause() ); + } + } + + @Entity + static class That { + @Id @GeneratedValue + long id; + + @Column(nullable = false) + String singleKey; + + int compositeKeyOne; + int compositeKeyTwo; + } + + @Entity + static class This { + @Id @GeneratedValue + long id; + + @JoinColumn(referencedColumnName = "singleKey") + @ManyToOne + That thatBySingleKey; + + @JoinColumn(referencedColumnName = "compositeKeyOne") + @JoinColumn(referencedColumnName = "compositeKeyTwo") + @ManyToOne + That thatByCompositeKey; + } +} From af5a8ded871b2f7502b3178cfbc6bbaafcef97e4 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 23 May 2025 12:16:44 +0200 Subject: [PATCH 031/168] Constraints should be consistently created with a Table (cherry picked from commit 7c6b7c69fbbb8acac9daf94cd57ec48af6d52792) --- .../src/main/java/org/hibernate/mapping/Constraint.java | 7 +++++++ .../src/main/java/org/hibernate/mapping/ForeignKey.java | 5 +++-- .../src/main/java/org/hibernate/mapping/PrimaryKey.java | 5 +++-- .../src/main/java/org/hibernate/mapping/UniqueKey.java | 5 +++-- .../orm/test/dialect/PostgreSQLDialectTestCase.java | 3 +-- .../orm/test/schemaupdate/ExportIdentifierTest.java | 6 ++---- 6 files changed, 19 insertions(+), 12 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java b/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java index c615dcbd9ffd..221d770159e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java @@ -24,6 +24,12 @@ public abstract class Constraint implements Exportable, Serializable { private Table table; private String options = ""; + Constraint() {} + + Constraint(Table table) { + this.table = table; + } + public String getName() { return name; } @@ -76,6 +82,7 @@ public Table getTable() { return table; } + @Deprecated(since = "7") public void setTable(Table table) { this.table = table; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java b/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java index dd4589d1b5d0..8568c9be0d58 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java @@ -29,10 +29,11 @@ public class ForeignKey extends Constraint { private final List referencedColumns = new ArrayList<>(); private boolean creationEnabled = true; - public ForeignKey(Table table){ - setTable( table ); + public ForeignKey(Table table) { + super( table ); } + @Deprecated(since = "7") public ForeignKey() { } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java b/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java index 33c21e839622..12db9bbd96f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java @@ -22,10 +22,11 @@ public class PrimaryKey extends Constraint { private UniqueKey orderingUniqueKey = null; private int[] originalOrder; - public PrimaryKey(Table table){ - setTable( table ); + public PrimaryKey(Table table) { + super( table ); } + @Deprecated(since = "7") public PrimaryKey() { } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java b/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java index 27b8fca3afb9..2a3d62a3364c 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java @@ -22,10 +22,11 @@ public class UniqueKey extends Constraint { private boolean nameExplicit; // true when the constraint name was explicitly specified by @UniqueConstraint annotation private boolean explicit; // true when the constraint was explicitly specified by @UniqueConstraint annotation - public UniqueKey(Table table){ - setTable( table ); + public UniqueKey(Table table) { + super( table ); } + @Deprecated(since = "7") public UniqueKey() { } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/PostgreSQLDialectTestCase.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/PostgreSQLDialectTestCase.java index 75afc583393f..ea33710414f8 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/PostgreSQLDialectTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/PostgreSQLDialectTestCase.java @@ -143,9 +143,8 @@ public void testAlterTableDropConstraintString() { PostgreSQLDialect dialect = new PostgreSQLDialect(); AlterTableUniqueDelegate alterTable = new AlterTableUniqueDelegate( dialect ); final Table table = new Table( "orm", "table_name" ); - final UniqueKey uniqueKey = new UniqueKey(); + final UniqueKey uniqueKey = new UniqueKey( table ); uniqueKey.setName( "unique_something" ); - uniqueKey.setTable( table ); final String sql = alterTable.getAlterTableToDropUniqueKeyCommand( uniqueKey, null, diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/ExportIdentifierTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/ExportIdentifierTest.java index 86d8dff462ae..25a425492221 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/ExportIdentifierTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/ExportIdentifierTest.java @@ -88,9 +88,8 @@ private void addTables( final Table table = new Table( "orm", namespace, Identifier.toIdentifier( name ), false ); addExportIdentifier( table, exportIdentifierList, exportIdentifierSet ); - final ForeignKey foreignKey = new ForeignKey(); + final ForeignKey foreignKey = new ForeignKey( table ); foreignKey.setName( name ); - foreignKey.setTable( table ); addExportIdentifier( foreignKey, exportIdentifierList, exportIdentifierSet ); final Index index = new Index(); @@ -102,9 +101,8 @@ private void addTables( primaryKey.setName( name ); addExportIdentifier( primaryKey, exportIdentifierList, exportIdentifierSet ); - final UniqueKey uniqueKey = new UniqueKey(); + final UniqueKey uniqueKey = new UniqueKey( table ); uniqueKey.setName( name ); - uniqueKey.setTable( table ); addExportIdentifier( uniqueKey, exportIdentifierList, exportIdentifierSet ); } } From 627ff9dfde9a7887cdbd2efd1172534f04126a7f Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 23 May 2025 13:18:58 +0200 Subject: [PATCH 032/168] clean up handling of ForeignKey access from Table (cherry picked from commit 8fb6a0e1fc6605dd1abd8604f78eb2dbfb7f2255) --- .../InFlightMetadataCollectorImpl.java | 2 +- .../hibernate/boot/internal/MetadataImpl.java | 2 +- .../dialect/SpannerDialectTableExporter.java | 2 +- .../hibernate/mapping/DenormalizedTable.java | 2 +- .../java/org/hibernate/mapping/Table.java | 34 +++++++++++------ .../internal/AbstractSchemaMigrator.java | 2 +- .../schema/internal/SchemaCreatorImpl.java | 2 +- .../schema/internal/SchemaDropperImpl.java | 2 +- .../schema/internal/SchemaTruncatorImpl.java | 4 +- .../DefaultNamingCollectionElementTest.java | 23 +++++------ .../ManyToManyImplicitNamingTest.java | 7 +--- .../LongKeyNamingStrategyTest.java | 8 ++-- .../AbstractCharsetNamingStrategyTest.java | 8 ++-- .../OverrideOneToOneJoinColumnTest.java | 2 +- .../orm/test/constraint/ConstraintTest.java | 15 +++----- .../ForeignKeyConstraintMapsIdTest.java | 5 +-- .../constraint/ForeignKeyConstraintTest.java | 38 ++++++++----------- .../ForeignKeyNoConstraintTest.java | 4 +- .../NonRootTablePolymorphicTests.java | 8 ++-- .../orm/test/foreignkeys/HHH14230.java | 2 +- .../disabled/DefaultConstraintModeTest.java | 4 +- .../disabled/DisabledForeignKeyTest.java | 5 +-- .../OneToManyBidirectionalForeignKeyTest.java | 4 +- ...QualifiedEntityNameNamingStrategyTest.java | 6 +-- .../propertyref/basic/PropertyRefTest.java | 19 ++++------ .../StandardForeignKeyExporterTest.java | 10 +++-- .../EmbeddedIdManyToOneForeignKeyTest.java | 2 +- 27 files changed, 101 insertions(+), 121 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java index 42a0b3a3c65f..c53f4918856f 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java @@ -1941,7 +1941,7 @@ protected void secondPassCompileForeignKeys(Table table, Set done, M table.createForeignKeys( buildingContext ); final Dialect dialect = getDatabase().getJdbcEnvironment().getDialect(); - for ( ForeignKey foreignKey : table.getForeignKeys().values() ) { + for ( ForeignKey foreignKey : table.getForeignKeyCollection() ) { if ( !done.contains( foreignKey ) ) { done.add( foreignKey ); final PersistentClass referencedClass = foreignKey.resolveReferencedClass(this); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java index 4af6c2b31045..8e12c2900be6 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java @@ -408,7 +408,7 @@ private void handleUDT(UserDefinedType userDefinedType, ColumnOrderingStrategy c } private void handleForeignKeys(Table table, ColumnOrderingStrategy columnOrderingStrategy) { - for ( ForeignKey foreignKey : table.getForeignKeys().values() ) { + for ( ForeignKey foreignKey : table.getForeignKeyCollection() ) { final List columns = foreignKey.getColumns(); if ( columns.size() > 1 ) { if ( foreignKey.getReferencedColumns().isEmpty() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialectTableExporter.java b/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialectTableExporter.java index b41ac8cc295e..a5e9d0dc339a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialectTableExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialectTableExporter.java @@ -53,7 +53,7 @@ public String[] getSqlCreateStrings(Table table, Metadata metadata, SqlStringGen // a typical table that corresponds to an entity type keyColumns = table.getPrimaryKey().getColumns(); } - else if ( !table.getForeignKeys().isEmpty() ) { + else if ( !table.getForeignKeyCollection().isEmpty() ) { // a table with no PK's but has FK's; often corresponds to element collection properties keyColumns = table.getColumns(); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/DenormalizedTable.java b/hibernate-core/src/main/java/org/hibernate/mapping/DenormalizedTable.java index 8ff01b351c42..9118803aee97 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/DenormalizedTable.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/DenormalizedTable.java @@ -60,7 +60,7 @@ public DenormalizedTable( @Override public void createForeignKeys(MetadataBuildingContext context) { includedTable.createForeignKeys( context ); - for ( ForeignKey foreignKey : includedTable.getForeignKeys().values() ) { + for ( ForeignKey foreignKey : includedTable.getForeignKeyCollection() ) { final PersistentClass referencedClass = foreignKey.resolveReferencedClass( context.getMetadataCollector() ); // the ForeignKeys created in the first pass did not have their referenced table initialized diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java index 9ea8b5f47b91..758cb7af2273 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java @@ -15,6 +15,7 @@ import java.util.Objects; import java.util.function.Function; +import org.hibernate.Incubating; import org.hibernate.Internal; import org.hibernate.MappingException; import org.hibernate.boot.model.naming.Identifier; @@ -32,6 +33,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static java.util.Collections.unmodifiableCollection; import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableMap; import static java.util.stream.Collectors.toList; @@ -318,6 +320,15 @@ public Map getIndexes() { return unmodifiableMap( indexes ); } + @Incubating + public Collection getForeignKeyCollection() { + return unmodifiableCollection( foreignKeys.values() ); + } + + /** + * @deprecated because {@link ForeignKeyKey} should be private. + */ + @Deprecated(since = "7", forRemoval = true) public Map getForeignKeys() { return unmodifiableMap( foreignKeys ); } @@ -592,6 +603,8 @@ public ForeignKey createForeignKey( for ( Column keyColumn : keyColumns ) { foreignKey.addColumn( keyColumn ); } + + // null referencedColumns means a reference to primary key if ( referencedColumns != null ) { foreignKey.addReferencedColumns( referencedColumns ); } @@ -611,16 +624,16 @@ public ForeignKey createForeignKey( } /** - * Checks for uniqueKey containing only whole primary key and sets - * order of the columns accordingly + * Checks for unique key containing only whole primary key and sets + * order of the columns accordingly */ private void checkPrimaryKeyUniqueKey() { - final Iterator> uniqueKeyEntries = uniqueKeys.entrySet().iterator(); + final var uniqueKeyEntries = uniqueKeys.entrySet().iterator(); while ( uniqueKeyEntries.hasNext() ) { - final Map.Entry uniqueKeyEntry = uniqueKeyEntries.next(); - - if ( isSameAsPrimaryKeyColumns( uniqueKeyEntry.getValue() ) ) { - primaryKey.setOrderingUniqueKey(uniqueKeyEntry.getValue()); + final var uniqueKeyEntry = uniqueKeyEntries.next(); + final UniqueKey uniqueKey = uniqueKeyEntry.getValue(); + if ( isSameAsPrimaryKeyColumns( uniqueKey ) ) { + primaryKey.setOrderingUniqueKey( uniqueKey ); uniqueKeyEntries.remove(); } } @@ -749,6 +762,7 @@ public void setViewQuery(String viewQuery) { this.viewQuery = viewQuery; } + @Deprecated(since = "7") // this class should be private! public static class ForeignKeyKey implements Serializable { private final String referencedClassName; private final Column[] columns; @@ -769,10 +783,8 @@ public int hashCode() { } public boolean equals(Object other) { - if ( !(other instanceof ForeignKeyKey foreignKeyKey) ) { - return false; - } - return Arrays.equals( foreignKeyKey.columns, columns ) + return other instanceof ForeignKeyKey foreignKeyKey + && Arrays.equals( foreignKeyKey.columns, columns ) && Arrays.equals( foreignKeyKey.referencedColumns, referencedColumns ); } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java index 0ca9a095c9fd..cb969ea10e89 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java @@ -435,7 +435,7 @@ protected void applyForeignKeys( GenerationTarget... targets) { if ( dialect.hasAlterTable() ) { final Exporter exporter = dialect.getForeignKeyExporter(); - for ( ForeignKey foreignKey : table.getForeignKeys().values() ) { + for ( ForeignKey foreignKey : table.getForeignKeyCollection() ) { if ( foreignKey.isPhysicalConstraint() && foreignKey.isCreationEnabled() && ( tableInformation == null || !checkForExistingForeignKey( foreignKey, tableInformation ) ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaCreatorImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaCreatorImpl.java index d50f24c26de6..711196135ac5 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaCreatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaCreatorImpl.java @@ -290,7 +290,7 @@ private static void createForeignKeys( if ( schemaFilter.includeTable( table ) && contributableInclusionMatcher.matches( table ) ) { // foreign keys - for ( ForeignKey foreignKey : table.getForeignKeys().values() ) { + for ( ForeignKey foreignKey : table.getForeignKeyCollection() ) { applySqlStrings( dialect.getForeignKeyExporter().getSqlCreateStrings( foreignKey, metadata, context ), formatter, diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaDropperImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaDropperImpl.java index 287dd26375bb..ff9300587d4c 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaDropperImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaDropperImpl.java @@ -475,7 +475,7 @@ private void applyConstraintDropping( if ( table.isPhysicalTable() && schemaFilter.includeTable( table ) && inclusionFilter.matches( table ) ) { - for ( ForeignKey foreignKey : table.getForeignKeys().values() ) { + for ( ForeignKey foreignKey : table.getForeignKeyCollection() ) { applySqlStrings( dialect.getForeignKeyExporter().getSqlDropStrings( foreignKey, metadata, context ), formatter, diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaTruncatorImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaTruncatorImpl.java index 873a4e9384bf..c4df57c7e4d6 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaTruncatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/SchemaTruncatorImpl.java @@ -198,7 +198,7 @@ private void disableConstraints( continue; } - for ( ForeignKey foreignKey : table.getForeignKeys().values() ) { + for ( ForeignKey foreignKey : table.getForeignKeyCollection() ) { if ( dialect.canDisableConstraints() ) { applySqlString( dialect.getTableCleaner().getSqlDisableConstraintString( foreignKey, metadata, context ), @@ -241,7 +241,7 @@ private void enableConstraints( continue; } - for ( ForeignKey foreignKey : table.getForeignKeys().values() ) { + for ( ForeignKey foreignKey : table.getForeignKeyCollection() ) { if ( dialect.canDisableConstraints() ) { applySqlString( dialect.getTableCleaner().getSqlEnableConstraintString( foreignKey, metadata, context ), diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/collectionelement/DefaultNamingCollectionElementTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/collectionelement/DefaultNamingCollectionElementTest.java index f91c0fd7a54a..783f326028de 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/collectionelement/DefaultNamingCollectionElementTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/collectionelement/DefaultNamingCollectionElementTest.java @@ -6,7 +6,6 @@ import java.util.HashMap; import java.util.Iterator; -import java.util.List; import java.util.Locale; import org.hibernate.Filter; @@ -15,7 +14,6 @@ import org.hibernate.mapping.Collection; import org.hibernate.mapping.Column; import org.hibernate.mapping.ForeignKey; -import org.hibernate.query.Query; import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.DomainModel; @@ -39,7 +37,6 @@ * @author Hardy Ferentschik * @author Gail Badner */ -@SuppressWarnings("unchecked") @DomainModel( annotatedClasses = { Boy.class, @@ -102,10 +99,10 @@ public void testSimpleElement(SessionFactoryScope scope) { assertNotNull( boy.getFavoriteNumbers() ); assertEquals( 3, boy.getFavoriteNumbers()[1] ); assertTrue( boy.getCharacters().contains( CharacterTrait.CRAFTY ) ); - assertTrue( boy.getFavoriteFood().get( "dinner" ).equals( FavoriteFood.SUSHI ) ); - assertTrue( boy.getFavoriteFood().get( "lunch" ).equals( FavoriteFood.KUNGPAOCHICKEN ) ); - assertTrue( boy.getFavoriteFood().get( "breakfast" ).equals( FavoriteFood.PIZZA ) ); - List result = session.createQuery( + assertEquals( FavoriteFood.SUSHI, boy.getFavoriteFood().get( "dinner" ) ); + assertEquals( FavoriteFood.KUNGPAOCHICKEN, boy.getFavoriteFood().get( "lunch" ) ); + assertEquals( FavoriteFood.PIZZA, boy.getFavoriteFood().get( "breakfast" ) ); + var result = session.createQuery( "select boy from Boy boy join boy.nickNames names where names = :name" ) .setParameter( "name", "Thing" ).list(); assertEquals( 1, result.size() ); @@ -210,11 +207,11 @@ public void testLazyCollectionofElements(SessionFactoryScope scope) { assertTrue( boy.getNickNames().contains( "Thing" ) ); assertNotNull( boy.getScorePerNickName() ); assertTrue( boy.getScorePerNickName().containsKey( "Thing" ) ); - assertEquals( new Integer( 5 ), boy.getScorePerNickName().get( "Thing" ) ); + assertEquals( Integer.valueOf( 5 ), boy.getScorePerNickName().get( "Thing" ) ); assertNotNull( boy.getFavoriteNumbers() ); assertEquals( 3, boy.getFavoriteNumbers()[1] ); assertTrue( boy.getCharacters().contains( CharacterTrait.CRAFTY ) ); - List result = session.createQuery( + var result = session.createQuery( "select boy from Boy boy join boy.nickNames names where names = :name" ) .setParameter( "name", "Thing" ).list(); assertEquals( 1, result.size() ); @@ -240,9 +237,8 @@ public void testFetchEagerAndFilter(SessionFactoryScope scope) { Filter filter = session.enableFilter( "selectedLocale" ); filter.setParameter( "param", "fr" ); - Query q = session.createQuery( "from TestCourse t" ); - List l = q.list(); - assertEquals( 1, l.size() ); + assertEquals( 1, + session.createQuery( "from TestCourse t" ).list().size() ); TestCourse t = session.get( TestCourse.class, test.getTestCourseId() ); assertEquals( 1, t.getTitle().getVariations().size() ); @@ -407,8 +403,7 @@ protected void checkDefaultJoinColumnName( assertEquals( ownerForeignKeyNameExpected, ownerCollection.getKey().getSelectables().get( 0 ).getText() ); boolean hasOwnerFK = false; - for (Iterator it = ownerCollection.getCollectionTable().getForeignKeys().values().iterator(); it.hasNext(); ) { - final ForeignKey fk = (ForeignKey) it.next(); + for ( final ForeignKey fk : ownerCollection.getCollectionTable().getForeignKeyCollection() ) { assertSame( ownerCollection.getCollectionTable(), fk.getTable() ); if ( fk.getColumnSpan() > 1 ) { continue; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/defaults/ManyToManyImplicitNamingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/defaults/ManyToManyImplicitNamingTest.java index 1c52feafa572..59f185f34463 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/defaults/ManyToManyImplicitNamingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/defaults/ManyToManyImplicitNamingTest.java @@ -4,8 +4,6 @@ */ package org.hibernate.orm.test.annotations.manytomany.defaults; -import java.util.Iterator; - import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl; import org.hibernate.mapping.ForeignKey; @@ -200,8 +198,7 @@ protected void checkDefaultJoinTablAndJoinColumnNames( } boolean hasOwnerFK = false; boolean hasInverseFK = false; - for (Iterator it = ownerCollection.getCollectionTable().getForeignKeys().values().iterator(); it.hasNext(); ) { - final ForeignKey fk = (ForeignKey) it.next(); + for ( final ForeignKey fk : ownerCollection.getCollectionTable().getForeignKeyCollection() ) { assertSame( ownerCollection.getCollectionTable(), fk.getTable() ); if ( fk.getColumnSpan() > 1 ) { continue; @@ -210,7 +207,7 @@ protected void checkDefaultJoinTablAndJoinColumnNames( assertSame( ownerCollection.getOwner().getTable(), fk.getReferencedTable() ); hasOwnerFK = true; } - else if ( fk.getColumn( 0 ).getText().equals( inverseForeignKeyNameExpected ) ) { + else if ( fk.getColumn( 0 ).getText().equals( inverseForeignKeyNameExpected ) ) { assertSame( associatedPersistentClass.getTable(), fk.getReferencedTable() ); hasInverseFK = true; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/namingstrategy/LongKeyNamingStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/namingstrategy/LongKeyNamingStrategyTest.java index 5068214b4671..d9a868509c76 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/namingstrategy/LongKeyNamingStrategyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/namingstrategy/LongKeyNamingStrategyTest.java @@ -16,7 +16,6 @@ import org.hibernate.boot.Metadata; import org.hibernate.boot.MetadataSources; import org.hibernate.cfg.Environment; -import org.hibernate.mapping.UniqueKey; import org.hibernate.service.ServiceRegistry; import org.hibernate.testing.ServiceRegistryBuilder; @@ -58,14 +57,13 @@ public void testWithCustomNamingStrategy() throws Exception { .applyImplicitNamingStrategy( new LongIdentifierNamingStrategy() ) .build(); - org.hibernate.mapping.ForeignKey foreignKey = - (org.hibernate.mapping.ForeignKey) metadata.getEntityBinding(Address.class.getName()).getTable().getForeignKeys().values().iterator().next(); + var foreignKey = metadata.getEntityBinding(Address.class.getName()).getTable().getForeignKeyCollection().iterator().next(); assertEquals( "FK_way_longer_than_the_30_char", foreignKey.getName() ); - UniqueKey uniqueKey = metadata.getEntityBinding(Address.class.getName()).getTable().getUniqueKeys().values().iterator().next(); + var uniqueKey = metadata.getEntityBinding(Address.class.getName()).getTable().getUniqueKeys().values().iterator().next(); assertEquals( "UK_way_longer_than_the_30_char", uniqueKey.getName() ); - org.hibernate.mapping.Index index = metadata.getEntityBinding(Address.class.getName()).getTable().getIndexes().values().iterator().next(); + var index = metadata.getEntityBinding(Address.class.getName()).getTable().getIndexes().values().iterator().next(); assertEquals( "IDX_way_longer_than_the_30_cha", index.getName() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/namingstrategy/charset/AbstractCharsetNamingStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/namingstrategy/charset/AbstractCharsetNamingStrategyTest.java index f5a335a822f4..35f4de1d5086 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/namingstrategy/charset/AbstractCharsetNamingStrategyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/namingstrategy/charset/AbstractCharsetNamingStrategyTest.java @@ -17,7 +17,6 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Environment; import org.hibernate.internal.util.PropertiesHelper; -import org.hibernate.mapping.UniqueKey; import org.hibernate.service.ServiceRegistry; import org.hibernate.testing.ServiceRegistryBuilder; @@ -62,14 +61,13 @@ public void testWithCustomNamingStrategy() throws Exception { .applyImplicitNamingStrategy( new LongIdentifierNamingStrategy() ) .build(); - UniqueKey uniqueKey = metadata.getEntityBinding(Address.class.getName()).getTable().getUniqueKeys().values().iterator().next(); + var uniqueKey = metadata.getEntityBinding(Address.class.getName()).getTable().getUniqueKeys().values().iterator().next(); assertEquals( expectedUniqueKeyName(), uniqueKey.getName() ); - org.hibernate.mapping.ForeignKey foreignKey = - (org.hibernate.mapping.ForeignKey) metadata.getEntityBinding(Address.class.getName()).getTable().getForeignKeys().values().iterator().next(); + var foreignKey = metadata.getEntityBinding(Address.class.getName()).getTable().getForeignKeyCollection().iterator().next(); assertEquals( expectedForeignKeyName(), foreignKey.getName() ); - org.hibernate.mapping.Index index = metadata.getEntityBinding(Address.class.getName()).getTable().getIndexes().values().iterator().next(); + var index = metadata.getEntityBinding(Address.class.getName()).getTable().getIndexes().values().iterator().next(); assertEquals( expectedIndexName(), index.getName() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/onetoone/OverrideOneToOneJoinColumnTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/onetoone/OverrideOneToOneJoinColumnTest.java index 5896f8c584c2..7618649b7c70 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/onetoone/OverrideOneToOneJoinColumnTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/onetoone/OverrideOneToOneJoinColumnTest.java @@ -52,7 +52,7 @@ public void allowIfJoinColumnIsAbsent() { final Table personTable = metadata.getDatabase().getDefaultNamespace().locateTable( Identifier.toIdentifier( "PERSON_TABLE" ) ); - final Collection foreignKeys = personTable.getForeignKeys().values(); + final Collection foreignKeys = personTable.getForeignKeyCollection(); assertThat( foreignKeys.size(), is( 1 ) ); final Optional foreignKey = foreignKeys.stream().findFirst(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/ConstraintTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/ConstraintTest.java index e4ebc6c7d5ab..a2aacbc56d2e 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/ConstraintTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/ConstraintTest.java @@ -4,7 +4,6 @@ */ package org.hibernate.orm.test.constraint; -import java.util.Iterator; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; @@ -69,9 +68,7 @@ public void testConstraintNameLength() { int foundCount = 0; for ( Namespace namespace : metadata().getDatabase().getNamespaces() ) { for ( org.hibernate.mapping.Table table : namespace.getTables() ) { - Iterator fkItr = table.getForeignKeys().values().iterator(); - while (fkItr.hasNext()) { - ForeignKey fk = (ForeignKey) fkItr.next(); + for ( ForeignKey fk : table.getForeignKeyCollection() ) { assertTrue( fk.getName().length() <= MAX_NAME_LENGTH ); // ensure the randomly generated constraint name doesn't @@ -79,17 +76,15 @@ public void testConstraintNameLength() { Column column = fk.getColumn( 0 ); if ( column.getName().equals( "explicit_native" ) ) { foundCount++; - assertEquals( fk.getName(), EXPLICIT_FK_NAME_NATIVE ); + assertEquals( EXPLICIT_FK_NAME_NATIVE, fk.getName() ); } else if ( column.getName().equals( "explicit_jpa" ) ) { foundCount++; - assertEquals( fk.getName(), EXPLICIT_FK_NAME_JPA ); + assertEquals( EXPLICIT_FK_NAME_JPA, fk.getName() ); } } - Iterator ukItr = table.getUniqueKeys().values().iterator(); - while (ukItr.hasNext()) { - UniqueKey uk = (UniqueKey) ukItr.next(); + for ( UniqueKey uk : table.getUniqueKeys().values() ) { assertTrue( uk.getName().length() <= MAX_NAME_LENGTH ); // ensure the randomly generated constraint name doesn't @@ -97,7 +92,7 @@ else if ( column.getName().equals( "explicit_jpa" ) ) { Column column = uk.getColumn( 0 ); if ( column.getName().equals( "explicit" ) ) { foundCount++; - assertEquals( uk.getName(), EXPLICIT_UK_NAME ); + assertEquals( EXPLICIT_UK_NAME, uk.getName() ); } } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/ForeignKeyConstraintMapsIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/ForeignKeyConstraintMapsIdTest.java index 3f373941d52f..ea72722e2059 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/ForeignKeyConstraintMapsIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/ForeignKeyConstraintMapsIdTest.java @@ -4,7 +4,6 @@ */ package org.hibernate.orm.test.constraint; -import java.util.Iterator; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.ForeignKey; @@ -88,9 +87,7 @@ public void testForeignKeyNameSetForMapsIdJoinColumn() { for ( Namespace namespace : metadata().getDatabase().getNamespaces() ) { for ( Table table : namespace.getTables() ) { if ( table.getName().equals( "Post" ) ) { - Iterator foreignKeyIterator = table.getForeignKeys().values().iterator(); - while ( foreignKeyIterator.hasNext() ) { - org.hibernate.mapping.ForeignKey foreignKey = foreignKeyIterator.next(); + for ( var foreignKey : table.getForeignKeyCollection() ) { if ( foreignKey.getColumn( 0 ).getName().equals( "PD_ID" ) ) { assertEquals( "FK_PD", foreignKey.getName() ); return; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/ForeignKeyConstraintTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/ForeignKeyConstraintTest.java index 7572584dd35a..0864cbebad9f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/ForeignKeyConstraintTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/ForeignKeyConstraintTest.java @@ -12,7 +12,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import org.hibernate.boot.model.relational.Namespace; import org.hibernate.mapping.Column; @@ -54,10 +53,10 @@ import jakarta.persistence.PrimaryKeyJoinColumns; import jakarta.persistence.SecondaryTable; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; /** * @author Christian Beikov @@ -178,17 +177,16 @@ private void assertForeignKey(DomainModelScope scope, String foreignKeyName, Str Set columnSet = new LinkedHashSet<>( Arrays.asList( columns ) ); for ( Namespace namespace : scope.getDomainModel().getDatabase().getNamespaces() ) { for ( org.hibernate.mapping.Table table : namespace.getTables() ) { - Iterator fkItr = table.getForeignKeys().values().iterator(); + Iterator fkItr = table.getForeignKeyCollection().iterator(); while ( fkItr.hasNext() ) { org.hibernate.mapping.ForeignKey fk = fkItr.next(); if ( foreignKeyName.equals( fk.getName() ) ) { - assertEquals( "ForeignKey column count not like expected", columnSet.size(), fk.getColumnSpan() ); - List columnNames = fk.getColumns().stream().map(Column::getName).collect(Collectors.toList()); - assertTrue( - "ForeignKey columns [" + columnNames + "] do not match expected columns [" + columnSet + "]", - columnSet.containsAll( columnNames ) - ); + assertEquals( columnSet.size(), fk.getColumnSpan(), + "ForeignKey column count not like expected" ); + List columnNames = fk.getColumns().stream().map(Column::getName).toList(); + assertTrue( columnSet.containsAll( columnNames ), + "ForeignKey columns [" + columnNames + "] do not match expected columns [" + columnSet + "]" ); return; } } @@ -199,14 +197,10 @@ private void assertForeignKey(DomainModelScope scope, String foreignKeyName, Str private void assertNoForeignKey(DomainModelScope scope, String foreignKeyName, String... columns) { for ( Namespace namespace : scope.getDomainModel().getDatabase().getNamespaces() ) { - for ( org.hibernate.mapping.Table table : namespace.getTables() ) { - Iterator fkItr = table.getForeignKeys().values().iterator(); - while ( fkItr.hasNext() ) { - org.hibernate.mapping.ForeignKey fk = fkItr.next(); - assertFalse( - "ForeignKey [" + foreignKeyName + "] defined and shouldn't have been.", - foreignKeyName.equals( fk.getName() ) - ); + for ( var table : namespace.getTables() ) { + for ( var fk : table.getForeignKeyCollection() ) { + assertNotEquals( foreignKeyName, fk.getName(), + "ForeignKey [" + foreignKeyName + "] defined and shouldn't have been." ); } } } @@ -345,8 +339,8 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = (int) ( vehicleVendorNumber ^ ( vehicleVendorNumber >>> 32 ) ); - result = 31 * result + (int) ( vehicleNumber ^ ( vehicleNumber >>> 32 ) ); + int result = Long.hashCode( vehicleVendorNumber ); + result = 31 * result + Long.hashCode( vehicleNumber ); return result; } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/ForeignKeyNoConstraintTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/ForeignKeyNoConstraintTest.java index 06a15937499b..be7e246d6a36 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/ForeignKeyNoConstraintTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/ForeignKeyNoConstraintTest.java @@ -43,7 +43,7 @@ public void testPrimaryKeyJoinColumnForeignKeyNoConstraint() { for ( Namespace namespace : metadata().getDatabase().getNamespaces() ) { for ( Table table : namespace.getTables() ) { if ( "Car".equals( table.getName() ) ) { - assertEquals( 0, table.getForeignKeys().size() ); + assertEquals( 0, table.getForeignKeyCollection().size() ); } } } @@ -55,7 +55,7 @@ public void testMapsIdJoinColumnForeignKeyNoConstraint() { for ( Namespace namespace : metadata().getDatabase().getNamespaces() ) { for ( Table table : namespace.getTables() ) { if ( "Post".equals( table.getName() ) ) { - assertEquals( 0, table.getForeignKeys().size() ); + assertEquals( 0, table.getForeignKeyCollection().size() ); } } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/NonRootTablePolymorphicTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/NonRootTablePolymorphicTests.java index cbbeb1e55784..166e460bc926 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/NonRootTablePolymorphicTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/constraint/NonRootTablePolymorphicTests.java @@ -94,8 +94,8 @@ public void verifyBootModel(DomainModelScope scope) { // 1) for the sub->root inheritance fk // 2) for the sub->child fk - assertThat( subclassTable.getForeignKeys().size(), is( 2 ) ); - subclassTable.getForeignKeys().values().iterator().forEachRemaining( + assertThat( subclassTable.getForeignKeyCollection().size(), is( 2 ) ); + subclassTable.getForeignKeyCollection().iterator().forEachRemaining( (foreignKey) -> { assertThat( foreignKey.getTable(), sameInstance( subclassTable ) ); @@ -159,8 +159,8 @@ else if ( subclass.getJpaEntityName().equals( "Leaf" ) ) { final Selectable selectable = toOne.getSelectables().get( 0 ); assertThat( selectable.getText(), is( "parent_sub_fk" ) ); - assertThat( subParent.getTable().getForeignKeys().size(), is( 1 ) ); - final ForeignKey foreignKey = subParent.getTable().getForeignKeys().values().iterator().next(); + assertThat( subParent.getTable().getForeignKeyCollection().size(), is( 1 ) ); + final ForeignKey foreignKey = subParent.getTable().getForeignKeyCollection().iterator().next(); assertThat( foreignKey.getReferencedTable().getName(), is( "sub" ) ); assertThat( foreignKey.getTable(), sameInstance( toOne.getTable() ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/foreignkeys/HHH14230.java b/hibernate-core/src/test/java/org/hibernate/orm/test/foreignkeys/HHH14230.java index cdd355f966e2..c84834482bc7 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/foreignkeys/HHH14230.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/foreignkeys/HHH14230.java @@ -41,7 +41,7 @@ public void test() { .flatMap( namespace -> namespace.getTables().stream() ) .filter( t -> t.getName().equals( TABLE_NAME ) ).findFirst().orElse( null ); assertNotNull( table ); - assertEquals( 1, table.getForeignKeys().size() ); + assertEquals( 1, table.getForeignKeyCollection().size() ); // ClassCastException before HHH-14230 assertTrue( table.getForeignKeys().keySet().iterator().next().toString().contains( JOIN_COLUMN_NAME ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/foreignkeys/disabled/DefaultConstraintModeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/foreignkeys/disabled/DefaultConstraintModeTest.java index 572708bed595..59865c18f5eb 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/foreignkeys/disabled/DefaultConstraintModeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/foreignkeys/disabled/DefaultConstraintModeTest.java @@ -52,8 +52,8 @@ private void testForeignKeyCreation(boolean created) { .applySetting( Environment.HBM2DDL_DEFAULT_CONSTRAINT_MODE, created ? "CONSTRAINT" : "NO_CONSTRAINT" ) .build()) { Metadata metadata = new MetadataSources( ssr ).addAnnotatedClasses( TestEntity.class, ChildEntity.class ).buildMetadata(); - assertThat( findTable( metadata, "TestEntity" ).getForeignKeys().isEmpty(), is( !created ) ); - assertThat( findTable( metadata, "ChildEntity" ).getForeignKeys().isEmpty(), is( !created ) ); + assertThat( findTable( metadata, "TestEntity" ).getForeignKeyCollection().isEmpty(), is( !created ) ); + assertThat( findTable( metadata, "ChildEntity" ).getForeignKeyCollection().isEmpty(), is( !created ) ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/foreignkeys/disabled/DisabledForeignKeyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/foreignkeys/disabled/DisabledForeignKeyTest.java index 0b89bc065b36..e48c0a873b48 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/foreignkeys/disabled/DisabledForeignKeyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/foreignkeys/disabled/DisabledForeignKeyTest.java @@ -5,15 +5,12 @@ package org.hibernate.orm.test.foreignkeys.disabled; import java.util.EnumSet; -import java.util.Map; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.spi.MetadataImplementor; -import org.hibernate.mapping.ForeignKey; import org.hibernate.mapping.Table; -import org.hibernate.mapping.Table.ForeignKeyKey; import org.hibernate.tool.hbm2ddl.SchemaExport; import org.hibernate.tool.hbm2ddl.SchemaUpdate; import org.hibernate.tool.schema.TargetType; @@ -53,7 +50,7 @@ public void basicTests() { int fkCount = 0; for ( Table table : metadata.collectTableMappings() ) { - for ( Map.Entry entry : table.getForeignKeys().entrySet() ) { + for ( var entry : table.getForeignKeys().entrySet() ) { assertFalse( "Creation for ForeignKey [" + entry.getKey() + "] was not disabled", entry.getValue().isCreationEnabled() diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/foreignkeys/disabled/OneToManyBidirectionalForeignKeyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/foreignkeys/disabled/OneToManyBidirectionalForeignKeyTest.java index 390081937271..a6d3f4f5200d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/foreignkeys/disabled/OneToManyBidirectionalForeignKeyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/foreignkeys/disabled/OneToManyBidirectionalForeignKeyTest.java @@ -47,8 +47,8 @@ public void testForeignKeyShouldNotBeCreated() { Metadata metadata = new MetadataSources( serviceRegistry ) .addAnnotatedClass( PlainTreeEntity.class ).addAnnotatedClass( TreeEntityWithOnDelete.class ) .buildMetadata(); - assertTrue( findTable( metadata, TABLE_NAME_PLAIN ).getForeignKeys().isEmpty() ); - assertFalse( findTable( metadata, TABLE_NAME_WITH_ON_DELETE ).getForeignKeys().isEmpty() ); + assertTrue( findTable( metadata, TABLE_NAME_PLAIN ).getForeignKeyCollection().isEmpty() ); + assertFalse( findTable( metadata, TABLE_NAME_WITH_ON_DELETE ).getForeignKeyCollection().isEmpty() ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/namingstrategy/FullyQualifiedEntityNameNamingStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/namingstrategy/FullyQualifiedEntityNameNamingStrategyTest.java index 568b480e05e9..bcbf403cc8d7 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/namingstrategy/FullyQualifiedEntityNameNamingStrategyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/namingstrategy/FullyQualifiedEntityNameNamingStrategyTest.java @@ -4,8 +4,6 @@ */ package org.hibernate.orm.test.namingstrategy; -import java.util.Iterator; - import org.hibernate.boot.MetadataSources; import org.hibernate.boot.model.naming.EntityNaming; import org.hibernate.boot.model.naming.Identifier; @@ -92,8 +90,8 @@ public void testManyToManyForeignKeys() { boolean ownerFKFound = false; boolean inverseFKFound = false; - for (Iterator it = ownerCollectionMapping.getCollectionTable().getForeignKeys().values().iterator(); it.hasNext(); ) { - final String fkColumnName = ( (ForeignKey) it.next() ).getColumn( 0 ).getName(); + for ( ForeignKey foreignKey : ownerCollectionMapping.getCollectionTable().getForeignKeyCollection() ) { + final String fkColumnName = foreignKey.getColumn( 0 ).getName(); if ( expectedOwnerFK.equals( fkColumnName ) ) { ownerFKFound = true; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/propertyref/basic/PropertyRefTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/propertyref/basic/PropertyRefTest.java index 282f3ae19d3c..49ed8040ac52 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/propertyref/basic/PropertyRefTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/propertyref/basic/PropertyRefTest.java @@ -4,8 +4,6 @@ */ package org.hibernate.orm.test.propertyref.basic; -import java.util.Iterator; -import java.util.List; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; @@ -73,10 +71,9 @@ public void testNonLazyBagKeyPropertyRef(SessionFactoryScope scope) { scope.inTransaction( session -> { - List results = session.createQuery( "from Person" ).list(); - Iterator itr = results.iterator(); - while ( itr.hasNext() ) { - session.remove( itr.next() ); + var results = session.createQuery( "from Person" ).list(); + for ( Object result : results ) { + session.remove( result ); } } ); @@ -150,7 +147,7 @@ public void testOneToOnePropertyRef(SessionFactoryScope scope) { p2 = session.get( Person.class, p2.getId() ); //get null address reference by outer join assertNull( p2.getAddress() ); assertNotNull( p.getAddress() ); - List l = session.createQuery( "from Person" ).list(); //pull address references for cache + var l = session.createQuery( "from Person" ).list(); //pull address references for cache assertEquals( 2, l.size() ); assertTrue( l.contains( p ) && l.contains( p2 ) ); session.clear(); @@ -267,15 +264,15 @@ public void testJoinFetchPropertyRef(SessionFactoryScope scope) { public void testForeignKeyCreation(SessionFactoryScope scope) { PersistentClass classMapping = scope.getMetadataImplementor().getEntityBinding( Account.class.getName() ); - Iterator foreignKeyIterator = classMapping.getTable().getForeignKeys().values().iterator(); + var foreignKeyIterator = classMapping.getTable().getForeignKeyCollection().iterator(); boolean found = false; while ( foreignKeyIterator.hasNext() ) { - ForeignKey element = (ForeignKey) foreignKeyIterator.next(); + final ForeignKey element = foreignKeyIterator.next(); if ( element.getReferencedEntityName().equals( Person.class.getName() ) ) { if ( !element.isReferenceToPrimaryKey() ) { - List referencedColumns = element.getReferencedColumns(); - Column column = (Column) referencedColumns.get( 0 ); + var referencedColumns = element.getReferencedColumns(); + Column column = referencedColumns.get( 0 ); if ( column.getName().equals( "person_userid" ) ) { found = true; // extend test to include the columns } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/tool/schema/internal/StandardForeignKeyExporterTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/tool/schema/internal/StandardForeignKeyExporterTest.java index de9000a9902c..b28dbf4b06d1 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/tool/schema/internal/StandardForeignKeyExporterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/tool/schema/internal/StandardForeignKeyExporterTest.java @@ -51,8 +51,8 @@ public void testForeignKeySqlStringForCompositePK() { SqlStringGenerationContext sqlStringGenerationContext = SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() ); - Collection fks = database.getDefaultNamespace().locateTable( Identifier.toIdentifier( "PERSON" ) ).getForeignKeys().values(); - assertEquals( fks.size(), 1 ); + var fks = database.getDefaultNamespace().locateTable( Identifier.toIdentifier( "PERSON" ) ).getForeignKeyCollection(); + assertEquals( 1, fks.size() ); final Optional foreignKey = fks.stream().findFirst(); final String[] sqlCreateStrings = new H2Dialect().getForeignKeyExporter().getSqlCreateStrings( @@ -60,8 +60,10 @@ public void testForeignKeySqlStringForCompositePK() { bootModel, sqlStringGenerationContext ); - assertEquals( sqlCreateStrings.length, 1 ); - assertEquals( sqlCreateStrings[0], "alter table if exists PERSON add constraint fk_firstLastName foreign key (pkFirstName, pkLastName) references PERSON" ); + assertEquals( 1, sqlCreateStrings.length ); + assertEquals( + "alter table if exists PERSON add constraint fk_firstLastName foreign key (pkFirstName, pkLastName) references PERSON", + sqlCreateStrings[0] ); } } diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/manytoone/EmbeddedIdManyToOneForeignKeyTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/manytoone/EmbeddedIdManyToOneForeignKeyTest.java index 7688f0a39c42..1cd3d7555344 100644 --- a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/manytoone/EmbeddedIdManyToOneForeignKeyTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/manytoone/EmbeddedIdManyToOneForeignKeyTest.java @@ -44,7 +44,7 @@ public void testJoinTableForeignKeyToNonAuditTables() { // there should only be references to REVINFO and not to the Customer or Address tables for ( Table table : metadata().getDatabase().getDefaultNamespace().getTables() ) { if ( table.getName().equals( "CustomerAddress_AUD" ) ) { - for ( org.hibernate.mapping.ForeignKey foreignKey : table.getForeignKeys().values() ) { + for ( var foreignKey : table.getForeignKeyCollection() ) { assertEquals( "REVINFO", foreignKey.getReferencedTable().getName() ); } } From 8da98840fc185e1096faea16077d5726f9310fee Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 23 May 2025 13:41:35 +0200 Subject: [PATCH 033/168] minor cleanups in PersistentClass (cherry picked from commit 671f83905668e6821f67c93f467dcfd52db1e001) --- .../hibernate/mapping/PersistentClass.java | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java b/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java index 82b8f82001eb..12c0d9e4f65a 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java @@ -158,14 +158,17 @@ public void setProxyInterfaceName(String proxyInterfaceName) { this.proxyInterface = null; } + private Class getClassForName(String className) { + return classForName( className, metadataBuildingContext.getBootstrapContext() ); + } + public Class getMappedClass() throws MappingException { if ( className == null ) { return null; } - try { if ( mappedClass == null ) { - mappedClass = classForName( className, metadataBuildingContext.getBootstrapContext() ); + mappedClass = getClassForName( className ); } return mappedClass; } @@ -180,7 +183,7 @@ public Class getProxyInterface() { } try { if ( proxyInterface == null ) { - proxyInterface = classForName( proxyInterfaceName, metadataBuildingContext.getBootstrapContext() ); + proxyInterface = getClassForName( proxyInterfaceName ); } return proxyInterface; } @@ -538,26 +541,26 @@ else if ( identifierProperty == null && getIdentifierMapper() != null ) { } private Property getProperty(String propertyName, List properties) throws MappingException { - String root = root( propertyName ); - for ( Property prop : properties ) { - if ( prop.getName().equals( root ) - || ( prop instanceof Backref || prop instanceof IndexBackref ) - && prop.getName().equals( propertyName ) ) { - return prop; + final String root = root( propertyName ); + for ( Property property : properties ) { + if ( property.getName().equals( root ) + || ( property instanceof Backref || property instanceof IndexBackref ) + && property.getName().equals( propertyName ) ) { + return property; } } throw new MappingException( "property [" + propertyName + "] not found on entity [" + getEntityName() + "]" ); } public Property getProperty(String propertyName) throws MappingException { - Property identifierProperty = getIdentifierProperty(); + final Property identifierProperty = getIdentifierProperty(); if ( identifierProperty != null && identifierProperty.getName().equals( root( propertyName ) ) ) { return identifierProperty; } else { List closure = getPropertyClosure(); - Component identifierMapper = getIdentifierMapper(); + final Component identifierMapper = getIdentifierMapper(); if ( identifierMapper != null ) { closure = new JoinedList<>( identifierMapper.getProperties(), closure ); } @@ -577,14 +580,14 @@ public boolean hasProperty(String name) { if ( identifierProperty != null && identifierProperty.getName().equals( name ) ) { return true; } - - for ( Property property : getPropertyClosure() ) { - if ( property.getName().equals(name) ) { - return true; + else { + for ( Property property : getPropertyClosure() ) { + if ( property.getName().equals( name ) ) { + return true; + } } + return false; } - - return false; } /** @@ -644,9 +647,9 @@ public void validate(Metadata mapping) throws MappingException { private void checkPropertyDuplication() throws MappingException { final HashSet names = new HashSet<>(); - for ( Property prop : getProperties() ) { - if ( !names.add( prop.getName() ) ) { - throw new MappingException( "Duplicate property mapping of " + prop.getName() + " found in " + getEntityName() ); + for ( Property property : getProperties() ) { + if ( !names.add( property.getName() ) ) { + throw new MappingException( "Duplicate property mapping of " + property.getName() + " found in " + getEntityName() ); } } } From af791441dbed6bda8c73c3465ef481bf6a8a60e4 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 23 May 2025 13:43:27 +0200 Subject: [PATCH 034/168] HHH-19480 add missing foreign key constraint to unidirectional @OneToMany with referencedColumnName (cherry picked from commit c8ef3d0e65f53dd6fd97ab9614a9ff7ffdfda364) --- .../java/org/hibernate/mapping/Collection.java | 9 ++++++++- .../java/org/hibernate/mapping/KeyValue.java | 4 ++++ .../org/hibernate/mapping/SimpleValue.java | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java b/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java index 88e9c41c1dfc..0b7d492b6647 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java @@ -548,9 +548,16 @@ && isSame( element, other.element ) private void createForeignKeys() throws MappingException { // if ( !isInverse() ) { // for inverse collections, let the "other end" handle it + final String entityName = getOwner().getEntityName(); if ( referencedPropertyName == null ) { getElement().createForeignKey(); - key.createForeignKeyOfEntity( getOwner().getEntityName() ); + key.createForeignKeyOfEntity( entityName ); + } + else { + final Property property = owner.getProperty( referencedPropertyName ); + assert property != null; + key.createForeignKeyOfEntity( entityName, + property.getValue().getConstraintColumns() ); } // } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/KeyValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/KeyValue.java index ca0481ed4546..b1b1e8f731b4 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/KeyValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/KeyValue.java @@ -8,6 +8,8 @@ import org.hibernate.dialect.Dialect; import org.hibernate.generator.Generator; +import java.util.List; + /** * A mapping model {@link Value} which may be treated as an identifying key of a * relational database table. A {@code KeyValue} might represent the primary key @@ -18,6 +20,8 @@ */ public interface KeyValue extends Value { + ForeignKey createForeignKeyOfEntity(String entityName, List referencedColumns); + ForeignKey createForeignKeyOfEntity(String entityName); boolean isCascadeDeleteEnabled(); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java index 6ec39cabe7ab..d5aaaf47d78d 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -351,6 +351,24 @@ public ForeignKey createForeignKeyOfEntity(String entityName) { return null; } + @Override + public ForeignKey createForeignKeyOfEntity(String entityName, List referencedColumns) { + if ( isConstrained() ) { + final ForeignKey foreignKey = table.createForeignKey( + getForeignKeyName(), + getConstraintColumns(), + entityName, + getForeignKeyDefinition(), + getForeignKeyOptions(), + referencedColumns + ); + foreignKey.setOnDeleteAction( onDeleteAction ); + return foreignKey; + } + + return null; + } + @Override public void createUniqueKey(MetadataBuildingContext context) { if ( hasFormula() ) { From e640d70c678b46bf70c6d736788949c9125d0caf Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 23 May 2025 14:14:39 +0200 Subject: [PATCH 035/168] HHH-19480 add tests (cherry picked from commit 09906828e66f0f1d81d5e69d96d3ffeb28527165) --- .../OneToManyRefColumnNameTest.java | 160 ++++++++++++++++++ ...onPkCompositeJoinColumnCollectionTest.java | 2 +- .../NonPkJoinColumnCollectionTest.java | 2 +- .../StandardForeignKeyExporterTest.java | 1 - 4 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/annotations/refcolnames/constraints/OneToManyRefColumnNameTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/refcolnames/constraints/OneToManyRefColumnNameTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/refcolnames/constraints/OneToManyRefColumnNameTest.java new file mode 100644 index 000000000000..272520546e00 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/refcolnames/constraints/OneToManyRefColumnNameTest.java @@ -0,0 +1,160 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.annotations.refcolnames.constraints; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import jakarta.persistence.RollbackException; +import org.hibernate.exception.ConstraintViolationException; +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.HashSet; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +@Jpa(annotatedClasses = {OneToManyRefColumnNameTest.This.class, OneToManyRefColumnNameTest.That.class}) +@JiraKey("HHH-19480") +class OneToManyRefColumnNameTest { + + @Test void test(EntityManagerFactoryScope scope) { + scope.getEntityManagerFactory().getSchemaManager().truncate(); + This thisThing = new This(); + That thatThing = new That(); + thatThing.compositeKeyOne = 1; + thatThing.compositeKeyTwo = 2; + thatThing.singleKey = "hello"; + thatThing.theseOnSingleKey.add( thisThing ); + thatThing.theseOnCompositeKey.add( thisThing ); + scope.inTransaction( em -> { + em.persist( thatThing ); + em.persist( thisThing ); + } ); + That thing = new That(); + thing.singleKey = "goodbye"; + thing.compositeKeyOne = 5; + thing.compositeKeyTwo = 3; + scope.inTransaction( em -> { + em.persist( thing ); + } ); + } + + @Test void testForeignKey(EntityManagerFactoryScope scope) { + scope.getEntityManagerFactory().getSchemaManager().truncate(); + This thisThing = new This(); + That thatThing = new That(); + thatThing.compositeKeyOne = 1; + thatThing.compositeKeyTwo = 2; + thatThing.singleKey = "hello"; + thatThing.theseOnSingleKey.add( thisThing ); + thatThing.theseOnCompositeKey.add( thisThing ); + scope.inTransaction( em -> { + em.persist( thatThing ); + em.persist( thisThing ); + } ); + + assertThrows( ConstraintViolationException.class, () -> + scope.inTransaction( em -> { + em.createQuery( "update OneToManyRefColumnNameTest$That set singleKey = 'goodbye'" ).executeUpdate(); + } ) + ); + + assertThrows( ConstraintViolationException.class, () -> + scope.inTransaction( em -> { + em.createQuery( "update OneToManyRefColumnNameTest$That set compositeKeyOne = 69" ).executeUpdate(); + } ) + ); + } + + @Test void testUnique(EntityManagerFactoryScope scope) { + scope.getEntityManagerFactory().getSchemaManager().truncate(); + This thisThing = new This(); + That thatThing = new That(); + thatThing.compositeKeyOne = 1; + thatThing.compositeKeyTwo = 2; + thatThing.singleKey = "hello"; + thatThing.theseOnSingleKey.add( thisThing ); + thatThing.theseOnCompositeKey.add( thisThing ); + scope.inTransaction( em -> { + em.persist( thatThing ); + em.persist( thisThing ); + } ); + That thing = new That(); + try { + thing.singleKey = "hello"; + scope.inTransaction( em -> { + em.persist( thing ); + } ); + fail(); + } + catch (RollbackException re) { + assertInstanceOf( ConstraintViolationException.class, re.getCause() ); + } + } + + @Test void testUniqueKey(EntityManagerFactoryScope scope) { + scope.getEntityManagerFactory().getSchemaManager().truncate(); + This thisThing = new This(); + That thatThing = new That(); + thatThing.compositeKeyOne = 1; + thatThing.compositeKeyTwo = 2; + thatThing.singleKey = "hello"; + thatThing.theseOnSingleKey.add( thisThing ); + thatThing.theseOnCompositeKey.add( thisThing ); + scope.inTransaction( em -> { + em.persist( thatThing ); + em.persist( thisThing ); + } ); + That thing = new That(); + thing.singleKey = "goodbye"; + thing.compositeKeyOne = 1; + thing.compositeKeyTwo = 2; + try { + scope.inTransaction( em -> { + em.persist( thing ); + } ); + fail(); + } + catch (RollbackException re) { + assertInstanceOf( ConstraintViolationException.class, re.getCause() ); + } + } + + @Entity + static class That { + @Id @GeneratedValue + long id; + + @Column(nullable = false) + String singleKey; + + int compositeKeyOne; + int compositeKeyTwo; + + @OneToMany + @JoinColumn(referencedColumnName = "singleKey") + Set theseOnSingleKey = new HashSet<>(); + + @OneToMany + @JoinColumn(referencedColumnName = "compositeKeyOne") + @JoinColumn(referencedColumnName = "compositeKeyTwo") + Set theseOnCompositeKey = new HashSet<>(); + } + + @Entity + static class This { + @Id @GeneratedValue + long id; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/collection/NonPkCompositeJoinColumnCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/NonPkCompositeJoinColumnCollectionTest.java index ece95e71d9c9..cc10c4e94223 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/collection/NonPkCompositeJoinColumnCollectionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/NonPkCompositeJoinColumnCollectionTest.java @@ -65,8 +65,8 @@ public void testCollectionInsert(SessionFactoryScope scope) { Order order = new Order( "O1" ); Item item = new Item( "Item 1" ); order.addItem( item ); - session.persist( item ); session.persist( order ); + session.persist( item ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/collection/NonPkJoinColumnCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/NonPkJoinColumnCollectionTest.java index bb73caa94abf..6f753a4db9d5 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/collection/NonPkJoinColumnCollectionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/NonPkJoinColumnCollectionTest.java @@ -108,8 +108,8 @@ public void testInsertCollection(SessionFactoryScope scope) { Order order = new Order( "some_ref" ); Item item = new Item( "Abc" ); order.addItem( item ); - session.persist( item ); session.persist( order ); + session.persist( item ); session.flush(); session.clear(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/tool/schema/internal/StandardForeignKeyExporterTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/tool/schema/internal/StandardForeignKeyExporterTest.java index b28dbf4b06d1..46e8dfa16084 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/tool/schema/internal/StandardForeignKeyExporterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/tool/schema/internal/StandardForeignKeyExporterTest.java @@ -4,7 +4,6 @@ */ package org.hibernate.orm.test.tool.schema.internal; -import java.util.Collection; import java.util.Optional; import org.hibernate.boot.MetadataSources; From 51aa1e7de3e107a3a4ec5be2e36496116c884152 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sat, 24 May 2025 13:07:19 +0200 Subject: [PATCH 036/168] add additional test for findMultiple() with second-level cache (cherry picked from commit 37c1f8829d36b57308b4e08bf42cdd34656518ff) --- .../multiLoad/FindMultipleFromCacheTest.java | 68 +++++++++++++++++++ .../stateless/GetMultipleFromCacheTest.java | 2 + 2 files changed, 70 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/FindMultipleFromCacheTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/FindMultipleFromCacheTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/FindMultipleFromCacheTest.java new file mode 100644 index 000000000000..af2cb21d6c47 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/FindMultipleFromCacheTest.java @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.loading.multiLoad; + +import jakarta.persistence.Cacheable; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.LockMode; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +@SessionFactory(generateStatistics = true) +@DomainModel(annotatedClasses = FindMultipleFromCacheTest.Record.class) +public class FindMultipleFromCacheTest { + @Test void test(SessionFactoryScope scope) { + scope.inStatelessTransaction(s-> { + s.insert(new Record(123L,"hello earth")); + s.insert(new Record(456L,"hello mars")); + }); + scope.inTransaction(s-> { + List all = s.findMultiple(Record.class, List.of(456L, 123L, 2L)); + Record mars = all.get( 0 ); + Record earth = all.get( 1 ); + assertEquals( LockMode.READ, s.getCurrentLockMode( mars ) ); + assertEquals( LockMode.READ, s.getCurrentLockMode( earth ) ); + assertEquals("hello mars", mars.message); + assertEquals("hello earth", earth.message); + assertNull(all.get(2)); + }); + assertEquals( 0, + scope.getSessionFactory().getStatistics().getSecondLevelCacheHitCount() ); + scope.getSessionFactory().getStatistics().clear(); + scope.inTransaction(s-> { + List all = s.findMultiple(Record.class, List.of(123L, 2L, 456L)); + Record earth = all.get( 0 ); + Record mars = all.get( 2 ); + assertEquals( LockMode.NONE, s.getCurrentLockMode( mars ) ); + assertEquals( LockMode.NONE, s.getCurrentLockMode( earth ) ); + assertEquals("hello earth", earth.message); + assertEquals("hello mars", mars.message); + assertNull(all.get(1)); + }); + assertEquals( 2, + scope.getSessionFactory().getStatistics().getSecondLevelCacheHitCount() ); + } + @Entity @Cacheable + static class Record { + @Id Long id; + String message; + + Record(Long id, String message) { + this.id = id; + this.message = message; + } + + Record() { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/GetMultipleFromCacheTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/GetMultipleFromCacheTest.java index 096d56490c30..f279fdab0248 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/GetMultipleFromCacheTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/GetMultipleFromCacheTest.java @@ -31,6 +31,8 @@ public class GetMultipleFromCacheTest { assertEquals("hello earth",all.get(1).message); assertNull(all.get(2)); }); + assertEquals( 0, + scope.getSessionFactory().getStatistics().getSecondLevelCacheHitCount() ); scope.getSessionFactory().getStatistics().clear(); scope.inStatelessTransaction(s-> { List all = s.getMultiple(Record.class, List.of(123L, 2L, 456L)); From 6076711f67e803c2236a0db0e8dc0252b8960e73 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sat, 24 May 2025 15:42:21 +0200 Subject: [PATCH 037/168] add test for lock modes (cherry picked from commit 50440e01f11df073e3fa9d6579199ae42261fe20) --- .../LockModeAcrossTransactionsTest.java | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockModeAcrossTransactionsTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockModeAcrossTransactionsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockModeAcrossTransactionsTest.java new file mode 100644 index 000000000000..7542a81e6417 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockModeAcrossTransactionsTest.java @@ -0,0 +1,107 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.locking; + +import jakarta.persistence.Cacheable; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.LockMode; +import org.hibernate.ObjectDeletedException; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SessionFactory +@DomainModel(annotatedClasses = LockModeAcrossTransactionsTest.Cached.class) +class LockModeAcrossTransactionsTest { + + @Test void testWithEvict(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.persist( new Cached(5L) ); + } ); + scope.getSessionFactory().getCache().evict(Cached.class); + scope.inSession( session -> { + Cached cached = session.find( Cached.class, 5L ); + assertEquals( LockMode.NONE, session.getCurrentLockMode( cached ) ); + } ); + scope.getSessionFactory().getCache().evict(Cached.class); + scope.inTransaction( session -> { + Cached cached = session.find( Cached.class, 5L ); + assertEquals( LockMode.READ, session.getCurrentLockMode( cached ) ); + } ); + scope.getSessionFactory().getCache().evict(Cached.class); + scope.inSession( session -> { + Cached cached = session.createQuery( "from Cached", Cached.class ).getSingleResult(); + assertEquals( LockMode.NONE, session.getCurrentLockMode( cached ) ); + } ); + scope.getSessionFactory().getCache().evict(Cached.class); + scope.inTransaction( session -> { + Cached cached = session.createQuery( "from Cached", Cached.class ).getSingleResult(); + assertEquals( LockMode.READ, session.getCurrentLockMode( cached ) ); + } ); + scope.getSessionFactory().getCache().evict(Cached.class); + scope.inSession( session -> { + Cached cached = session.fromTransaction( tx -> { + Cached c = session.find( Cached.class, 5L ); + assertEquals( LockMode.READ, session.getCurrentLockMode( c ) ); + return c; + } ); + session.inTransaction( tx -> { + assertEquals( LockMode.NONE, session.getCurrentLockMode( cached ) ); + } ); + } ); + scope.inSession( session -> { + Cached cached = session.find( Cached.class, 5L ); + assertEquals( LockMode.NONE, session.getCurrentLockMode( cached ) ); + } ); + scope.inTransaction( session -> { + Cached cached = session.find( Cached.class, 5L ); + assertEquals( LockMode.NONE, session.getCurrentLockMode( cached ) ); + } ); + } + + @Test void testWithoutEvict(SessionFactoryScope scope) { + scope.inTransaction( session -> { + Cached cached = new Cached( 3L ); + session.persist( cached ); + assertEquals( LockMode.WRITE, session.getCurrentLockMode( cached ) ); + } ); + scope.inSession( session -> { + Cached cached = session.find( Cached.class, 3L ); + assertEquals( LockMode.NONE, session.getCurrentLockMode( cached ) ); + } ); + scope.inTransaction( session -> { + Cached cached = session.find( Cached.class, 3L ); + assertEquals( LockMode.NONE, session.getCurrentLockMode( cached ) ); + cached.name = "Gavin"; + assertEquals( LockMode.NONE, session.getCurrentLockMode( cached ) ); + session.flush(); + assertEquals( LockMode.WRITE, session.getCurrentLockMode( cached ) ); + } ); + scope.inTransaction( session -> { + Cached cached = session.find( Cached.class, 3L ); + assertEquals( LockMode.NONE, session.getCurrentLockMode( cached ) ); + session.remove( cached ); + assertThrows( ObjectDeletedException.class, + () -> session.getCurrentLockMode( cached ) ); + } ); + } + + @Cacheable @Entity(name = "Cached") + static class Cached { + @Id + Long id; + String name; + Cached(Long id) { + this.id = id; + } + Cached() { + } + } +} From 39e8da96910ede6f831967b8e06a15e63ecc84f3 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sat, 24 May 2025 16:58:58 +0200 Subject: [PATCH 038/168] HHH-19482 fix handling of lock modes for reads outside tx and clean up some error messages (cherry picked from commit 3189f0e771e9210b6ac6695db2cac15682c8cef6) --- .../src/main/java/org/hibernate/LockMode.java | 2 +- .../src/main/java/org/hibernate/Session.java | 25 ++++-- .../engine/internal/ImmutableEntityEntry.java | 7 +- .../internal/ImmutableEntityEntryFactory.java | 3 +- .../engine/internal/MutableEntityEntry.java | 7 +- .../internal/MutableEntityEntryFactory.java | 3 +- .../internal/StatefulPersistenceContext.java | 79 +++++++------------ .../engine/spi/EntityEntryFactory.java | 3 + .../AbstractSharedSessionContract.java | 4 +- .../org/hibernate/internal/SessionImpl.java | 16 ++-- .../entity/AbstractEntityPersister.java | 2 +- .../persister/entity/EntityPersister.java | 5 +- .../procedure/internal/ProcedureCallImpl.java | 5 -- .../hibernate/query/spi/AbstractQuery.java | 6 +- .../query/sqm/internal/QuerySqmImpl.java | 6 +- .../exec/internal/BaseExecutionContext.java | 7 ++ .../sql/exec/spi/ExecutionContext.java | 4 + .../internal/EntityInitializerImpl.java | 22 ++++-- 18 files changed, 106 insertions(+), 100 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/LockMode.java b/hibernate-core/src/main/java/org/hibernate/LockMode.java index 0d5dbf5aed6a..7e103adad675 100644 --- a/hibernate-core/src/main/java/org/hibernate/LockMode.java +++ b/hibernate-core/src/main/java/org/hibernate/LockMode.java @@ -52,7 +52,7 @@ public enum LockMode implements FindOption, RefreshOption { * rather than pull it from a cache. *

* This is the "default" lock mode, the mode requested by calling - * {@link Session#get(Class, Object)} without passing an explicit + * {@link Session#find(Class, Object)} without passing an explicit * mode. It permits the state of an object to be retrieved from * the cache without the cost of database access. * diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java index d48ca8d3094e..cd5c02d0f6ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/Session.java +++ b/hibernate-core/src/main/java/org/hibernate/Session.java @@ -825,19 +825,32 @@ public interface Session extends SharedSessionContract, EntityManager { void remove(Object object); /** - * Determine the current {@link LockMode} of the given managed instance associated - * with this session. + * Determine the current {@linkplain LockMode lock mode} held on the given + * managed instance associated with this session. + *

+ * Unlike the JPA-standard {@link #getLockMode}, this operation may be + * called when no transaction is active, in which case it should return + * {@link LockMode#NONE}, indicating that no pessimistic lock is held on + * the given entity. * * @param object a persistent instance associated with this session * - * @return the current lock mode + * @return the lock mode currently held on the given entity + * + * @throws IllegalStateException if the given instance is not associated + * with this persistence context + * @throws ObjectDeletedException if the given instance was already + * {@linkplain #remove removed} */ LockMode getCurrentLockMode(Object object); /** - * Completely clear the session. Evict all loaded instances and cancel all pending - * saves, updates and deletions. Do not close open iterators or instances of - * {@link ScrollableResults}. + * Completely clear the persistence context. Evict all loaded instances, + * causing every managed entity currently associated with this session to + * transition to the detached state, and cancel all pending insertions, + * updates, and deletions. + *

+ * Does not close open iterators or instances of {@link ScrollableResults}. */ @Override void clear(); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/ImmutableEntityEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/ImmutableEntityEntry.java index e135e80bdc69..bfba4920a81b 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/ImmutableEntityEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/ImmutableEntityEntry.java @@ -113,9 +113,10 @@ public static EntityEntry deserialize( (String) ois.readObject(), ois.readObject(), Status.valueOf( (String) ois.readObject() ), - ( previousStatusString = (String) ois.readObject() ).length() == 0 - ? null - : Status.valueOf( previousStatusString ), + ( previousStatusString = (String) ois.readObject() ) + .isEmpty() + ? null + : Status.valueOf( previousStatusString ), (Object[]) ois.readObject(), (Object[]) ois.readObject(), ois.readObject(), diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/ImmutableEntityEntryFactory.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/ImmutableEntityEntryFactory.java index 7dd1daaa0457..ca910513c509 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/ImmutableEntityEntryFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/ImmutableEntityEntryFactory.java @@ -18,7 +18,8 @@ * * @author Emmanuel Bernard */ -public class ImmutableEntityEntryFactory implements EntityEntryFactory { +@Deprecated(since = "7", forRemoval = true) +public final class ImmutableEntityEntryFactory implements EntityEntryFactory { /** * Singleton access */ diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/MutableEntityEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/MutableEntityEntry.java index fba004857847..906dc2ed1e9f 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/MutableEntityEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/MutableEntityEntry.java @@ -83,9 +83,10 @@ public static EntityEntry deserialize( (String) ois.readObject(), ois.readObject(), Status.valueOf( (String) ois.readObject() ), - ( previousStatusString = (String) ois.readObject() ).length() == 0 - ? null - : Status.valueOf( previousStatusString ), + ( previousStatusString = (String) ois.readObject() ) + .isEmpty() + ? null + : Status.valueOf( previousStatusString ), (Object[]) ois.readObject(), (Object[]) ois.readObject(), ois.readObject(), diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/MutableEntityEntryFactory.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/MutableEntityEntryFactory.java index 17caac436de5..41aa3e788eba 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/MutableEntityEntryFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/MutableEntityEntryFactory.java @@ -18,7 +18,8 @@ * * @author Emmanuel Bernard */ -public class MutableEntityEntryFactory implements EntityEntryFactory { +@Deprecated(since = "7", forRemoval = true) +public final class MutableEntityEntryFactory implements EntityEntryFactory { /** * Singleton access */ diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java index 852404e3e729..03f494e185bc 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java @@ -654,58 +654,34 @@ public EntityEntry addEntry( final EntityPersister persister, final boolean disableVersionIncrement) { assert lockMode != null; - - final EntityEntry e; - - /* - IMPORTANT!!! - - The following instanceof checks and castings are intentional. - - DO NOT REFACTOR to make calls through the EntityEntryFactory interface, which would result - in polymorphic call sites which will severely impact performance. - - When a virtual method is called via an interface the JVM needs to resolve which concrete - implementation to call. This takes CPU cycles and is a performance penalty. It also prevents method - inlining which further degrades performance. Casting to an implementation and making a direct method call - removes the virtual call, and allows the methods to be inlined. In this critical code path, it has a very - large impact on performance to make virtual method calls. - */ - if ( persister.getEntityEntryFactory() instanceof MutableEntityEntryFactory ) { - //noinspection RedundantCast - e = ( (MutableEntityEntryFactory) persister.getEntityEntryFactory() ).createEntityEntry( - status, - loadedState, - rowId, - id, - version, - lockMode, - existsInDatabase, - persister, - disableVersionIncrement, - this - ); - } - else { - //noinspection RedundantCast - e = ( (ImmutableEntityEntryFactory) persister.getEntityEntryFactory() ).createEntityEntry( - status, - loadedState, - rowId, - id, - version, - lockMode, - existsInDatabase, - persister, - disableVersionIncrement, - this - ); - } - - entityEntryContext.addEntityEntry( entity, e ); - + final EntityEntry entityEntry = + persister.isMutable() + ? new MutableEntityEntry( + status, + loadedState, + rowId, + id, + version, + lockMode, + existsInDatabase, + persister, + disableVersionIncrement, + this + ) + : new ImmutableEntityEntry( + status, + loadedState, + rowId, + id, + version, + lockMode, + existsInDatabase, + persister, + disableVersionIncrement + ); + entityEntryContext.addEntityEntry( entity, entityEntry ); setHasNonReadOnlyEnties( status ); - return e; + return entityEntry; } @Override @@ -715,7 +691,6 @@ public EntityEntry addReferenceEntry( final EntityEntry entityEntry = asManagedEntity( entity ).$$_hibernate_getEntityEntry(); entityEntry.setStatus( status ); entityEntryContext.addEntityEntry( entity, entityEntry ); - setHasNonReadOnlyEnties( status ); return entityEntry; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntryFactory.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntryFactory.java index d0bcc2e9d237..fc7b2420f922 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntryFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntryFactory.java @@ -13,7 +13,10 @@ * Contract to build {@link EntityEntry} * * @author Emmanuel Bernard + * + * @deprecated No longer used */ +@Deprecated(since = "7", forRemoval = true) public interface EntityEntryFactory extends Serializable { /** diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index 141f18cd032c..5a6c26fdc701 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -525,9 +525,7 @@ private void checksBeforeQueryCreation() { public void prepareForQueryExecution(boolean requiresTxn) { checksBeforeQueryCreation(); if ( requiresTxn && !isTransactionInProgress() ) { - throw new TransactionRequiredException( - "Query requires transaction be in progress, but no transaction is known to be in progress" - ); + throw new TransactionRequiredException( "No active transaction" ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 9f54ecb571d1..02f25916ebf9 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -554,13 +554,13 @@ public LockMode getCurrentLockMode(Object object) { if ( e == null ) { throw new IllegalArgumentException( "Given entity is not associated with the persistence context" ); } - - if ( e.getStatus().isDeletedOrGone() ) { - throw new ObjectDeletedException( "The given object was deleted", e.getId(), + else if ( e.getStatus().isDeletedOrGone() ) { + throw new ObjectDeletedException( "Given entity was removed", e.getId(), e.getPersister().getEntityName() ); } - - return e.getLockMode(); + else { + return e.getLockMode(); + } } @Override @@ -2611,7 +2611,7 @@ private static CacheStoreMode determineCacheStoreMode(Map settin } private void checkTransactionNeededForUpdateOperation() { - checkTransactionNeededForUpdateOperation( "no transaction is in progress" ); + checkTransactionNeededForUpdateOperation( "No active transaction" ); } @Override @@ -2772,11 +2772,11 @@ public LockModeType getLockMode(Object entity) { checkOpen(); if ( !isTransactionInProgress() ) { - throw new TransactionRequiredException( "Call to EntityManager#getLockMode should occur within transaction according to spec" ); + throw new TransactionRequiredException( "No active transaction" ); } if ( !contains( entity ) ) { - throw getExceptionConverter().convert( new IllegalArgumentException( "entity not in the persistence context" ) ); + throw getExceptionConverter().convert( new IllegalArgumentException( "Entity not associated with the persistence context" ) ); } return LockModeTypeHelper.getLockModeType( getCurrentLockMode( entity ) ); 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 8834f05520e9..e4b04b68ae53 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 @@ -4575,7 +4575,7 @@ private SQLQueryParser createSqlQueryParser(Table table) { @Override public EntityEntryFactory getEntityEntryFactory() { - return this.entityEntryFactory; + return entityEntryFactory; } /** diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java index 9bb01a8b1610..fb34ed7fc586 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java @@ -163,10 +163,11 @@ default String getSqlAliasStem() { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** - * Get the EntityEntryFactory indicated for the entity mapped by this persister. + * Get the {@link EntityEntryFactory} indicated for the entity mapped by this persister. * - * @return The proper EntityEntryFactory. + * @deprecated No longer used */ + @Deprecated(since = "7", forRemoval = true) EntityEntryFactory getEntityEntryFactory(); /** diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java index 03573f9fa780..17fd2006987c 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java @@ -87,7 +87,6 @@ import jakarta.persistence.ParameterMode; import jakarta.persistence.PersistenceException; import jakarta.persistence.TemporalType; -import jakarta.persistence.TransactionRequiredException; import jakarta.persistence.metamodel.Type; import static java.lang.Boolean.parseBoolean; @@ -823,10 +822,6 @@ protected ProcedureOutputs outputs() { @Override protected int doExecuteUpdate() { - if ( !getSession().isTransactionInProgress() ) { - throw new TransactionRequiredException( "jakarta.persistence.Query.executeUpdate requires active transaction" ); - } - // the expectation is that there is just one Output, of type UpdateCountOutput try { execute(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQuery.java b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQuery.java index e1b609b12f6a..1abb7ce170d4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQuery.java @@ -8,7 +8,6 @@ import java.util.Calendar; import java.util.Collection; import java.util.Date; -import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -634,8 +633,9 @@ protected void prepareForExecution() { @Override public int executeUpdate() throws HibernateException { - getSession().checkTransactionNeededForUpdateOperation( "Executing an update/delete query" ); - final HashSet fetchProfiles = beforeQueryHandlingFetchProfiles(); + //TODO: refactor copy/paste of QuerySqmImpl.executeUpdate() + getSession().checkTransactionNeededForUpdateOperation( "No active transaction for update or delete query" ); + final var fetchProfiles = beforeQueryHandlingFetchProfiles(); boolean success = false; try { final int result = doExecuteUpdate(); 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 397e9d0f96be..05ec67530773 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 @@ -86,7 +86,6 @@ import java.util.Calendar; import java.util.Collection; import java.util.Date; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.function.BooleanSupplier; @@ -507,9 +506,10 @@ private QueryInterpretationCache interpretationCache() { @Override public int executeUpdate() { + //TODO: refactor copy/paste of AbstractQuery.executeUpdate() verifyUpdate(); - getSession().checkTransactionNeededForUpdateOperation( "Executing an update/delete query" ); - final HashSet fetchProfiles = beforeQueryHandlingFetchProfiles(); + getSession().checkTransactionNeededForUpdateOperation( "No active transaction for update or delete query" ); + final var fetchProfiles = beforeQueryHandlingFetchProfiles(); boolean success = false; try { final int result = doExecuteUpdate(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/BaseExecutionContext.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/BaseExecutionContext.java index 0afdb826c0ad..7e4732944d01 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/BaseExecutionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/BaseExecutionContext.java @@ -15,9 +15,11 @@ public class BaseExecutionContext implements ExecutionContext { private final SharedSessionContractImplementor session; + private final boolean transactionActive; public BaseExecutionContext(SharedSessionContractImplementor session) { this.session = session; + transactionActive = session.isTransactionInProgress(); } // Optimization: mark this as final so to avoid a megamorphic call on this @@ -27,6 +29,11 @@ public final SharedSessionContractImplementor getSession() { return session; } + @Override + public final boolean isTransactionActive() { + return transactionActive; + } + // Also marked as final for the same reason @Override public final LoadQueryInfluencers getLoadQueryInfluencers() { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/ExecutionContext.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/ExecutionContext.java index a848e7352c46..c58dc56911db 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/ExecutionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/ExecutionContext.java @@ -25,6 +25,10 @@ default boolean isScrollResult(){ SharedSessionContractImplementor getSession(); + default boolean isTransactionActive() { + return getSession().isTransactionInProgress(); + } + QueryOptions getQueryOptions(); LoadQueryInfluencers getLoadQueryInfluencers(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java index 290ef46ea22a..06790df6714c 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java @@ -1321,14 +1321,13 @@ protected void registerReloadedEntity(EntityInitializerData data) { @Override public void initializeInstance(EntityInitializerData data) { - if ( data.getState() != State.RESOLVED ) { - return; - } - if ( !skipInitialization( data ) ) { - assert consistentInstance( data ); - initializeEntityInstance( data ); + if ( data.getState() == State.RESOLVED ) { + if ( !skipInitialization( data ) ) { + assert consistentInstance( data ); + initializeEntityInstance( data ); + } + data.setState( State.INITIALIZED ); } - data.setState( State.INITIALIZED ); } protected boolean consistentInstance(EntityInitializerData data) { @@ -1375,7 +1374,14 @@ protected void initializeEntityInstance(EntityInitializerData data) { // from the perspective of Hibernate, an entity is read locked as soon as it is read // so regardless of the requested lock mode, we upgrade to at least the read level - final LockMode lockModeToAcquire = data.lockMode == LockMode.NONE ? LockMode.READ : data.lockMode; + final LockMode lockModeToAcquire; + if ( data.getRowProcessingState().isTransactionActive() ) { + lockModeToAcquire = data.lockMode == LockMode.NONE ? LockMode.READ : data.lockMode; + } + else { + // data read outside transaction is marked as unlocked + lockModeToAcquire = LockMode.NONE; + } final EntityEntry entityEntry = persistenceContext.addEntry( entityInstanceForNotify, From c6f23128435be560eee6934255ad1abcd04f4215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A2=85=EC=B0=AC?= Date: Tue, 27 May 2025 17:51:54 +0900 Subject: [PATCH 039/168] HHH-19490 Fix NPE when using array_position on sql array type Also fix hsqldb's param rendering (cherry picked from commit 47f4af91f50cb191f249d6944a29422b17af9616) --- .../array/ArrayAndElementArgumentValidator.java | 4 +++- .../function/array/HSQLArrayPositionFunction.java | 3 ++- .../array/HSQLArrayPositionsFunction.java | 3 ++- .../test/function/array/ArrayPositionTest.java | 12 ++++++++++++ .../test/function/array/ArrayPositionsTest.java | 15 +++++++++++++++ 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayAndElementArgumentValidator.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayAndElementArgumentValidator.java index 17cf5cafed42..4c87172ac87c 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayAndElementArgumentValidator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayAndElementArgumentValidator.java @@ -6,6 +6,7 @@ import java.util.List; +import org.hibernate.query.sqm.SqmBindableType; import org.hibernate.type.BindingContext; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.produce.function.ArgumentsValidator; @@ -36,7 +37,8 @@ public void validate( for ( int elementIndex : elementIndexes ) { if ( elementIndex < arguments.size() ) { final SqmTypedNode elementArgument = arguments.get( elementIndex ); - final SqmExpressible elementType = elementArgument.getExpressible().getSqmType(); + final SqmBindableType expressible = elementArgument.getExpressible(); + final SqmExpressible elementType = expressible != null ? expressible.getSqmType() : null; if ( expectedElementType != null && elementType != null && expectedElementType != elementType ) { throw new FunctionArgumentException( String.format( diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayPositionFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayPositionFunction.java index 434160174077..bab60661f1a8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayPositionFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayPositionFunction.java @@ -7,6 +7,7 @@ import java.util.List; import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstNodeRenderingMode; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.sql.ast.tree.SqlAstNode; @@ -35,7 +36,7 @@ public void render( sqlAppender.append( " is not null then coalesce((select t.idx from unnest("); arrayExpression.accept( walker ); sqlAppender.append(") with ordinality t(val,idx) where t.val is not distinct from " ); - elementExpression.accept( walker ); + walker.render( elementExpression, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER ); if ( sqlAstArguments.size() > 2 ) { sqlAppender.append( " and t.idx>=" ); sqlAstArguments.get( 2 ).accept( walker ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayPositionsFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayPositionsFunction.java index d20252f689c6..881f3575211d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayPositionsFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/HSQLArrayPositionsFunction.java @@ -7,6 +7,7 @@ import java.util.List; import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstNodeRenderingMode; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.sql.ast.tree.SqlAstNode; @@ -35,7 +36,7 @@ public void render( sqlAppender.append( " is not null then coalesce((select array_agg(t.idx) from unnest("); arrayExpression.accept( walker ); sqlAppender.append(") with ordinality t(val,idx) where t.val is not distinct from " ); - elementExpression.accept( walker ); + walker.render( elementExpression, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER ); sqlAppender.append( "),cast(array[] as integer array)) end" ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPositionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPositionTest.java index 539411c6f8b2..020d2165a449 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPositionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPositionTest.java @@ -85,6 +85,18 @@ public void testPositionNull(SessionFactoryScope scope) { } ); } + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19490") + public void testPositionParam(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "from EntityWithArrays e where array_position(e.theArray, ?1) = 1", EntityWithArrays.class ) + .setParameter( 1, "abc" ) + .getResultList(); + assertEquals( 1, results.size() ); + assertEquals( 2L, results.get( 0 ).getId() ); + } ); + } + @Test @Jira("https://hibernate.atlassian.net/browse/HHH-17801") public void testEnumPosition(SessionFactoryScope scope) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPositionsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPositionsTest.java index 28afc5cf7bfc..909192adeaf9 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPositionsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayPositionsTest.java @@ -15,6 +15,7 @@ import org.hibernate.testing.orm.junit.BootstrapServiceRegistry; import org.hibernate.testing.orm.junit.DialectFeatureChecks; import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; @@ -93,6 +94,20 @@ public void testPositionsNull(SessionFactoryScope scope) { } ); } + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19490") + public void testPositionsParam(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "select array_positions(e.theArray, ?1) from EntityWithArrays e order by e.id", int[].class ) + .setParameter( 1, "abc" ) + .getResultList(); + assertEquals( 3, results.size() ); + assertArrayEquals( new int[0], results.get( 0 ) ); + assertArrayEquals( new int[]{ 1, 4 }, results.get( 1 ) ); + assertNull( results.get( 2 ) ); + } ); + } + @Test public void testPositionsList(SessionFactoryScope scope) { scope.inSession( em -> { From f03483a17db22bd2c6b0bca882e63df33c9ee954 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 3 Jun 2025 20:02:37 +0200 Subject: [PATCH 040/168] HHH-19515 don't call PhysicalNamingStrategy to produce a logical column name (cherry picked from commit 1ba13e3324470676a982a0da791b90df2605a785) --- .../org/hibernate/boot/model/internal/AnnotatedColumn.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumn.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumn.java index bade05554c3d..3d2bd27d3d39 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumn.java @@ -458,10 +458,7 @@ public MetadataBuildingContext getBuildingContext() { ); } - final Database database = getDatabase(); - return getPhysicalNamingStrategy() - .toPhysicalColumnName( implicitName, database.getJdbcEnvironment() ) - .render( database.getDialect() ); + return implicitName.render( getDatabase().getDialect() ); } private ObjectNameNormalizer getObjectNameNormalizer() { From fa1141c743db8b212fde5550b16f1435b8bf6cbc Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 3 Jun 2025 21:49:53 +0200 Subject: [PATCH 041/168] HHH-19515 add a test for the issue (cherry picked from commit f6c7d64c06a7cccc8712f17d35f5bd11197525a2) --- .../orm/test/naming/PhysicalNamingTest.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/naming/PhysicalNamingTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/naming/PhysicalNamingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/naming/PhysicalNamingTest.java new file mode 100644 index 000000000000..44fb617e938c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/naming/PhysicalNamingTest.java @@ -0,0 +1,54 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.naming; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy; +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.cfg.MappingSettings; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.Jpa; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + +@Jpa(annotatedClasses = PhysicalNamingTest.TheEntity.class, + integrationSettings = @Setting( + name = MappingSettings.PHYSICAL_NAMING_STRATEGY, + value = "org.hibernate.orm.test.naming.PhysicalNamingTest$NamingStrategy")) +public class PhysicalNamingTest { + + public static class NamingStrategy + extends CamelCaseToUnderscoresNamingStrategy { + @Override + protected Identifier unquotedIdentifier(Identifier name) { + Identifier identifier = super.unquotedIdentifier( name ); + return new Identifier( identifier.getText() + "_", + identifier.isQuoted() ); + } + } + + + @JiraKey("HHH-19515") + @Test void test(EntityManagerFactoryScope scope) { + scope.inTransaction( em -> { + // make sure _ was not appended more than once + em.createNativeQuery( + "select its_id_, its_name_, its_friend_its_id_ from the_entity_", + Object[].class ) + .getResultList(); + }); + } + + @Entity(name = "TheEntity") + static class TheEntity { + @Id long itsId; + String itsName; + @ManyToOne + TheEntity itsFriend; + } +} From 2b37e202c2d749ce1529902366e720c1334026ca Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Wed, 4 Jun 2025 14:05:22 -0500 Subject: [PATCH 042/168] HHH-19507 - Upgrade to Hibernate Models 1.0.0 (cherry picked from commit 49c7169f665d90232000bb5989403edf199ed5b1) --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index ed32f50af7b4..442e3708aa73 100644 --- a/settings.gradle +++ b/settings.gradle @@ -76,7 +76,7 @@ dependencyResolutionManagement { def byteBuddyVersion = version "byteBuddy", "1.15.11" def classmateVersion = version "classmate", "1.7.0" def geolatteVersion = version "geolatte", "1.9.1" - def hibernateModelsVersion = version "hibernateModels", "1.0.0.CR3" + def hibernateModelsVersion = version "hibernateModels", "1.0.0" def jandexVersion = version "jandex", "3.3.0" def jacksonVersion = version "jackson", "2.18.2" def jbossLoggingVersion = version "jbossLogging", "3.6.1.Final" From 283955abb6222b52678380b9547e502d2fb8b4d3 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Thu, 5 Jun 2025 10:33:02 +0200 Subject: [PATCH 043/168] HHH-19446 HHH-18633 `hibernate-scan-jandex` migration guide --- migration-guide.adoc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/migration-guide.adoc b/migration-guide.adoc index 06a4b27004f8..96f4813c666a 100644 --- a/migration-guide.adoc +++ b/migration-guide.adoc @@ -627,6 +627,13 @@ specify a name for JNDI binding. The new behavior is as follows (assuming `hibe Hibernate can use the persistence-unit name for binding into JNDI as well, but `hibernate.session_factory_name_is_jndi` must be explicitly set to true. +[[jandex-scanning]] +=== Entity Discovery + +Entity discovery, i.e. automatic detection of mapped Java classes through classpath scanning, now requires by default the new `hibernate-scan-jandex` module. Standalone Hibernate ORM applications relying on this feature will need to add a dependency to this new module in order to continue using it. + +Users can still provide custom `org.hibernate.Scanner` implementations via the `org.hibernate.boot.archive.scan.spi.ScannerFactory` service or the `hibernate.archive.scanner` configuration parameter. + [[unowned-order-column]] === @OrderColumn in Unowned @OneToMany Associations From 9874ccda41ea0f60dcf9e4ff1688589f86d49b75 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Fri, 6 Jun 2025 23:36:01 +0200 Subject: [PATCH 044/168] HHH-19495 Remove the default "true" from optimistic-lock so that no extra annotations are added unless explicitly defined. --- .../mapping/spi/JaxbLockableAttribute.java | 2 +- .../attr/CommonAttributeProcessing.java | 15 +++-- .../org/hibernate/xsd/mapping/mapping-7.0.xsd | 20 +++---- .../hibernate/orm/test/jpa/xml/Consumer.java | 45 ++++++++++++++ .../orm/test/jpa/xml/ConsumerItem.java | 41 +++++++++++++ ...NoDefaultOptimisticLockAnnotationTest.java | 60 +++++++++++++++++++ .../NoDefaultOptimisticLockAnnotationTest.xml | 52 ++++++++++++++++ 7 files changed, 218 insertions(+), 17 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/Consumer.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/ConsumerItem.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/NoDefaultOptimisticLockAnnotationTest.java create mode 100644 hibernate-core/src/test/resources/org/hibernate/orm/test/jpa/xml/NoDefaultOptimisticLockAnnotationTest.xml diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/mapping/spi/JaxbLockableAttribute.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/mapping/spi/JaxbLockableAttribute.java index 98a2ee6814ce..50619d918364 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/mapping/spi/JaxbLockableAttribute.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/mapping/spi/JaxbLockableAttribute.java @@ -10,6 +10,6 @@ * @author Steve Ebersole */ public interface JaxbLockableAttribute extends JaxbPersistentAttribute { - boolean isOptimisticLock(); + Boolean isOptimisticLock(); void setOptimisticLock(Boolean value); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/attr/CommonAttributeProcessing.java b/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/attr/CommonAttributeProcessing.java index 47f483f13efa..d672ccec2708 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/attr/CommonAttributeProcessing.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/attr/CommonAttributeProcessing.java @@ -84,12 +84,15 @@ public static void applyOptimisticLock( JaxbLockableAttribute jaxbAttribute, MutableMemberDetails memberDetails, XmlDocumentContext xmlDocumentContext) { - final boolean includeInOptimisticLock = jaxbAttribute.isOptimisticLock(); - final OptimisticLockAnnotation optLockAnn = (OptimisticLockAnnotation) memberDetails.applyAnnotationUsage( - HibernateAnnotations.OPTIMISTIC_LOCK, - xmlDocumentContext.getModelBuildingContext() - ); - optLockAnn.excluded( !includeInOptimisticLock ); + final Boolean includeInOptimisticLock = jaxbAttribute.isOptimisticLock(); + + if ( includeInOptimisticLock != null ) { + final OptimisticLockAnnotation optLockAnn = (OptimisticLockAnnotation) memberDetails.applyAnnotationUsage( + HibernateAnnotations.OPTIMISTIC_LOCK, + xmlDocumentContext.getModelBuildingContext() + ); + optLockAnn.excluded( !includeInOptimisticLock ); + } } public static void applyFetching( diff --git a/hibernate-core/src/main/resources/org/hibernate/xsd/mapping/mapping-7.0.xsd b/hibernate-core/src/main/resources/org/hibernate/xsd/mapping/mapping-7.0.xsd index 058843f32b66..83f066cccec6 100644 --- a/hibernate-core/src/main/resources/org/hibernate/xsd/mapping/mapping-7.0.xsd +++ b/hibernate-core/src/main/resources/org/hibernate/xsd/mapping/mapping-7.0.xsd @@ -601,7 +601,7 @@ - + @@ -1027,7 +1027,7 @@ - + @@ -1109,7 +1109,7 @@ - + @@ -1614,7 +1614,7 @@ - + @@ -1661,7 +1661,7 @@ - + @@ -2057,7 +2057,7 @@ - + @@ -2113,7 +2113,7 @@ - + @@ -3139,7 +3139,7 @@ - + @@ -3208,7 +3208,7 @@ - + @@ -3532,4 +3532,4 @@ - \ No newline at end of file + diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/Consumer.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/Consumer.java new file mode 100644 index 000000000000..b5e935259811 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/Consumer.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa.xml; + +import jakarta.persistence.Id; +import jakarta.persistence.Version; + +import java.util.List; + +public class Consumer { + + @Id + private int id; + + private List consumerItems; + + @Version + private int version; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public List getConsumerItems() { + return consumerItems; + } + + public void setConsumerItems(List consumerItems) { + this.consumerItems = consumerItems; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/ConsumerItem.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/ConsumerItem.java new file mode 100644 index 000000000000..d984e44f275f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/ConsumerItem.java @@ -0,0 +1,41 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa.xml; + +import jakarta.persistence.Version; + +public class ConsumerItem { + + private int id; + + private Consumer consumer; + + @Version + private int version; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Consumer getConsumer() { + return consumer; + } + + public void setConsumer(Consumer consumer) { + this.consumer = consumer; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/NoDefaultOptimisticLockAnnotationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/NoDefaultOptimisticLockAnnotationTest.java new file mode 100644 index 000000000000..425937965357 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/NoDefaultOptimisticLockAnnotationTest.java @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa.xml; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +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.BeforeEach; +import org.junit.jupiter.api.Test; + + +@JiraKey(value = "HHH-19495") +@Jpa( + xmlMappings = {"org/hibernate/orm/test/jpa/xml/NoDefaultOptimisticLockAnnotationTest.xml"}, + annotatedClasses = {Consumer.class, ConsumerItem.class}, + useCollectingStatementInspector = true +) +class NoDefaultOptimisticLockAnnotationTest { + + private static SQLStatementInspector statementInspector; + private static int consumerId; + + + @BeforeEach + public void setUp(EntityManagerFactoryScope scope) { + statementInspector = scope.getCollectingStatementInspector(); + + scope.inTransaction( em -> { + Consumer consumer = new Consumer(); + em.persist( consumer ); + consumerId = consumer.getId(); + + ConsumerItem item1 = new ConsumerItem(); + item1.setConsumer( consumer ); + em.persist( item1 ); + + ConsumerItem item2 = new ConsumerItem(); + item2.setConsumer( consumer ); + em.persist( item2 ); + } ); + } + + @Test + void test(EntityManagerFactoryScope scope) { + statementInspector.clear(); + + scope.inTransaction( em -> { + Consumer consumer = em.find( Consumer.class, consumerId ); + ConsumerItem inventory = new ConsumerItem(); + inventory.setConsumer( consumer ); + consumer.getConsumerItems().add( inventory ); + } ); + + statementInspector.assertIsInsert( 1 ); + statementInspector.assertNoUpdate(); + } +} diff --git a/hibernate-core/src/test/resources/org/hibernate/orm/test/jpa/xml/NoDefaultOptimisticLockAnnotationTest.xml b/hibernate-core/src/test/resources/org/hibernate/orm/test/jpa/xml/NoDefaultOptimisticLockAnnotationTest.xml new file mode 100644 index 000000000000..92dbdabbb6ae --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/orm/test/jpa/xml/NoDefaultOptimisticLockAnnotationTest.xml @@ -0,0 +1,52 @@ + + + + + org.hibernate.orm.test.jpa.xml + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + From dbe4ac14fbc9d1873b70267ac08dbf06778de4bd Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Mon, 9 Jun 2025 15:48:48 +0200 Subject: [PATCH 045/168] HHH-19528 Read and apply the version from XML --- .../xml/internal/AttributeProcessor.java | 26 +++++- .../xml/internal/XmlAnnotationHelper.java | 32 ++++++++ .../hibernate/orm/test/jpa/xml/BaseShop.java | 30 +++++++ ...LockAnnotationOnCollectionXmlOnlyTest.java | 82 +++++++++++++++++++ .../orm/test/jpa/xml/Supermarket.java | 18 ++++ ...cLockAnnotationOnCollectionXmlOnlyTest.xml | 67 +++++++++++++++ .../testing/jdbc/SQLStatementInspector.java | 12 +++ 7 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/BaseShop.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/ExplicitOptimisticLockAnnotationOnCollectionXmlOnlyTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/Supermarket.java create mode 100644 hibernate-core/src/test/resources/org/hibernate/orm/test/jpa/xml/ExplicitOptimisticLockAnnotationOnCollectionXmlOnlyTest.xml diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/AttributeProcessor.java b/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/AttributeProcessor.java index c579b42a51fe..e3e3ee18fb15 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/AttributeProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/AttributeProcessor.java @@ -10,6 +10,7 @@ import org.hibernate.boot.jaxb.mapping.spi.JaxbAssociationOverrideImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbAttributeOverrideImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbAttributesContainer; +import org.hibernate.boot.jaxb.mapping.spi.JaxbAttributesContainerImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbBaseAttributesContainer; import org.hibernate.boot.jaxb.mapping.spi.JaxbBasicImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbElementCollectionImpl; @@ -22,6 +23,7 @@ import org.hibernate.boot.jaxb.mapping.spi.JaxbPersistentAttribute; import org.hibernate.boot.jaxb.mapping.spi.JaxbPluralAnyMappingImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbTransientImpl; +import org.hibernate.boot.jaxb.mapping.spi.JaxbVersionImpl; import org.hibernate.boot.models.HibernateAnnotations; import org.hibernate.boot.models.xml.internal.attr.AnyMappingAttributeProcessing; import org.hibernate.boot.models.xml.internal.attr.BasicAttributeProcessing; @@ -151,12 +153,21 @@ public interface MemberAdjuster { } public static void processAttributes( - JaxbAttributesContainer attributesContainer, + JaxbAttributesContainerImpl attributesContainer, MutableClassDetails mutableClassDetails, AccessType classAccessType, XmlDocumentContext xmlDocumentContext) { processAttributes( attributesContainer, mutableClassDetails, classAccessType, null, xmlDocumentContext ); } + public static void processAttributes( + JaxbAttributesContainerImpl attributesContainer, + MutableClassDetails mutableClassDetails, + AccessType classAccessType, + MemberAdjuster memberAdjuster, + XmlDocumentContext xmlDocumentContext) { + processAttributes( (JaxbAttributesContainer) attributesContainer, mutableClassDetails, classAccessType, memberAdjuster, xmlDocumentContext ); + processVersionAttribute( attributesContainer.getVersion(), mutableClassDetails, classAccessType, xmlDocumentContext ); + } public static void processAttributes( JaxbAttributesContainer attributesContainer, @@ -265,4 +276,17 @@ public static void processAssociationOverrides( xmlDocumentContext ); } + + public static void processVersionAttribute( + JaxbVersionImpl version, + MutableClassDetails mutableClassDetails, AccessType classAccessType, + XmlDocumentContext xmlDocumentContext + ) { + XmlAnnotationHelper.applyVersion( + version, + mutableClassDetails, + classAccessType, + xmlDocumentContext + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/XmlAnnotationHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/XmlAnnotationHelper.java index 9a944d0b0b50..36608dcaed68 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/XmlAnnotationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/XmlAnnotationHelper.java @@ -24,6 +24,7 @@ import java.util.UUID; import java.util.function.Consumer; +import jakarta.persistence.AccessType; import org.hibernate.AnnotationException; import org.hibernate.annotations.CascadeType; import org.hibernate.annotations.NotFoundAction; @@ -73,6 +74,7 @@ import org.hibernate.boot.jaxb.mapping.spi.JaxbUniqueConstraintImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbUserTypeImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbUuidGeneratorImpl; +import org.hibernate.boot.jaxb.mapping.spi.JaxbVersionImpl; import org.hibernate.boot.models.HibernateAnnotations; import org.hibernate.boot.models.JpaAnnotations; import org.hibernate.boot.models.XmlAnnotations; @@ -81,6 +83,7 @@ import org.hibernate.boot.models.annotations.spi.DatabaseObjectDetails; import org.hibernate.boot.models.JpaEventListenerStyle; import org.hibernate.boot.models.spi.JpaEventListener; +import org.hibernate.boot.models.xml.internal.attr.CommonAttributeProcessing; import org.hibernate.boot.models.xml.internal.db.ForeignKeyProcessing; import org.hibernate.boot.models.xml.internal.db.JoinColumnProcessing; import org.hibernate.boot.models.xml.internal.db.TableProcessing; @@ -136,6 +139,7 @@ import static org.hibernate.boot.models.JpaAnnotations.UNIQUE_CONSTRAINT; import static org.hibernate.boot.models.xml.internal.UserTypeCasesMapKey.MAP_KEY_USER_TYPE_CASES; import static org.hibernate.boot.models.xml.internal.UserTypeCasesStandard.STANDARD_USER_TYPE_CASES; +import static org.hibernate.internal.util.NullnessHelper.coalesce; import static org.hibernate.internal.util.StringHelper.isEmpty; import static org.hibernate.internal.util.StringHelper.isNotEmpty; import static org.hibernate.internal.util.StringHelper.unqualify; @@ -1701,4 +1705,32 @@ public static void applyCollectionClassification( ); collectionClassification.value( classification ); } + + public static void applyVersion(JaxbVersionImpl version, MutableClassDetails mutableClassDetails, AccessType classAccessType, XmlDocumentContext xmlDocumentContext) { + if ( version != null ) { + final AccessType accessType = coalesce( version.getAccess(), classAccessType ); + final String versionAttributeName = version.getName(); + + final MutableMemberDetails memberDetails = XmlProcessingHelper.getAttributeMember( + versionAttributeName, + accessType, + mutableClassDetails + ); + memberDetails.applyAnnotationUsage( + JpaAnnotations.VERSION, + xmlDocumentContext.getModelBuildingContext() + ); + CommonAttributeProcessing.applyAccess( accessType, memberDetails, xmlDocumentContext ); + CommonAttributeProcessing.applyAttributeAccessor( version, memberDetails, xmlDocumentContext ); + XmlAnnotationHelper.applyTemporal( version.getTemporal(), memberDetails, xmlDocumentContext ); + if ( version.getColumn() != null ) { + final ColumnJpaAnnotation columnAnn = (ColumnJpaAnnotation) memberDetails.applyAnnotationUsage( + JpaAnnotations.COLUMN, + xmlDocumentContext.getModelBuildingContext() + ); + columnAnn.apply( version.getColumn(), xmlDocumentContext ); + XmlAnnotationHelper.applyColumnTransformation( version.getColumn(), memberDetails, xmlDocumentContext ); + } + } + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/BaseShop.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/BaseShop.java new file mode 100644 index 000000000000..75cfab1df2f8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/BaseShop.java @@ -0,0 +1,30 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa.xml; + + + +public class BaseShop { + + private int id; + + private int version; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/ExplicitOptimisticLockAnnotationOnCollectionXmlOnlyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/ExplicitOptimisticLockAnnotationOnCollectionXmlOnlyTest.java new file mode 100644 index 000000000000..8aae05a896ee --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/ExplicitOptimisticLockAnnotationOnCollectionXmlOnlyTest.java @@ -0,0 +1,82 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa.xml; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +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.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + + +@JiraKey(value = "HHH-19528") +@Jpa( + xmlMappings = {"org/hibernate/orm/test/jpa/xml/ExplicitOptimisticLockAnnotationOnCollectionXmlOnlyTest.xml"}, + useCollectingStatementInspector = true +) +class ExplicitOptimisticLockAnnotationOnCollectionXmlOnlyTest { + + private static SQLStatementInspector statementInspector; + private static int consumerId; + + + @BeforeEach + public void setUp(EntityManagerFactoryScope scope) { + statementInspector = scope.getCollectingStatementInspector(); + + scope.inTransaction( em -> { + Consumer consumer = new Consumer(); + em.persist( consumer ); + consumerId = consumer.getId(); + + ConsumerItem item1 = new ConsumerItem(); + item1.setConsumer( consumer ); + em.persist( item1 ); + + ConsumerItem item2 = new ConsumerItem(); + item2.setConsumer( consumer ); + em.persist( item2 ); + } ); + } + + @Test + void test(EntityManagerFactoryScope scope) { + statementInspector.clear(); + + scope.inTransaction( em -> { + Consumer consumer = em.find( Consumer.class, consumerId ); + ConsumerItem inventory = new ConsumerItem(); + inventory.setConsumer( consumer ); + consumer.getConsumerItems().add( inventory ); + } ); + statementInspector.assertUpdate(); + statementInspector.assertInsert(); + } + + @Test + void testVersionOnMappedSupertype(EntityManagerFactoryScope scope) { + var shop = scope.fromTransaction( em -> { + Supermarket supermarket = new Supermarket(); + supermarket.setName( "Tesco" ); + em.persist( supermarket ); + return supermarket; + } ); + + statementInspector.clear(); + scope.inTransaction( em -> { + Supermarket supermarket = em.find( Supermarket.class, shop.getId() ); + supermarket.setName( "Leclerc" ); + } ); + scope.inTransaction( em -> { + Supermarket supermarket = em.find( Supermarket.class, shop.getId() ); + assertThat( shop.getVersion() ).isNotEqualTo( supermarket.getVersion() ); + } ); + + statementInspector.assertHasQueryMatching( "update.*version.*" ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/Supermarket.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/Supermarket.java new file mode 100644 index 000000000000..f520b6469db7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/xml/Supermarket.java @@ -0,0 +1,18 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa.xml; + +public class Supermarket extends BaseShop { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/hibernate-core/src/test/resources/org/hibernate/orm/test/jpa/xml/ExplicitOptimisticLockAnnotationOnCollectionXmlOnlyTest.xml b/hibernate-core/src/test/resources/org/hibernate/orm/test/jpa/xml/ExplicitOptimisticLockAnnotationOnCollectionXmlOnlyTest.xml new file mode 100644 index 000000000000..4ea384661d8a --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/orm/test/jpa/xml/ExplicitOptimisticLockAnnotationOnCollectionXmlOnlyTest.xml @@ -0,0 +1,67 @@ + + + + + org.hibernate.orm.test.jpa.xml + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInspector.java b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInspector.java index d5c42adc15e9..a322069b3cab 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInspector.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInspector.java @@ -127,7 +127,19 @@ public void assertUpdate() { .anySatisfy( sql -> Assertions.assertThat( sql.toLowerCase( Locale.ROOT ) ).startsWith( "update" ) ); } + public void assertInsert() { + Assertions.assertThat( sqlQueries ) + .isNotEmpty() + .anySatisfy( sql -> Assertions.assertThat( sql.toLowerCase( Locale.ROOT ) ).startsWith( "insert" ) ); + } + public static SQLStatementInspector extractFromSession(SessionImplementor session) { return (SQLStatementInspector) session.getJdbcSessionContext().getStatementInspector(); } + + public void assertHasQueryMatching(String queryPattern) { + Assertions.assertThat( sqlQueries ) + .isNotEmpty() + .anySatisfy( sql -> Assertions.assertThat( sql.toLowerCase( Locale.ROOT ) ).matches( queryPattern ) ); + } } From 0d7539fd325e3481dc72c8e0829bc65855e2edf8 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Tue, 10 Jun 2025 13:24:08 +0000 Subject: [PATCH 046/168] Pre-steps for release : `7.0.1.Final` --- changelog.txt | 39 +++++++++++++++++++++++++++++++++++++++ gradle/version.properties | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index b47823ee7033..008878839e20 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,45 @@ Hibernate 7 Changelog Note: Please refer to JIRA to learn more about each issue. +Changes in 7.0.1.Final (June 10, 2025) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/33571 + +** Bug + * [HHH-19528] - Version xml mapping is ignored + * [HHH-19515] - PhysicalNamingStrategy called to produce a logical column name + * [HHH-19495] - [Hibernate 7] - Extra update when mixing entity annotation with XML when a collection is dirty + * [HHH-19490] - NPE when using array_position function + * [HHH-19482] - entity assigned LockMode.READ when read outside transaction + * [HHH-19480] - missing FK constraint for unidirectional @OneToMany on a non-primary key + * [HHH-19479] - single referenced column not marked unique + * [HHH-19477] - ConnectionReleaseMode.AFTER_STATEMENT ineffective due to missing connection release + * [HHH-19476] - claimEntityHolderIfPossible Assertion Error + * [HHH-19473] - Bytecode enhancement incorrectly generates code for Bidirectional Generic Entities + * [HHH-19472] - Native query "SELECT 1" with result type Object[] return singular object + * [HHH-19463] - Hibernate processor silently ignores incorrect repository definitions (e.g. @Save on non-entity type) + * [HHH-19446] - [Regression] UnknownEntityException: Could not resolve root entity + * [HHH-19387] - AssertionError in EntityInitializerImpl data.concreteDescriptor is null + * [HHH-19383] - validation of NativeQuery result mappings + * [HHH-19372] - AccessOptimizer.setPropertyValues() and getPropertyValues() error with entity hierarchy. + * [HHH-19369] - Entity hierarchy ordering error within metamodel building with enhancement + * [HHH-18891] - java.lang.AssertionError generated in getResolvedInstance even though NotFound IGNORE set + * [HHH-18876] - ArrayInitializer#resolveInstanceSubInitializers should consider @ListIndexBase + * [HHH-18813] - DML update of secondary table column fails + * [HHH-18774] - two bad bugs in cascade refresh + * [HHH-18252] - Support @IdClass during annotation processing for Jakarta Data + +** Improvement + * [HHH-19507] - Upgrade to Hibernate Models 1.0.0 + +** Patch + * [HHH-19471] - Server startup fails in FIPS mode because of MD5 usage for @OneToMany Constraint Name and FK name generation. + +** Task + * [HHH-19519] - Document breaking changes in new Hibernate Maven Plugin + + Changes in 7.0.0.Final (May 19, 2025) ------------------------------------------------------------------------------------------------------------------------ diff --git a/gradle/version.properties b/gradle/version.properties index 36545aa38b6d..9b8d1991ff3f 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=7.0.1-SNAPSHOT \ No newline at end of file +hibernateVersion=7.0.1.Final \ No newline at end of file From 0745291985bf1afe8d00ece19345f126ffcae407 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Tue, 10 Jun 2025 13:30:15 +0000 Subject: [PATCH 047/168] Post-steps for release : `7.0.1.Final` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 9b8d1991ff3f..520a19c404c6 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=7.0.1.Final \ No newline at end of file +hibernateVersion=7.0.2-SNAPSHOT \ No newline at end of file From f8020fa5265d51b329a247476a403080b8e76344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 10 Jun 2025 16:10:46 +0200 Subject: [PATCH 048/168] Fix changelog for 7.0.1.Final A few not-yet-fixed issues got caught in the process. --- changelog.txt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/changelog.txt b/changelog.txt index 008878839e20..aed18aa7813f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -29,8 +29,7 @@ https://hibernate.atlassian.net/projects/HHH/versions/33571 * [HHH-18891] - java.lang.AssertionError generated in getResolvedInstance even though NotFound IGNORE set * [HHH-18876] - ArrayInitializer#resolveInstanceSubInitializers should consider @ListIndexBase * [HHH-18813] - DML update of secondary table column fails - * [HHH-18774] - two bad bugs in cascade refresh - * [HHH-18252] - Support @IdClass during annotation processing for Jakarta Data + * [HHH-18252] - Support @IdClass during annotation processing for Jakarta Data ** Improvement * [HHH-19507] - Upgrade to Hibernate Models 1.0.0 @@ -38,9 +37,6 @@ https://hibernate.atlassian.net/projects/HHH/versions/33571 ** Patch * [HHH-19471] - Server startup fails in FIPS mode because of MD5 usage for @OneToMany Constraint Name and FK name generation. -** Task - * [HHH-19519] - Document breaking changes in new Hibernate Maven Plugin - Changes in 7.0.0.Final (May 19, 2025) ------------------------------------------------------------------------------------------------------------------------ From 199269a5f0350357d8357f38604f8f0ca7676cc7 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Wed, 4 Jun 2025 11:16:33 +0200 Subject: [PATCH 049/168] HHH-19518 Make hibernate-maven-plugin configuration parameters non-readonly --- .../hibernate/orm/tooling/maven/HibernateEnhancerMojo.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tooling/hibernate-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/HibernateEnhancerMojo.java b/tooling/hibernate-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/HibernateEnhancerMojo.java index 9fd9b3688279..061af2c34be0 100644 --- a/tooling/hibernate-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/HibernateEnhancerMojo.java +++ b/tooling/hibernate-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/HibernateEnhancerMojo.java @@ -38,31 +38,26 @@ public class HibernateEnhancerMojo extends AbstractMojo { @Parameter( defaultValue = "${project.build.directory}/classes", - readonly = true, required = true) private File classesDirectory; @Parameter( defaultValue = "false", - readonly = true, required = true) private boolean enableAssociationManagement; @Parameter( defaultValue = "false", - readonly = true, required = true) private boolean enableDirtyTracking; @Parameter( defaultValue = "false", - readonly = true, required = true) private boolean enableLazyInitialization; @Parameter( defaultValue = "false", - readonly = true, required = true) private boolean enableExtendedEnhancement; From e65057a7d37fe08ece67c1ee8465c49327b09c2b Mon Sep 17 00:00:00 2001 From: Scott Marlow Date: Tue, 10 Jun 2025 14:15:52 -0400 Subject: [PATCH 050/168] HHH-19529 Check bytecode generated classes with stable names class loaders to ensure EE subdeployment contained entity classes are used in preference to top level deployment (e.g. EAR/lib) Signed-off-by: Scott Marlow --- .../bytecode/internal/bytebuddy/ByteBuddyState.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java index 9c5871b1d1f5..e3b108e557f8 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java @@ -214,7 +214,10 @@ void clearState() { */ public Class load(Class referenceClass, String className, BiFunction> makeClassFunction) { try { - return referenceClass.getClassLoader().loadClass( className ); + Class result = referenceClass.getClassLoader().loadClass(className); + if ( result.getClassLoader() == referenceClass.getClassLoader() ) { + return result; + } } catch (ClassNotFoundException e) { // Ignore From 148e6ae7d05a53165518ee3e8cacd5921869f0c2 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 11 Jun 2025 08:39:57 +0200 Subject: [PATCH 051/168] implement equals() / hashCode() for NativeQueryConstructorTransformer to help the query interpretation cache --- .../jpa/spi/NativeQueryConstructorTransformer.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryConstructorTransformer.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryConstructorTransformer.java index c45bda1919ef..f65b2bcb84c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryConstructorTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryConstructorTransformer.java @@ -24,7 +24,7 @@ public class NativeQueryConstructorTransformer implements TupleTransformer { private final Class resultClass; - private Constructor constructor; + private transient Constructor constructor; private Constructor constructor(Object[] elements) { if ( constructor == null ) { @@ -75,4 +75,16 @@ public T transformTuple(Object[] tuple, String[] aliases) { throw new InstantiationException( "Cannot instantiate query result type", resultClass, e ); } } + + @Override + public boolean equals(Object obj) { + return obj instanceof NativeQueryConstructorTransformer that + && this.resultClass == that.resultClass; + // should be safe to ignore the cached constructor here + } + + @Override + public int hashCode() { + return resultClass.hashCode(); + } } From 6b82404b26732a0a2386882a84556c1b0835e6d0 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Sat, 7 Jun 2025 16:46:57 +0200 Subject: [PATCH 052/168] Add methods to CompositeNestedGeneratedValueGenerator for Hibernate reactive --- ...ompositeNestedGeneratedValueGenerator.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java index 368918fdde52..6600d2da689b 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java @@ -116,6 +116,16 @@ public CompositeNestedGeneratedValueGenerator( this.compositeType = compositeType; } + // Used by Hibernate Reactive + public CompositeNestedGeneratedValueGenerator( + GenerationContextLocator generationContextLocator, + CompositeType compositeType, + List generationPlans) { + this.generationContextLocator = generationContextLocator; + this.compositeType = compositeType; + this.generationPlans.addAll( generationPlans ); + } + public void addGeneratedValuePlan(GenerationPlan plan) { generationPlans.add( plan ); } @@ -172,4 +182,19 @@ public void initialize(SqlStringGenerationContext context) { plan.initialize( context ); } } + + // Used by Hibernate Reactive + public List getGenerationPlans() { + return generationPlans; + } + + // Used by Hibernate Reactive + public GenerationContextLocator getGenerationContextLocator() { + return generationContextLocator; + } + + // Used by Hibernate Reactive + public CompositeType getCompositeType() { + return compositeType; + } } From f9bf7f8bb94b52538030d7cf8de4869a73201c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 12 Jun 2025 13:21:27 +0200 Subject: [PATCH 053/168] Enable weekly releases (if there's something new) for branch 7.0 --- ci/release/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index 0b1dad76412d..637561251ef4 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -15,7 +15,7 @@ import org.hibernate.jenkins.pipeline.helpers.version.Version // Global build configuration env.PROJECT = "orm" env.JIRA_KEY = "HHH" -def RELEASE_ON_SCHEDULE = false // Set to `true` *only* on branches where you want a scheduled release. +def RELEASE_ON_SCHEDULE = true // Set to `true` *only* on branches where you want a scheduled release. print "INFO: env.PROJECT = ${env.PROJECT}" print "INFO: env.JIRA_KEY = ${env.JIRA_KEY}" From 09a863e4598ae3fe842e2fc655808a4a8300394e Mon Sep 17 00:00:00 2001 From: Gavin King Date: Thu, 5 Jun 2025 22:56:09 +0200 Subject: [PATCH 054/168] HHH-19522 report failed optimistic lock checking for upsert() upsert() should throw StaleObjectStateException if the version verification fails --- .../sql/model/jdbc/MergeOperation.java | 3 +- .../sql/model/jdbc/UpsertOperation.java | 3 +- .../test/stateless/UpsertVersionedTest.java | 29 ++++++++++++++++++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/MergeOperation.java b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/MergeOperation.java index 2f98e88e0d5a..0e1bf7d0dd94 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/MergeOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/MergeOperation.java @@ -23,7 +23,7 @@ public MergeOperation( MutationTarget mutationTarget, String sql, List parameterBinders) { - super( tableDetails, mutationTarget, sql, false, Expectation.None.INSTANCE, parameterBinders ); + super( tableDetails, mutationTarget, sql, false, new Expectation.RowCount(), parameterBinders ); } @Override @@ -31,5 +31,4 @@ public MutationType getMutationType() { return MutationType.UPDATE; } - } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/UpsertOperation.java b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/UpsertOperation.java index a1a262c36240..6ba371d381c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/UpsertOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/UpsertOperation.java @@ -23,7 +23,7 @@ public UpsertOperation( MutationTarget mutationTarget, String sql, List parameterBinders) { - super( tableDetails, mutationTarget, sql, false, Expectation.None.INSTANCE, parameterBinders ); + super( tableDetails, mutationTarget, sql, false, new Expectation.RowCount(), parameterBinders ); } @Override @@ -31,5 +31,4 @@ public MutationType getMutationType() { return MutationType.UPDATE; } - } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/UpsertVersionedTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/UpsertVersionedTest.java index c08f714408c1..ad2dc1458801 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/UpsertVersionedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/UpsertVersionedTest.java @@ -7,17 +7,21 @@ import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Version; +import org.hibernate.StaleObjectStateException; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; @SessionFactory @DomainModel(annotatedClasses = UpsertVersionedTest.Record.class) public class UpsertVersionedTest { + @Test void test(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncate(); scope.inStatelessTransaction(s-> { s.upsert(new Record(123L,null,"hello earth")); s.upsert(new Record(456L,2L,"hello mars")); @@ -41,6 +45,29 @@ public class UpsertVersionedTest { assertEquals("goodbye mars",s.get(Record.class,456L).message); }); } + + @Test void testStaleUpsert(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncate(); + scope.inStatelessTransaction( s -> { + s.insert(new Record(789L, 1L, "hello world")); + } ); + scope.inStatelessTransaction( s -> { + s.upsert(new Record(789L, 1L, "hello mars")); + } ); + try { + scope.inStatelessTransaction( s -> { + s.upsert(new Record( 789L, 1L, "hello venus")); + } ); + fail(); + } + catch (StaleObjectStateException sose) { + //expected + } + scope.inStatelessTransaction( s-> { + assertEquals( "hello mars", s.get(Record.class,789L).message ); + } ); + } + @Entity(name = "Record") static class Record { @Id Long id; From 58df98eccdc67b711d7fc4f87ce2f41bb60f8344 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Thu, 5 Jun 2025 22:56:24 +0200 Subject: [PATCH 055/168] use @linkplain in jdoc --- .../src/main/java/org/hibernate/jdbc/Expectation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/jdbc/Expectation.java b/hibernate-core/src/main/java/org/hibernate/jdbc/Expectation.java index bd595d58f0a6..941eb5e65d97 100644 --- a/hibernate-core/src/main/java/org/hibernate/jdbc/Expectation.java +++ b/hibernate-core/src/main/java/org/hibernate/jdbc/Expectation.java @@ -181,7 +181,7 @@ protected int expectedRowCount() { /** * Essentially identical to {@link RowCount} except that the row count - * is obtained via an output parameter of a {@link CallableStatement + * is obtained via an output parameter of a {@linkplain CallableStatement * stored procedure}. *

* Statement batching is disabled when {@code OutParameter} is used. From c145678f27a772a22865c16f60cfbdac49aa1b2b Mon Sep 17 00:00:00 2001 From: Gavin King Date: Thu, 5 Jun 2025 23:35:04 +0200 Subject: [PATCH 056/168] HHH-19522 report failed optimistic lock checking for upsert() - also fix the problem for upsert emulation with UPDATE/INSERT --- .../jdbc/OptionalTableUpdateOperation.java | 33 ++++++++++++------- .../test/stateless/UpsertVersionedTest.java | 4 +-- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java index e34a5fbf339a..cc02e17755a3 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java @@ -13,6 +13,7 @@ import java.util.Objects; import java.util.Set; +import org.hibernate.StaleStateException; import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; import org.hibernate.engine.jdbc.mutation.ParameterUsage; import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; @@ -25,6 +26,7 @@ import org.hibernate.engine.jdbc.spi.MutationStatementPreparer; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.exception.ConstraintViolationException; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.jdbc.Expectation; import org.hibernate.persister.entity.mutation.EntityMutationTarget; @@ -52,6 +54,7 @@ import org.hibernate.sql.model.internal.TableUpdateCustomSql; import org.hibernate.sql.model.internal.TableUpdateStandard; +import static org.hibernate.exception.ConstraintViolationException.ConstraintKind.UNIQUE; import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER; /** @@ -152,20 +155,28 @@ public void performMutation( wasUpdated = false; } - if ( !wasUpdated ) { - MODEL_MUTATION_LOGGER.debugf( - "Upsert update altered no rows - inserting : %s", - tableMapping.getTableName() - ); - performInsert( jdbcValueBindings, session ); + if ( !wasUpdated ) { + MODEL_MUTATION_LOGGER.debugf( + "Upsert update altered no rows - inserting : %s", + tableMapping.getTableName() + ); + try { + performInsert( jdbcValueBindings, session ); + } + catch (ConstraintViolationException cve) { + throw cve.getKind() == UNIQUE + // assume it was the primary key constraint which was violated, + // due to a new version of the row existing in the database + ? new StaleStateException( mutationTarget.getRolePath(), cve ) + : cve; + } + } } } + finally { + jdbcValueBindings.afterStatement( tableMapping ); + } } - finally { - jdbcValueBindings.afterStatement( tableMapping ); - } - - } private void performDelete(JdbcValueBindings jdbcValueBindings, SharedSessionContractImplementor session) { final JdbcDeleteMutation jdbcDelete = createJdbcDelete( session ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/UpsertVersionedTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/UpsertVersionedTest.java index ad2dc1458801..c6f21c49343a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/UpsertVersionedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/UpsertVersionedTest.java @@ -7,7 +7,7 @@ import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Version; -import org.hibernate.StaleObjectStateException; +import org.hibernate.StaleStateException; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; @@ -60,7 +60,7 @@ public class UpsertVersionedTest { } ); fail(); } - catch (StaleObjectStateException sose) { + catch (StaleStateException sse) { //expected } scope.inStatelessTransaction( s-> { From 1e028f41f7b704063dd95a3d14bfc13bf157b08a Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 6 Jun 2025 00:50:18 +0200 Subject: [PATCH 057/168] HHH-19522 report failed optimistic lock checking for upsert() - also fix the problem for Oracle --- .../model/jdbc/DeleteOrUpsertOperation.java | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/DeleteOrUpsertOperation.java b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/DeleteOrUpsertOperation.java index 6af41093defa..5de3a1a086b7 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/DeleteOrUpsertOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/DeleteOrUpsertOperation.java @@ -18,7 +18,9 @@ import org.hibernate.engine.jdbc.mutation.internal.PreparedStatementGroupSingleTable; import org.hibernate.engine.jdbc.mutation.spi.Binding; import org.hibernate.engine.jdbc.mutation.spi.BindingGroup; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.jdbc.Expectation; import org.hibernate.persister.entity.mutation.EntityMutationTarget; import org.hibernate.persister.entity.mutation.EntityTableMapping; import org.hibernate.persister.entity.mutation.UpdateValuesAnalysis; @@ -44,6 +46,8 @@ public class DeleteOrUpsertOperation implements SelfExecutingUpdateOperation { private final OptionalTableUpdate optionalTableUpdate; + private final Expectation expectation = new Expectation.RowCount(); + public DeleteOrUpsertOperation( EntityMutationTarget mutationTarget, EntityTableMapping tableMapping, @@ -125,7 +129,8 @@ private void performDelete(JdbcValueBindings jdbcValueBindings, SharedSessionCon try { final PreparedStatement upsertDeleteStatement = statementDetails.resolveStatement(); - session.getJdbcServices().getSqlStatementLogger().logStatement( statementDetails.getSqlString() ); + final JdbcServices jdbcServices = session.getJdbcServices(); + jdbcServices.getSqlStatementLogger().logStatement( statementDetails.getSqlString() ); bindDeleteKeyValues( jdbcValueBindings, @@ -137,6 +142,16 @@ private void performDelete(JdbcValueBindings jdbcValueBindings, SharedSessionCon final int rowCount = session.getJdbcCoordinator().getResultSetReturn() .executeUpdate( upsertDeleteStatement, statementDetails.getSqlString() ); MODEL_MUTATION_LOGGER.tracef( "`%s` rows upsert-deleted from `%s`", rowCount, tableMapping.getTableName() ); + try { + expectation.verifyOutcome( rowCount, upsertDeleteStatement, -1, statementDetails.getSqlString() ); + } + catch (SQLException e) { + throw jdbcServices.getSqlExceptionHelper().convert( + e, + "Unable to verify outcome for upsert delete", + statementDetails.getSqlString() + ); + } } finally { statementDetails.releaseStatement( session ); @@ -204,14 +219,24 @@ private void performUpsert(JdbcValueBindings jdbcValueBindings, SharedSessionCon try { final PreparedStatement updateStatement = statementDetails.resolveStatement(); - session.getJdbcServices().getSqlStatementLogger().logStatement( statementDetails.getSqlString() ); - + final JdbcServices jdbcServices = session.getJdbcServices(); + jdbcServices.getSqlStatementLogger().logStatement( statementDetails.getSqlString() ); jdbcValueBindings.beforeStatement( statementDetails ); final int rowCount = session.getJdbcCoordinator().getResultSetReturn() .executeUpdate( updateStatement, statementDetails.getSqlString() ); MODEL_MUTATION_LOGGER.tracef( "`%s` rows upserted into `%s`", rowCount, tableMapping.getTableName() ); + try { + expectation.verifyOutcome( rowCount, updateStatement, -1, statementDetails.getSqlString() ); + } + catch (SQLException e) { + throw jdbcServices.getSqlExceptionHelper().convert( + e, + "Unable to verify outcome for upsert", + statementDetails.getSqlString() + ); + } } finally { statementDetails.releaseStatement( session ); From 2daccac241f2f7582584f58817ea05032faf969d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 12 Jun 2025 10:49:54 +0200 Subject: [PATCH 058/168] HHH-19532 Add unwrap() method to StatelessSession/SharedSessionContract --- .../src/main/java/org/hibernate/Session.java | 5 ++++ .../org/hibernate/SharedSessionContract.java | 25 +++++++++++++++++++ .../org/hibernate/internal/SessionImpl.java | 3 ++- .../internal/StatelessSessionImpl.java | 13 ++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java index cd5c02d0f6ab..575e46b2f6b2 100644 --- a/hibernate-core/src/main/java/org/hibernate/Session.java +++ b/hibernate-core/src/main/java/org/hibernate/Session.java @@ -1494,4 +1494,9 @@ public interface Session extends SharedSessionContract, EntityManager { */ @Override @Deprecated(since = "6.0") @SuppressWarnings("rawtypes") Query createQuery(CriteriaUpdate updateQuery); + + @Override + default T unwrap(Class type) { + return SharedSessionContract.super.unwrap(type); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java index 89dd21243404..9c1b0ef123c7 100644 --- a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java @@ -10,6 +10,7 @@ import java.util.function.Function; import jakarta.persistence.EntityGraph; +import jakarta.persistence.PersistenceException; import org.hibernate.graph.RootGraph; import org.hibernate.jdbc.ReturningWork; import org.hibernate.jdbc.Work; @@ -472,4 +473,28 @@ default R fromTransaction(Function action) { final Transaction transaction = beginTransaction(); return manageTransaction( transaction, transaction, action ); } + + /** + * Return an object of the specified type to allow access to + * a provider-specific API. + * + * @param type the class of the object to be returned. + * This is usually either the underlying class + * implementing {@code SharedSessionContract} or an + * interface it implements. + * @return an instance of the specified class + * @throws PersistenceException if the provider does not + * support the given type + */ + default T unwrap(Class type) { + // Not checking type.isInstance(...) because some implementations + // might want to hide that they implement some types. + // Implementations wanting a more liberal behavior need to override this method. + if ( type.isAssignableFrom( SharedSessionContract.class ) ) { + return type.cast( this ); + } + + throw new PersistenceException( + "Hibernate cannot unwrap '" + getClass().getName() + "' as '" + type.getName() + "'" ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 02f25916ebf9..ed6c54b0b67d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -2948,7 +2948,8 @@ public T unwrap(Class type) { return type.cast( persistenceContext ); } - throw new PersistenceException( "Hibernate cannot unwrap EntityManager as '" + type.getName() + "'" ); + throw new PersistenceException( + "Hibernate cannot unwrap '" + getClass().getName() + "' as '" + type.getName() + "'" ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java index 5369ac021434..b4725a1968b0 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java @@ -9,6 +9,7 @@ import java.util.Set; import java.util.function.BiConsumer; +import jakarta.persistence.PersistenceException; import org.hibernate.AssertionFailure; import org.hibernate.FlushMode; import org.hibernate.HibernateException; @@ -1427,6 +1428,18 @@ public Object loadFromSecondLevelCache(EntityPersister persister, EntityKey enti return CacheLoadHelper.loadFromSecondLevelCache( this, instanceToLoad, lockMode, persister, entityKey ); } + @Override + public T unwrap(Class type) { + checkOpen(); + + if ( type.isInstance( this ) ) { + return type.cast( this ); + } + + throw new PersistenceException( + "Hibernate cannot unwrap '" + getClass().getName() + "' as '" + type.getName() + "'" ); + } + private static final class MultiLoadOptions implements MultiIdLoadOptions { private final LockOptions lockOptions; From 5df154abbad68bd6ee476fb681ceb06712d08c2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 12 Jun 2025 10:50:26 +0200 Subject: [PATCH 059/168] HHH-19531 Test use of session proxies with SelectionSpecification/MutationSpecification --- .../SimpleQuerySpecificationTests.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/dynamic/SimpleQuerySpecificationTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/dynamic/SimpleQuerySpecificationTests.java index 25803ba3f949..3d452cdc0475 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/dynamic/SimpleQuerySpecificationTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/dynamic/SimpleQuerySpecificationTests.java @@ -8,6 +8,8 @@ import jakarta.persistence.criteria.CriteriaQuery; import org.hibernate.SessionFactory; +import org.hibernate.engine.spi.SessionLazyDelegator; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.IllegalMutationQueryException; import org.hibernate.query.IllegalSelectQueryException; import org.hibernate.query.Order; @@ -19,6 +21,7 @@ import org.hibernate.query.restriction.Restriction; import org.hibernate.testing.jdbc.SQLStatementInspector; import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.junit.jupiter.api.Test; @@ -160,6 +163,46 @@ void testSimpleMutationRestriction(SessionFactoryScope factoryScope) { assertThat( sqlCollector.getSqlQueries().get( 0 ) ).contains( " where be1_0.position between ? and ?" ); } + @Test + @JiraKey("HHH-19531") + void testSelectionOnSessionProxy(SessionFactoryScope factoryScope) { + final SQLStatementInspector sqlCollector = factoryScope.getCollectingStatementInspector(); + + factoryScope.inTransaction( (session) -> { + var sessionProxy = new SessionLazyDelegator( () -> session ); + // The test only makes sense if this is true. It currently is, but who knows what the future has in store for us. + //noinspection ConstantValue + assert !(sessionProxy instanceof SharedSessionContractImplementor); + + sqlCollector.clear(); + SelectionSpecification.create( BasicEntity.class, "from BasicEntity" ) + .createQuery( sessionProxy ) + .list(); + } ); + + assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 ); + } + + @Test + @JiraKey("HHH-19531") + void testMutationOnSessionProxy(SessionFactoryScope factoryScope) { + final SQLStatementInspector sqlCollector = factoryScope.getCollectingStatementInspector(); + + factoryScope.inTransaction( (session) -> { + var sessionProxy = new SessionLazyDelegator( () -> session ); + // The test only makes sense if this is true. It currently is, but who knows what the future has in store for us. + //noinspection ConstantValue + assert !(sessionProxy instanceof SharedSessionContractImplementor); + + sqlCollector.clear(); + MutationSpecification.create( BasicEntity.class, "delete BasicEntity" ) + .createQuery( sessionProxy ) + .executeUpdate(); + } ); + + assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 ); + } + @Test void testSimpleMutationRestrictionAsReference(SessionFactoryScope factoryScope) { final SQLStatementInspector sqlCollector = factoryScope.getCollectingStatementInspector(); From 62489a061d487baa92f16ecb240eb938908c71f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 12 Jun 2025 10:54:00 +0200 Subject: [PATCH 060/168] HHH-19531 Prefer unwrap() to casting sessions in SelectionSpecification/MutationSpecification Because casts can fail on session proxies, e.g. SessionLazyDelegator, or Spring's session proxies, or Quarkus'. --- .../query/specification/internal/MutationSpecificationImpl.java | 2 +- .../specification/internal/SelectionSpecificationImpl.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/specification/internal/MutationSpecificationImpl.java b/hibernate-core/src/main/java/org/hibernate/query/specification/internal/MutationSpecificationImpl.java index 47331c7f3200..4ea754a98b9f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/specification/internal/MutationSpecificationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/specification/internal/MutationSpecificationImpl.java @@ -122,7 +122,7 @@ public MutationQuery createQuery(StatelessSession session) { } public MutationQuery createQuery(SharedSessionContract session) { - final var sessionImpl = (SharedSessionContractImplementor) session; + final var sessionImpl = session.unwrap(SharedSessionContractImplementor.class); final SqmDeleteOrUpdateStatement sqmStatement = build( sessionImpl.getFactory().getQueryEngine() ); return new QuerySqmImpl<>( sqmStatement, true, null, sessionImpl ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/specification/internal/SelectionSpecificationImpl.java b/hibernate-core/src/main/java/org/hibernate/query/specification/internal/SelectionSpecificationImpl.java index 5cdd424dc8ea..6a4e3db5b132 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/specification/internal/SelectionSpecificationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/specification/internal/SelectionSpecificationImpl.java @@ -164,7 +164,7 @@ public SelectionQuery createQuery(StatelessSession session) { } public SelectionQuery createQuery(SharedSessionContract session) { - final var sessionImpl = (SharedSessionContractImplementor) session; + final var sessionImpl = session.unwrap(SharedSessionContractImplementor.class); final SqmSelectStatement sqmStatement = build( sessionImpl.getFactory().getQueryEngine() ); return new SqmSelectionQueryImpl<>( sqmStatement, true, resultType, sessionImpl ); } From b5514cbd2f06263adeb7738c80edeb49290ff27e Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Thu, 12 Jun 2025 14:46:00 +0000 Subject: [PATCH 061/168] Pre-steps for release : `7.0.2.Final` --- changelog.txt | 18 ++++++++++++++++++ gradle/version.properties | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index aed18aa7813f..874ddd870ddf 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,24 @@ Hibernate 7 Changelog Note: Please refer to JIRA to learn more about each issue. +Changes in 7.0.2.Final (June 12, 2025) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/33901 + +** Bug + * [HHH-19533] - Implement equals() and hashCode() for NativeQueryConstructorTransformer + * [HHH-19531] - Jakarta Data implementation casts StatlessSession to *Implementor interfaces + * [HHH-19529] - Check bytecode generated classes with stable names class loaders + * [HHH-19522] - upserts fail silently instead of throwing StaleObjectStateException + +** Improvement + * [HHH-19532] - Add unwrap() method to StatelessSession/SharedSessionContract + +** Task + * [HHH-19518] - Make hibernate-maven-plugin configuration parameters non-readonly + + Changes in 7.0.1.Final (June 10, 2025) ------------------------------------------------------------------------------------------------------------------------ diff --git a/gradle/version.properties b/gradle/version.properties index 520a19c404c6..a6b94125bb19 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=7.0.2-SNAPSHOT \ No newline at end of file +hibernateVersion=7.0.2.Final \ No newline at end of file From 4b0ee0418dcb4a6ba664be2a042345a9f2abecc5 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Thu, 12 Jun 2025 14:51:14 +0000 Subject: [PATCH 062/168] Post-steps for release : `7.0.2.Final` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index a6b94125bb19..98b5368a188b 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=7.0.2.Final \ No newline at end of file +hibernateVersion=7.0.3-SNAPSHOT \ No newline at end of file From 23ac16c2e6b14f9a8281bf2e7853ea46e5ece4e7 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Fri, 13 Jun 2025 23:24:42 +0200 Subject: [PATCH 063/168] Moved to Central publishing --- ci/release/Jenkinsfile | 10 +--------- ci/snapshot-publish.Jenkinsfile | 4 +--- .../src/main/groovy/local.publishing.gradle | 2 +- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index 637561251ef4..38aaa7fa4574 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -208,15 +208,7 @@ pipeline { configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts") ]) { withCredentials([ - // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh - // TODO: HHH-19309: - // Once we switch to maven-central publishing (from nexus2) we need to add a new credentials - // to use the following env variable names to set the user/password: - // - JRELEASER_MAVENCENTRAL_USERNAME - // - JRELEASER_MAVENCENTRAL_TOKEN - // Also use the new `credentialsId` for Maven Central, e.g.: - // usernamePassword(credentialsId: '???????', passwordVariable: 'JRELEASER_MAVENCENTRAL_TOKEN', usernameVariable: 'JRELEASER_MAVENCENTRAL_USERNAME'), - usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'JRELEASER_NEXUS2_PASSWORD', usernameVariable: 'JRELEASER_NEXUS2_USERNAME'), + usernamePassword(credentialsId: 'central.sonatype.com', passwordVariable: 'JRELEASER_MAVENCENTRAL_TOKEN', usernameVariable: 'JRELEASER_MAVENCENTRAL_USERNAME'), // https://docs.gradle.org/current/userguide/publishing_gradle_plugins.html#account_setup usernamePassword(credentialsId: 'gradle-plugin-portal-api-key', passwordVariable: 'GRADLE_PUBLISH_SECRET', usernameVariable: 'GRADLE_PUBLISH_KEY'), gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default'), diff --git a/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile index 8db51e97eba5..fb6479b5a20d 100644 --- a/ci/snapshot-publish.Jenkinsfile +++ b/ci/snapshot-publish.Jenkinsfile @@ -41,10 +41,8 @@ pipeline { script { withCredentials([ // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh - // TODO: HHH-19309: - // Once we switch to maven-central publishing (from nexus2) we need to update credentialsId: // https://docs.gradle.org/current/samples/sample_publishing_credentials.html#:~:text=via%20environment%20variables - usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'ORG_GRADLE_PROJECT_snapshotsPassword', usernameVariable: 'ORG_GRADLE_PROJECT_snapshotsUsername'), + usernamePassword(credentialsId: 'central.sonatype.com', passwordVariable: 'ORG_GRADLE_PROJECT_snapshotsPassword', usernameVariable: 'ORG_GRADLE_PROJECT_snapshotsUsername'), string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN'), // https://docs.gradle.org/current/userguide/publishing_gradle_plugins.html#account_setup usernamePassword(credentialsId: 'gradle-plugin-portal-api-key', passwordVariable: 'GRADLE_PUBLISH_SECRET', usernameVariable: 'GRADLE_PUBLISH_KEY'), diff --git a/local-build-plugins/src/main/groovy/local.publishing.gradle b/local-build-plugins/src/main/groovy/local.publishing.gradle index 92fbaa032fbf..d5bc77b1b947 100644 --- a/local-build-plugins/src/main/groovy/local.publishing.gradle +++ b/local-build-plugins/src/main/groovy/local.publishing.gradle @@ -69,7 +69,7 @@ publishingExtension.repositories { } maven { name = 'snapshots' - url = "https://oss.sonatype.org/content/repositories/snapshots/" + url = "https://central.sonatype.com/repository/maven-snapshots/" // So that Gradle uses the `ORG_GRADLE_PROJECT_snapshotsPassword` / `ORG_GRADLE_PROJECT_snapshotsUsername` // env variables to read the username/password for the `snapshots` repository publishing: credentials(PasswordCredentials) From 7e199fe14b1ec4d3c115707fd485f18164fdcca9 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Mon, 16 Jun 2025 13:48:28 +0200 Subject: [PATCH 064/168] Allow builds with test jdk versions on PRs with custom labels --- Jenkinsfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index b8799520ae58..2d6d9db78255 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -100,8 +100,9 @@ stage('Build') { Map executions = [:] Map> state = [:] environments.each { BuildEnvironment buildEnv -> - // Don't build environments for newer JDKs when this is a PR - if ( helper.scmSource.pullRequest && buildEnv.testJdkVersion ) { + // Don't build environments for newer JDKs when this is a PR, unless the PR is labelled with 'jdk' or 'jdk-' + if ( helper.scmSource.pullRequest && buildEnv.testJdkVersion && + !pullRequest.labels.contains( 'jdk' ) && !pullRequest.labels.contains( "jdk-${buildEnv.testJdkVersion}" ) ) { return } state[buildEnv.tag] = [:] From f183b6c3d49f8061bbd4015629827129d4265406 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Mon, 16 Jun 2025 13:17:21 +0200 Subject: [PATCH 065/168] HHH-19547 fix an exception message that has been fixed and unfixed many times "oid" never meant "old id" --- .../DefaultFlushEntityEventListener.java | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java index 52f2559dee96..50230acbc13e 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java @@ -70,28 +70,26 @@ public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { /** * Make sure user didn't mangle the id. */ - public void checkId(Object object, EntityPersister persister, Object id, Status status, SessionImplementor session) + public void checkId(Object object, EntityPersister persister, EntityEntry entry, SessionImplementor session) throws HibernateException { - - if ( id instanceof DelayedPostInsertIdentifier ) { - // this is a situation where the entity id is assigned by a post-insert generator - // and was saved outside the transaction forcing it to be delayed - return; - } - - final Object oid = persister.getIdentifier( object, session ); - if ( id == null ) { - throw new AssertionFailure( "null id in " + persister.getEntityName() - + " entry (don't flush the Session after an exception occurs)" ); - } - // Small optimisation: always try to avoid getIdentifierType().isEqual(..) when possible. - // (However it's not safe to invoke the equals() method as it might trigger side effects.) - else if ( id != oid - && !status.isDeletedOrGone() - && !persister.getIdentifierType().isEqual( id, oid, session.getFactory() ) ) { - throw new HibernateException( "identifier of an instance of " + persister.getEntityName() - + " was altered from " + oid + " to " + id ); + final Object entryId = entry.getId(); + if ( entryId == null ) { + throw new AssertionFailure( "Entry for instance of '" + persister.getEntityName() + + "' has a null identifier (this can happen if the session is flushed after an exception occurs)" ); + } + if ( !(entryId instanceof DelayedPostInsertIdentifier) ) { + final Object currentId = persister.getIdentifier( object, session ); + // Small optimisation: always try to avoid getIdentifierType().isEqual(..) when possible. + // (However it's not safe to invoke the equals() method as it might trigger side effects.) + if ( entryId != currentId + && !entry.getStatus().isDeletedOrGone() + && !persister.getIdentifierType().isEqual( entryId, currentId, session.getFactory() ) ) { + throw new HibernateException( "Identifier of an instance of '" + persister.getEntityName() + + "' was altered from " + entryId + " to " + currentId ); + } } + // else this is a situation where the entity id is assigned by a post-insert + // generator and was saved outside the transaction, forcing it to be delayed } private void checkNaturalId( @@ -174,7 +172,7 @@ private Object[] getValues(Object entity, EntityEntry entry, boolean mightBeDirt } else { final EntityPersister persister = entry.getPersister(); - checkId( entity, persister, entry.getId(), entry.getStatus(), session ); + checkId( entity, persister, entry, session ); // grab its current state final Object[] values = persister.getValues( entity ); checkNaturalId( persister, entity, entry, values, loadedState, session ); From d1b44ae697b88208a480c355419d552f3fab572b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 16 Jun 2025 12:38:03 +0200 Subject: [PATCH 066/168] HHH-19548 Upgrade to ByteBuddy 1.17.5 --- Jenkinsfile | 2 +- settings.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 2d6d9db78255..b6a163989bd6 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -46,11 +46,11 @@ stage('Configure') { new BuildEnvironment( testJdkVersion: '21', testJdkLauncherArgs: '--enable-preview' ), new BuildEnvironment( testJdkVersion: '23', testJdkLauncherArgs: '--enable-preview' ), new BuildEnvironment( testJdkVersion: '24', testJdkLauncherArgs: '--enable-preview' ), + new BuildEnvironment( testJdkVersion: '25', testJdkLauncherArgs: '--enable-preview' ) // The following JDKs aren't supported by Hibernate ORM out-of-the box yet: // they require the use of -Dnet.bytebuddy.experimental=true. // Make sure to remove that argument as soon as possible // -- generally that requires upgrading bytebuddy after the JDK goes GA. - new BuildEnvironment( testJdkVersion: '25', testJdkLauncherArgs: '--enable-preview -Dnet.bytebuddy.experimental=true' ) ]; if ( env.CHANGE_ID ) { diff --git a/settings.gradle b/settings.gradle index 442e3708aa73..90b3c1818675 100644 --- a/settings.gradle +++ b/settings.gradle @@ -73,7 +73,7 @@ dependencyResolutionManagement { def antlrVersion = version "antlr", "4.13.2" // WARNING: When upgrading to a version of bytebuddy that supports a new bytecode version, // make sure to remove the now unnecessary net.bytebuddy.experimental=true in relevant CI jobs (Jenkinsfile). - def byteBuddyVersion = version "byteBuddy", "1.15.11" + def byteBuddyVersion = version "byteBuddy", "1.17.5" def classmateVersion = version "classmate", "1.7.0" def geolatteVersion = version "geolatte", "1.9.1" def hibernateModelsVersion = version "hibernateModels", "1.0.0" From 8fef79fc7367d15384b592f2cb3a4ce74d1d4f8b Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 22 Jun 2025 00:03:52 +0000 Subject: [PATCH 067/168] Pre-steps for release : `7.0.3.Final` --- changelog.txt | 13 +++++++++++++ gradle/version.properties | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 874ddd870ddf..0d8b4930869d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,19 @@ Hibernate 7 Changelog Note: Please refer to JIRA to learn more about each issue. +Changes in 7.0.3.Final (June 22, 2025) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/33936 + +** Bug + * [HHH-18774] - two bad bugs in cascade refresh + +** Task + * [HHH-19548] - Upgrade to ByteBuddy 1.17.5 + * [HHH-19519] - Document breaking changes in new Hibernate Maven Plugin + + Changes in 7.0.2.Final (June 12, 2025) ------------------------------------------------------------------------------------------------------------------------ diff --git a/gradle/version.properties b/gradle/version.properties index 98b5368a188b..67c1d7c0590e 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=7.0.3-SNAPSHOT \ No newline at end of file +hibernateVersion=7.0.3.Final \ No newline at end of file From fd070c25d285734cfe9fa1e144a7bd1b846bf04a Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 22 Jun 2025 00:17:39 +0000 Subject: [PATCH 068/168] Post-steps for release : `7.0.3.Final` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 67c1d7c0590e..57cebe4cd0cf 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=7.0.3.Final \ No newline at end of file +hibernateVersion=7.0.4-SNAPSHOT \ No newline at end of file From b2c3297b8053c731601817cad174343d9c266435 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Mon, 23 Jun 2025 19:30:46 +0200 Subject: [PATCH 069/168] HHH-19571 Make AccessOptimizer bytecode generation deterministic --- .../bytebuddy/BytecodeProviderImpl.java | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java index 00b381bf9056..8b14942383a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java @@ -16,7 +16,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.concurrent.Callable; import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; @@ -183,7 +182,7 @@ public ReflectionOptimizer getReflectionOptimizer( .method( setPropertyValuesMethodName ) .intercept( new Implementation.Simple( new SetPropertyValues( clazz, getterNames, setters ) ) ) .method( getPropertyNamesMethodName ) - .intercept( MethodCall.call( new CloningPropertyCall( getterNames ) ) ) + .intercept( new Implementation.Simple( new GetPropertyNames( getterNames ) ) ) ); try { @@ -252,7 +251,7 @@ public ReflectionOptimizer getReflectionOptimizer( .method( setPropertyValuesMethodName ) .intercept( new Implementation.Simple( new SetPropertyValues( clazz, propertyNames, setters ) ) ) .method( getPropertyNamesMethodName ) - .intercept( MethodCall.call( new CloningPropertyCall( propertyNames ) ) ) + .intercept( new Implementation.Simple( new GetPropertyNames( propertyNames ) ) ) ); } else { @@ -265,7 +264,7 @@ public ReflectionOptimizer getReflectionOptimizer( .method( setPropertyValuesMethodName ) .intercept( new Implementation.Simple( new SetPropertyValues( clazz, propertyNames, setters ) ) ) .method( getPropertyNamesMethodName ) - .intercept( MethodCall.call( new CloningPropertyCall( propertyNames ) ) ) + .intercept( new Implementation.Simple( new GetPropertyNames( propertyNames ) ) ) ); } @@ -1341,17 +1340,29 @@ private static Constructor findConstructor(Class clazz) { } } - public static class CloningPropertyCall implements Callable { + public static class GetPropertyNames implements ByteCodeAppender { private final String[] propertyNames; - private CloningPropertyCall(String[] propertyNames) { + private GetPropertyNames(String[] propertyNames) { this.propertyNames = propertyNames; } @Override - public String[] call() { - return propertyNames.clone(); + public Size apply( + MethodVisitor methodVisitor, + Implementation.Context implementationContext, + MethodDescription instrumentedMethod) { + methodVisitor.visitLdcInsn( propertyNames.length ); + methodVisitor.visitTypeInsn( Opcodes.ANEWARRAY, Type.getInternalName( String.class ) ); + for ( int i = 0; i < propertyNames.length; i++ ) { + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitLdcInsn( i ); + methodVisitor.visitLdcInsn( propertyNames[i] ); + methodVisitor.visitInsn( Opcodes.AASTORE ); + } + methodVisitor.visitInsn( Opcodes.ARETURN ); + return new Size( 4, instrumentedMethod.getStackSize() + 1 ); } } From aa3f8df93c097cc235b61e2120b0c99c10007039 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 24 Jun 2025 09:57:13 +0200 Subject: [PATCH 070/168] HHH-19572 fix where(List) and having(List) these new operations in JPA 3.2 were implemented incorrectly --- .../query/criteria/JpaQueryStructure.java | 4 ++ .../org/hibernate/query/sqm/NodeBuilder.java | 4 +- .../sqm/internal/SqmCriteriaNodeBuilder.java | 13 +++++ .../sqm/tree/predicate/SqmWhereClause.java | 10 ++-- .../tree/select/AbstractSqmSelectQuery.java | 18 +++++-- .../query/sqm/tree/select/SqmQuerySpec.java | 42 +++++++++++++--- .../sqm/tree/select/SqmSelectStatement.java | 50 ++++++++++--------- .../query/sqm/tree/select/SqmSubQuery.java | 49 +++++++++--------- 8 files changed, 127 insertions(+), 63 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaQueryStructure.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaQueryStructure.java index 9d2c4b2b4bae..9820da5dbdfd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaQueryStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaQueryStructure.java @@ -60,6 +60,8 @@ public interface JpaQueryStructure extends JpaQueryPart { JpaQueryStructure setRestriction(Predicate... restrictions); + JpaQueryStructure setRestriction(List restrictions); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Grouping (group-by / having) clause @@ -78,6 +80,8 @@ public interface JpaQueryStructure extends JpaQueryPart { JpaQueryStructure setGroupRestriction(Predicate... restrictions); + JpaQueryStructure setGroupRestriction(List restrictions); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Covariant overrides diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java index c14fecb51365..26748874eaba 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java @@ -972,9 +972,11 @@ SqmJsonValueExpression jsonValue( @Override SqmPredicate wrap(Expression expression); - @Override + @Override @SuppressWarnings("unchecked") SqmPredicate wrap(Expression... expressions); + SqmPredicate wrap(List> restrictions); + @Override SqmExpression fk(Path

path); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java index c1ad16c8977d..2007a901f707 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java @@ -596,6 +596,19 @@ public final SqmPredicate wrap(Expression... expressions) { return new SqmJunctionPredicate( Predicate.BooleanOperator.AND, predicates, this ); } + @Override + public SqmPredicate wrap(List> restrictions) { + if ( restrictions.size() == 1 ) { + return wrap( restrictions.get( 0 ) ); + } + + final List predicates = new ArrayList<>( restrictions.size() ); + for ( Expression expression : restrictions ) { + predicates.add( wrap( expression ) ); + } + return new SqmJunctionPredicate( Predicate.BooleanOperator.AND, predicates, this ); + } + @Override @SuppressWarnings("unchecked") public T unwrap(Class clazz) { return (T) extensions.get( clazz ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmWhereClause.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmWhereClause.java index 7b6048fd451f..ea7684324f10 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmWhereClause.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmWhereClause.java @@ -45,12 +45,10 @@ public void setPredicate(SqmPredicate predicate) { @Override public void applyPredicate(SqmPredicate predicate) { - if ( this.predicate == null ) { - this.predicate = predicate; - } - else { - this.predicate = nodeBuilder.and( this.predicate, predicate ); - } + this.predicate = + this.predicate == null + ? predicate + : nodeBuilder.and( this.predicate, predicate ); } @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 347938dd3d15..f762de2a90fa 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 @@ -366,6 +366,12 @@ public SqmSelectQuery where(Predicate... restrictions) { return this; } + @Override + public SqmSelectQuery where(List restrictions) { + getQuerySpec().setRestriction( restrictions ); + return this; + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Grouping @@ -377,7 +383,7 @@ public List> getGroupList() { @Override public SqmSelectQuery groupBy(Expression... expressions) { - getQuerySpec().setGroupingExpressions( List.of( expressions ) ); + getQuerySpec().setGroupingExpressions( expressions ); return this; } @@ -394,13 +400,19 @@ public SqmPredicate getGroupRestriction() { @Override public SqmSelectQuery having(Expression booleanExpression) { - getQuerySpec().setGroupRestriction( nodeBuilder().wrap( booleanExpression ) ); + getQuerySpec().setGroupRestriction( booleanExpression ); return this; } @Override public SqmSelectQuery having(Predicate... predicates) { - getQuerySpec().setGroupRestriction( nodeBuilder().wrap( predicates ) ); + getQuerySpec().setGroupRestriction( predicates ); + return this; + } + + @Override + public AbstractQuery having(List restrictions) { + getQuerySpec().setGroupRestriction( restrictions ); return this; } 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 d6b45f9dbba1..ac197c9962fa 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 @@ -374,13 +374,24 @@ else if ( restrictions.length == 0 ) { setWhereClause( null ); } else { - SqmWhereClause whereClause = getWhereClause(); - if ( whereClause == null ) { - setWhereClause( whereClause = new SqmWhereClause( nodeBuilder() ) ); - } - else { - whereClause.setPredicate( null ); + final SqmWhereClause whereClause = resetWhereClause(); + for ( Predicate restriction : restrictions ) { + whereClause.applyPredicate( (SqmPredicate) restriction ); } + } + return this; + } + + @Override + public SqmQuerySpec setRestriction(List restrictions) { + if ( restrictions == null ) { + throw new IllegalArgumentException( "The predicate list cannot be null" ); + } + else if ( restrictions.isEmpty() ) { + setWhereClause( null ); + } + else { + final SqmWhereClause whereClause = resetWhereClause(); for ( Predicate restriction : restrictions ) { whereClause.applyPredicate( (SqmPredicate) restriction ); } @@ -388,6 +399,19 @@ else if ( restrictions.length == 0 ) { return this; } + private SqmWhereClause resetWhereClause() { + final SqmWhereClause whereClause = getWhereClause(); + if ( whereClause == null ) { + final SqmWhereClause newWhereClause = new SqmWhereClause( nodeBuilder() ); + setWhereClause( newWhereClause ); + return newWhereClause; + } + else { + whereClause.setPredicate( null ); + return whereClause; + } + } + @Override public List> getGroupingExpressions() { return groupByClauseExpressions; @@ -442,6 +466,12 @@ public SqmQuerySpec setGroupRestriction(Predicate... restrictions) { return this; } + @Override + public SqmQuerySpec setGroupRestriction(List restrictions) { + havingClausePredicate = nodeBuilder().wrap( restrictions ); + return this; + } + @Override public SqmQuerySpec setSortSpecifications(List sortSpecifications) { super.setSortSpecifications( sortSpecifications ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java index 9a6630ae57d9..7605615fa139 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java @@ -28,7 +28,6 @@ import org.hibernate.query.sqm.tree.expression.ValueBindJpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.from.SqmFromClause; -import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.query.sqm.tree.from.SqmRoot; import jakarta.persistence.Tuple; @@ -43,7 +42,6 @@ import static java.util.Collections.emptySet; import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableSet; -import static org.hibernate.query.sqm.spi.SqmCreationHelper.combinePredicates; import static org.hibernate.query.sqm.SqmQuerySource.CRITERIA; import static org.hibernate.query.sqm.tree.SqmCopyContext.noParamCopyContext; import static org.hibernate.query.sqm.tree.jpa.ParameterCollector.collectParameters; @@ -298,7 +296,8 @@ protected JpaCteCriteria withInternal( @Override public SqmSelectStatement distinct(boolean distinct) { - return (SqmSelectStatement) super.distinct( distinct ); + super.distinct( distinct ); + return this; } @Override @@ -319,21 +318,6 @@ public SqmSubQuery subquery(EntityType type) { return new SqmSubQuery<>( this, type, nodeBuilder() ); } - @Override - public SqmSelectStatement where(List restrictions) { - //noinspection rawtypes,unchecked - getQuerySpec().getWhereClause().applyPredicates( (List) restrictions ); - return this; - } - - @Override - public SqmSelectStatement having(List restrictions) { - //noinspection unchecked,rawtypes - final SqmPredicate combined = combinePredicates( getQuerySpec().getHavingClausePredicate(), (List) restrictions ); - getQuerySpec().setHavingClausePredicate( combined ); - return this; - } - @Override @SuppressWarnings("unchecked") public SqmSelectStatement select(Selection selection) { @@ -424,32 +408,50 @@ public SqmSubQuery subquery(Class type) { @Override public SqmSelectStatement where(Expression restriction) { - return (SqmSelectStatement) super.where( restriction ); + super.where( restriction ); + return this; } @Override public SqmSelectStatement where(Predicate... restrictions) { - return (SqmSelectStatement) super.where( restrictions ); + super.where( restrictions ); + return this; + } + + @Override + public SqmSelectStatement where(List restrictions) { + super.where( restrictions ); + return this; } @Override public SqmSelectStatement groupBy(Expression... expressions) { - return (SqmSelectStatement) super.groupBy( expressions ); + super.groupBy( expressions ); + return this; } @Override public SqmSelectStatement groupBy(List> grouping) { - return (SqmSelectStatement) super.groupBy( grouping ); + super.groupBy( grouping ); + return this; } @Override public SqmSelectStatement having(Expression booleanExpression) { - return (SqmSelectStatement) super.having( booleanExpression ); + super.having( booleanExpression ); + return this; } @Override public SqmSelectStatement having(Predicate... predicates) { - return (SqmSelectStatement) super.having( predicates ); + super.having( predicates ); + return this; + } + + @Override + public SqmSelectStatement having(List restrictions) { + super.having( restrictions ); + return this; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSubQuery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSubQuery.java index d652376d4b49..714dd78cabea 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSubQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSubQuery.java @@ -76,7 +76,6 @@ import jakarta.persistence.criteria.Subquery; import jakarta.persistence.metamodel.EntityType; -import static org.hibernate.query.sqm.spi.SqmCreationHelper.combinePredicates; /** * @author Steve Ebersole @@ -345,37 +344,56 @@ public List> getCompoundSelectionItems() { @Override public SqmSubQuery distinct(boolean distinct) { - return (SqmSubQuery) super.distinct( distinct ); + super.distinct( distinct ); + return this; } @Override public SqmSubQuery where(Expression restriction) { - return (SqmSubQuery) super.where( restriction ); + super.where( restriction ); + return this; } @Override public SqmSubQuery where(Predicate... restrictions) { - return (SqmSubQuery) super.where( restrictions ); + super.where( restrictions ); + return this; + } + + @Override + public SqmSubQuery where(List restrictions) { + super.where( restrictions ); + return this; } @Override public SqmSubQuery groupBy(Expression... expressions) { - return (SqmSubQuery) super.groupBy( expressions ); + super.groupBy( expressions ); + return this; } @Override public SqmSubQuery groupBy(List> grouping) { - return (SqmSubQuery) super.groupBy( grouping ); + super.groupBy( grouping ); + return this; } @Override public SqmSubQuery having(Expression booleanExpression) { - return (SqmSubQuery) super.having( booleanExpression ); + super.having( booleanExpression ); + return this; } @Override public SqmSubQuery having(Predicate... predicates) { - return (SqmSubQuery) super.having( predicates ); + super.having( predicates ); + return this; + } + + @Override + public SqmSubQuery having(List restrictions) { + super.having( restrictions ); + return this; } @Override @@ -719,21 +737,6 @@ public Subquery subquery(EntityType type) { return new SqmSubQuery<>( this, type, nodeBuilder() ); } - @Override - public Subquery where(List restrictions) { - //noinspection rawtypes,unchecked - getQuerySpec().getWhereClause().applyPredicates( (List) restrictions ); - return this; - } - - @Override - public Subquery having(List restrictions) { - //noinspection unchecked,rawtypes - final SqmPredicate combined = combinePredicates( getQuerySpec().getHavingClausePredicate(), (List) restrictions ); - getQuerySpec().setHavingClausePredicate( combined ); - return this; - } - @Override public Set> getParameters() { return Collections.emptySet(); From 00b149acc1a882a1d7dc1f264db1c8cba0ac4261 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 24 Jun 2025 10:11:50 +0200 Subject: [PATCH 071/168] HHH-19572 test for where(List) --- .../jpa/criteria/CriteriaRestrictionTest.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/CriteriaRestrictionTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/CriteriaRestrictionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/CriteriaRestrictionTest.java new file mode 100644 index 000000000000..2e18854ab1c0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/CriteriaRestrictionTest.java @@ -0,0 +1,79 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.jpa.criteria; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +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.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Jpa(annotatedClasses = CriteriaRestrictionTest.Doc.class) +class CriteriaRestrictionTest { + @JiraKey( "HHH-19572" ) + @Test void test(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + Doc doc1 = new Doc(); + doc1.title = "Hibernate ORM"; + doc1.author = "Gavin King"; + doc1.text = "Hibernate ORM is a Java Persistence API implementation"; + entityManager.persist( doc1 ); + Doc doc2 = new Doc(); + doc2.title = "Hibernate ORM"; + doc2.author = "Hibernate Team"; + doc2.text = "Hibernate ORM is a Jakarta Persistence implementation"; + entityManager.persist( doc2 ); + } + ); + scope.inTransaction( + entityManager -> { + var builder = entityManager.getCriteriaBuilder(); + var query = builder.createQuery( Doc.class ); + var d = query.from( Doc.class ); + // test with list + query.where( List.of( + builder.like( d.get( "title" ), "Hibernate%" ), + builder.equal( d.get( "author" ), "Gavin King" ) + ) ); + var resultList = entityManager.createQuery( query ).getResultList(); + assertEquals( 1, resultList.size() ); + assertEquals( "Hibernate ORM is a Java Persistence API implementation", + resultList.get( 0 ).text ); + } + ); + scope.inTransaction( + entityManager -> { + var builder = entityManager.getCriteriaBuilder(); + var query = builder.createQuery( Doc.class ); + var d = query.from( Doc.class ); + // test with varargs + query.where( + builder.like( d.get( "title" ), "Hibernate%" ), + builder.equal( d.get( "author" ), "Hibernate Team" ) + ); + var resultList = entityManager.createQuery( query ).getResultList(); + assertEquals( 1, resultList.size() ); + assertEquals( "Hibernate ORM is a Jakarta Persistence implementation", + resultList.get( 0 ).text ); + } + ); + } + @Entity static class Doc { + @Id + @GeneratedValue + UUID uuid; + private String title; + private String author; + private String text; + } +} From 112c37c0baf8238f246963e16fab6ccbb844422f Mon Sep 17 00:00:00 2001 From: Gavin King Date: Mon, 23 Jun 2025 17:24:46 +0200 Subject: [PATCH 072/168] HHH-19570 HQL with jpamodelgen fails compilation when querying by natural id named `id` --- .../validation/MockEntityPersister.java | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockEntityPersister.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockEntityPersister.java index 940df2e4ae07..0557025955ea 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockEntityPersister.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockEntityPersister.java @@ -98,25 +98,36 @@ public String getEntityName() { @Override public final Type getPropertyType(String propertyPath) { - Type result = propertyTypesByName.get(propertyPath); - if (result!=null) { - return result; + final Type cached = propertyTypesByName.get(propertyPath); + if ( cached == null ) { + final Type type = propertyType( propertyPath ); + if ( type != null ) { + propertyTypesByName.put( propertyPath, type ); + } + return type; + } + else { + return cached; } + } - result = createPropertyType(propertyPath); - if (result == null) { - //check subclasses, needed for treat() - result = getSubclassPropertyType(propertyPath); + private Type propertyType(String propertyPath) { + final Type type = createPropertyType( propertyPath ); + if ( type != null ) { + return type; } - if ("id".equals( propertyPath )) { - result = identifierType(); + //check subclasses, needed for treat() + final Type typeFromSubclass = getSubclassPropertyType( propertyPath ); + if ( typeFromSubclass != null ) { + return typeFromSubclass; } - if (result!=null) { - propertyTypesByName.put(propertyPath, result); + if ( "id".equals( propertyPath ) ) { + return identifierType(); } - return result; + + return null; } abstract Type createPropertyType(String propertyPath); From c1888a20daa1531d016c58e3c82acf38cace8a71 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Wed, 25 Jun 2025 09:28:10 +0200 Subject: [PATCH 073/168] Trigger Hibernate Search Update Dependency job when building ORM 7 patches --- Jenkinsfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index b6a163989bd6..3c488611091c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -197,6 +197,9 @@ stage('Build') { } }) } + executions.put('Hibernate Search Update Dependency', { + build job: '/hibernate-search-dependency-update/8.0', propagate: true, parameters: [string(name: 'UPDATE_JOB', value: 'orm7'), string(name: 'ORM_REPOSITORY', value: helper.scmSource.remoteUrl), string(name: 'ORM_PULL_REQUEST_ID', value: helper.scmSource.pullRequest.id)] + }) parallel(executions) } From 9b28bb1e335f6c446f3f71e64c19700dbad45d30 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 24 Jun 2025 11:05:10 +0200 Subject: [PATCH 074/168] HHH-19560 Remove TupleTransformer and ResultListTransformer from interpretation caching --- .../dialect/function/SqlFunction.java | 27 +++++++++++++++++-- .../query/sql/internal/NativeQueryImpl.java | 4 +-- .../sql/spi/SelectInterpretationsKey.java | 22 +++++++-------- .../internal/ConcreteSqmSelectQueryPlan.java | 9 +++---- .../sqm/internal/SqmInterpretationsKey.java | 14 ---------- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/SqlFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/SqlFunction.java index 047456bc8e48..ab3303249b89 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/SqlFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/SqlFunction.java @@ -5,16 +5,22 @@ package org.hibernate.dialect.function; import java.util.List; +import java.util.function.Supplier; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.model.domain.ReturnableType; import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver; import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; -import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; +import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; +import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.expression.QueryLiteral; import org.hibernate.type.JavaObjectType; +import org.hibernate.type.spi.TypeConfiguration; /** * A function to pass through a SQL fragment. @@ -28,7 +34,24 @@ public SqlFunction() { super( "sql", StandardArgumentsValidators.min( 1 ), - StandardFunctionReturnTypeResolvers.invariant( JavaObjectType.INSTANCE ), + new FunctionReturnTypeResolver() { + @Override + public ReturnableType resolveFunctionReturnType( + ReturnableType impliedType, + @Nullable SqmToSqlAstConverter converter, + List> arguments, + TypeConfiguration typeConfiguration) { + return impliedType != null + ? impliedType + : typeConfiguration.getBasicTypeForJavaType( Object.class ); + } + + @Override + public BasicValuedMapping resolveFunctionReturnType(Supplier impliedTypeAccess, List arguments) { + final BasicValuedMapping impliedType = impliedTypeAccess.get(); + return impliedType != null ? impliedType : JavaObjectType.INSTANCE; + } + }, null ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java index 7bc1b24efa3a..d4e6b6d5f165 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java @@ -1005,9 +1005,7 @@ private SelectInterpretationsKey selectInterpretationsKey(ResultSetMapping resul return new SelectInterpretationsKey( getQueryString(), resultSetMapping, - getSynchronizedQuerySpaces(), - getQueryOptions().getTupleTransformer(), - getQueryOptions().getResultListTransformer() + getSynchronizedQuerySpaces() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/spi/SelectInterpretationsKey.java b/hibernate-core/src/main/java/org/hibernate/query/sql/spi/SelectInterpretationsKey.java index 77be9276835b..da7c3864f1ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/spi/SelectInterpretationsKey.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/spi/SelectInterpretationsKey.java @@ -20,21 +20,25 @@ public class SelectInterpretationsKey implements QueryInterpretationCache.Key { private final String sql; private final JdbcValuesMappingProducer jdbcValuesMappingProducer; private final Collection querySpaces; - private final TupleTransformer tupleTransformer; - private final ResultListTransformer resultListTransformer; private final int hash; + @Deprecated(forRemoval = true) public SelectInterpretationsKey( String sql, JdbcValuesMappingProducer jdbcValuesMappingProducer, Collection querySpaces, TupleTransformer tupleTransformer, ResultListTransformer resultListTransformer) { + this( sql, jdbcValuesMappingProducer, querySpaces ); + } + + public SelectInterpretationsKey( + String sql, + JdbcValuesMappingProducer jdbcValuesMappingProducer, + Collection querySpaces) { this.sql = sql; this.jdbcValuesMappingProducer = jdbcValuesMappingProducer; this.querySpaces = querySpaces; - this.tupleTransformer = tupleTransformer; - this.resultListTransformer = resultListTransformer; this.hash = generateHashCode(); } @@ -42,14 +46,10 @@ private SelectInterpretationsKey( String sql, JdbcValuesMappingProducer jdbcValuesMappingProducer, Collection querySpaces, - TupleTransformer tupleTransformer, - ResultListTransformer resultListTransformer, int hash) { this.sql = sql; this.jdbcValuesMappingProducer = jdbcValuesMappingProducer; this.querySpaces = querySpaces; - this.tupleTransformer = tupleTransformer; - this.resultListTransformer = resultListTransformer; this.hash = hash; } @@ -64,8 +64,6 @@ public QueryInterpretationCache.Key prepareForStore() { sql, jdbcValuesMappingProducer.cacheKeyInstance(), new HashSet<>( querySpaces ), - tupleTransformer, - resultListTransformer, hash ); } @@ -89,8 +87,6 @@ public boolean equals(Object o) { } return sql.equals( that.sql ) && Objects.equals( jdbcValuesMappingProducer, that.jdbcValuesMappingProducer ) - && Objects.equals( querySpaces, that.querySpaces ) - && Objects.equals( tupleTransformer, that.tupleTransformer ) - && Objects.equals( resultListTransformer, that.resultListTransformer ); + && Objects.equals( querySpaces, that.querySpaces ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java index b32143dfc179..11c5cc88d61a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java @@ -74,7 +74,6 @@ public class ConcreteSqmSelectQueryPlan implements SelectQueryPlan { private final SqmSelectStatement sqm; private final DomainParameterXref domainParameterXref; - private final RowTransformer rowTransformer; private final SqmInterpreter> executeQueryInterpreter; private final SqmInterpreter, Void> listInterpreter; private final SqmInterpreter, ScrollMode> scrollInterpreter; @@ -91,8 +90,6 @@ public ConcreteSqmSelectQueryPlan( this.sqm = sqm; this.domainParameterXref = domainParameterXref; - this.rowTransformer = determineRowTransformer( sqm, resultType, tupleMetadata, queryOptions ); - final ListResultsConsumer.UniqueSemantic uniqueSemantic = sqm.producesUniqueResults() && !containsCollectionFetches( queryOptions ) ? ListResultsConsumer.UniqueSemantic.NONE @@ -117,7 +114,7 @@ public ConcreteSqmSelectQueryPlan( jdbcSelect, jdbcParameterBindings, listInterpreterExecutionContext( hql, executionContext, jdbcSelect, subSelectFetchKeyHandler ), - rowTransformer, + determineRowTransformer( sqm, resultType, tupleMetadata, executionContext.getQueryOptions() ), null, resultCountEstimate, resultsConsumer @@ -149,7 +146,7 @@ public ConcreteSqmSelectQueryPlan( jdbcSelect, jdbcParameterBindings, listInterpreterExecutionContext( hql, executionContext, jdbcSelect, subSelectFetchKeyHandler ), - rowTransformer, + determineRowTransformer( sqm, resultType, tupleMetadata, executionContext.getQueryOptions() ), (Class) executionContext.getResultType(), uniqueSemantic, resultCountEstimate @@ -185,7 +182,7 @@ public ConcreteSqmSelectQueryPlan( scrollMode, jdbcParameterBindings, new SqmJdbcExecutionContextAdapter( executionContext, jdbcSelect ), - rowTransformer, + determineRowTransformer( sqm, resultType, tupleMetadata, executionContext.getQueryOptions() ), resultCountEstimate ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmInterpretationsKey.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmInterpretationsKey.java index 4cc6212eafc2..3db3cb126864 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmInterpretationsKey.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmInterpretationsKey.java @@ -9,8 +9,6 @@ import java.util.Set; import org.hibernate.LockOptions; -import org.hibernate.query.ResultListTransformer; -import org.hibernate.query.TupleTransformer; import org.hibernate.query.spi.QueryInterpretationCache; import org.hibernate.query.sqm.spi.InterpretationsKeySource; @@ -28,8 +26,6 @@ public static SqmInterpretationsKey createInterpretationsKey(InterpretationsKeyS query.hashCode(), keySource.getResultType(), keySource.getQueryOptions().getLockOptions(), - keySource.getQueryOptions().getTupleTransformer(), - keySource.getQueryOptions().getResultListTransformer(), memoryEfficientDefensiveSetCopy( keySource.getLoadQueryInfluencers().getEnabledFetchProfileNames() ) ); } @@ -86,8 +82,6 @@ public static QueryInterpretationCache.Key generateNonSelectKey(InterpretationsK private final Object query; private final Class resultType; private final LockOptions lockOptions; - private final TupleTransformer tupleTransformer; - private final ResultListTransformer resultListTransformer; private final Collection enabledFetchProfiles; private final int hashCode; @@ -96,15 +90,11 @@ private SqmInterpretationsKey( int hash, Class resultType, LockOptions lockOptions, - TupleTransformer tupleTransformer, - ResultListTransformer resultListTransformer, Collection enabledFetchProfiles) { this.query = query; this.hashCode = hash; this.resultType = resultType; this.lockOptions = lockOptions; - this.tupleTransformer = tupleTransformer; - this.resultListTransformer = resultListTransformer; this.enabledFetchProfiles = enabledFetchProfiles; } @@ -116,8 +106,6 @@ public QueryInterpretationCache.Key prepareForStore() { resultType, // Since lock options might be mutable, we need a copy for the cache key lockOptions.makeDefensiveCopy(), - tupleTransformer, - resultListTransformer, enabledFetchProfiles ); } @@ -139,8 +127,6 @@ public boolean equals(Object other) { && this.query.equals( that.query ) && Objects.equals( this.resultType, that.resultType ) && Objects.equals( this.lockOptions, that.lockOptions ) - && Objects.equals( this.tupleTransformer, that.tupleTransformer ) - && Objects.equals( this.resultListTransformer, that.resultListTransformer ) && Objects.equals( this.enabledFetchProfiles, that.enabledFetchProfiles ); } From 3a46cfc49d0e24447412cbcb3454bd44f41f97b0 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Wed, 25 Jun 2025 09:50:04 +0200 Subject: [PATCH 075/168] HHH-19573 Special case primitive type names for plural basic types --- .../java/org/hibernate/type/BasicArrayType.java | 15 ++++++++++++++- .../org/hibernate/type/BasicCollectionType.java | 13 ++++++++----- .../hibernate/type/ConvertedBasicArrayType.java | 4 +++- .../test/mapping/basic/ByteArrayMappingTests.java | 8 ++++---- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/BasicArrayType.java b/hibernate-core/src/main/java/org/hibernate/type/BasicArrayType.java index 2fee93813add..33cd993cd4cd 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/BasicArrayType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/BasicArrayType.java @@ -26,7 +26,20 @@ public class BasicArrayType public BasicArrayType(BasicType baseDescriptor, JdbcType arrayJdbcType, JavaType arrayTypeDescriptor) { super( arrayJdbcType, arrayTypeDescriptor ); this.baseDescriptor = baseDescriptor; - this.name = baseDescriptor.getName() + "[]"; + this.name = determineArrayTypeName( baseDescriptor ); + } + + static String determineElementTypeName(BasicType baseDescriptor) { + final String elementName = baseDescriptor.getName(); + return switch ( elementName ) { + case "boolean", "byte", "char", "short", "int", "long", "float", "double" -> + Character.toUpperCase( elementName.charAt( 0 ) ) + elementName.substring( 1 ); + default -> elementName; + }; + } + + static String determineArrayTypeName(BasicType baseDescriptor) { + return determineElementTypeName( baseDescriptor ) + "[]"; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/BasicCollectionType.java b/hibernate-core/src/main/java/org/hibernate/type/BasicCollectionType.java index 8d61cabae8ad..70160c445fc5 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/BasicCollectionType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/BasicCollectionType.java @@ -12,6 +12,8 @@ import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; +import static org.hibernate.type.BasicArrayType.determineElementTypeName; + /** * A type that maps between {@link java.sql.Types#ARRAY ARRAY} and {@code Collection} * @@ -34,18 +36,19 @@ public BasicCollectionType( } private static String determineName(BasicCollectionJavaType collectionTypeDescriptor, BasicType baseDescriptor) { + final String elementTypeName = determineElementTypeName( baseDescriptor ); switch ( collectionTypeDescriptor.getSemantics().getCollectionClassification() ) { case BAG: case ID_BAG: - return "Collection<" + baseDescriptor.getName() + ">"; + return "Collection<" + elementTypeName + ">"; case LIST: - return "List<" + baseDescriptor.getName() + ">"; + return "List<" + elementTypeName + ">"; case SET: - return "Set<" + baseDescriptor.getName() + ">"; + return "Set<" + elementTypeName + ">"; case SORTED_SET: - return "SortedSet<" + baseDescriptor.getName() + ">"; + return "SortedSet<" + elementTypeName + ">"; case ORDERED_SET: - return "OrderedSet<" + baseDescriptor.getName() + ">"; + return "OrderedSet<" + elementTypeName + ">"; } return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/ConvertedBasicArrayType.java b/hibernate-core/src/main/java/org/hibernate/type/ConvertedBasicArrayType.java index 448ff6a1a75d..eb2fa0aef200 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ConvertedBasicArrayType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ConvertedBasicArrayType.java @@ -14,6 +14,8 @@ import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; +import static org.hibernate.type.BasicArrayType.determineArrayTypeName; + /** * Given a {@link BasicValueConverter} for an array type, * @@ -48,7 +50,7 @@ public ConvertedBasicArrayType( this.jdbcValueExtractor = (ValueExtractor) arrayJdbcType.getExtractor( converter.getRelationalJavaType() ); this.jdbcLiteralFormatter = (JdbcLiteralFormatter) arrayJdbcType.getJdbcLiteralFormatter( converter.getRelationalJavaType() ); this.baseDescriptor = baseDescriptor; - this.name = baseDescriptor.getName() + "[]"; + this.name = determineArrayTypeName( baseDescriptor ); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/ByteArrayMappingTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/ByteArrayMappingTests.java index 7f6a7f1fdbbc..5cfeab55146e 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/ByteArrayMappingTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/ByteArrayMappingTests.java @@ -63,7 +63,7 @@ public void verifyMappings(SessionFactoryScope scope) { } { - final BasicAttributeMapping primitive = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("wrapper"); + final BasicAttributeMapping primitive = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("boxed"); final JdbcMapping jdbcMapping = primitive.getJdbcMapping(); assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(Byte[].class)); if ( dialect.supportsStandardArrays() ) { @@ -132,7 +132,7 @@ public static class EntityOfByteArrays { //tag::basic-bytearray-example[] // mapped as VARBINARY private byte[] primitive; - private Byte[] wrapper; + private Byte[] boxed; @JavaType( ByteArrayJavaType.class ) private Byte[] wrapperOld; @@ -149,7 +149,7 @@ public EntityOfByteArrays() { public EntityOfByteArrays(Integer id, byte[] primitive, Byte[] wrapper) { this.id = id; this.primitive = primitive; - this.wrapper = wrapper; + this.boxed = wrapper; this.primitiveLob = primitive; this.wrapperLob = wrapper; } @@ -157,7 +157,7 @@ public EntityOfByteArrays(Integer id, byte[] primitive, Byte[] wrapper) { public EntityOfByteArrays(Integer id, byte[] primitive, Byte[] wrapper, byte[] primitiveLob, Byte[] wrapperLob) { this.id = id; this.primitive = primitive; - this.wrapper = wrapper; + this.boxed = wrapper; this.primitiveLob = primitiveLob; this.wrapperLob = wrapperLob; } From cb87575935dba726206fa3c882cc34fd83bfac64 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Thu, 26 Jun 2025 10:17:39 +0200 Subject: [PATCH 076/168] HHH-19577 Avoid duplicate stack map frames for SetPropertyValues --- .../bytebuddy/BytecodeProviderImpl.java | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java index 8b14942383a5..25cba0cef8c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java @@ -886,17 +886,17 @@ public Size apply( Label nextLabel = new Label(); for ( int index = 0; index < setters.length; index++ ) { final Member setterMember = setters[index]; - if ( enhanced && currentLabel != null ) { + if ( setterMember == EMBEDDED_MEMBER ) { + // The embedded property access does a no-op + continue; + } + if ( currentLabel != null ) { methodVisitor.visitLabel( currentLabel ); implementationContext.getFrameGeneration().same( methodVisitor, instrumentedMethod.getParameters().asTypeList() ); } - if ( setterMember == EMBEDDED_MEMBER ) { - // The embedded property access does a no-op - continue; - } // Push entity on stack methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, Type.getInternalName( clazz ) ); @@ -1023,6 +1023,7 @@ else if ( setterMember instanceof Field field ) { } if ( enhanced ) { final boolean compositeTracker = CompositeTracker.class.isAssignableFrom( type ); + boolean alreadyHasFrame = false; // The composite owner check and setting only makes sense if // * the value type is a composite tracker // * a value subtype can be a composite tracker @@ -1104,6 +1105,7 @@ else if ( setterMember instanceof Field field ) { // Clean stack after the if block methodVisitor.visitLabel( compositeTrackerEndLabel ); implementationContext.getFrameGeneration().same(methodVisitor, instrumentedMethod.getParameters().asTypeList()); + alreadyHasFrame = true; } if ( persistentAttributeInterceptable ) { // Load the owner @@ -1168,9 +1170,20 @@ else if ( setterMember instanceof Field field ) { // Clean stack after the if block methodVisitor.visitLabel( instanceofEndLabel ); implementationContext.getFrameGeneration().same(methodVisitor, instrumentedMethod.getParameters().asTypeList()); + alreadyHasFrame = true; } - currentLabel = nextLabel; + if ( alreadyHasFrame ) { + // Usually, the currentLabel is visited as well generating a frame, + // but if a frame was already generated, only visit the label here, + // otherwise two frames for the same bytecode index are generated, + // which is wrong and will produce an error when the JDK ClassFile API is used + methodVisitor.visitLabel( nextLabel ); + currentLabel = null; + } + else { + currentLabel = nextLabel; + } nextLabel = new Label(); } } From dcd427bb3fa7991e17966847400043afe067b3b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=8Cedomir=20Igaly?= Date: Thu, 15 May 2025 18:35:13 +0200 Subject: [PATCH 077/168] HHH-19464 Test case adapted from https://hibernate.atlassian.net/browse/HHH-19464 --- .../orm/test/lob/JarFileEntryBlobTest.java | 67 ++++++++++++++++++ .../orm/test/lob/JarFileEntryBlobTest.zip | Bin 0 -> 317085 bytes 2 files changed, 67 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/lob/JarFileEntryBlobTest.java create mode 100644 hibernate-core/src/test/resources/org/hibernate/orm/test/lob/JarFileEntryBlobTest.zip diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/lob/JarFileEntryBlobTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/lob/JarFileEntryBlobTest.java new file mode 100644 index 000000000000..e9c6e3efe727 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/lob/JarFileEntryBlobTest.java @@ -0,0 +1,67 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.lob; + +import org.hibernate.bugs.TestEntity; +import org.hibernate.engine.jdbc.env.internal.NonContextualLobCreator; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.sql.Blob; +import java.sql.SQLException; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@DomainModel(annotatedClasses = TestEntity.class) +@SessionFactory +@JiraKey( "HHH-19464" ) +class JarFileEntryBlobTest { + + @Test + void hibernate_blob_streaming(SessionFactoryScope scope) throws Exception { + final var zipFilePath = getClass().getClassLoader().getResource( "org/hibernate/orm/test/lob/JarFileEntryBlobTest.zip" ); + File file = new File( zipFilePath.toURI() ); + + try (JarFile jarFile = new JarFile( file )) { + JarEntry entry = jarFile.getJarEntry( "pizza.png" ); + long size = entry.getSize(); + scope.inTransaction( entityManager -> { + try { + InputStream is = jarFile.getInputStream( entry ); + Blob blob = NonContextualLobCreator.INSTANCE.wrap( + NonContextualLobCreator.INSTANCE.createBlob( is, size ) + ); + TestEntity e = new TestEntity(); + e.setId( 1L ); + e.setData( blob ); + + entityManager.persist( e ); + } + catch (IOException e) { + throw new RuntimeException( e ); + } + } + ); + + scope.inStatelessSession( session -> { + final var entity = session.get( TestEntity.class, 1L ); + try { + assertEquals( size, entity.getData().length() ); + } + catch (SQLException e) { + throw new RuntimeException( e ); + } + } ); + } + } +} diff --git a/hibernate-core/src/test/resources/org/hibernate/orm/test/lob/JarFileEntryBlobTest.zip b/hibernate-core/src/test/resources/org/hibernate/orm/test/lob/JarFileEntryBlobTest.zip new file mode 100644 index 0000000000000000000000000000000000000000..f9302d78531858a92d7a545963ae6940ef51d1f4 GIT binary patch literal 317085 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 (%?=OTZgs$I28%oON<3)R(6CgvU1LL| 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≪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

kw&i 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