diff --git a/ci/jpa-3.1-tck.Jenkinsfile b/ci/jpa-3.1-tck.Jenkinsfile index 8e23210456ed..5e3e1e334446 100644 --- a/ci/jpa-3.1-tck.Jenkinsfile +++ b/ci/jpa-3.1-tck.Jenkinsfile @@ -34,6 +34,7 @@ pipeline { stages { stage('Build') { steps { + requireApprovalForPullRequest 'hibernate' script { docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { docker.image('openjdk:11-jdk').pull() diff --git a/ci/quarkus.Jenkinsfile b/ci/quarkus.Jenkinsfile index 709cb3cd66ea..1a3ec6bb992d 100644 --- a/ci/quarkus.Jenkinsfile +++ b/ci/quarkus.Jenkinsfile @@ -1,4 +1,4 @@ -@Library('hibernate-jenkins-pipeline-helpers@1.5') _ +@Library('hibernate-jenkins-pipeline-helpers@1.13') _ // Avoid running the pipeline on branch indexing if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) { @@ -28,6 +28,7 @@ pipeline { stages { stage('Build') { steps { + requireApprovalForPullRequest 'hibernate' script { dir('hibernate') { checkout scm diff --git a/docker_db.sh b/docker_db.sh index 295339a5310b..85035e7f91b8 100755 --- a/docker_db.sh +++ b/docker_db.sh @@ -908,8 +908,11 @@ informix() { } informix_14_10() { + temp_dir=$(mktemp -d) + echo "ALLOW_NEWLINE 1" >$temp_dir/onconfig.mod + chmod 777 -R $temp_dir $PRIVILEGED_CLI $CONTAINER_CLI rm -f informix || true - $PRIVILEGED_CLI $CONTAINER_CLI run --name informix --privileged -p 9088:9088 -e LICENSE=accept -e GL_USEGLU=1 -d ${DB_IMAGE_INFORMIX_14_10:-icr.io/informix/informix-developer-database:14.10.FC9W1DE} + $PRIVILEGED_CLI $CONTAINER_CLI run --name informix --privileged -p 9088:9088 -v $temp_dir:/opt/ibm/config -e LICENSE=accept -e GL_USEGLU=1 -d ${DB_IMAGE_INFORMIX_14_10:-icr.io/informix/informix-developer-database:14.10.FC9W1DE} echo "Starting Informix. This can take a few minutes" # Give the container some time to start OUTPUT= diff --git a/gradle/databases.gradle b/gradle/databases.gradle index d00a6fbf689c..9e4ac21bb51d 100644 --- a/gradle/databases.gradle +++ b/gradle/databases.gradle @@ -295,7 +295,7 @@ ext { 'jdbc.driver': 'com.informix.jdbc.IfxDriver', 'jdbc.user' : 'informix', 'jdbc.pass' : 'in4mix', - 'jdbc.url' : 'jdbc:informix-sqli://' + dbHost + ':9088/dev:INFORMIXSERVER=informix;user=informix;password=in4mix;DELIMIDENT=Y;DB_LOCALE=en_US.utf8', + 'jdbc.url' : 'jdbc:informix-sqli://' + dbHost + ':9088/dev:INFORMIXSERVER=informix;user=informix;password=in4mix;DBDATE=Y4MD-;DELIMIDENT=Y;DB_LOCALE=en_US.utf8', 'jdbc.datasource' : 'com.informix.jdbc.IfxDriver', // 'jdbc.datasource' : 'com.informix.jdbcx.IfxDataSource', 'connection.init_sql' : '' diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java index 5326d79fa71c..22e7c0caba7b 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java @@ -55,11 +55,13 @@ import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers; import org.hibernate.query.sqm.sql.SqmTranslator; import org.hibernate.query.sqm.sql.SqmTranslatorFactory; import org.hibernate.query.sqm.sql.StandardSqmTranslatorFactory; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.service.ServiceRegistry; +import org.hibernate.sql.ast.SqlAstNodeRenderingMode; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.spi.SqlAppender; @@ -71,7 +73,10 @@ import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.tool.schema.internal.StandardForeignKeyExporter; import org.hibernate.tool.schema.spi.Exporter; +import org.hibernate.type.JavaObjectType; +import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.jdbc.ClobJdbcType; +import org.hibernate.type.descriptor.jdbc.ObjectNullAsBinaryTypeJdbcType; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.descriptor.sql.DdlType; import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType; @@ -82,6 +87,7 @@ import jakarta.persistence.TemporalType; import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate; +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING; import static org.hibernate.type.SqlTypes.BIGINT; import static org.hibernate.type.SqlTypes.BINARY; import static org.hibernate.type.SqlTypes.FLOAT; @@ -276,10 +282,10 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio super.initializeFunctionRegistry(functionContributions); CommonFunctionFactory functionFactory = new CommonFunctionFactory(functionContributions); + functionFactory.aggregates( this, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER ); functionFactory.instr(); functionFactory.substr(); - functionFactory.substring_substr(); - //also natively supports ANSI-style substring() + functionFactory.substringFromFor(); functionFactory.trunc(); functionFactory.trim2(); functionFactory.space(); @@ -302,12 +308,30 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.monthsBetween(); functionFactory.stddev(); functionFactory.variance(); - functionFactory.locate_positionSubstring(); + functionFactory.bitLength_pattern( "length(?1)*8" ); + + if ( getVersion().isSameOrAfter( 12 ) ) { + functionFactory.locate_charindex(); + } //coalesce() and nullif() both supported since Informix 12 functionContributions.getFunctionRegistry().register( "least", new CaseLeastGreatestEmulation( true ) ); functionContributions.getFunctionRegistry().register( "greatest", new CaseLeastGreatestEmulation( false ) ); + functionContributions.getFunctionRegistry().namedDescriptorBuilder( "matches" ) + .setInvariantType( functionContributions.getTypeConfiguration() + .getBasicTypeRegistry() + .resolve( StandardBasicTypes.STRING ) + ) + .setExactArgumentCount( 2 ) + .setArgumentTypeResolver( + StandardFunctionArgumentTypeResolvers.impliedOrInvariant( + functionContributions.getTypeConfiguration(), + STRING + ) + ) + .setArgumentListSignature( "(STRING string, STRING pattern)" ) + .register(); if ( supportsWindowFunctions() ) { functionFactory.windowFunctions(); } @@ -672,6 +696,11 @@ public String currentDate() { return "today"; } + @Override + public String currentTime() { + return currentTimestamp(); + } + @Override public String currentTimestamp() { return "current"; @@ -816,6 +845,17 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry(); jdbcTypeRegistry.addDescriptor( Types.NCLOB, ClobJdbcType.DEFAULT ); typeContributions.contributeJdbcType( VarcharUUIDJdbcType.INSTANCE ); + typeContributions.contributeJdbcType( ObjectNullAsBinaryTypeJdbcType.INSTANCE ); + + // Until we remove StandardBasicTypes, we have to keep this + typeContributions.contributeType( + new JavaObjectType( + ObjectNullAsBinaryTypeJdbcType.INSTANCE, + typeContributions.getTypeConfiguration() + .getJavaTypeRegistry() + .getDescriptor( Object.class ) + ) + ); } @Override diff --git a/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/InformixFunctionTest.java b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/InformixFunctionTest.java index 91070aaf6587..762bf92940bf 100644 --- a/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/InformixFunctionTest.java +++ b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/InformixFunctionTest.java @@ -181,6 +181,23 @@ public void testCurrentTimestamp(SessionFactoryScope scope) { ); } + @Test + @TestForIssue(jiraKey = "HHH-18369") + public void testMatches(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + String country = (String) session.createQuery( + "select e.country " + + "from Event e " + + "where e.id = :id and matches(e.country, :country) = 'T'" ) + .setParameter( "id", event.id ) + .setParameter( "country", "R*" ) + .getSingleResult(); + assertEquals( "Romania", country ); + } + ); + } + private Calendar todayCalendar() { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.HOUR_OF_DAY, 0); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/NamedHqlQueryDefinitionImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/NamedHqlQueryDefinitionImpl.java index 284a97cf9699..5bdb30e81522 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/NamedHqlQueryDefinitionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/NamedHqlQueryDefinitionImpl.java @@ -70,6 +70,7 @@ public String getHqlString() { public NamedSqmQueryMemento resolve(SessionFactoryImplementor factory) { return new NamedHqlQueryMementoImpl( getRegistrationName(), + null, hqlString, firstResult, maxResults, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/NamedNativeQueryDefinitionImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/NamedNativeQueryDefinitionImpl.java index 688d8dc5e16e..987b873730b4 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/NamedNativeQueryDefinitionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/NamedNativeQueryDefinitionImpl.java @@ -18,6 +18,8 @@ import org.hibernate.query.sql.internal.NamedNativeQueryMementoImpl; import org.hibernate.query.sql.spi.NamedNativeQueryMemento; +import org.checkerframework.checker.nullness.qual.Nullable; + import static org.hibernate.internal.util.StringHelper.isNotEmpty; /** @@ -86,15 +88,16 @@ public String getResultSetMappingClassName() { @Override public NamedNativeQueryMemento resolve(SessionFactoryImplementor factory) { + Class resultClass = isNotEmpty( resultSetMappingClassName ) + ? factory.getServiceRegistry().requireService( ClassLoaderService.class ).classForName( resultSetMappingClassName ) + : null; return new NamedNativeQueryMementoImpl( getRegistrationName(), + resultClass, sqlString, sqlString, resultSetMappingName, - isNotEmpty( resultSetMappingClassName ) - ? factory.getServiceRegistry().requireService( ClassLoaderService.class ) - .classForName( resultSetMappingClassName ) - : null, + resultClass, querySpaces, getCacheable(), getCacheRegion(), diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java index 77922aed7ae9..4b113207747a 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java @@ -629,7 +629,7 @@ private static List collectClassElements( //embeddable elements can have type defs final PropertyContainer container = new PropertyContainer( returnedClassOrElement, annotatedClass, propertyAccessor ); - addElementsOfClass( classElements, container, context); + addElementsOfClass( classElements, container, context, 0 ); //add elements of the embeddable's mapped superclasses XClass subclass = returnedClassOrElement; XClass superClass; @@ -637,7 +637,7 @@ private static List collectClassElements( //FIXME: proper support of type variables incl var resolved at upper levels final PropertyContainer superContainer = new PropertyContainer( superClass, annotatedClass, propertyAccessor ); - addElementsOfClass( classElements, superContainer, context ); + addElementsOfClass( classElements, superContainer, context, 0 ); if ( subclassToSuperclass != null ) { subclassToSuperclass.put( subclass.getName(), superClass.getName() ); } @@ -668,7 +668,7 @@ private static void collectSubclassElements( assert put == null; // collect property of subclass final PropertyContainer superContainer = new PropertyContainer( subclass, superclass, propertyAccessor ); - addElementsOfClass( classElements, superContainer, context ); + addElementsOfClass( classElements, superContainer, context, 0 ); // recursively do that same for all subclasses collectSubclassElements( propertyAccessor, @@ -739,7 +739,7 @@ private static List collectBaseClassElements( while ( !Object.class.getName().equals( baseReturnedClassOrElement.getName() ) ) { final PropertyContainer container = new PropertyContainer( baseReturnedClassOrElement, annotatedClass, propertyAccessor ); - addElementsOfClass( baseClassElements, container, context ); + addElementsOfClass( baseClassElements, container, context, 0 ); baseReturnedClassOrElement = baseReturnedClassOrElement.getSuperclass(); } return baseClassElements; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java index 6db3a2868960..751c8b90f9d7 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java @@ -617,7 +617,8 @@ private static PropertyData getUniqueIdPropertyFromBaseClass( inferredData.getPropertyClass(), propertyAccessor ); - addElementsOfClass( baseClassElements, propContainer, context ); + final int idPropertyCount = addElementsOfClass( baseClassElements, propContainer, context, 0 ); + assert idPropertyCount == 1; //Id properties are on top and there is only one return baseClassElements.get( 0 ); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/InheritanceState.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/InheritanceState.java index 16156c606db8..7d7a9ba9d561 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/InheritanceState.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/InheritanceState.java @@ -234,12 +234,11 @@ private ElementsToProcess getElementsToProcess() { clazz, accessType ); - int currentIdPropertyCount = addElementsOfClass( + idPropertyCount = addElementsOfClass( elements, propertyContainer, - buildingContext - ); - idPropertyCount += currentIdPropertyCount; + buildingContext, + idPropertyCount ); } if ( idPropertyCount == 0 && !inheritanceState.hasParents() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java index df494300e321..611543cabd54 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java @@ -555,17 +555,17 @@ private void validateOptimisticLock(OptimisticLock optimisticLock) { /** * @param elements List of {@link PropertyData} instances * @param propertyContainer Metadata about a class and its properties + * @param idPropertyCounter number of id properties already present in list of {@link PropertyData} instances * - * @return the number of id properties found while iterating the elements of - * {@code annotatedClass} using the determined access strategy + * @return total number of id properties found after iterating the elements of {@code annotatedClass} + * using the determined access strategy (starting from the provided {@code idPropertyCounter}) */ static int addElementsOfClass( List elements, PropertyContainer propertyContainer, - MetadataBuildingContext context) { - int idPropertyCounter = 0; + MetadataBuildingContext context, int idPropertyCounter) { for ( XProperty property : propertyContainer.propertyIterator() ) { - idPropertyCounter += addProperty( propertyContainer, property, elements, context ); + idPropertyCounter = addProperty( propertyContainer, property, elements, context, idPropertyCounter ); } return idPropertyCounter; } @@ -574,20 +574,20 @@ private static int addProperty( PropertyContainer propertyContainer, XProperty property, List inFlightPropertyDataList, - MetadataBuildingContext context) { + MetadataBuildingContext context, + int idPropertyCounter) { // see if inFlightPropertyDataList already contains a PropertyData for this name, // and if so, skip it.. for ( PropertyData propertyData : inFlightPropertyDataList ) { if ( propertyData.getPropertyName().equals( property.getName() ) ) { checkIdProperty( property, propertyData ); // EARLY EXIT!!! - return 0; + return idPropertyCounter; } } final XClass declaringClass = propertyContainer.getDeclaringClass(); final XClass entity = propertyContainer.getEntityAtStake(); - int idPropertyCounter = 0; final PropertyData propertyAnnotatedElement = new PropertyInferredData( declaringClass, property, diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index c79f3e4b2350..23727af5d500 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -8,23 +8,31 @@ import jakarta.persistence.Access; import jakarta.persistence.AccessType; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; import jakarta.persistence.metamodel.Type; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.annotation.AnnotationDescription; import net.bytebuddy.description.annotation.AnnotationList; +import net.bytebuddy.description.annotation.AnnotationSource; import net.bytebuddy.description.field.FieldDescription; import net.bytebuddy.description.field.FieldDescription.InDefinedShape; import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.method.MethodList; import net.bytebuddy.description.type.TypeDefinition; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.description.type.TypeDescription.Generic; -import net.bytebuddy.description.type.TypeList; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.scaffold.MethodGraph; import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.implementation.FixedValue; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.StubMethod; + +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.AssertionFailure; import org.hibernate.Version; import org.hibernate.bytecode.enhance.VersionMismatchException; @@ -60,9 +68,12 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.function.Supplier; import static net.bytebuddy.matcher.ElementMatchers.isDefaultFinalizer; +import static net.bytebuddy.matcher.ElementMatchers.isGetter; +import static net.bytebuddy.matcher.ElementMatchers.isSetter; public class EnhancerImpl implements Enhancer { @@ -175,7 +186,7 @@ private DynamicType.Builder doEnhance(Supplier> builde } if ( enhancementContext.isEntityClass( managedCtClass ) ) { - if ( checkUnsupportedAttributeNaming( managedCtClass ) ) { + if ( checkUnsupportedAttributeNaming( managedCtClass, enhancementContext ) ) { // do not enhance classes with mismatched names for PROPERTY-access persistent attributes return null; } @@ -339,7 +350,7 @@ private DynamicType.Builder doEnhance(Supplier> builde return createTransformer( managedCtClass ).applyTo( builder ); } else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { - if ( checkUnsupportedAttributeNaming( managedCtClass ) ) { + if ( checkUnsupportedAttributeNaming( managedCtClass, enhancementContext ) ) { // do not enhance classes with mismatched names for PROPERTY-access persistent attributes return null; } @@ -377,9 +388,8 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { return createTransformer( managedCtClass ).applyTo( builder ); } else if ( enhancementContext.isMappedSuperclassClass( managedCtClass ) ) { - // Check for HHH-16572 (PROPERTY attributes with mismatched field and method names) - if ( checkUnsupportedAttributeNaming( managedCtClass ) ) { + if ( checkUnsupportedAttributeNaming( managedCtClass, enhancementContext ) ) { return null; } @@ -399,19 +409,101 @@ else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) { } } + /** + * Utility that determines the access-type of a mapped class based on an explicit annotation + * or guessing it from the placement of its identifier property. Implementation should be + * aligned with {@code InheritanceState#determineDefaultAccessType()}. + * + * @return the {@link AccessType} used by the mapped class + * + * @implNote this does not fully account for embeddables, as they should inherit the access-type + * from the entities they're used in - defaulting to PROPERTY to always run the accessor check + */ + private static AccessType determineDefaultAccessType(TypeDefinition typeDefinition) { + for ( TypeDefinition candidate = typeDefinition; candidate != null && !candidate.represents( Object.class ); candidate = candidate.getSuperClass() ) { + final AnnotationList annotations = candidate.asErasure().getDeclaredAnnotations(); + if ( hasMappingAnnotation( annotations ) ) { + final AnnotationDescription.Loadable access = annotations.ofType( Access.class ); + if ( access != null ) { + return access.load().value(); + } + } + } + // Guess from identifier. + // FIX: Shouldn't this be determined by the first attribute (i.e., field or property) with annotations, + // but without an explicit Access annotation, according to JPA 2.0 spec 2.3.1: Default Access Type? + for ( TypeDefinition candidate = typeDefinition; candidate != null && !candidate.represents( Object.class ); candidate = candidate.getSuperClass() ) { + final AnnotationList annotations = candidate.asErasure().getDeclaredAnnotations(); + if ( hasMappingAnnotation( annotations ) ) { + for ( FieldDescription ctField : candidate.getDeclaredFields() ) { + if ( !Modifier.isStatic( ctField.getModifiers() ) ) { + final AnnotationList annotationList = ctField.getDeclaredAnnotations(); + if ( annotationList.isAnnotationPresent( Id.class ) || annotationList.isAnnotationPresent( EmbeddedId.class ) ) { + return AccessType.FIELD; + } + } + } + } + } + // We can assume AccessType.PROPERTY here + return AccessType.PROPERTY; + } + + /** + * Determines the access-type of the given annotation source if an explicit {@link Access} annotation + * is present, otherwise defaults to the provided {@code defaultAccessType} + */ + private static AccessType determineAccessType(AnnotationSource annotationSource, AccessType defaultAccessType) { + final AnnotationDescription.Loadable access = annotationSource.getDeclaredAnnotations().ofType( Access.class ); + return access != null ? access.load().value() : defaultAccessType; + } + + private static boolean hasMappingAnnotation(AnnotationList annotations) { + return annotations.isAnnotationPresent( Entity.class ) + || annotations.isAnnotationPresent( MappedSuperclass.class ) + || annotations.isAnnotationPresent( Embeddable.class ); + } + + private static boolean hasPersistenceAnnotation(AnnotationList annotations) { + boolean found = false; + for ( AnnotationDescription annotation : annotations ) { + final String annotationName = annotation.getAnnotationType().getName(); + if ( annotationName.startsWith( "jakarta.persistence" ) ) { + if ( annotationName.equals( "jakarta.persistence.Transient" ) ) { + // transient property so ignore it + return false; + } + else if ( !found && !IGNORED_PERSISTENCE_ANNOTATIONS.contains( annotationName ) ) { + found = true; + } + } + } + return found; + } + + private static final Set IGNORED_PERSISTENCE_ANNOTATIONS = Set.of( + "jakarta.persistence.PostLoad", + "jakarta.persistence.PostPersist", + "jakarta.persistence.PostRemove", + "jakarta.persistence.PostUpdate", + "jakarta.persistence.PrePersist", + "jakarta.persistence.PreRemove", + "jakarta.persistence.PreUpdate" + ); + /** * Check whether an entity class ({@code managedCtClass}) has mismatched names between a persistent field and its * getter/setter when using {@link AccessType#PROPERTY}, which Hibernate does not currently support for enhancement. - * See https://hibernate.atlassian.net/browse/HHH-16572 + * See HHH-16572 * - * @return {@code true} if enhancement of the class must be {@link org.hibernate.bytecode.enhance.spi.UnsupportedEnhancementStrategy#SKIP skipped} + * @return {@code true} if enhancement of the class must be {@link UnsupportedEnhancementStrategy#SKIP skipped} * because it has mismatched names. * {@code false} if enhancement of the class must proceed, either because it doesn't have any mismatched names, - * or because {@link org.hibernate.bytecode.enhance.spi.UnsupportedEnhancementStrategy#LEGACY legacy mode} was opted into. - * @throws EnhancementException if enhancement of the class must {@link org.hibernate.bytecode.enhance.spi.UnsupportedEnhancementStrategy#FAIL abort} because it has mismatched names. + * or because {@link UnsupportedEnhancementStrategy#LEGACY legacy mode} was opted into. + * @throws EnhancementException if enhancement of the class must {@link UnsupportedEnhancementStrategy#FAIL abort} because it has mismatched names. */ @SuppressWarnings("deprecation") - private boolean checkUnsupportedAttributeNaming(TypeDescription managedCtClass) { + private static boolean checkUnsupportedAttributeNaming(TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext) { var strategy = enhancementContext.getUnsupportedEnhancementStrategy(); if ( UnsupportedEnhancementStrategy.LEGACY.equals( strategy ) ) { // Don't check anything and act as if there was nothing unsupported in the class. @@ -435,71 +527,67 @@ private boolean checkUnsupportedAttributeNaming(TypeDescription managedCtClass) // // Check name of the getter/setter method with persistence annotation and getter/setter method name that doesn't refer to an entity field // and will return false. If the property accessor method(s) are named to match the field name(s), return true. - boolean propertyHasAnnotation = false; - MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile((TypeDefinition) managedCtClass); - for (MethodGraph.Node node : methodGraph.listNodes()) { - MethodDescription methodDescription = node.getRepresentative(); - if (methodDescription.getDeclaringType().represents(Object.class)) { // skip class java.lang.Object methods + final AccessType defaultAccessType = determineDefaultAccessType( managedCtClass ); + final MethodList methods = MethodGraph.Compiler.DEFAULT.compile( (TypeDefinition) managedCtClass ) + .listNodes() + .asMethodList() + .filter( isGetter().or( isSetter() ) ); + for ( final MethodDescription methodDescription : methods ) { + if ( determineAccessType( methodDescription, defaultAccessType ) != AccessType.PROPERTY ) { + // We only need to check this for AccessType.PROPERTY continue; } - String methodName = methodDescription.getActualName(); - if (methodName.equals("") || - (!methodName.startsWith("get") && !methodName.startsWith("set") && !methodName.startsWith("is"))) { - continue; - } + final String methodName = methodDescription.getActualName(); String methodFieldName; - if (methodName.startsWith("is")) { // skip past "is" - methodFieldName = methodName.substring(2); - } - else if (methodName.startsWith("get") || - methodName.startsWith("set")) { // skip past "get" or "set" - methodFieldName = methodName.substring(3); + if ( methodName.startsWith( "get" ) || methodName.startsWith( "set" ) ) { + methodFieldName = methodName.substring( 3 ); } else { - // not a property accessor method so ignore it - continue; - } - boolean propertyNameMatchesFieldName = false; - // extract the property name from method name - methodFieldName = getJavaBeansFieldName(methodFieldName); - TypeList typeList = methodDescription.getDeclaredAnnotations().asTypeList(); - if (typeList.stream().anyMatch(typeDefinitions -> - (typeDefinitions.getName().equals("jakarta.persistence.Transient")))) { - // transient property so ignore it - continue; + assert methodName.startsWith( "is" ); + methodFieldName = methodName.substring( 2 ); } - if (typeList.stream().anyMatch(typeDefinitions -> - (typeDefinitions.getName().contains("jakarta.persistence")))) { - propertyHasAnnotation = true; - } - for (FieldDescription ctField : methodDescription.getDeclaringType().getDeclaredFields()) { - if (!Modifier.isStatic(ctField.getModifiers())) { - AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription(enhancementContext, ctField); - if (enhancementContext.isPersistentField(annotatedField)) { - if (methodFieldName.equals(ctField.getActualName())) { - propertyNameMatchesFieldName = true; - break; + // convert first field letter to lower case + methodFieldName = getJavaBeansFieldName( methodFieldName ); + if ( methodFieldName != null && hasPersistenceAnnotation( methodDescription.getDeclaredAnnotations() ) ) { + boolean propertyNameMatchesFieldName = false; + for ( final FieldDescription field : methodDescription.getDeclaringType().getDeclaredFields() ) { + if ( !Modifier.isStatic( field.getModifiers() ) ) { + final AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( + enhancementContext, + field + ); + if ( enhancementContext.isPersistentField( annotatedField ) ) { + if ( methodFieldName.equals( field.getActualName() ) ) { + propertyNameMatchesFieldName = true; + break; + } } } } - } - if ( propertyHasAnnotation && !propertyNameMatchesFieldName ) { - switch ( strategy ) { - case SKIP: - log.debugf( - "Skipping enhancement of [%s] because no field named [%s] could be found for property accessor method [%s]." - + " To fix this, make sure all property accessor methods have a matching field.", - managedCtClass.getName(), methodFieldName, methodDescription.getName() ); - return true; - case FAIL: - throw new EnhancementException( String.format( - "Enhancement of [%s] failed because no field named [%s] could be found for property accessor method [%s]." - + " To fix this, make sure all property accessor methods have a matching field.", - managedCtClass.getName(), methodFieldName, methodDescription.getName() ) ); - default: - // We shouldn't even be in this method if using LEGACY, see top of this method. - throw new AssertionFailure( "Unexpected strategy at this point: " + strategy ); + if ( !propertyNameMatchesFieldName ) { + // We shouldn't even be in this method if using LEGACY, see top of this method. + switch ( strategy ) { + case SKIP: + log.debugf( + "Skipping enhancement of [%s] because no field named [%s] could be found for property accessor method [%s]." + + " To fix this, make sure all property accessor methods have a matching field.", + managedCtClass.getName(), + methodFieldName, + methodDescription.getName() + ); + return true; + case FAIL: + throw new EnhancementException( String.format( + "Enhancement of [%s] failed because no field named [%s] could be found for property accessor method [%s]." + + " To fix this, make sure all property accessor methods have a matching field.", + managedCtClass.getName(), + methodFieldName, + methodDescription.getName() + ) ); + default: + throw new AssertionFailure( "Unexpected strategy at this point: " + strategy ); + } } } } @@ -509,17 +597,20 @@ else if (methodName.startsWith("get") || /** * If the first two characters are upper case, assume all characters are upper case to be returned as is. * Otherwise, return the name with the first character converted to lower case and the remaining part returned as is. - * @param fieldName is the property accessor name to be updated following Persistence property name rules. - * @return name that follows JavaBeans rules. + * + * @param name is the property accessor name to be updated following Persistence property name rules. + * @return name that follows JavaBeans rules, or {@code null} if the provided string is empty */ - private static String getJavaBeansFieldName(String fieldName) { - - if (fieldName.length() == 0 || - (fieldName.length() > 1 && Character.isUpperCase(fieldName.charAt(0)) && Character.isUpperCase(fieldName.charAt(1))) - ) { - return fieldName; + private static @Nullable String getJavaBeansFieldName(String name) { + if ( name.isEmpty() ) { + return null; + } + if ( name.length() > 1 && Character.isUpperCase( name.charAt( 1 ) ) && Character.isUpperCase( name.charAt( 0 ) ) ) { + return name; } - return Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1); + final char[] chars = name.toCharArray(); + chars[0] = Character.toLowerCase( chars[0] ); + return new String( chars ); } private static void verifyVersions(TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext) { diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java index 3555d8d1acca..bc8a58e3a0b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java @@ -201,4 +201,8 @@ public void addLazyFieldByGraph(String fieldName) { } mutableLazyFields.add( fieldName ); } + + public void clearInitializedLazyFields() { + initializedLazyFields = null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java index 1df1aed42822..f90dde51591c 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java @@ -238,31 +238,30 @@ else if ( !session.isConnected() ) { SharedSessionContractImplementor originalSession = null; boolean isJTA = false; + try { + if ( tempSession != null ) { + isTempSession = true; + originalSession = session; + session = tempSession; + + isJTA = session.getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta(); + + if ( !isJTA ) { + // Explicitly handle the transactions only if we're not in + // a JTA environment. A lazy loading temporary session can + // be created even if a current session and transaction are + // open (ex: session.clear() was used). We must prevent + // multiple transactions. + session.beginTransaction(); + } - if ( tempSession != null ) { - isTempSession = true; - originalSession = session; - session = tempSession; - - isJTA = session.getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta(); - - if ( !isJTA ) { - // Explicitly handle the transactions only if we're not in - // a JTA environment. A lazy loading temporary session can - // be created even if a current session and transaction are - // open (ex: session.clear() was used). We must prevent - // multiple transactions. - session.beginTransaction(); + final CollectionPersister collectionDescriptor = + session.getSessionFactory() + .getMappingMetamodel() + .getCollectionDescriptor( getRole() ); + session.getPersistenceContextInternal() + .addUninitializedDetachedCollection( collectionDescriptor, this ); } - - final CollectionPersister collectionDescriptor = - session.getSessionFactory() - .getMappingMetamodel() - .getCollectionDescriptor( getRole() ); - session.getPersistenceContextInternal().addUninitializedDetachedCollection( collectionDescriptor, this ); - } - - try { return lazyInitializationWork.doWork(); } finally { @@ -1298,8 +1297,8 @@ protected static Collection getOrphans( // iterate over the *old* list for ( E old : oldElements ) { if ( !currentSaving.contains( old ) ) { - final Object oldId = ForeignKeys.getEntityIdentifierIfNotUnsaved( entityName, old, session ); - if ( !currentIds.contains( useIdDirect ? oldId : new TypedValue( idType, oldId ) ) ) { + final Object oldId = ForeignKeys.getEntityIdentifier( entityName, old, session ); + if ( oldId != null && !currentIds.contains( useIdDirect ? oldId : new TypedValue( idType, oldId ) ) ) { res.add( old ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java index 608b1b8f464a..3db99c98e0f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java @@ -371,6 +371,21 @@ public static Object getEntityIdentifierIfNotUnsaved( } } + public static Object getEntityIdentifier( + final String entityName, + final Object object, + final SharedSessionContractImplementor session) { + if ( object == null ) { + return null; + } + else { + final Object id = session.getContextEntityIdentifier( object ); + return id == null + ? session.getEntityPersister( entityName, object ).getIdentifier( object, session ) + : id; + } + } + private static void throwIfTransient(String entityName, Object object, SharedSessionContractImplementor session) { if ( isTransient( entityName, object, Boolean.FALSE, session ) ) { throw new TransientObjectException( diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java index 1e6c5109348b..9791b4098a24 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java @@ -7,10 +7,16 @@ package org.hibernate.engine.internal; import org.hibernate.LockMode; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.proxy.LazyInitializer; + +import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; /** * Functionality relating to the Hibernate two-phase loading process, that may be reused by persisters @@ -39,16 +45,27 @@ public static void addUninitializedCachedEntity( final LockMode lockMode, final Object version, final SharedSessionContractImplementor session) { - session.getPersistenceContextInternal().addEntity( + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + final EntityHolder entityHolder = persistenceContext.addEntityHolder( key, object ); + final EntityEntry entityEntry = persistenceContext.addEntry( object, Status.LOADING, null, - key, + null, + key.getIdentifier(), version, lockMode, true, persister, false ); + entityHolder.setEntityEntry( entityEntry ); + final Object proxy = entityHolder.getProxy(); + if ( proxy != null ) { + // there is already a proxy for this impl + final LazyInitializer lazyInitializer = extractLazyInitializer( proxy ); + assert lazyInitializer != null; + lazyInitializer.setImplementation( object ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/BlobProxy.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/BlobProxy.java index fa70329ce446..556768b41ac9 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/BlobProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/BlobProxy.java @@ -38,6 +38,8 @@ public final class BlobProxy implements Blob, BlobImplementer { // no longer necessary. The class name could be updated to reflect this but that would break APIs. private final BinaryStream binaryStream; + private final int markBytes; + private boolean resetAllowed; private boolean needsReset; /** @@ -47,7 +49,9 @@ public final class BlobProxy implements Blob, BlobImplementer { * @see #generateProxy(byte[]) */ private BlobProxy(byte[] bytes) { - binaryStream = new BinaryStreamImpl( bytes ); + binaryStream = new BinaryStreamImpl(bytes); + markBytes = bytes.length + 1; + setStreamMark(); } /** @@ -59,6 +63,19 @@ private BlobProxy(byte[] bytes) { */ private BlobProxy(InputStream stream, long length) { this.binaryStream = new StreamBackedBinaryStream( stream, length ); + this.markBytes = (int) length + 1; + setStreamMark(); + } + + private void setStreamMark() { + final InputStream inputStream = binaryStream.getInputStream(); + if ( inputStream != null && inputStream.markSupported() ) { + inputStream.mark( markBytes ); + resetAllowed = true; + } + else { + resetAllowed = false; + } } private InputStream getStream() throws SQLException { @@ -73,7 +90,14 @@ public BinaryStream getUnderlyingStream() throws SQLException { private void resetIfNeeded() throws SQLException { try { if ( needsReset ) { - binaryStream.getInputStream().reset(); + final InputStream inputStream = binaryStream.getInputStream(); + if ( !resetAllowed && inputStream != null) { + throw new SQLException( "Underlying stream does not allow reset" ); + } + if ( inputStream != null ) { + inputStream.reset(); + setStreamMark(); + } } } catch ( IOException ioe) { @@ -96,6 +120,11 @@ public static Blob generateProxy(byte[] bytes) { /** * Generates a BlobImpl proxy using a given number of bytes from an InputStream. * + * Be aware that certain database drivers will automatically close the provided InputStream after the + * contents have been written to the database. This may cause unintended side effects if the entity + * is also audited by Envers. In this case, it's recommended to use {@link #generateProxy(byte[])} + * instead as it isn't affected by this non-standard behavior. + * * @param stream The input stream of bytes to be created as a Blob. * @param length The number of bytes from stream to be written to the Blob. * diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java index f5e455ba34d1..797f28b70972 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java @@ -12,6 +12,8 @@ import org.hibernate.NonUniqueObjectException; import org.hibernate.TransientObjectException; import org.hibernate.UnresolvableObjectException; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.cache.spi.access.CollectionDataAccess; import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.SoftLock; @@ -194,6 +196,14 @@ private static void refresh( EntityEntry entry, Object id, PersistenceContext persistenceContext) { + final BytecodeEnhancementMetadata instrumentationMetadata = persister.getInstrumentationMetadata(); + if ( object != null && instrumentationMetadata.isEnhancedForLazyLoading() ) { + final LazyAttributeLoadingInterceptor interceptor = instrumentationMetadata.extractInterceptor( object ); + if ( interceptor != null ) { + // The list of initialized lazy fields have to be cleared in order to refresh them from the database. + interceptor.clearInitializedLazyFields(); + } + } final Object result = source.getLoadQueryInfluencers().fromInternalFetchProfile( CascadingFetchProfile.REFRESH, 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 3a3e80c088dc..e24c4a54d7c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -19,6 +19,7 @@ import java.util.function.Function; import jakarta.persistence.EntityGraph; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.CacheMode; import org.hibernate.EntityNameResolver; import org.hibernate.Filter; @@ -891,22 +892,8 @@ public QueryImplementor createQuery(String queryString, Class expected // dynamic native (SQL) query handling @Override @SuppressWarnings("rawtypes") - public NativeQueryImpl createNativeQuery(String sqlString) { - checkOpen(); - pulseTransactionCoordinator(); - delayedAfterCompletion(); - - try { - final NativeQueryImpl query = new NativeQueryImpl<>( sqlString, this ); - if ( isEmpty( query.getComment() ) ) { - query.setComment( "dynamic native SQL query" ); - } - applyQuerySettingsAndHints( query ); - return query; - } - catch (RuntimeException he) { - throw getExceptionConverter().convert( he ); - } + public NativeQueryImplementor createNativeQuery(String sqlString) { + return createNativeQuery( sqlString, (Class) null ); } @Override @SuppressWarnings("rawtypes") @@ -939,12 +926,28 @@ protected NamedResultSetMappingMemento getResultSetMappingMemento(String resultS @Override @SuppressWarnings({"rawtypes", "unchecked"}) //note: we're doing something a bit funny here to work around // the clashing signatures declared by the supertypes - public NativeQueryImplementor createNativeQuery(String sqlString, Class resultClass) { - final NativeQueryImpl query = createNativeQuery( sqlString ); - addResultType( resultClass, query ); - return query; + public NativeQueryImplementor createNativeQuery(String sqlString, @Nullable Class resultClass) { + checkOpen(); + pulseTransactionCoordinator(); + delayedAfterCompletion(); + + try { + final NativeQueryImpl query = new NativeQueryImpl<>( sqlString, resultClass, this ); + if ( isEmpty( query.getComment() ) ) { + query.setComment( "dynamic native SQL query" ); + } + applyQuerySettingsAndHints( query ); + return query; + } + catch (RuntimeException he) { + throw getExceptionConverter().convert( he ); + } } + /** + * @deprecated Use {@link NativeQueryImpl#NativeQueryImpl(String, Class, SharedSessionContractImplementor)} instead + */ + @Deprecated(forRemoval = true) protected void addResultType(Class resultClass, NativeQueryImplementor query) { if ( Tuple.class.equals( resultClass ) ) { query.setTupleTransformer( NativeQueryTupleTransformer.INSTANCE ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CacheEntityLoaderHelper.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CacheEntityLoaderHelper.java index 833cc62b96f4..424c78789c70 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CacheEntityLoaderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CacheEntityLoaderHelper.java @@ -35,7 +35,7 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; -import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.LazyInitializer; import org.hibernate.sql.results.LoadingLogger; import org.hibernate.stat.internal.StatsHelper; import org.hibernate.stat.spi.StatisticsImplementor; @@ -47,6 +47,7 @@ import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; import static org.hibernate.engine.internal.Versioning.getVersion; import static org.hibernate.loader.ast.internal.LoaderHelper.upgradeLock; +import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; /** * @author Vlad Mihalcea @@ -388,7 +389,6 @@ private Object convertCacheEntryToEntity( EntityKey entityKey) { final SessionFactoryImplementor factory = source.getFactory(); - final EntityPersister subclassPersister; if ( LOG.isTraceEnabled() ) { LOG.tracef( @@ -398,19 +398,20 @@ private Object convertCacheEntryToEntity( ); } - final Object entity; + final EntityPersister subclassPersister = + factory.getRuntimeMetamodels().getMappingMetamodel() + .getEntityDescriptor( entry.getSubclass() ); + final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); + final EntityHolder oldHolder = persistenceContext.getEntityHolder( entityKey ); - subclassPersister = factory.getRuntimeMetamodels() - .getMappingMetamodel() - .getEntityDescriptor( entry.getSubclass() ); + final Object entity; if ( instanceToLoad != null ) { entity = instanceToLoad; } else { - final EntityHolder holder = source.getPersistenceContextInternal().getEntityHolder( entityKey ); - if ( holder != null && holder.getEntity() != null ) { + if ( oldHolder != null && oldHolder.getEntity() != null ) { // Use the entity which might already be - entity = holder.getEntity(); + entity = oldHolder.getEntity(); } else { entity = source.instantiate( subclassPersister, entityId ); @@ -430,23 +431,39 @@ private Object convertCacheEntryToEntity( } // make it circular-reference safe - TwoPhaseLoad.addUninitializedCachedEntity( - entityKey, - entity, - subclassPersister, - LockMode.NONE, - entry.getVersion(), - source - ); - - final PersistenceContext persistenceContext = source.getPersistenceContext(); - final Object[] values; - final Object version; + final EntityHolder holder = persistenceContext.addEntityHolder( entityKey, entity ); + final Object proxy = holder.getProxy(); final boolean isReadOnly; + if ( proxy != null ) { + // there is already a proxy for this impl + // only set the status to read-only if the proxy is read-only + final LazyInitializer lazyInitializer = extractLazyInitializer( proxy ); + assert lazyInitializer != null; + lazyInitializer.setImplementation( entity ); + + isReadOnly = lazyInitializer.isReadOnly(); + } + else { + isReadOnly = source.isDefaultReadOnly(); + } + holder.setEntityEntry( + persistenceContext.addEntry( + entity, + Status.LOADING, + null, + null, + entityKey.getIdentifier(), + entry.getVersion(), + LockMode.NONE, + true, + persister, + false + ) + ); final Type[] types = subclassPersister.getPropertyTypes(); // initializes the entity by (desired) side-effect - values = ( (StandardCacheEntryImpl) entry ).assemble( + final Object[] values = ( (StandardCacheEntryImpl) entry ).assemble( entity, entityId, subclassPersister, @@ -462,32 +479,23 @@ private Object convertCacheEntryToEntity( source ); } - version = getVersion( values, subclassPersister ); + final Object version = getVersion( values, subclassPersister ); LOG.tracef( "Cached Version : %s", version ); - final Object proxy = persistenceContext.getProxy( entityKey ); - if ( proxy != null ) { - // there is already a proxy for this impl - // only set the status to read-only if the proxy is read-only - isReadOnly = HibernateProxy.extractLazyInitializer( proxy ).isReadOnly(); - } - else { - isReadOnly = source.isDefaultReadOnly(); - } - - EntityEntry entityEntry = persistenceContext.addEntry( - entity, - ( isReadOnly ? Status.READ_ONLY : Status.MANAGED ), - values, - null, - entityId, - version, - LockMode.NONE, - true, - subclassPersister, - false + holder.setEntityEntry( + persistenceContext.addEntry( + entity, + isReadOnly ? Status.READ_ONLY : Status.MANAGED, + values, + null, + entityId, + version, + LockMode.NONE, + true, + subclassPersister, + false + ) ); - persistenceContext.getEntityHolder( entityKey ).setEntityEntry( entityEntry ); subclassPersister.afterInitialize( entity, source ); persistenceContext.initializeNonLazyCollections(); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java index 863cd8f9213b..0a4181bc5978 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java @@ -25,8 +25,8 @@ import org.hibernate.internal.EntityManagerMessageLogger; import org.hibernate.internal.HEMLogging; import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.internal.util.collections.JoinedList; import org.hibernate.mapping.Component; import org.hibernate.mapping.MappedSuperclass; import org.hibernate.mapping.PersistentClass; @@ -61,8 +61,6 @@ import jakarta.persistence.metamodel.SingularAttribute; import jakarta.persistence.metamodel.Type; -import static org.hibernate.internal.util.StringHelper.root; - /** * Defines a context for storing information during the building of the {@link MappingMetamodelImpl}. *

@@ -367,7 +365,13 @@ else if ( MappedSuperclass.class.isAssignableFrom( mapping.getClass() ) ) { // applyNaturalIdAttribute( safeMapping, jpaType ); for ( Property property : safeMapping.getDeclaredProperties() ) { - if ( safeMapping.isVersioned() && property == safeMapping.getVersion() ) { + if ( isIdentifierProperty( property, safeMapping ) ) { + // property represents special handling for id-class mappings but we have already + // accounted for the embedded property mappings in #applyIdMetadata && + // #buildIdClassAttributes + continue; + } + else if ( safeMapping.isVersioned() && property == safeMapping.getVersion() ) { // skip the version property, it was already handled previously. continue; } @@ -379,7 +383,7 @@ else if ( MappedSuperclass.class.isAssignableFrom( mapping.getClass() ) ) { if ( attribute != null ) { addAttribute( jpaType, attribute ); if ( property.isNaturalIdentifier() ) { - ( ( AttributeContainer) jpaType ).getInFlightAccess() + ( (AttributeContainer) jpaType ).getInFlightAccess() .applyNaturalIdAttribute( attribute ); } } @@ -447,6 +451,14 @@ else if ( MappedSuperclass.class.isAssignableFrom( mapping.getClass() ) ) { } } + private static boolean isIdentifierProperty(Property property, MappedSuperclass mappedSuperclass) { + final Component identifierMapper = mappedSuperclass.getIdentifierMapper(); + return identifierMapper != null && ArrayHelper.contains( + identifierMapper.getPropertyNames(), + property.getName() + ); + } + private void addAttribute(ManagedDomainType type, PersistentAttribute attribute) { //noinspection unchecked AttributeContainer container = (AttributeContainer) type; @@ -581,27 +593,29 @@ private EmbeddableTypeImpl applyIdClassMetadata(Component idClassComponent) { } private void applyIdMetadata(MappedSuperclass mappingType, MappedSuperclassDomainType jpaMappingType) { + @SuppressWarnings("unchecked") + final AttributeContainer attributeContainer = (AttributeContainer) jpaMappingType; if ( mappingType.hasIdentifierProperty() ) { final Property declaredIdentifierProperty = mappingType.getDeclaredIdentifierProperty(); if ( declaredIdentifierProperty != null ) { - //noinspection unchecked - final SingularPersistentAttribute attribute = (SingularPersistentAttribute) buildAttribute( - declaredIdentifierProperty, - jpaMappingType, - attributeFactory::buildIdAttribute - ); - //noinspection unchecked - ( (AttributeContainer) jpaMappingType ).getInFlightAccess().applyIdAttribute( attribute ); + final SingularPersistentAttribute attribute = + (SingularPersistentAttribute) + buildAttribute( + declaredIdentifierProperty, + jpaMappingType, + attributeFactory::buildIdAttribute + ); + attributeContainer.getInFlightAccess().applyIdAttribute( attribute ); } } //a MappedSuperclass can have no identifier if the id is set below in the hierarchy else if ( mappingType.getIdentifierMapper() != null ) { - Set> attributes = buildIdClassAttributes( - jpaMappingType, - mappingType.getIdentifierMapper().getProperties() - ); - //noinspection unchecked - ( ( AttributeContainer) jpaMappingType ).getInFlightAccess().applyIdClassAttributes( attributes ); + final Set> attributes = + buildIdClassAttributes( + jpaMappingType, + mappingType.getIdentifierMapper().getProperties() + ); + attributeContainer.getInFlightAccess().applyIdClassAttributes( attributes ); } } @@ -810,15 +824,28 @@ private static void injectField( // + "; expected type : " + attribute.getClass().getName() // + "; encountered type : " + field.getType().getName() // ); - LOG.illegalArgumentOnStaticMetamodelFieldInjection( - metamodelClass.getName(), - name, - model.getClass().getName(), - field.getType().getName() - ); + // Avoid logging an error for Enver's default revision classes that are both entities and mapped-supers. + // This is a workaround for https://hibernate.atlassian.net/browse/HHH-17612 + if ( !isDefaultEnversRevisionType( metamodelClass ) ) { + LOG.illegalArgumentOnStaticMetamodelFieldInjection( + metamodelClass.getName(), + name, + model.getClass().getName(), + field.getType().getName() + ); + } } } + private static boolean isDefaultEnversRevisionType(Class metamodelClass) { + return Set.of( + "org.hibernate.envers.DefaultRevisionEntity_", + "org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity_", + "org.hibernate.envers.enhanced.SequenceIdRevisionEntity_", + "org.hibernate.envers.enhanced.SequenceIdTrackingModifiedEntitiesRevisionEntity_" + ).contains( metamodelClass.getName() ); + } + public MappedSuperclassDomainType locateMappedSuperclassType(MappedSuperclass mappedSuperclass) { return mappedSuperclassByMappedSuperclassMapping.get( mappedSuperclass ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java index c360a0563a00..58a1b7d79d63 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java @@ -918,6 +918,10 @@ public Cardinality getCardinality() { return cardinality; } + public boolean hasJoinTable() { + return hasJoinTable; + } + @Override public EntityMappingType getMappedType() { return getEntityMappingType(); @@ -1557,7 +1561,19 @@ public static class EntityB { having the left join we don't want to add an extra implicit join that will be translated into an SQL inner join (see HHH-15342) */ - if ( fetchTiming == FetchTiming.IMMEDIATE && selected ) { + + final ForeignKeyDescriptor.Nature resolvingKeySideOfForeignKey = creationState.getCurrentlyResolvingForeignKeyPart(); + final ForeignKeyDescriptor.Nature side; + if ( resolvingKeySideOfForeignKey == ForeignKeyDescriptor.Nature.KEY && this.sideNature == ForeignKeyDescriptor.Nature.TARGET ) { + // If we are currently resolving the key part of a foreign key we do not want to add joins. + // So if the lhs of this association is the target of the FK, we have to use the KEY part to avoid a join + side = ForeignKeyDescriptor.Nature.KEY; + } + else { + side = this.sideNature; + } + + if ( ( fetchTiming == FetchTiming.IMMEDIATE && selected ) || needsJoinFetch( side ) ) { final TableGroup tableGroup = determineTableGroupForFetch( fetchablePath, fetchParent, @@ -1635,16 +1651,6 @@ else if ( hasNotFoundAction() */ - final ForeignKeyDescriptor.Nature resolvingKeySideOfForeignKey = creationState.getCurrentlyResolvingForeignKeyPart(); - final ForeignKeyDescriptor.Nature side; - if ( resolvingKeySideOfForeignKey == ForeignKeyDescriptor.Nature.KEY && this.sideNature == ForeignKeyDescriptor.Nature.TARGET ) { - // If we are currently resolving the key part of a foreign key we do not want to add joins. - // So if the lhs of this association is the target of the FK, we have to use the KEY part to avoid a join - side = ForeignKeyDescriptor.Nature.KEY; - } - else { - side = this.sideNature; - } final DomainResult keyResult; if ( side == ForeignKeyDescriptor.Nature.KEY ) { final TableGroup tableGroup = sideNature == ForeignKeyDescriptor.Nature.KEY @@ -1696,6 +1702,22 @@ else if ( hasNotFoundAction() ); } + private boolean needsJoinFetch(ForeignKeyDescriptor.Nature side) { + if ( side == ForeignKeyDescriptor.Nature.TARGET ) { + // The target model part doesn't correspond to the identifier of the target entity mapping + // so we must eagerly fetch with a join (subselect would still cause problems). + final EntityIdentifierMapping identifier = entityMappingType.getIdentifierMapping(); + final ValuedModelPart targetPart = foreignKeyDescriptor.getTargetPart(); + if ( identifier != targetPart ) { + // If the identifier and the target part of the same class, we can preserve laziness as deferred loading will still work + return identifier.getExpressibleJavaType().getJavaTypeClass() != targetPart.getExpressibleJavaType() + .getJavaTypeClass(); + } + } + + return false; + } + private boolean isAffectedByEnabledFilters(DomainResultCreationState creationState) { final LoadQueryInfluencers loadQueryInfluencers = creationState.getSqlAstCreationState() .getLoadQueryInfluencers(); 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 4e9a79f9ef2d..abef1ac61a24 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 @@ -747,6 +747,15 @@ public AbstractEntityPersister( final ArrayList definedBySubclass = new ArrayList<>(); final ArrayList propNullables = new ArrayList<>(); + if ( persistentClass.hasSubclasses() ) { + for ( Selectable selectable : persistentClass.getIdentifier().getSelectables() ) { + if ( !selectable.isFormula() ) { + // Identifier columns are always shared between subclasses + sharedColumnNames.add( ( (Column) selectable ).getQuotedName( dialect ) ); + } + } + } + for ( Property prop : persistentClass.getSubclassPropertyClosure() ) { names.add( prop.getName() ); types.add( prop.getType() ); diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedCallableQueryMementoImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedCallableQueryMementoImpl.java index d79f9d4aba49..e7ffc89325dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedCallableQueryMementoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedCallableQueryMementoImpl.java @@ -64,6 +64,7 @@ public NamedCallableQueryMementoImpl( Map hints) { super( name, + Object.class, cacheable, cacheRegion, cacheMode, diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/NamedCriteriaQueryMementoImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/NamedCriteriaQueryMementoImpl.java index 1c0ef4f2f623..049c4dab3a1d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/NamedCriteriaQueryMementoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/NamedCriteriaQueryMementoImpl.java @@ -22,6 +22,8 @@ import org.hibernate.query.sqm.spi.NamedSqmQueryMemento; import org.hibernate.query.sqm.tree.SqmStatement; +import org.checkerframework.checker.nullness.qual.Nullable; + public class NamedCriteriaQueryMementoImpl extends AbstractNamedQueryMemento implements NamedSqmQueryMemento, Serializable { private final SqmStatement sqmStatement; @@ -33,6 +35,7 @@ public class NamedCriteriaQueryMementoImpl extends AbstractNamedQueryMemento imp public NamedCriteriaQueryMementoImpl( String name, + @Nullable Class resultType, SqmStatement sqmStatement, Integer firstResult, Integer maxResults, @@ -47,7 +50,7 @@ public NamedCriteriaQueryMementoImpl( String comment, Map parameterTypes, Map hints) { - super( name, cacheable, cacheRegion, cacheMode, flushMode, readOnly, timeout, fetchSize, comment, hints ); + super( name, resultType, cacheable, cacheRegion, cacheMode, flushMode, readOnly, timeout, fetchSize, comment, hints ); this.sqmStatement = sqmStatement; this.firstResult = firstResult; this.maxResults = maxResults; @@ -114,6 +117,7 @@ public Map getParameterTypes() { public NamedSqmQueryMemento makeCopy(String name) { return new NamedCriteriaQueryMementoImpl( name, + getResultType(), sqmStatement, firstResult, maxResults, diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/NamedHqlQueryMementoImpl.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/NamedHqlQueryMementoImpl.java index 2cc32c0eb3ff..272c4a7cd6bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/NamedHqlQueryMementoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/NamedHqlQueryMementoImpl.java @@ -22,6 +22,8 @@ import org.hibernate.query.sqm.spi.NamedSqmQueryMemento; import org.hibernate.query.sqm.tree.SqmStatement; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * Definition of a named query, defined in the mapping metadata. * Additionally, as of JPA 2.1, named query definition can also come @@ -41,6 +43,7 @@ public class NamedHqlQueryMementoImpl extends AbstractNamedQueryMemento implemen public NamedHqlQueryMementoImpl( String name, + @Nullable Class resultType, String hqlString, Integer firstResult, Integer maxResults, @@ -57,6 +60,7 @@ public NamedHqlQueryMementoImpl( Map hints) { super( name, + resultType, cacheable, cacheRegion, cacheMode, @@ -103,6 +107,7 @@ public Map getParameterTypes() { public NamedSqmQueryMemento makeCopy(String name) { return new NamedHqlQueryMementoImpl( name, + getResultType(), hqlString, firstResult, maxResults, diff --git a/hibernate-core/src/main/java/org/hibernate/query/named/AbstractNamedQueryMemento.java b/hibernate-core/src/main/java/org/hibernate/query/named/AbstractNamedQueryMemento.java index 960cdeb2d68f..14e1bc408fa2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/named/AbstractNamedQueryMemento.java +++ b/hibernate-core/src/main/java/org/hibernate/query/named/AbstractNamedQueryMemento.java @@ -14,12 +14,15 @@ import org.hibernate.CacheMode; import org.hibernate.FlushMode; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * @author Steve Ebersole * @author Gavin King */ public abstract class AbstractNamedQueryMemento implements NamedQueryMemento { private final String name; + private final @Nullable Class resultType; private final Boolean cacheable; private final String cacheRegion; @@ -37,6 +40,7 @@ public abstract class AbstractNamedQueryMemento implements NamedQueryMemento { protected AbstractNamedQueryMemento( String name, + @Nullable Class resultType, Boolean cacheable, String cacheRegion, CacheMode cacheMode, @@ -47,6 +51,7 @@ protected AbstractNamedQueryMemento( String comment, Map hints) { this.name = name; + this.resultType = resultType; this.cacheable = cacheable; this.cacheRegion = cacheRegion; this.cacheMode = cacheMode; @@ -63,6 +68,10 @@ public String getRegistrationName() { return name; } + public @Nullable Class getResultType() { + return resultType; + } + @Override public Boolean getCacheable() { return cacheable; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NamedNativeQueryMementoImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NamedNativeQueryMementoImpl.java index 8ba29214b482..5d9f383aae08 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NamedNativeQueryMementoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NamedNativeQueryMementoImpl.java @@ -38,6 +38,7 @@ public class NamedNativeQueryMementoImpl extends AbstractNamedQueryMemento imple public NamedNativeQueryMementoImpl( String name, + Class resultClass, String sqlString, String originalSqlString, String resultSetMappingName, @@ -56,6 +57,7 @@ public NamedNativeQueryMementoImpl( Map hints) { super( name, + resultClass, cacheable, cacheRegion, cacheMode, @@ -123,6 +125,7 @@ public Integer getMaxResults() { public NamedNativeQueryMemento makeCopy(String name) { return new NamedNativeQueryMementoImpl( name, + getResultType(), sqlString, originalSqlString, resultSetMappingName, @@ -149,7 +152,8 @@ public void validate(QueryEngine queryEngine) { @Override public NativeQueryImplementor toQuery(SharedSessionContractImplementor session) { - return new NativeQueryImpl<>( this, session ); + //noinspection unchecked + return new NativeQueryImpl<>( this, (Class) getResultType(), session ); } @Override 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 6179773fc609..c445e4a421f3 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 @@ -20,8 +20,12 @@ import java.util.function.Consumer; import java.util.function.Supplier; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.CacheMode; import org.hibernate.FlushMode; +import org.hibernate.jpa.spi.NativeQueryConstructorTransformer; +import org.hibernate.jpa.spi.NativeQueryListTransformer; +import org.hibernate.jpa.spi.NativeQueryMapTransformer; import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.LockOptions; @@ -55,7 +59,6 @@ import org.hibernate.query.internal.DelegatingDomainQueryExecutionContext; import org.hibernate.query.internal.ParameterMetadataImpl; import org.hibernate.query.internal.QueryOptionsImpl; -import org.hibernate.query.internal.QueryParameterBindingsImpl; import org.hibernate.query.internal.ResultSetMappingResolutionContext; import org.hibernate.query.named.NamedResultSetMappingMemento; import org.hibernate.query.results.Builders; @@ -92,7 +95,6 @@ import org.hibernate.query.sql.spi.SelectInterpretationsKey; import org.hibernate.sql.exec.internal.CallbackImpl; import org.hibernate.sql.exec.spi.Callback; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer; import org.hibernate.sql.results.spi.SingleResultConsumer; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.BasicType; @@ -110,8 +112,13 @@ import jakarta.persistence.TemporalType; import jakarta.persistence.Tuple; import jakarta.persistence.metamodel.SingularAttribute; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.spi.UnknownBasicJavaType; +import static org.hibernate.internal.util.ReflectHelper.isClass; +import static org.hibernate.internal.util.StringHelper.unqualify; import static org.hibernate.jpa.HibernateHints.HINT_NATIVE_LOCK_MODE; +import static org.hibernate.query.sqm.internal.SqmUtil.isResultTypeAlwaysAllowed; import static org.hibernate.query.results.Builders.resultClassBuilder; /** @@ -126,6 +133,7 @@ public class NativeQueryImpl private final List parameterOccurrences; private final QueryParameterBindings parameterBindings; + private final Class resultType; private final ResultSetMapping resultSetMapping; private final boolean resultMappingSuppliedToCtor; @@ -177,6 +185,7 @@ else if ( memento.getResultMappingClass() != null ) { return false; }, + null, session ); } @@ -206,39 +215,16 @@ public NativeQueryImpl( } } - if ( memento.getResultMappingClass() != null ) { - resultSetMapping.addResultBuilder( resultClassBuilder( - memento.getResultMappingClass(), - context - ) ); + if ( memento.getResultType() != null ) { + resultSetMapping.addResultBuilder( resultClassBuilder( memento.getResultType(), context ) ); return true; } return false; }, + resultJavaType, session ); - - if ( resultJavaType == Tuple.class ) { - setTupleTransformer( new NativeQueryTupleTransformer() ); - } - else if ( resultJavaType != null && !resultJavaType.isArray() ) { - 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 && !resultJavaType.isAssignableFrom( actualResultJavaType ) ) { - throw buildIncompatibleException( resultJavaType, actualResultJavaType ); - } - break; - } - default: { - throw new IllegalArgumentException( "Cannot create TypedQuery for query with more than one return" ); - } - } - } } /** @@ -260,6 +246,7 @@ public NativeQueryImpl( mappingMemento.resolve( resultSetMapping, querySpaceConsumer, context ); return true; }, + null, session ); @@ -270,6 +257,15 @@ public NativeQueryImpl( Supplier resultSetMappingCreator, ResultSetMappingHandler resultSetMappingHandler, SharedSessionContractImplementor session) { + this( memento, resultSetMappingCreator, resultSetMappingHandler, null, session ); + } + + public NativeQueryImpl( + NamedNativeQueryMemento memento, + Supplier resultSetMappingCreator, + ResultSetMappingHandler resultSetMappingHandler, + @Nullable Class resultType, + SharedSessionContractImplementor session) { super( session ); this.originalSqlString = memento.getOriginalSqlString(); @@ -283,6 +279,7 @@ public NativeQueryImpl( this.parameterMetadata = parameterInterpretation.toParameterMetadata( session ); this.parameterOccurrences = parameterInterpretation.getOrderedParameterOccurrences(); this.parameterBindings = parameterMetadata.createBindings( session.getFactory() ); + this.resultType = resultType; this.querySpaces = new HashSet<>(); this.resultSetMapping = resultSetMappingCreator.get(); @@ -296,6 +293,27 @@ public NativeQueryImpl( this.resultMappingSuppliedToCtor = appliedAnyResults; + if ( resultType != null ) { + if ( !isResultTypeAlwaysAllowed( 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" ); + } + } + else { + setTupleTransformerForResultType( resultType ); + } + } applyOptions( memento ); } @@ -312,6 +330,7 @@ public NativeQueryImpl( this.parameterMetadata = parameterInterpretation.toParameterMetadata( session ); this.parameterOccurrences = parameterInterpretation.getOrderedParameterOccurrences(); this.parameterBindings = parameterMetadata.createBindings( session.getFactory() ); + this.resultType = null; this.querySpaces = new HashSet<>(); this.resultSetMapping = buildResultSetMapping( resultSetMappingMemento.getName(), false, session ); @@ -325,6 +344,10 @@ public NativeQueryImpl( } public NativeQueryImpl(String sqlString, SharedSessionContractImplementor session) { + this( sqlString, null, session ); + } + + public NativeQueryImpl(String sqlString, @Nullable Class resultType, SharedSessionContractImplementor session) { super( session ); this.querySpaces = new HashSet<>(); @@ -335,11 +358,47 @@ public NativeQueryImpl(String sqlString, SharedSessionContractImplementor sessio this.parameterMetadata = parameterInterpretation.toParameterMetadata( session ); this.parameterOccurrences = parameterInterpretation.getOrderedParameterOccurrences(); this.parameterBindings = parameterMetadata.createBindings( session.getFactory() ); + this.resultType = resultType; + if ( resultType != null ) { + setTupleTransformerForResultType( resultType ); + } this.resultSetMapping = ResultSetMapping.resolveResultSetMapping( sqlString, true, session.getFactory() ); this.resultMappingSuppliedToCtor = false; } + protected void setTupleTransformerForResultType(Class resultClass) { + final TupleTransformer tupleTransformer = determineTupleTransformerForResultType( resultClass ); + if ( tupleTransformer != null ) { + setTupleTransformer( tupleTransformer ); + } + } + + protected @Nullable TupleTransformer determineTupleTransformerForResultType(Class resultClass) { + if ( Tuple.class.equals( resultClass ) ) { + return NativeQueryTupleTransformer.INSTANCE; + } + else if ( Map.class.equals( resultClass ) ) { + return NativeQueryMapTransformer.INSTANCE; + } + else if ( List.class.equals( resultClass ) ) { + return NativeQueryListTransformer.INSTANCE; + } + else if ( resultClass != Object.class && resultClass != Object[].class ) { + if ( isClass( resultClass ) && !hasJavaTypeDescriptor( resultClass ) ) { + // not a basic type + return new NativeQueryConstructorTransformer<>( resultClass ); + } + } + return null; + } + + private boolean hasJavaTypeDescriptor(Class resultClass) { + final JavaType descriptor = getSessionFactory().getTypeConfiguration().getJavaTypeRegistry() + .findDescriptor( resultClass ); + return descriptor != null && descriptor.getClass() != UnknownBasicJavaType.class; + } + @FunctionalInterface private interface ResultSetMappingHandler { boolean resolveResultSetMapping( @@ -456,10 +515,16 @@ public QueryParameterBindings getParameterBindings() { return getQueryParameterBindings(); } + @Override + public Class getResultType() { + return resultType; + } + @Override public NamedNativeQueryMemento toMemento(String name) { return new NamedNativeQueryMementoImpl( name, + resultType != null ? resultType : extractResultClass( resultSetMapping ), sqlString, originalSqlString, resultSetMapping.getMappingIdentifier(), @@ -479,14 +544,14 @@ public NamedNativeQueryMemento toMemento(String name) { ); } - private Class extractResultClass(ResultSetMapping resultSetMapping) { + private Class extractResultClass(ResultSetMapping resultSetMapping) { final List resultBuilders = resultSetMapping.getResultBuilders(); if ( resultBuilders.size() == 1 ) { final ResultBuilder resultBuilder = resultBuilders.get( 0 ); if ( resultBuilder instanceof ImplicitResultClassBuilder || resultBuilder instanceof ImplicitModelPartResultBuilderEntity || resultBuilder instanceof DynamicResultBuilderEntityCalculated ) { - return resultBuilder.getJavaType(); + return (Class) resultBuilder.getJavaType(); } } return null; @@ -645,19 +710,32 @@ public KeyedResultList getKeyedResultList(KeyedPage page) { } protected SelectQueryPlan resolveSelectQueryPlan() { - if ( isCacheableQuery() ) { - final QueryInterpretationCache.Key cacheKey = generateSelectInterpretationsKey( resultSetMapping ); - return getSession().getFactory().getQueryEngine().getInterpretationCache() - .resolveSelectQueryPlan( cacheKey, () -> createQueryPlan( resultSetMapping ) ); + final ResultSetMapping mapping; + if ( resultType != null && resultSetMapping.isDynamic() && resultSetMapping.getNumberOfResultBuilders() == 0 ) { + mapping = ResultSetMapping.resolveResultSetMapping( originalSqlString, true, getSessionFactory() ); + + if ( getSessionFactory().getMappingMetamodel().isEntityClass( resultType ) ) { + mapping.addResultBuilder( + Builders.entityCalculated( unqualify( resultType.getName() ), resultType.getName(), + LockMode.READ, getSessionFactory() ) ); + } + else if ( !isResultTypeAlwaysAllowed( resultType ) + && (!isClass( resultType ) || hasJavaTypeDescriptor( resultType )) ) { + mapping.addResultBuilder( Builders.resultClassBuilder( resultType, getSessionFactory() ) ); + } } else { - return createQueryPlan( resultSetMapping ); + mapping = resultSetMapping; } + return isCacheableQuery() + ? getSession().getFactory().getQueryEngine().getInterpretationCache().resolveSelectQueryPlan( selectInterpretationsKey( mapping ), () -> createQueryPlan( mapping ) ) + : createQueryPlan( mapping ); } private NativeSelectQueryPlan createQueryPlan(ResultSetMapping resultSetMapping) { - final String sqlString = expandParameterLists(); final NativeSelectQueryDefinition queryDefinition = new NativeSelectQueryDefinition<>() { + final String sqlString = expandParameterLists(); + @Override public String getSqlString() { return sqlString; @@ -864,7 +942,7 @@ public static int determineBindValueMaxCount(boolean paddingEnabled, int inExprL return bindValueMaxCount; } - private SelectInterpretationsKey generateSelectInterpretationsKey(JdbcValuesMappingProducer resultSetMapping) { + private SelectInterpretationsKey selectInterpretationsKey(ResultSetMapping resultSetMapping) { return new SelectInterpretationsKey( getQueryString(), resultSetMapping, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NamedNativeQueryMemento.java b/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NamedNativeQueryMemento.java index 54aad16b4cb4..8d4659b15f2f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NamedNativeQueryMemento.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NamedNativeQueryMemento.java @@ -9,6 +9,7 @@ import java.util.Set; import jakarta.persistence.SqlResultSetMapping; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.boot.query.NamedNativeQueryDefinition; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; @@ -50,6 +51,8 @@ default String getOriginalSqlString(){ */ Class getResultMappingClass(); + @Nullable Class getResultType(); + Integer getFirstResult(); Integer getMaxResults(); @@ -135,6 +138,7 @@ public void setResultSetMappingClassName(String resultSetMappingClassName) { public NamedNativeQueryMemento build(SessionFactoryImplementor sessionFactory) { return new NamedNativeQueryMementoImpl( name, + null, queryString, queryString, resultSetMappingName, 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 785f0ccaf0ee..91fa946753ee 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 @@ -904,6 +904,7 @@ public NamedQueryMemento toMemento(String name) { } return new NamedCriteriaQueryMementoImpl( name, + getResultType(), sqmStatement, getQueryOptions().getLimit().getFirstRow(), getQueryOptions().getLimit().getMaxRows(), @@ -923,6 +924,7 @@ public NamedQueryMemento toMemento(String name) { return new NamedHqlQueryMementoImpl( name, + getResultType(), getQueryString(), getQueryOptions().getLimit().getFirstRow(), getQueryOptions().getLimit().getMaxRows(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java index 69922cc8889e..11955083c778 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java @@ -38,6 +38,7 @@ import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.metamodel.model.domain.BasicDomainType; import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.EntityDomainType; @@ -286,7 +287,13 @@ public static boolean isFkOptimizationAllowed(SqmPath sqmPath) { * or one that has an explicit on clause predicate. */ public static boolean isFkOptimizationAllowed(SqmPath sqmPath, EntityAssociationMapping associationMapping) { - if ( associationMapping.isFkOptimizationAllowed() && sqmPath instanceof SqmJoin ) { + // By default, never allow the FK optimization if the path is a join, unless the association has a join table + // Hibernate ORM has no way for users to refer to collection/join table rows, + // so referring the columns of these rows by default when requesting FK column attributes is sensible. + // Users that need to refer to the actual target table columns will have to add an explicit entity join. + if ( associationMapping.isFkOptimizationAllowed() + && sqmPath instanceof SqmJoin + && hasJoinTable( associationMapping ) ) { final SqmJoin sqmJoin = (SqmJoin) sqmPath; switch ( sqmJoin.getSqmJoinType() ) { case LEFT: @@ -304,6 +311,16 @@ public static boolean isFkOptimizationAllowed(SqmPath sqmPath, EntityAssociat return false; } + private static boolean hasJoinTable(EntityAssociationMapping associationMapping) { + if ( associationMapping instanceof CollectionPart ) { + return !( (CollectionPart) associationMapping ).getCollectionAttribute().getCollectionDescriptor().isOneToMany(); + } + else if ( associationMapping instanceof ToOneAttributeMapping ) { + return ( (ToOneAttributeMapping) associationMapping ).hasJoinTable(); + } + return false; + } + private static boolean isFiltered(EntityAssociationMapping associationMapping) { final EntityMappingType entityMappingType = associationMapping.getAssociatedEntityMappingType(); return !associationMapping.isFkOptimizationAllowed() diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java index d44a614cf9a1..1fab71f008b2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java @@ -443,8 +443,8 @@ public static void assertAssignable( } public static void assertOperable(SqmExpression left, SqmExpression right, BinaryArithmeticOperator op) { - final SqmExpressible leftNodeType = left.getNodeType(); - final SqmExpressible rightNodeType = right.getNodeType(); + final SqmExpressible leftNodeType = left.getExpressible(); + final SqmExpressible rightNodeType = right.getExpressible(); if ( leftNodeType != null && rightNodeType != null ) { final Class leftJavaType = leftNodeType.getRelationalJavaType().getJavaTypeClass(); final Class rightJavaType = rightNodeType.getRelationalJavaType().getJavaTypeClass(); @@ -579,7 +579,7 @@ public static void assertDuration(SqmExpression expression) { } public static void assertNumeric(SqmExpression expression, UnaryArithmeticOperator op) { - final SqmExpressible nodeType = expression.getNodeType(); + final SqmExpressible nodeType = expression.getExpressible(); if ( nodeType != null ) { final DomainType domainType = nodeType.getSqmType(); if ( !( domainType instanceof JdbcMapping ) || !( (JdbcMapping) domainType ).getJdbcType().isNumber() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmBinaryArithmetic.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmBinaryArithmetic.java index dc80903ec5f2..aca3096867f2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmBinaryArithmetic.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmBinaryArithmetic.java @@ -14,8 +14,6 @@ import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.select.SqmSelectableNode; -import org.checkerframework.checker.nullness.qual.Nullable; - import static org.hibernate.query.sqm.BinaryArithmeticOperator.ADD; import static org.hibernate.query.sqm.BinaryArithmeticOperator.SUBTRACT; import static org.hibernate.type.spi.TypeConfiguration.isDuration; @@ -37,8 +35,8 @@ public SqmBinaryArithmetic( //noinspection unchecked super( (SqmExpressible) domainModel.getTypeConfiguration().resolveArithmeticType( - lhsOperand.getNodeType(), - rhsOperand.getNodeType(), + lhsOperand.getExpressible(), + rhsOperand.getExpressible(), operator ), nodeBuilder @@ -52,8 +50,8 @@ public SqmBinaryArithmetic( ( operator == ADD || operator == SUBTRACT ) ) { return; } - this.lhsOperand.applyInferableType( rhsOperand.getNodeType() ); - this.rhsOperand.applyInferableType( lhsOperand.getNodeType() ); + this.lhsOperand.applyInferableType( rhsOperand.getExpressible() ); + this.rhsOperand.applyInferableType( lhsOperand.getExpressible() ); } public SqmBinaryArithmetic( diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmUnaryOperation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmUnaryOperation.java index ba626e6d0a7f..6d4133262011 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmUnaryOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmUnaryOperation.java @@ -25,7 +25,7 @@ public SqmUnaryOperation(UnaryArithmeticOperator operation, SqmExpression ope operation, operand, operand.nodeBuilder().getTypeConfiguration().getBasicTypeForJavaType( - operand.getNodeType().getRelationalJavaType().getJavaType() + operand.getExpressible().getRelationalJavaType().getJavaType() ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmOrderByClause.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmOrderByClause.java index adb5301a63d5..a8b4bc88f798 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmOrderByClause.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmOrderByClause.java @@ -8,13 +8,15 @@ import java.io.Serializable; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef; import org.hibernate.query.sqm.tree.expression.SqmExpression; +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; + /** * @author Steve Ebersole */ @@ -71,12 +73,7 @@ public SqmOrderByClause addSortSpecification(SqmExpression expression) { } public List getSortSpecifications() { - if ( sortSpecifications == null ) { - return Collections.emptyList(); - } - else { - return Collections.unmodifiableList( sortSpecifications ); - } + return sortSpecifications == null ? emptyList() : unmodifiableList( sortSpecifications ); } public void setSortSpecifications(List sortSpecifications) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryPart.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryPart.java index 230ca8cf1eb6..22f494796130 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryPart.java @@ -6,7 +6,6 @@ */ package org.hibernate.query.sqm.tree.select; -import java.util.Collections; import java.util.List; import org.hibernate.query.sqm.FetchClauseType; @@ -18,6 +17,8 @@ import org.hibernate.query.sqm.tree.SqmVisitableNode; import org.hibernate.query.sqm.tree.expression.SqmExpression; +import static java.util.Collections.emptyList; + /** * Defines the ordering and fetch/offset part of a query which is shared with query groups. * @@ -129,11 +130,7 @@ public FetchClauseType getFetchClauseType() { @Override public List getSortSpecifications() { - if ( getOrderByClause() == null ) { - return Collections.emptyList(); - } - - return getOrderByClause().getSortSpecifications(); + return getOrderByClause() == null ? emptyList() : getOrderByClause().getSortSpecifications(); } @Override 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 cb347a41ae91..6d23adb216ad 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 @@ -492,6 +492,9 @@ public SqmSelectStatement createCountQuery() { final SqmSelectStatement query = nodeBuilder().createQuery( Long.class ); query.from( subquery ); query.select( nodeBuilder().count() ); + if ( subquery.getFetch() == null && subquery.getOffset() == null ) { + subquery.getQueryPart().setOrderByClause( null ); + } return query; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java index 61721bb9566c..050b292c56d7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java @@ -183,7 +183,9 @@ public SqmUpdateStatement set(SingularAttribute attribute, @Override public SqmUpdateStatement set(Path attribute, X value) { - applyAssignment( (SqmPath) attribute, (SqmExpression) nodeBuilder().value( value ) ); + final SqmCriteriaNodeBuilder nodeBuilder = (SqmCriteriaNodeBuilder) nodeBuilder(); + final SqmPath sqmAttribute = (SqmPath) attribute; + applyAssignment( sqmAttribute, nodeBuilder.value( value, sqmAttribute ) ); return this; } @@ -195,15 +197,15 @@ public SqmUpdateStatement set(Path attribute, Expression @Override @SuppressWarnings({"rawtypes", "unchecked"}) public SqmUpdateStatement set(String attributeName, Object value) { - final SqmPath sqmPath = getTarget().get(attributeName); + final SqmPath sqmPath = getTarget().get( attributeName ); final SqmExpression expression; if ( value instanceof SqmExpression ) { expression = (SqmExpression) value; } else { - expression = (SqmExpression) nodeBuilder().value( value ); + final SqmCriteriaNodeBuilder nodeBuilder = (SqmCriteriaNodeBuilder) nodeBuilder(); + expression = nodeBuilder.value( value, sqmPath ); } - assertAssignable( null, sqmPath, expression, nodeBuilder().getSessionFactory() ); applyAssignment( sqmPath, expression ); return this; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java index de61d207e2cd..4e97900ec79c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java @@ -731,8 +731,13 @@ protected final Object resolveIdentifier(Object id, SharedSessionContractImpleme getAssociatedEntityPersister( session.getFactory() ) .isInstrumented(); - final Object proxyOrEntity = - session.internalLoad( getAssociatedEntityName(), id, isEager( overridingEager ), isNullable() ); + final boolean isEager = isEager( overridingEager ); + // If the association is lazy, retrieve the concrete type if required + final String entityName = isEager ? getAssociatedEntityName() + : getAssociatedEntityPersister( session.getFactory() ).resolveConcreteProxyTypeForId( id, session ) + .getEntityName(); + + final Object proxyOrEntity = session.internalLoad( entityName, id, isEager, isNullable() ); final LazyInitializer lazyInitializer = extractLazyInitializer( proxyOrEntity ); if ( lazyInitializer != null ) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/entity/Forest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/entity/Forest.java index 93aa841e523b..2d9814b4d373 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/entity/Forest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/entity/Forest.java @@ -27,6 +27,7 @@ import org.hibernate.annotations.SelectBeforeUpdate; import org.hibernate.annotations.Where; +import jakarta.persistence.Column; import jakarta.persistence.Convert; import jakarta.persistence.ElementCollection; import jakarta.persistence.Entity; @@ -63,6 +64,7 @@ public class Forest { @OptimisticLock(excluded=true) @JdbcTypeCode( Types.LONGVARCHAR ) + @Column(length = 10000) public String getLongDescription() { return longDescription; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/uniqueconstraint/UniqueConstraintTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/uniqueconstraint/UniqueConstraintTest.java index 5b2c2f935f2c..8a14234ba0a0 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/uniqueconstraint/UniqueConstraintTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/uniqueconstraint/UniqueConstraintTest.java @@ -7,6 +7,7 @@ package org.hibernate.orm.test.annotations.uniqueconstraint; import org.hibernate.JDBCException; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.SybaseDialect; import org.hibernate.testing.orm.junit.DomainModel; @@ -27,6 +28,9 @@ */ @DomainModel( annotatedClasses = { Room.class, Building.class, House.class } ) @SessionFactory +@SkipForDialect( dialectClass = InformixDialect.class, + matchSubTypes = true, + reason = "Informix does not properly support unique constraints on nullable columns" ) @SkipForDialect( dialectClass = SybaseDialect.class, matchSubTypes = true, reason = "Sybase does not properly support unique constraints on nullable columns" ) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/access/UnsupportedEnhancementStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/access/UnsupportedEnhancementStrategyTest.java index 902b19c95994..0e60fe6647b9 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/access/UnsupportedEnhancementStrategyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/access/UnsupportedEnhancementStrategyTest.java @@ -9,7 +9,10 @@ import jakarta.persistence.Basic; import jakarta.persistence.Entity; import jakarta.persistence.Id; -import jakarta.persistence.Table; +import jakarta.persistence.PostLoad; +import jakarta.persistence.PrePersist; +import jakarta.persistence.Transient; + import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl; import org.hibernate.bytecode.enhance.spi.EnhancementContext; import org.hibernate.bytecode.enhance.spi.EnhancementException; @@ -18,11 +21,13 @@ import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; import org.hibernate.bytecode.spi.ByteCodeHelper; import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; +import org.hibernate.testing.orm.junit.Jira; import org.hibernate.testing.orm.junit.JiraKey; import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.InputStream; +import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -39,28 +44,31 @@ public UnsupportedEnhancementStrategy getUnsupportedEnhancementStrategy() { return UnsupportedEnhancementStrategy.SKIP; } }; - byte[] originalBytes = getAsBytes( SomeEntity.class ); - byte[] enhancedBytes = doEnhance( SomeEntity.class, originalBytes, context ); + byte[] enhancedBytes = doEnhance( SomeEntity.class, context ); assertThat( enhancedBytes ).isNull(); // null means "not enhanced" } @Test public void fail() throws IOException { - var context = new EnhancerTestContext() { - @Override - public UnsupportedEnhancementStrategy getUnsupportedEnhancementStrategy() { - return UnsupportedEnhancementStrategy.FAIL; - } - }; - byte[] originalBytes = getAsBytes( SomeEntity.class ); - assertThatThrownBy( () -> doEnhance( SomeEntity.class, originalBytes, context ) ) - .isInstanceOf( EnhancementException.class ) + var context = new UnsupportedEnhancerContext(); + assertThatThrownBy( () -> doEnhance( SomeEntity.class, context ) ).isInstanceOf( EnhancementException.class ) .hasMessageContainingAll( String.format( "Enhancement of [%s] failed because no field named [%s] could be found for property accessor method [%s].", - SomeEntity.class.getName(), "propertyMethod", "getPropertyMethod" ), - "To fix this, make sure all property accessor methods have a matching field." + SomeEntity.class.getName(), + "propertyMethod", + "getPropertyMethod" + ), "To fix this, make sure all property accessor methods have a matching field." ); + assertThatThrownBy( () -> doEnhance( SomeOtherEntity.class, context ) ).isInstanceOf( EnhancementException.class ) + .hasMessageContainingAll( + String.format( + "Enhancement of [%s] failed because no field named [%s] could be found for property accessor method [%s].", + SomeOtherEntity.class.getName(), + "propertyMethod", + "setPropertyMethod" + ), "To fix this, make sure all property accessor methods have a matching field." + ); } @Test @@ -73,26 +81,49 @@ public UnsupportedEnhancementStrategy getUnsupportedEnhancementStrategy() { return UnsupportedEnhancementStrategy.LEGACY; } }; - byte[] originalBytes = getAsBytes( SomeEntity.class ); - byte[] enhancedBytes = doEnhance( SomeEntity.class, originalBytes, context ); + byte[] enhancedBytes = doEnhance( SomeEntity.class, context ); assertThat( enhancedBytes ).isNotNull(); // non-null means enhancement _was_ performed } - private byte[] doEnhance(Class someEntityClass, byte[] originalBytes, EnhancementContext context) { + @Test + @Jira( "https://hibernate.atlassian.net/browse/HHH-18903" ) + @Jira( "https://hibernate.atlassian.net/browse/HHH-18904" ) + public void testEntityListeners() throws IOException { + // non-null means check passed and enhancement _was_ performed + assertThat( doEnhance( EventListenersEntity.class, new UnsupportedEnhancerContext() ) ).isNotNull(); + } + + @Test + @Jira( "https://hibernate.atlassian.net/browse/HHH-18903" ) + @Jira( "https://hibernate.atlassian.net/browse/HHH-18904" ) + public void testAccessTypeFieldEntity() throws IOException { + var context = new UnsupportedEnhancerContext(); + // non-null means check passed and enhancement _was_ performed + assertThat( doEnhance( ExplicitAccessTypeFieldEntity.class, context ) ).isNotNull(); + assertThat( doEnhance( ImplicitAccessTypeFieldEntity.class, context ) ).isNotNull(); + } + + private static byte[] doEnhance(Class entityClass, EnhancementContext context) throws IOException { final ByteBuddyState byteBuddyState = new ByteBuddyState(); final Enhancer enhancer = new EnhancerImpl( context, byteBuddyState ); - return enhancer.enhance( someEntityClass.getName(), originalBytes ); + return enhancer.enhance( entityClass.getName(), getAsBytes( entityClass ) ); } - private byte[] getAsBytes(Class clazz) throws IOException { + private static byte[] getAsBytes(Class clazz) throws IOException { final String classFile = clazz.getName().replace( '.', '/' ) + ".class"; try (InputStream classFileStream = clazz.getClassLoader().getResourceAsStream( classFile )) { return ByteCodeHelper.readByteCode( classFileStream ); } } - @Entity - @Table(name = "SOME_ENTITY") + static class UnsupportedEnhancerContext extends EnhancerTestContext { + @Override + public UnsupportedEnhancementStrategy getUnsupportedEnhancementStrategy() { + return UnsupportedEnhancementStrategy.FAIL; + } + } + + @Entity(name = "SomeEntity") static class SomeEntity { @Id Long id; @@ -102,15 +133,6 @@ static class SomeEntity { String property; - public SomeEntity() { - } - - public SomeEntity(Long id, String field, String property) { - this.id = id; - this.field = field; - this.property = property; - } - /** * The following property accessor methods are purposely named incorrectly to * not match the "property" field. The HHH-16572 change ensures that @@ -128,4 +150,105 @@ public void setPropertyMethod(String property) { this.property = property; } } + + @Entity(name = "SomeOtherEntity") + static class SomeOtherEntity { + @Id + Long id; + + @Basic + String field; + + String property; + @Access(AccessType.PROPERTY) + public void setPropertyMethod(String property) { + this.property = property; + } + } + + @Entity(name = "EventListenersEntity") + static class EventListenersEntity { + private UUID id; + + private String status = "new"; + + @Id + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + // special case, we should let it through + public UUID get() { + return id; + } + + @PrePersist + public void setId() { + id = UUID.randomUUID(); + } + + @Transient + public String getState() { + return status; + } + + @PostLoad + public void setState() { + status = "loaded"; + } + + @Transient + public boolean isLoaded() { + return status.equals( "loaded" ); + } + } + + @Entity(name = "ExplicitAccessTypeFieldEntity") + @Access( AccessType.FIELD ) + static class ExplicitAccessTypeFieldEntity { + @Id + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long get() { + return id; + } + + public String getSomething() { + return "something"; + } + } + + @Entity(name = "ImplicitAccessTypeFieldEntity") + static class ImplicitAccessTypeFieldEntity { + @Id + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long get() { + return id; + } + + public String getAnother() { + return "another"; + } + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/refresh/RefreshEntityWithLazyPropertyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/refresh/RefreshEntityWithLazyPropertyTest.java new file mode 100644 index 000000000000..f0c4caa58508 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/refresh/RefreshEntityWithLazyPropertyTest.java @@ -0,0 +1,346 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.refresh; + +import jakarta.persistence.Basic; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import org.hibernate.annotations.Formula; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.SQLServerDialect; +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.hibernate.testing.orm.junit.SkipForDialect; +import org.hibernate.testing.orm.junit.SkipForDialectGroup; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + + +@Jira("HHH-13377") +@DomainModel( + annotatedClasses = { + RefreshEntityWithLazyPropertyTest.Person.class, + RefreshEntityWithLazyPropertyTest.Course.class, + RefreshEntityWithLazyPropertyTest.Position.class} +) +@SessionFactory +@BytecodeEnhanced +@SkipForDialectGroup( + { + @SkipForDialect( dialectClass = MySQLDialect.class, matchSubTypes = true, reason = "does not support || as String concatenation"), + @SkipForDialect( dialectClass = SQLServerDialect.class, reason = "does not support || as String concatenation"), + } +) +public class RefreshEntityWithLazyPropertyTest { + + private static final Long PERSON_ID = 1L; + private static final Long ASSISTANT_PROFESSOR_POSITION_ID = 1L; + private static final Long PROFESSOR_POSITION_ID = 2L; + private static final String ASSISTANT_POSITION_DESCRIPTION = "Assistant Professor"; + private static final String POSITION_DESCRIPTION = "Professor"; + private static final String PROFESSOR_FIRST_NAME = "John"; + private static final String PROFESSOR_LAST_NAME = "Doe"; + + @BeforeEach + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + Position professorPosition = new Position( PROFESSOR_POSITION_ID, POSITION_DESCRIPTION ); + session.persist( professorPosition ); + + Position assistantProfessor = new Position( ASSISTANT_PROFESSOR_POSITION_ID, + ASSISTANT_POSITION_DESCRIPTION ); + session.persist( assistantProfessor ); + + Person person = new Person( PERSON_ID, PROFESSOR_FIRST_NAME, PROFESSOR_LAST_NAME, assistantProfessor, + professorPosition ); + session.persist( person ); + } ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from Course" ).executeUpdate(); + session.createMutationQuery( "delete from Person" ).executeUpdate(); + session.createMutationQuery( "delete from Position" ).executeUpdate(); + } ); + } + + @Test + public void testRefreshOfLazyField(SessionFactoryScope scope) { + scope.inTransaction( session -> { + Person p = session.find( Person.class, PERSON_ID ); + assertThat( p.getLastName() ).isEqualTo( PROFESSOR_LAST_NAME ); + + String updatedLastName = "Johnson"; + session.createMutationQuery( "update Person p " + + "set p.lastName = :lastName " + + "where p.id = :id" + ) + .setParameter( "lastName", updatedLastName ) + .setParameter( "id", PERSON_ID ) + .executeUpdate(); + + session.refresh( p ); + assertThat( p.getLastName() ).isEqualTo( updatedLastName ); + } ); + } + + @Test + public void testRefreshOfLazyFormula(SessionFactoryScope scope) { + scope.inTransaction( session -> { + Person p = session.find( Person.class, PERSON_ID ); + assertThat( p.getFullName() ).isEqualTo( "John Doe" ); + + p.setLastName( "Johnson" ); + session.flush(); + session.refresh( p ); + assertThat( p.getFullName() ).isEqualTo( "John Johnson" ); + } ); + } + + @Test + public void testRefreshOfLazyOneToMany(SessionFactoryScope scope) { + scope.inTransaction( session -> { + Person p = session.find( Person.class, PERSON_ID ); + assertThat( p.getCourses().size() ).isEqualTo( 0 ); + + session.createMutationQuery( "insert into Course (id, title, person) values (:id, :title, :person) " ) + .setParameter( "id", 0 ) + .setParameter( "title", "Book Title" ) + .setParameter( "person", p ) + .executeUpdate(); + + session.refresh( p ); + assertThat( p.getCourses().size() ).isEqualTo( 1 ); + } ); + } + + @Test + public void testRefreshOfLazyManyToOne(SessionFactoryScope scope) { + scope.inTransaction( session -> { + Person p = session.find( Person.class, PERSON_ID ); + assertThat( p.getPosition().id ).isEqualTo( ASSISTANT_PROFESSOR_POSITION_ID ); + + Position professorPosition = session.find( Position.class, PROFESSOR_POSITION_ID ); + + session.createMutationQuery( + "update Person p " + + "set p.position = :position " + + "where p.id = :personId " + ) + .setParameter( "position", professorPosition ) + .setParameter( "personId", p.getId() ) + .executeUpdate(); + + session.refresh( p ); + assertThat( p.getPosition().id ).isEqualTo( PROFESSOR_POSITION_ID ); + + } ); + } + + @Test + public void testRefreshOfLazyManyToOneCascadeRefresh(SessionFactoryScope scope) { + scope.inTransaction( session -> { + Person p = session.find( Person.class, PERSON_ID ); + Position position = p.getPosition(); + assertThat( position.getId() ).isEqualTo( ASSISTANT_PROFESSOR_POSITION_ID ); + assertThat( position.getDescription() ).isEqualTo( ASSISTANT_POSITION_DESCRIPTION ); + + String newAssistantProfessorDescription = "Assistant Professor 2"; + session.createMutationQuery( + "update Position " + + "set description = :description " + + "where id = :id " + ) + .setParameter( "description", newAssistantProfessorDescription ) + .setParameter( "id", ASSISTANT_PROFESSOR_POSITION_ID ) + .executeUpdate(); + + session.refresh( p ); + // the association has been refreshed because it's annotated with `cascade = CascadeType.REFRESH` + assertThat( p.getPosition().getDescription() ).isEqualTo( newAssistantProfessorDescription ); + } ); + } + + @Test + public void testRefreshOfLazyManyToOneNoCascadeRefresh(SessionFactoryScope scope) { + scope.inTransaction( session -> { + Person p = session.find( Person.class, PERSON_ID ); + Position position = p.getPreviousPosition(); + assertThat( position.getId() ).isEqualTo( PROFESSOR_POSITION_ID ); + assertThat( position.getDescription() ).isEqualTo( POSITION_DESCRIPTION ); + + String newAssistantProfessorDescription = "Assistant Professor 2"; + session.createMutationQuery( + "update Position " + + "set description = :description " + + "where id = :id " + ) + .setParameter( "description", newAssistantProfessorDescription ) + .setParameter( "id", PROFESSOR_POSITION_ID ) + .executeUpdate(); + + session.refresh( p ); + // the association has not been refreshed because it's not annotated with `cascade = CascadeType.REFRESH` + assertThat( p.getPreviousPosition().getDescription() ).isEqualTo( POSITION_DESCRIPTION ); + } ); + } + + @Entity(name = "Person") + public static class Person { + + @Id + private Long id; + + private String firstName; + + @Basic(fetch = FetchType.LAZY) + private String lastName; + + @Basic(fetch = FetchType.LAZY) + @Formula("firstName || ' ' || lastName") + private String fullName; + + @OneToMany(mappedBy = "person", fetch = FetchType.LAZY, cascade = CascadeType.REFRESH, orphanRemoval = true) + private Set courses = new HashSet<>(); + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH) + private Position position; + + @ManyToOne(fetch = FetchType.LAZY) + private Position previousPosition; + + protected Person() { + } + + public Person(Long id, String firstName, String lastName, Position position, Position previousPosition) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.position = position; + this.previousPosition = previousPosition; + } + + public Long getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getFullName() { + return fullName; + } + + public Set getCourses() { + return courses; + } + + public Position getPosition() { + return position; + } + + public Position getPreviousPosition() { + return previousPosition; + } + } + + @Entity(name = "Course") + public static class Course { + + @Id + private Long id; + + private String title; + + @ManyToOne(fetch = FetchType.LAZY) + private Person person; + + protected Course() { + } + + public Course(Long id, String title, Person person) { + this.id = id; + this.title = title; + this.person = person; + } + + public Long getId() { + return id; + } + + public String getTitle() { + return title; + } + + public Person getPerson() { + return person; + } + } + + @Entity(name = "Position") + @Table(name = "POSITION_TABLE") + public static class Position { + + @Id + private Long id; + + @Basic(fetch = FetchType.LAZY) + private String description; + + public Position() { + } + + public Position(Long id, String description) { + this.id = id; + this.description = description; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/cache/QueryCacheIncompleteTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/cache/QueryCacheIncompleteTest.java new file mode 100644 index 000000000000..77575b06d239 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/cache/QueryCacheIncompleteTest.java @@ -0,0 +1,116 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.cache; + +import org.hibernate.CacheMode; +import org.hibernate.Session; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import static org.assertj.core.api.Assertions.assertThat; + +@DomainModel(annotatedClasses = { + QueryCacheIncompleteTest.Admin.class, +}) +@SessionFactory +@ServiceRegistry( + settings = { + @Setting(name = AvailableSettings.USE_SECOND_LEVEL_CACHE, value = "true"), + @Setting(name = AvailableSettings.USE_QUERY_CACHE, value = "true"), + @Setting(name = AvailableSettings.QUERY_CACHE_LAYOUT, value = "FULL") + } +) +@JiraKey(value = "HHH-18689") +public class QueryCacheIncompleteTest { + + private Long adminId; + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + adminId = scope.fromTransaction( + session -> { + Admin admin = new Admin(); + admin.setAge( 42 ); + session.persist( admin ); + return admin.getId(); + } + ); + } + + @Test + void testQueryWithEmbeddableParameter(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + // load uninitialized proxy + session.getReference( Admin.class, adminId ); + // load entity + var multiLoader = session.byMultipleIds( Admin.class ); + multiLoader.with( CacheMode.NORMAL ); + multiLoader.multiLoad( adminId ); + + // store in query cache + Admin admin = queryAdmin( session ); + assertThat( admin.getAge() ).isEqualTo( 42 ); + } + ); + + scope.inTransaction( + session -> { + // use query cache + Admin admin = queryAdmin( session ); + assertThat( admin.getAge() ).isEqualTo( 42 ); + } + ); + } + + private Admin queryAdmin(Session s) { + return s.createQuery( "from Admin", Admin.class ).setCacheable( true ).getSingleResult(); + } + + @Entity(name = "Admin") + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + public static class Admin { + + @Id + @GeneratedValue + private Long id; + + @Column(nullable = false) + private int age; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/ASTParserLoadingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/ASTParserLoadingTest.java index 62e8bb02c126..2ac157068bf9 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/ASTParserLoadingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/ASTParserLoadingTest.java @@ -26,12 +26,13 @@ import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.TypeMismatchException; -import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.DerbyDialect; +import org.hibernate.dialect.Dialect; import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.MySQLDialect; @@ -49,7 +50,6 @@ import org.hibernate.query.Query; import org.hibernate.query.SyntaxException; import org.hibernate.query.spi.QueryImplementor; -import org.hibernate.query.sqm.ParsingException; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.internal.QuerySqmImpl; import org.hibernate.query.sqm.tree.domain.SqmPath; @@ -60,13 +60,17 @@ import org.hibernate.transform.ResultTransformer; import org.hibernate.transform.Transformers; -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.FailureExpected; -import org.hibernate.testing.RequiresDialect; -import org.hibernate.testing.RequiresDialectFeature; -import org.hibernate.testing.SkipForDialect; -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.FailureExpected; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.hibernate.testing.orm.junit.SkipForDialect; import org.hibernate.orm.test.cid.Customer; import org.hibernate.orm.test.cid.LineItem; @@ -74,8 +78,10 @@ import org.hibernate.orm.test.cid.Order; import org.hibernate.orm.test.cid.Product; -import org.junit.After; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import org.jboss.logging.Logger; import org.hamcrest.CoreMatchers; @@ -86,7 +92,6 @@ import static org.hamcrest.Matchers.notNullValue; import static org.hibernate.testing.junit4.ExtraAssertions.assertClassAssignability; import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; -import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -106,15 +111,40 @@ * * @author Steve */ -@RequiresDialectFeature(DialectChecks.SupportsTemporaryTable.class) -public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { +@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsTemporaryTable.class) +@DomainModel( annotatedClasses = {Department.class, Employee.class, Title.class}, + xmlMappings = {"/org/hibernate/orm/test/hql/Animal.hbm.xml", + "/org/hibernate/orm/test/hql/FooBarCopy.hbm.xml", + "/org/hibernate/orm/test/hql/SimpleEntityWithAssociation.hbm.xml", + "/org/hibernate/orm/test/hql/CrazyIdFieldNames.hbm.xml", + "/org/hibernate/orm/test/hql/Image.hbm.xml", + "/org/hibernate/orm/test/hql/ComponentContainer.hbm.xml", + "/org/hibernate/orm/test/hql/VariousKeywordPropertyEntity.hbm.xml", + "/org/hibernate/orm/test/hql/Constructor.hbm.xml", + "/org/hibernate/orm/test/batchfetch/ProductLine.hbm.xml", + "/org/hibernate/orm/test/cid/Customer.hbm.xml", + "/org/hibernate/orm/test/cid/Order.hbm.xml", + "/org/hibernate/orm/test/cid/LineItem.hbm.xml", + "/org/hibernate/orm/test/cid/Product.hbm.xml", + "/org/hibernate/orm/test/any/hbm/Properties.hbm.xml", + "/org/hibernate/orm/test/legacy/Commento.hbm.xml", + "/org/hibernate/orm/test/legacy/Marelo.hbm.xml"}) +@SessionFactory +@ServiceRegistry( + settings = { + @Setting(name = Environment.USE_QUERY_CACHE, value = "true"), + @Setting(name = Environment.GENERATE_STATISTICS, value = "true") + } +) +@SuppressWarnings("JUnitMalformedDeclaration") +public class ASTParserLoadingTest { + private static final Logger log = Logger.getLogger(ASTParserLoadingTest.class); private final List createdAnimalIds = new ArrayList<>(); - @After - public void cleanUpTestData() { - inTransaction( - (session) -> { + @AfterEach + public void cleanUpTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { session.createQuery( "from Animal" ).list().forEach( (animal) -> session.delete( animal ) ); @@ -193,51 +223,10 @@ public void cleanUpTestData() { } ); } - @Override - protected boolean isCleanupTestDataRequired() { - return false; - } - - @Override - public String[] getMappings() { - return new String[] { - "/org/hibernate/orm/test/hql/Animal.hbm.xml", - "/org/hibernate/orm/test/hql/FooBarCopy.hbm.xml", - "/org/hibernate/orm/test/hql/SimpleEntityWithAssociation.hbm.xml", - "/org/hibernate/orm/test/hql/CrazyIdFieldNames.hbm.xml", - "/org/hibernate/orm/test/hql/Image.hbm.xml", - "/org/hibernate/orm/test/hql/ComponentContainer.hbm.xml", - "/org/hibernate/orm/test/hql/VariousKeywordPropertyEntity.hbm.xml", - "/org/hibernate/orm/test/hql/Constructor.hbm.xml", - "/org/hibernate/orm/test/batchfetch/ProductLine.hbm.xml", - "/org/hibernate/orm/test/cid/Customer.hbm.xml", - "/org/hibernate/orm/test/cid/Order.hbm.xml", - "/org/hibernate/orm/test/cid/LineItem.hbm.xml", - "/org/hibernate/orm/test/cid/Product.hbm.xml", - "/org/hibernate/orm/test/any/hbm/Properties.hbm.xml", - "/org/hibernate/orm/test/legacy/Commento.hbm.xml", - "/org/hibernate/orm/test/legacy/Marelo.hbm.xml" - }; - } - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Department.class, - Employee.class, - Title.class - }; - } - - @Override - public void configure(Configuration cfg) { - super.configure( cfg ); - cfg.setProperty( Environment.USE_QUERY_CACHE, true ); - cfg.setProperty( Environment.GENERATE_STATISTICS, true ); - } @Test - public void testSubSelectAsArithmeticOperand() { - inTransaction( + public void testSubSelectAsArithmeticOperand(SessionFactoryScope scope) { + scope.inTransaction( (s) -> { s.createQuery( "from Zoo z where ( select count(*) from Zoo ) = 0" ).list(); @@ -252,8 +241,8 @@ public void testSubSelectAsArithmeticOperand() { } @Test - @TestForIssue( jiraKey = "HHH-8432" ) - public void testExpandListParameter() { + @JiraKey( "HHH-8432" ) + public void testExpandListParameter(SessionFactoryScope scope) { final Object[] namesArray = new Object[] { "ZOO 1", "ZOO 2", "ZOO 3", "ZOO 4", "ZOO 5", "ZOO 6", "ZOO 7", "ZOO 8", "ZOO 9", "ZOO 10", "ZOO 11", "ZOO 12" @@ -263,7 +252,7 @@ public void testExpandListParameter() { "City 8", "City 9", "City 10", "City 11", "City 12" }; - inTransaction( + scope.inTransaction( (session) -> { Address address = new Address(); Zoo zoo = new Zoo( "ZOO 1", address ); @@ -272,7 +261,7 @@ public void testExpandListParameter() { } ); - inTransaction( + scope.inTransaction( (session) -> { List result = session.createQuery( "FROM Zoo z WHERE z.name IN (?1) and z.address.city IN (?2)" ) .setParameterList( 1, namesArray ) @@ -284,9 +273,9 @@ public void testExpandListParameter() { } @Test - @TestForIssue(jiraKey = "HHH-8699") - public void testBooleanPredicate() { - final Constructor created = fromTransaction( + @JiraKey( "HHH-8699") + public void testBooleanPredicate(SessionFactoryScope scope) { + final Constructor created = scope.fromTransaction( (session) -> { final Constructor constructor = new Constructor(); session.save( constructor ); @@ -296,7 +285,7 @@ public void testBooleanPredicate() { Constructor.resetConstructorExecutionCount(); - inTransaction( + scope.inTransaction( (session) -> { final String qry = "select new Constructor( c.id, c.id is not null, c.id = c.id, c.id + 1, concat( str(c.id), 'foo' ) ) from Constructor c where c.id = :id"; final Constructor result = session.createQuery(qry, Constructor.class).setParameter( "id", created.getId() ).uniqueResult(); @@ -314,8 +303,8 @@ public void testBooleanPredicate() { } @Test - public void testJpaTypeOperator() { - inTransaction( + public void testJpaTypeOperator(SessionFactoryScope scope) { + scope.inTransaction( session -> { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // where clause @@ -352,8 +341,8 @@ public void testJpaTypeOperator() { } @Test - public void testComponentJoins() { - inTransaction( + public void testComponentJoins(SessionFactoryScope scope) { + scope.inTransaction( (s) -> { ComponentContainer root = new ComponentContainer( new ComponentContainer.Address( @@ -367,7 +356,7 @@ public void testComponentJoins() { } ); - inTransaction( + scope.inTransaction( (s) -> { List result = s.createQuery( "select a from ComponentContainer c join c.address a" ).list(); assertEquals( 1, result.size() ); @@ -389,9 +378,9 @@ public void testComponentJoins() { } @Test - @TestForIssue( jiraKey = "HHH-9642") - public void testLazyAssociationInComponent() { - inTransaction( + @JiraKey( "HHH-9642") + public void testLazyAssociationInComponent(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { Address address = new Address(); Zoo zoo = new Zoo( "ZOO 1", address ); @@ -404,7 +393,7 @@ public void testLazyAssociationInComponent() { } ); - inTransaction( + scope.inTransaction( (session) -> { final Zoo zoo = (Zoo) session.createQuery( "from Zoo z" ).uniqueResult(); assertNotNull( zoo ); @@ -417,7 +406,7 @@ public void testLazyAssociationInComponent() { ); - inTransaction( + scope.inTransaction( (session) -> { final Zoo zoo = (Zoo) session.createQuery( "from Zoo z join fetch z.address.stateProvince" ).uniqueResult(); assertNotNull( zoo ); @@ -428,7 +417,7 @@ public void testLazyAssociationInComponent() { } ); - inTransaction( + scope.inTransaction( (session) -> { final Zoo zoo = (Zoo) session.createQuery( "from Zoo z join fetch z.address a join fetch a.stateProvince" ).uniqueResult(); assertNotNull( zoo ); @@ -441,9 +430,9 @@ public void testLazyAssociationInComponent() { } @Test - public void testJPAQLQualifiedIdentificationVariablesControl() { + public void testJPAQLQualifiedIdentificationVariablesControl(SessionFactoryScope scope) { // just checking syntax here... - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.createQuery( "from VariousKeywordPropertyEntity where type = 'something'" ).list(); s.createQuery( "from VariousKeywordPropertyEntity where value = 'something'" ).list(); @@ -460,8 +449,8 @@ public void testJPAQLQualifiedIdentificationVariablesControl() { @Test @SuppressWarnings( {"unchecked"}) - public void testJPAQLMapKeyQualifier() { - Session s = openSession(); + public void testJPAQLMapKeyQualifier(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); Human me = new Human(); me.setName( new Name( "Steve", null, "Ebersole" ) ); @@ -477,7 +466,7 @@ public void testJPAQLMapKeyQualifier() { // in SELECT clause { // hibernate-only form - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); List results = s.createQuery( "select distinct key(h.family) from Human h" ).list(); assertEquals( 1, results.size() ); @@ -489,7 +478,7 @@ public void testJPAQLMapKeyQualifier() { { // jpa form - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); List results = s.createQuery( "select distinct KEY(f) from Human h join h.family f" ).list(); assertEquals( 1, results.size() ); @@ -502,7 +491,7 @@ public void testJPAQLMapKeyQualifier() { // in WHERE clause { // hibernate-only form - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); Long count = (Long) s.createQuery( "select count(*) from Human h where KEY(h.family) = 'son'" ).uniqueResult(); assertEquals( (Long)1L, count ); @@ -512,7 +501,7 @@ public void testJPAQLMapKeyQualifier() { { // jpa form - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); Long count = (Long) s.createQuery( "select count(*) from Human h join h.family f where key(f) = 'son'" ).uniqueResult(); assertEquals( (Long)1L, count ); @@ -520,7 +509,7 @@ public void testJPAQLMapKeyQualifier() { s.close(); } - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.delete( me ); s.delete( joe ); @@ -530,8 +519,8 @@ public void testJPAQLMapKeyQualifier() { @Test @SuppressWarnings( {"unchecked"}) - public void testJPAQLMapEntryQualifier() { - Session s = openSession(); + public void testJPAQLMapEntryQualifier(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); Human me = new Human(); me.setName( new Name( "Steve", null, "Ebersole" ) ); @@ -547,7 +536,7 @@ public void testJPAQLMapEntryQualifier() { // in SELECT clause { // hibernate-only form - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); List results = s.createQuery( "select entry(h.family) from Human h" ).list(); assertEquals( 1, results.size() ); @@ -562,7 +551,7 @@ public void testJPAQLMapEntryQualifier() { { // jpa form - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); List results = s.createQuery( "select ENTRY(f) from Human h join h.family f" ).list(); assertEquals( 1, results.size() ); @@ -578,7 +567,7 @@ public void testJPAQLMapEntryQualifier() { // not exactly sure of the syntax of ENTRY in the WHERE clause... - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.delete( me ); s.delete( joe ); @@ -588,8 +577,8 @@ public void testJPAQLMapEntryQualifier() { @Test @SuppressWarnings( {"unchecked"}) - public void testJPAQLMapValueQualifier() { - Session s = openSession(); + public void testJPAQLMapValueQualifier(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); Human me = new Human(); me.setName( new Name( "Steve", null, "Ebersole" ) ); @@ -605,7 +594,7 @@ public void testJPAQLMapValueQualifier() { // in SELECT clause { // hibernate-only form - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); List results = s.createQuery( "select value(h.family) from Human h" ).list(); assertEquals( 1, results.size() ); @@ -617,7 +606,7 @@ public void testJPAQLMapValueQualifier() { { // jpa form - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); List results = s.createQuery( "select VALUE(f) from Human h join h.family f" ).list(); assertEquals( 1, results.size() ); @@ -630,7 +619,7 @@ public void testJPAQLMapValueQualifier() { // in WHERE clause { // hibernate-only form - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); Long count = (Long) s.createQuery( "select count(*) from Human h where VALUE(h.family) = :joe" ).setParameter( "joe", joe ).uniqueResult(); // ACTUALLY EXACTLY THE SAME AS: @@ -642,7 +631,7 @@ public void testJPAQLMapValueQualifier() { { // jpa form - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); Long count = (Long) s.createQuery( "select count(*) from Human h join h.family f where value(f) = :joe" ).setParameter( "joe", joe ).uniqueResult(); // ACTUALLY EXACTLY THE SAME AS: @@ -652,7 +641,7 @@ public void testJPAQLMapValueQualifier() { s.close(); } - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.delete( me ); s.delete( joe ); @@ -661,9 +650,9 @@ public void testJPAQLMapValueQualifier() { } @Test - @RequiresDialectFeature( DialectChecks.SupportsSubqueryInSelect.class ) - public void testPaginationWithPolymorphicQuery() { - Session s = openSession(); + @RequiresDialectFeature( feature = DialectFeatureChecks.SupportsSubqueryInSelect.class ) + public void testPaginationWithPolymorphicQuery(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); Human h = new Human(); h.setName( new Name( "Steve", null, "Ebersole" ) ); @@ -671,14 +660,14 @@ public void testPaginationWithPolymorphicQuery() { s.getTransaction().commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); List results = s.createQuery( "from java.lang.Object" ).setMaxResults( 2 ).list(); assertEquals( 1, results.size() ); s.getTransaction().commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.delete( h ); s.getTransaction().commit(); @@ -686,10 +675,10 @@ public void testPaginationWithPolymorphicQuery() { } @Test - @TestForIssue( jiraKey = "HHH-2045" ) + @JiraKey( "HHH-2045" ) @RequiresDialect( H2Dialect.class ) - public void testEmptyInList() { - Session session = openSession(); + public void testEmptyInList(SessionFactoryScope scope) { + Session session = scope.getSessionFactory().openSession(); session.beginTransaction(); Human human = new Human(); human.setName( new Name( "Lukasz", null, "Antoniak" ) ); @@ -698,14 +687,14 @@ public void testEmptyInList() { session.getTransaction().commit(); session.close(); - session = openSession(); + session = scope.getSessionFactory().openSession(); session.beginTransaction(); List results = session.createQuery( "from Human h where h.nickName in ()" ).list(); assertEquals( 0, results.size() ); session.getTransaction().commit(); session.close(); - session = openSession(); + session = scope.getSessionFactory().openSession(); session.beginTransaction(); session.delete( human ); session.getTransaction().commit(); @@ -713,9 +702,9 @@ public void testEmptyInList() { } @Test - @TestForIssue( jiraKey = "HHH-8901" ) - public void testEmptyInListForDialectsNotSupportsEmptyInList() { - Session session = openSession(); + @JiraKey( "HHH-8901" ) + public void testEmptyInListForDialectsNotSupportsEmptyInList(SessionFactoryScope scope) { + Session session = scope.getSessionFactory().openSession(); session.beginTransaction(); Human human = new Human(); human.setName( new Name( "Lukasz", null, "Antoniak" ) ); @@ -724,7 +713,7 @@ public void testEmptyInListForDialectsNotSupportsEmptyInList() { session.getTransaction().commit(); session.close(); - session = openSession(); + session = scope.getSessionFactory().openSession(); session.beginTransaction(); List results = session.createQuery( "from Human h where h.nickName in (:nickNames)" ) .setParameter( "nickNames", Collections.emptySet() ) @@ -733,7 +722,7 @@ public void testEmptyInListForDialectsNotSupportsEmptyInList() { session.getTransaction().commit(); session.close(); - session = openSession(); + session = scope.getSessionFactory().openSession(); session.beginTransaction(); session.delete( human ); session.getTransaction().commit(); @@ -741,9 +730,9 @@ public void testEmptyInListForDialectsNotSupportsEmptyInList() { } @Test - @TestForIssue( jiraKey = "HHH-2851") - public void testMultipleRefsToSameParam() { - Session s = openSession(); + @JiraKey( "HHH-2851") + public void testMultipleRefsToSameParam(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); Human h = new Human(); h.setName( new Name( "Johnny", 'B', "Goode" ) ); @@ -764,7 +753,7 @@ public void testMultipleRefsToSameParam() { s.getTransaction().commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); List results = s.createQuery( "from Human where name.first = :name or name.last=:name" ) .setParameter( "name", "Johnny" ) @@ -813,7 +802,7 @@ public void testMultipleRefsToSameParam() { s.getTransaction().commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.createQuery( "delete Human" ).executeUpdate(); s.getTransaction().commit(); @@ -821,8 +810,8 @@ public void testMultipleRefsToSameParam() { } @Test - public void testComponentNullnessChecks() { - Session s = openSession(); + public void testComponentNullnessChecks(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); Human h = new Human(); h.setName( new Name( "Johnny", 'B', "Goode" ) ); @@ -839,18 +828,19 @@ public void testComponentNullnessChecks() { s.getTransaction().commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); List results = s.createQuery( "from Human where name is null" ).list(); assertEquals( 1, results.size() ); results = s.createQuery( "from Human where name is not null" ).list(); assertEquals( 3, results.size() ); + Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect(); String query = - ( getDialect() instanceof DB2Dialect || getDialect() instanceof HSQLDialect ) ? + ( dialect instanceof DB2Dialect || dialect instanceof HSQLDialect ) ? "from Human where cast(?1 as string) is null" : "from Human where ?1 is null" ; - if ( getDialect() instanceof DerbyDialect ) { + if ( dialect instanceof DerbyDialect ) { s.createQuery( query ).setParameter( 1, "null" ).list(); } else { @@ -860,7 +850,7 @@ public void testComponentNullnessChecks() { s.getTransaction().commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.createQuery( "delete Human" ).executeUpdate(); s.getTransaction().commit(); @@ -868,9 +858,9 @@ public void testComponentNullnessChecks() { } @Test - @TestForIssue( jiraKey = "HHH-4150" ) - public void testSelectClauseCaseWithSum() { - Session s = openSession(); + @JiraKey( "HHH-4150" ) + public void testSelectClauseCaseWithSum(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); Human h1 = new Human(); @@ -900,9 +890,10 @@ public void testSelectClauseCaseWithSum() { } @Test - @TestForIssue( jiraKey = "HHH-4150" ) - public void testSelectClauseCaseWithCountDistinct() { - Session s = openSession(); + @JiraKey( "HHH-4150" ) + @SkipForDialect( dialectClass = InformixDialect.class, majorVersion = 11, minorVersion = 70, reason = "Informix does not support case with count distinct") + public void testSelectClauseCaseWithCountDistinct(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); Human h1 = new Human(); @@ -937,12 +928,12 @@ public void testSelectClauseCaseWithCountDistinct() { } @Test - public void testInvalidCollectionDereferencesFail() { + public void testInvalidCollectionDereferencesFail(SessionFactoryScope scope) { - try ( final SessionImplementor s = (SessionImplementor) openSession() ) { + try ( final SessionImplementor s = (SessionImplementor) scope.getSessionFactory().openSession() ) { // control group... - inTransaction( + scope.inTransaction( s, session -> { s.createQuery( "from Animal a join a.offspring o where o.description = 'xyz'" ).list(); @@ -952,7 +943,7 @@ public void testInvalidCollectionDereferencesFail() { } ); - inTransaction( + scope.inTransaction( s, session -> { try { @@ -968,7 +959,7 @@ public void testInvalidCollectionDereferencesFail() { } ); - inTransaction( + scope.inTransaction( s, session -> { try { @@ -984,7 +975,7 @@ public void testInvalidCollectionDereferencesFail() { } ); - inTransaction( + scope.inTransaction( s, session -> { try { @@ -1000,7 +991,7 @@ public void testInvalidCollectionDereferencesFail() { } ); - inTransaction( + scope.inTransaction( s, session -> { try { @@ -1019,9 +1010,9 @@ public void testInvalidCollectionDereferencesFail() { } @Test - public void testConcatenation() { + public void testConcatenation(SessionFactoryScope scope) { // simple syntax checking... - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.createQuery( "from Human h where h.nickName = '1' || 'ov' || 'tha' || 'few'" ).list(); s.getTransaction().commit(); @@ -1029,13 +1020,14 @@ public void testConcatenation() { } @Test - @SkipForDialect(value = CockroachDialect.class, comment = "https://github.com/cockroachdb/cockroach/issues/41943") - public void testExpressionWithParamInFunction() { - Session s = openSession(); + @SkipForDialect(dialectClass = CockroachDialect.class, matchSubTypes = true ,reason = "https://github.com/cockroachdb/cockroach/issues/41943") + public void testExpressionWithParamInFunction(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.createQuery( "from Animal a where abs(a.bodyWeight-:param) < 2.0" ).setParameter( "param", 1 ).list(); s.createQuery( "from Animal a where abs(:param - a.bodyWeight) < 2.0" ).setParameter( "param", 1 ).list(); - if ( getDialect() instanceof HSQLDialect || getDialect() instanceof DB2Dialect || getDialect() instanceof DerbyDialect ) { + Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect(); + if ( dialect instanceof HSQLDialect || dialect instanceof DB2Dialect || dialect instanceof DerbyDialect ) { // HSQLDB and DB2 don't like the abs(? - ?) syntax. bit work if at least one parameter is typed... s.createQuery( "from Animal where abs(cast(:x as long) - :y) < 2.0" ).setParameter( "x", 1 ).setParameter( "y", 1 ).list(); s.createQuery( "from Animal where abs(:x - cast(:y as long)) < 2.0" ).setParameter( "x", 1 ).setParameter( "y", 1 ).list(); @@ -1045,7 +1037,7 @@ public void testExpressionWithParamInFunction() { s.createQuery( "from Animal where abs(:x - :y) < 2.0" ).setParameter( "x", 1 ).setParameter( "y", 1 ).list(); } - if ( getDialect() instanceof DB2Dialect ) { + if ( dialect instanceof DB2Dialect ) { s.createQuery( "from Animal where lower(upper(cast(:foo as string))) like 'f%'" ).setParameter( "foo", "foo" ).list(); } else { @@ -1054,17 +1046,17 @@ public void testExpressionWithParamInFunction() { s.createQuery( "from Animal a where abs(abs(a.bodyWeight - 1.0 + :param) * abs(length('ffobar')-3)) = 3.0" ).setParameter( "param", 1 ).list(); - if ( getDialect() instanceof DB2Dialect ) { + if ( dialect instanceof DB2Dialect ) { s.createQuery( "from Animal where lower(upper('foo') || upper(cast(:bar as string))) like 'f%'" ).setParameter( "bar", "xyz" ).list(); } else { s.createQuery( "from Animal where lower(upper('foo') || upper(:bar)) like 'f%'" ).setParameter( "bar", "xyz" ).list(); } - if ( getDialect() instanceof AbstractHANADialect ) { + if ( dialect instanceof AbstractHANADialect ) { s.createQuery( "from Animal where abs(cast(1 as double) - cast(:param as double)) = 1.0" ).setParameter( "param", 1 ).list(); } - else if ( !( getDialect() instanceof PostgreSQLDialect || getDialect() instanceof MySQLDialect ) ) { + else if ( !( dialect instanceof PostgreSQLDialect || dialect instanceof MySQLDialect ) ) { s.createQuery( "from Animal where abs(cast(1 as float) - cast(:param as float)) = 1.0" ).setParameter( "param", 1 ).list(); } @@ -1073,12 +1065,12 @@ else if ( !( getDialect() instanceof PostgreSQLDialect || getDialect() instanceo } @Test - public void testCrazyIdFieldNames() { + public void testCrazyIdFieldNames(SessionFactoryScope scope) { MoreCrazyIdFieldNameStuffEntity top = new MoreCrazyIdFieldNameStuffEntity( "top" ); HeresAnotherCrazyIdFieldName next = new HeresAnotherCrazyIdFieldName( "next" ); top.setHeresAnotherCrazyIdFieldName( next ); MoreCrazyIdFieldNameStuffEntity other = new MoreCrazyIdFieldNameStuffEntity( "other" ); - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.save( next ); s.save( top ); @@ -1108,15 +1100,15 @@ public void testCrazyIdFieldNames() { } @Test - @TestForIssue( jiraKey = "HHH-2257" ) - public void testImplicitJoinsInDifferentClauses() { + @JiraKey( "HHH-2257" ) + public void testImplicitJoinsInDifferentClauses(SessionFactoryScope scope) { // both the classic and ast translators output the same syntactically valid sql // for all of these cases; the issue is that shallow (iterate) and // non-shallow (list/scroll) queries return different results because the // shallow skips the inner join which "weeds out" results from the non-shallow queries. // The results were initially different depending upon the clause(s) in which the // implicit join occurred - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); SimpleEntityWithAssociation owner = new SimpleEntityWithAssociation( "owner" ); SimpleAssociatedEntity e1 = new SimpleAssociatedEntity( "thing one", owner ); @@ -1127,19 +1119,19 @@ public void testImplicitJoinsInDifferentClauses() { s.getTransaction().commit(); s.close(); - checkCounts( "select e.owner from SimpleAssociatedEntity e", 1, "implicit-join in select clause" ); - checkCounts( "select e.id, e.owner from SimpleAssociatedEntity e", 1, "implicit-join in select clause" ); + checkCounts( scope, "select e.owner from SimpleAssociatedEntity e", 1, "implicit-join in select clause" ); + checkCounts( scope, "select e.id, e.owner from SimpleAssociatedEntity e", 1, "implicit-join in select clause" ); // resolved to a "id short cut" when part of the order by clause -> no inner join = no weeding out... - checkCounts( "from SimpleAssociatedEntity e order by e.owner", 2, "implicit-join in order-by clause" ); + checkCounts( scope, "from SimpleAssociatedEntity e order by e.owner", 2, "implicit-join in order-by clause" ); // resolved to a "id short cut" when part of the group by clause -> no inner join = no weeding out... - checkCounts( + checkCounts( scope, "select e.owner.id, count(*) from SimpleAssociatedEntity e group by e.owner", 2, "implicit-join in select and group-by clauses" ); - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.delete( e1 ); s.delete( e2 ); @@ -1149,8 +1141,8 @@ public void testImplicitJoinsInDifferentClauses() { } @Test - public void testRowValueConstructorSyntaxInInList() { - Session s = openSession(); + public void testRowValueConstructorSyntaxInInList(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); Product product = new Product(); product.setDescription( "My Product" ); @@ -1197,8 +1189,8 @@ public void testRowValueConstructorSyntaxInInList() { } - private void checkCounts(String hql, int expected, String testCondition) { - inTransaction( + private void checkCounts(SessionFactoryScope scope, String hql, int expected, String testCondition) { + scope.inTransaction( session -> { int count = determineCount( session.createQuery( hql ).list().iterator() ); assertEquals( "list() [" + testCondition + "]", expected, count ); @@ -1207,13 +1199,13 @@ private void checkCounts(String hql, int expected, String testCondition) { } @Test - @TestForIssue( jiraKey = "HHH-2257" ) - public void testImplicitSelectEntityAssociationInShallowQuery() { + @JiraKey( "HHH-2257" ) + public void testImplicitSelectEntityAssociationInShallowQuery(SessionFactoryScope scope) { // both the classic and ast translators output the same syntactically valid sql. // the issue is that shallow and non-shallow queries return different // results because the shallow skips the inner join which "weeds out" results // from the non-shallow queries... - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); SimpleEntityWithAssociation owner = new SimpleEntityWithAssociation( "owner" ); SimpleAssociatedEntity e1 = new SimpleAssociatedEntity( "thing one", owner ); @@ -1224,7 +1216,7 @@ public void testImplicitSelectEntityAssociationInShallowQuery() { s.getTransaction().commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); int count = determineCount( s.createQuery( "select e.id, e.owner from SimpleAssociatedEntity e" ).list().iterator() ); // thing two would be removed from the result due to the inner join @@ -1232,7 +1224,7 @@ public void testImplicitSelectEntityAssociationInShallowQuery() { s.getTransaction().commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.delete( e1 ); s.delete( e2 ); @@ -1250,29 +1242,29 @@ private int determineCount(Iterator iterator) { return count; } - @Test - @TestForIssue( jiraKey = "HHH-6714" ) - public void testUnaryMinus(){ - Session s = openSession(); - s.beginTransaction(); - Human stliu = new Human(); - stliu.setIntValue( 26 ); + @Test + @JiraKey( "HHH-6714" ) + public void testUnaryMinus(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); + s.beginTransaction(); + Human stliu = new Human(); + stliu.setIntValue( 26 ); - s.persist( stliu ); - s.getTransaction().commit(); - s.clear(); - s.beginTransaction(); - List list =s.createQuery( "from Human h where -(h.intValue - 100)=74" ).list(); - assertEquals( 1, list.size() ); - s.getTransaction().commit(); - s.close(); + s.persist( stliu ); + s.getTransaction().commit(); + s.clear(); + s.beginTransaction(); + List list = s.createQuery( "from Human h where -(h.intValue - 100)=74" ).list(); + assertEquals( 1, list.size() ); + s.getTransaction().commit(); + s.close(); - } + } @Test - public void testEntityAndOneToOneReturnedByQuery() { - Session s = openSession(); + public void testEntityAndOneToOneReturnedByQuery(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); Human h = new Human(); h.setName( new Name( "Gail", null, "Badner" ) ); @@ -1284,7 +1276,7 @@ public void testEntityAndOneToOneReturnedByQuery() { s.getTransaction().commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); Object [] result = ( Object [] ) s.createQuery( "from User u, Human h where u.human = h" ).uniqueResult(); assertNotNull( result ); @@ -1298,8 +1290,8 @@ public void testEntityAndOneToOneReturnedByQuery() { } @Test - @TestForIssue( jiraKey = "HHH-9305") - public void testExplicitToOneInnerJoin() { + @JiraKey( "HHH-9305") + public void testExplicitToOneInnerJoin(SessionFactoryScope scope) { final Employee employee1 = new Employee(); employee1.setFirstName( "Jane" ); employee1.setLastName( "Doe" ); @@ -1317,7 +1309,7 @@ public void testExplicitToOneInnerJoin() { title2.setDescription( "John's title" ); employee2.setTitle( title2 ); - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.getTransaction().begin(); s.persist( title1 ); s.persist( dept1 ); @@ -1327,7 +1319,7 @@ public void testExplicitToOneInnerJoin() { s.getTransaction().commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); s.getTransaction().begin(); Department department = (Department) s.createQuery( "select e.department from Employee e inner join e.department" ).uniqueResult(); assertEquals( employee1.getDepartment().getDeptName(), department.getDeptName() ); @@ -1341,7 +1333,7 @@ public void testExplicitToOneInnerJoin() { } @Test - public void testExplicitToOneOuterJoin() { + public void testExplicitToOneOuterJoin(SessionFactoryScope scope) { final Employee employee1 = new Employee(); employee1.setFirstName( "Jane" ); employee1.setLastName( "Doe" ); @@ -1359,7 +1351,7 @@ public void testExplicitToOneOuterJoin() { title2.setDescription( "John's title" ); employee2.setTitle( title2 ); - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.getTransaction().begin(); s.persist( title1 ); s.persist( dept1 ); @@ -1368,7 +1360,7 @@ public void testExplicitToOneOuterJoin() { s.persist( employee2 ); s.getTransaction().commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); s.getTransaction().begin(); List list = s.createQuery( "select e.department from Employee e left join e.department" ).list(); assertEquals( 2, list.size() ); @@ -1391,7 +1383,7 @@ public void testExplicitToOneOuterJoin() { } @Test - public void testExplicitToOneInnerJoinAndImplicitToOne() { + public void testExplicitToOneInnerJoinAndImplicitToOne(SessionFactoryScope scope) { final Employee employee1 = new Employee(); employee1.setFirstName( "Jane" ); employee1.setLastName( "Doe" ); @@ -1409,7 +1401,7 @@ public void testExplicitToOneInnerJoinAndImplicitToOne() { title2.setDescription( "John's title" ); employee2.setTitle( title2 ); - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.getTransaction().begin(); s.persist( title1 ); s.persist( dept1 ); @@ -1418,7 +1410,7 @@ public void testExplicitToOneInnerJoinAndImplicitToOne() { s.persist( employee2 ); s.getTransaction().commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); s.getTransaction().begin(); Object[] result = (Object[]) s.createQuery( "select e.firstName, e.lastName, e.title.description, e.department from Employee e inner join e.department" @@ -1437,7 +1429,7 @@ public void testExplicitToOneInnerJoinAndImplicitToOne() { } @Test - public void testNestedComponentIsNull() { + public void testNestedComponentIsNull(SessionFactoryScope scope) { // (1) From MapTest originally... // (2) Was then moved into HQLTest... // (3) However, a bug fix to EntityType#getIdentifierOrUniqueKeyType (HHH-2138) @@ -1447,57 +1439,57 @@ public void testNestedComponentIsNull() { // // fyi... found and fixed the problem in the classic parser; still // leaving here for syntax checking - new SyntaxChecker( "from Commento c where c.marelo.commento.mcompr is null" ).checkAll(); + new SyntaxChecker( scope, "from Commento c where c.marelo.commento.mcompr is null" ).checkAll(); } @Test - @TestForIssue( jiraKey = "HHH-939" ) - public void testSpecialClassPropertyReference() { + @JiraKey( "HHH-939" ) + public void testSpecialClassPropertyReference(SessionFactoryScope scope) { // this is a long standing bug in Hibernate when applied to joined-subclasses; // see HHH-939 for details and history - new SyntaxChecker( "from Zoo zoo where zoo.class = PettingZoo" ).checkAll(); - new SyntaxChecker( "select a.description from Animal a where a.class = Mammal" ).checkAll(); - new SyntaxChecker( "select a.class from Animal a" ).checkAll(); - new SyntaxChecker( "from DomesticAnimal an where an.class = Dog" ).checkAll(); - new SyntaxChecker( "from Animal an where an.class = Dog" ).checkAll(); + new SyntaxChecker( scope, "from Zoo zoo where zoo.class = PettingZoo" ).checkAll(); + new SyntaxChecker( scope, "select a.description from Animal a where a.class = Mammal" ).checkAll(); + new SyntaxChecker( scope, "select a.class from Animal a" ).checkAll(); + new SyntaxChecker( scope, "from DomesticAnimal an where an.class = Dog" ).checkAll(); + new SyntaxChecker( scope, "from Animal an where an.class = Dog" ).checkAll(); } @Test - @TestForIssue( jiraKey = "HHH-2376" ) - public void testSpecialClassPropertyReferenceFQN() { - new SyntaxChecker( "from Zoo zoo where zoo.class = org.hibernate.orm.test.hql.PettingZoo" ).checkAll(); - new SyntaxChecker( "select a.description from Animal a where a.class = org.hibernate.orm.test.hql.Mammal" ).checkAll(); - new SyntaxChecker( "from DomesticAnimal an where an.class = org.hibernate.orm.test.hql.Dog" ).checkAll(); - new SyntaxChecker( "from Animal an where an.class = org.hibernate.orm.test.hql.Dog" ).checkAll(); + @JiraKey( "HHH-2376" ) + public void testSpecialClassPropertyReferenceFQN(SessionFactoryScope scope) { + new SyntaxChecker( scope, "from Zoo zoo where zoo.class = org.hibernate.orm.test.hql.PettingZoo" ).checkAll(); + new SyntaxChecker( scope, "select a.description from Animal a where a.class = org.hibernate.orm.test.hql.Mammal" ).checkAll(); + new SyntaxChecker( scope, "from DomesticAnimal an where an.class = org.hibernate.orm.test.hql.Dog" ).checkAll(); + new SyntaxChecker( scope, "from Animal an where an.class = org.hibernate.orm.test.hql.Dog" ).checkAll(); } @Test - @TestForIssue( jiraKey = "HHH-1631" ) - public void testSubclassOrSuperclassPropertyReferenceInJoinedSubclass() { + @JiraKey( "HHH-1631" ) + public void testSubclassOrSuperclassPropertyReferenceInJoinedSubclass(SessionFactoryScope scope) { // this is a long standing bug in Hibernate; see HHH-1631 for details and history // // (1) pregnant is defined as a property of the class (Mammal) itself // (2) description is defined as a property of the superclass (Animal) // (3) name is defined as a property of a particular subclass (Human) - new SyntaxChecker( "from Zoo z join z.mammals as m where m.name.first = 'John'" ).checkAll(); + new SyntaxChecker( scope, "from Zoo z join z.mammals as m where m.name.first = 'John'" ).checkAll(); - new SyntaxChecker( "from Zoo z join z.mammals as m where m.pregnant = false" ).checkAll(); - new SyntaxChecker( "select m.pregnant from Zoo z join z.mammals as m where m.pregnant = false" ).checkAll(); + new SyntaxChecker( scope, "from Zoo z join z.mammals as m where m.pregnant = false" ).checkAll(); + new SyntaxChecker( scope, "select m.pregnant from Zoo z join z.mammals as m where m.pregnant = false" ).checkAll(); - new SyntaxChecker( "from Zoo z join z.mammals as m where m.description = 'tabby'" ).checkAll(); - new SyntaxChecker( "select m.description from Zoo z join z.mammals as m where m.description = 'tabby'" ).checkAll(); + new SyntaxChecker( scope, "from Zoo z join z.mammals as m where m.description = 'tabby'" ).checkAll(); + new SyntaxChecker( scope, "select m.description from Zoo z join z.mammals as m where m.description = 'tabby'" ).checkAll(); - new SyntaxChecker( "from Zoo z join z.mammals as m where m.name.first = 'John'" ).checkAll(); - new SyntaxChecker( "select m.name from Zoo z join z.mammals as m where m.name.first = 'John'" ).checkAll(); + new SyntaxChecker( scope, "from Zoo z join z.mammals as m where m.name.first = 'John'" ).checkAll(); + new SyntaxChecker( scope, "select m.name from Zoo z join z.mammals as m where m.name.first = 'John'" ).checkAll(); - new SyntaxChecker( "select m.pregnant from Zoo z join z.mammals as m" ).checkAll(); - new SyntaxChecker( "select m.description from Zoo z join z.mammals as m" ).checkAll(); - new SyntaxChecker( "select m.name from Zoo z join z.mammals as m" ).checkAll(); + new SyntaxChecker( scope, "select m.pregnant from Zoo z join z.mammals as m" ).checkAll(); + new SyntaxChecker( scope, "select m.description from Zoo z join z.mammals as m" ).checkAll(); + new SyntaxChecker( scope, "select m.name from Zoo z join z.mammals as m" ).checkAll(); - new SyntaxChecker( "from DomesticAnimal da join da.owner as o where o.nickName = 'Gavin'" ).checkAll(); - new SyntaxChecker( "select da.father from DomesticAnimal da join da.owner as o where o.nickName = 'Gavin'" ).checkAll(); - new SyntaxChecker( "select da.father from DomesticAnimal da where da.owner.nickName = 'Gavin'" ).checkAll(); + new SyntaxChecker( scope, "from DomesticAnimal da join da.owner as o where o.nickName = 'Gavin'" ).checkAll(); + new SyntaxChecker( scope, "select da.father from DomesticAnimal da join da.owner as o where o.nickName = 'Gavin'" ).checkAll(); + new SyntaxChecker( scope, "select da.father from DomesticAnimal da where da.owner.nickName = 'Gavin'" ).checkAll(); } /** @@ -1506,19 +1498,19 @@ public void testSubclassOrSuperclassPropertyReferenceInJoinedSubclass() { * keyword. */ @Test - public void testExplicitEntityCasting() { - new SyntaxChecker( "from Zoo z join treat(z.mammals as Human) as m where m.name.first = 'John'" ).checkAll(); - new SyntaxChecker( "from Zoo z join z.mammals as m where treat(m as Human).name.first = 'John'" ).checkAll(); + public void testExplicitEntityCasting(SessionFactoryScope scope) { + new SyntaxChecker( scope, "from Zoo z join treat(z.mammals as Human) as m where m.name.first = 'John'" ).checkAll(); + new SyntaxChecker( scope, "from Zoo z join z.mammals as m where treat(m as Human).name.first = 'John'" ).checkAll(); } @Test @RequiresDialectFeature( - value = DialectChecks.SupportLimitAndOffsetCheck.class, + feature = DialectFeatureChecks.SupportLimitAndOffsetCheck.class, comment = "dialect does not support offset and limit combo" ) - public void testSimpleSelectWithLimitAndOffset() throws Exception { + public void testSimpleSelectWithLimitAndOffset(SessionFactoryScope scope) throws Exception { // just checking correctness of param binding code... - Session session = openSession(); + Session session = scope.getSessionFactory().openSession(); Transaction t = session.beginTransaction(); session.createQuery( "from Animal" ) .setFirstResult( 2 ) @@ -1529,8 +1521,8 @@ public void testSimpleSelectWithLimitAndOffset() throws Exception { } @Test - public void testJPAPositionalParameterList() { - Session s = openSession(); + public void testJPAPositionalParameterList(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); ArrayList params = new ArrayList(); params.add( "Doe" ); @@ -1572,8 +1564,8 @@ public void testJPAPositionalParameterList() { } @Test - public void testComponentQueries() { - inTransaction( + public void testComponentQueries(SessionFactoryScope scope) { + scope.inTransaction( session -> { final QueryImplementor query = session.createQuery( "select h.name from Human h" ); final SqmSelectStatement sqmStatement = (SqmSelectStatement) query.unwrap( QuerySqmImpl.class ).getSqmStatement(); @@ -1602,10 +1594,10 @@ public void testComponentQueries() { } @Test - @TestForIssue( jiraKey = "HHH-1774" ) - @RequiresDialectFeature( DialectChecks.SupportsSubqueryInSelect.class ) - public void testComponentParameterBinding() { - Session s = openSession(); + @JiraKey( "HHH-1774" ) + @RequiresDialectFeature( feature = DialectFeatureChecks.SupportsSubqueryInSelect.class ) + public void testComponentParameterBinding(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); Order.Id oId = new Order.Id( "1234", 1 ); @@ -1628,8 +1620,8 @@ public void testComponentParameterBinding() { @SuppressWarnings( {"unchecked"}) @Test - public void testAnyMappingReference() { - Session s = openSession(); + public void testAnyMappingReference(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); PropertyValue redValue = new StringPropertyValue( "red" ); @@ -1664,8 +1656,8 @@ public void testAnyMappingReference() { } @Test - public void testJdkEnumStyleEnumConstant() throws Exception { - Session s = openSession(); + public void testJdkEnumStyleEnumConstant(SessionFactoryScope scope) throws Exception { + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.createQuery( "from Zoo z where z.classification = org.hibernate.orm.test.hql.Classification.LAME" ).list(); @@ -1676,9 +1668,9 @@ public void testJdkEnumStyleEnumConstant() throws Exception { @Test @FailureExpected( jiraKey = "unknown" ) - public void testParameterTypeMismatch() { - try ( final SessionImplementor s = (SessionImplementor) openSession() ) { - inTransaction( + public void testParameterTypeMismatch(SessionFactoryScope scope) { + try ( final SessionImplementor s = (SessionImplementor) scope.getSessionFactory().openSession() ) { + scope.inTransaction( s, session -> { try { @@ -1699,9 +1691,9 @@ public void testParameterTypeMismatch() { } @Test - public void testMultipleBagFetchesFail() { - try ( final SessionImplementor s = (SessionImplementor) openSession() ) { - inTransaction( + public void testMultipleBagFetchesFail(SessionFactoryScope scope) { + try ( final SessionImplementor s = (SessionImplementor) scope.getSessionFactory().openSession() ) { + scope.inTransaction( s, session-> { try { @@ -1720,8 +1712,8 @@ public void testMultipleBagFetchesFail() { } @Test - @TestForIssue( jiraKey = "HHH-1248" ) - public void testCollectionJoinsInSubselect() { + @JiraKey( "HHH-1248" ) + public void testCollectionJoinsInSubselect(SessionFactoryScope scope) { // HHH-1248 : initially FromElementFactory treated any explicit join // as an implied join so that theta-style joins would always be used. // This was because correlated subqueries cannot use ANSI-style joins @@ -1729,7 +1721,7 @@ public void testCollectionJoinsInSubselect() { // to only correlated subqueries; it was applied to any subqueries -> // which in-and-of-itself is not necessarily bad. But somewhere later // the choices made there caused joins to be dropped. - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); String qryString = "select a.id, a.description" + @@ -1739,11 +1731,11 @@ public void testCollectionJoinsInSubselect() { " select a1 from Animal a1" + " left join a1.offspring o" + " where a1.id=1" + - ")"; + ")"; s.createQuery( qryString ).list(); qryString = "select h.id, h.description" + - " from Human h" + + " from Human h" + " left join h.friends" + " where h in (" + " select h1" + @@ -1754,7 +1746,7 @@ public void testCollectionJoinsInSubselect() { s.createQuery( qryString ).list(); qryString = "select h.id, h.description" + - " from Human h" + + " from Human h" + " left join h.friends f" + " where f in (" + " select h1" + @@ -1768,9 +1760,9 @@ public void testCollectionJoinsInSubselect() { } @Test - public void testCollectionFetchWithDistinctionAndLimit() { + public void testCollectionFetchWithDistinctionAndLimit(SessionFactoryScope scope) { // create some test data... - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); int parentCount = 30; for ( int i = 0; i < parentCount; i++ ) { @@ -1790,7 +1782,7 @@ public void testCollectionFetchWithDistinctionAndLimit() { t.commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); t = s.beginTransaction(); // Test simple distinction List results; @@ -1807,7 +1799,7 @@ public void testCollectionFetchWithDistinctionAndLimit() { t.commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); t = s.beginTransaction(); s.createQuery( "delete Animal where mother is not null" ).executeUpdate(); s.createQuery( "delete Animal" ).executeUpdate(); @@ -1816,8 +1808,8 @@ public void testCollectionFetchWithDistinctionAndLimit() { } @Test - public void testFetchInSubqueryFails() { - Session s = openSession(); + public void testFetchInSubqueryFails(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); try { s.createQuery( "from Animal a where a.mother in (select m from Animal a1 inner join a1.mother as m join fetch m.mother)" ).list(); fail( "fetch join allowed in subquery" ); @@ -1832,10 +1824,10 @@ public void testFetchInSubqueryFails() { } @Test - @TestForIssue(jiraKey = "HHH-1830") - @SkipForDialect(value = DerbyDialect.class, comment = "Derby doesn't see that the subquery is functionally dependent") - public void testAggregatedJoinAlias() { - Session s = openSession(); + @JiraKey( "HHH-1830") + @SkipForDialect(dialectClass = DerbyDialect.class, matchSubTypes = true, reason = "Derby doesn't see that the subquery is functionally dependent") + public void testAggregatedJoinAlias(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); s.getTransaction().begin(); s.createQuery( "select p.id, size( descendants ) " + @@ -1848,11 +1840,11 @@ public void testAggregatedJoinAlias() { } @Test - @TestForIssue( jiraKey = "HHH-1464" ) - public void testQueryMetadataRetrievalWithFetching() { + @JiraKey( "HHH-1464" ) + public void testQueryMetadataRetrievalWithFetching(SessionFactoryScope scope) { // HHH-1464 : there was a problem due to the fact they we polled // the shallow version of the query plan to get the metadata. - inSession( + scope.inSession( session -> { final Query query = session.createQuery( "from Animal a inner join fetch a.mother" ); final SqmSelectStatement sqmStatement = (SqmSelectStatement) query.unwrap( QuerySqmImpl.class ).getSqmStatement(); @@ -1863,16 +1855,16 @@ public void testQueryMetadataRetrievalWithFetching() { assertThat( selectionType.getExpressibleJavaType().getJavaTypeClass(), equalTo( Animal.class ) ); } ); - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.close(); } @Test - @TestForIssue( jiraKey = "HHH-429" ) + @JiraKey( "HHH-429" ) @SuppressWarnings( {"unchecked"}) - public void testSuperclassPropertyReferenceAfterCollectionIndexedAccess() { + public void testSuperclassPropertyReferenceAfterCollectionIndexedAccess(SessionFactoryScope scope) { // note: simply performing syntax checking in the db - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); Mammal tiger = new Mammal(); tiger.setDescription( "Tiger" ); @@ -1890,14 +1882,14 @@ public void testSuperclassPropertyReferenceAfterCollectionIndexedAccess() { s.getTransaction().commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); List results = s.createQuery( "from Zoo zoo where zoo.mammals['tiger'].mother.bodyWeight > 3.0f" ).list(); assertEquals( 1, results.size() ); s.getTransaction().commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.delete( tiger ); s.delete( mother ); @@ -1907,9 +1899,9 @@ public void testSuperclassPropertyReferenceAfterCollectionIndexedAccess() { } @Test - public void testJoinFetchCollectionOfValues() { + public void testJoinFetchCollectionOfValues(SessionFactoryScope scope) { // note: simply performing syntax checking in the db - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.createQuery( "select h from Human as h join fetch h.nickNames" ).list(); s.getTransaction().commit(); @@ -1917,9 +1909,9 @@ public void testJoinFetchCollectionOfValues() { } @Test - public void testIntegerLiterals() { + public void testIntegerLiterals(SessionFactoryScope scope) { // note: simply performing syntax checking in the db - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.createQuery( "from Foo where long = 1" ).list(); s.createQuery( "from Foo where long = " + Integer.MIN_VALUE ).list(); @@ -1933,9 +1925,9 @@ public void testIntegerLiterals() { } @Test - public void testDecimalLiterals() { + public void testDecimalLiterals(SessionFactoryScope scope) { // note: simply performing syntax checking in the db - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.createQuery( "from Animal where bodyWeight > 100.0e-10" ).list(); s.createQuery( "from Animal where bodyWeight > 100.0E-10" ).list(); @@ -1952,9 +1944,9 @@ public void testDecimalLiterals() { } @Test - public void testNakedPropertyRef() { + public void testNakedPropertyRef(SessionFactoryScope scope) { // note: simply performing syntax and column/table resolution checking in the db - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.createQuery( "from Animal where bodyWeight = bodyWeight" ).list(); s.createQuery( "select bodyWeight from Animal" ).list(); @@ -1964,9 +1956,9 @@ public void testNakedPropertyRef() { } @Test - public void testNakedComponentPropertyRef() { + public void testNakedComponentPropertyRef(SessionFactoryScope scope) { // note: simply performing syntax and column/table resolution checking in the db - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.createQuery( "from Human where name.first = 'Gavin'" ).list(); s.createQuery( "select name from Human" ).list(); @@ -1977,9 +1969,9 @@ public void testNakedComponentPropertyRef() { } @Test - public void testNakedImplicitJoins() { + public void testNakedImplicitJoins(SessionFactoryScope scope) { // note: simply performing syntax and column/table resolution checking in the db - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.createQuery( "from Animal where mother.father.id = 1" ).list(); s.getTransaction().commit(); @@ -1987,11 +1979,11 @@ public void testNakedImplicitJoins() { } @Test - public void testNakedEntityAssociationReference() { + public void testNakedEntityAssociationReference(SessionFactoryScope scope) { // note: simply performing syntax and column/table resolution checking in the db - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); - if ( getDialect() instanceof AbstractHANADialect ) { + if ( scope.getSessionFactory().getJdbcServices().getDialect() instanceof AbstractHANADialect ) { s.createQuery( "from Animal where mother is null" ).list(); } else { @@ -2003,9 +1995,9 @@ public void testNakedEntityAssociationReference() { } @Test - public void testNakedMapIndex() throws Exception { + public void testNakedMapIndex(SessionFactoryScope scope) throws Exception { // note: simply performing syntax and column/table resolution checking in the db - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); s.createQuery( "from Zoo where mammals['dog'].description like '%black%'" ).list(); s.getTransaction().commit(); @@ -2013,10 +2005,10 @@ public void testNakedMapIndex() throws Exception { } @Test - public void testInvalidFetchSemantics() { - try ( final SessionImplementor s = (SessionImplementor) openSession()) { + public void testInvalidFetchSemantics(SessionFactoryScope scope) { + try ( final SessionImplementor s = (SessionImplementor) scope.getSessionFactory().openSession()) { - inTransaction( + scope.inTransaction( s, session -> { try { @@ -2031,7 +2023,7 @@ public void testInvalidFetchSemantics() { } ); - inTransaction( + scope.inTransaction( s, session-> { try { @@ -2050,8 +2042,8 @@ public void testInvalidFetchSemantics() { } @Test - public void testArithmetic() { - Session s = openSession(); + public void testArithmetic(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); Zoo zoo = new Zoo(); zoo.setName("Melbourne Zoo"); @@ -2078,8 +2070,8 @@ public void testArithmetic() { } @Test - public void testNestedCollectionFetch() { - Session s = openSession(); + public void testNestedCollectionFetch(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); s.createQuery("from Animal a left join fetch a.offspring o left join fetch o.offspring where a.mother.id = 1 order by a.description").list(); s.createQuery("from Zoo z left join fetch z.animals a left join fetch a.offspring where z.name ='MZ' order by a.description").list(); @@ -2089,10 +2081,10 @@ public void testNestedCollectionFetch() { } @Test - @RequiresDialectFeature( DialectChecks.SupportsSubqueryInSelect.class ) + @RequiresDialectFeature( feature = DialectFeatureChecks.SupportsSubqueryInSelect.class ) @SuppressWarnings( {"unchecked"}) - public void testSelectClauseSubselect() { - Session s = openSession(); + public void testSelectClauseSubselect(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); Zoo zoo = new Zoo(); zoo.setName("Melbourne Zoo"); @@ -2119,8 +2111,8 @@ public void testSelectClauseSubselect() { } @Test - public void testInitProxy() { - Session s = openSession(); + public void testInitProxy(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); Mammal plat = new Mammal(); plat.setBodyWeight( 11f ); @@ -2140,8 +2132,8 @@ public void testInitProxy() { @Test @SuppressWarnings( {"unchecked"}) - public void testSelectClauseImplicitJoin() { - Session s = openSession(); + public void testSelectClauseImplicitJoin(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); Zoo zoo = new Zoo(); zoo.setName("The Zoo"); @@ -2187,10 +2179,10 @@ private static void verifyAnimalZooSelection(Query q) { } @Test - @TestForIssue( jiraKey = "HHH-9305") + @JiraKey( "HHH-9305") @SuppressWarnings( {"unchecked"}) - public void testSelectClauseImplicitJoinOrderByJoinedProperty() { - Session s = openSession(); + public void testSelectClauseImplicitJoinOrderByJoinedProperty(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); Zoo zoo = new Zoo(); zoo.setName("The Zoo"); @@ -2253,8 +2245,8 @@ public void testSelectClauseImplicitJoinOrderByJoinedProperty() { @Test @SuppressWarnings( {"unchecked"}) - public void testSelectClauseDistinctImplicitJoinOrderByJoinedProperty() { - Session s = openSession(); + public void testSelectClauseDistinctImplicitJoinOrderByJoinedProperty(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); Zoo zoo = new Zoo(); zoo.setName("The Zoo"); @@ -2317,8 +2309,8 @@ public void testSelectClauseDistinctImplicitJoinOrderByJoinedProperty() { @Test @SuppressWarnings( {"unchecked"}) - public void testSelectClauseImplicitJoinWithIterate() { - Session s = openSession(); + public void testSelectClauseImplicitJoinWithIterate(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); Zoo zoo = new Zoo(); zoo.setName("The Zoo"); @@ -2351,8 +2343,8 @@ public void testSelectClauseImplicitJoinWithIterate() { } @Test - public void testComponentOrderBy() { - Session s = openSession(); + public void testComponentOrderBy(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); Long id1 = ( Long ) s.save( genSimpleHuman( "John", "Jacob" ) ); @@ -2378,8 +2370,8 @@ public void testComponentOrderBy() { } @Test - public void testOrderedWithCustomColumnReadAndWrite() { - Session s = openSession(); + public void testOrderedWithCustomColumnReadAndWrite(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); SimpleEntityWithAssociation first = new SimpleEntityWithAssociation(); first.setNegatedNumber( 1 ); @@ -2408,8 +2400,8 @@ public void testOrderedWithCustomColumnReadAndWrite() { } @Test - public void testHavingWithCustomColumnReadAndWrite() { - Session s = openSession(); + public void testHavingWithCustomColumnReadAndWrite(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); SimpleEntityWithAssociation first = new SimpleEntityWithAssociation(); first.setNegatedNumber(5); @@ -2439,9 +2431,9 @@ public void testHavingWithCustomColumnReadAndWrite() { } @Test - public void testLoadSnapshotWithCustomColumnReadAndWrite() { + public void testLoadSnapshotWithCustomColumnReadAndWrite(SessionFactoryScope scope) { // Exercises entity snapshot load when select-before-update is true. - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); final double SIZE_IN_KB = 1536d; final double SIZE_IN_MB = SIZE_IN_KB / 1024d; @@ -2458,7 +2450,7 @@ public void testLoadSnapshotWithCustomColumnReadAndWrite() { t.commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); t = s.beginTransaction(); final double NEW_SIZE_IN_KB = 2048d; final double NEW_SIZE_IN_MB = NEW_SIZE_IN_KB / 1024d; @@ -2482,8 +2474,8 @@ private Human genSimpleHuman(String fName, String lName) { } @Test - public void testCastInSelect() { - Session s = openSession(); + public void testCastInSelect(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); Animal a = new Animal(); a.setBodyWeight(12.4f); @@ -2506,8 +2498,8 @@ public void testCastInSelect() { } @Test - public void testNumericExpressionReturnTypes() { - Session s = openSession(); + public void testNumericExpressionReturnTypes(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); Animal a = new Animal(); a.setBodyWeight(12.4f); @@ -2613,8 +2605,8 @@ public void testNumericExpressionReturnTypes() { } @Test - public void testAliases() { - Session s = openSession(); + public void testAliases(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); Animal a = new Animal(); a.setBodyWeight(12.4f); @@ -2650,9 +2642,9 @@ public void testAliases() { } @Test - @RequiresDialectFeature(DialectChecks.SupportsTemporaryTable.class) - public void testParameterMixing() { - Session s = openSession(); + @RequiresDialectFeature( feature = DialectFeatureChecks.SupportsTemporaryTable.class) + public void testParameterMixing(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); s.createQuery( "from Animal a where a.description = ?1 and a.bodyWeight = ?2 or a.bodyWeight = :bw" ) .setParameter( 1, "something" ) @@ -2664,8 +2656,8 @@ public void testParameterMixing() { } @Test - public void testOrdinalParameters() { - Session s = openSession(); + public void testOrdinalParameters(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); s.createQuery( "from Animal a where a.description = ?1 and a.bodyWeight = ?2" ) .setParameter( 1, "something" ) @@ -2680,8 +2672,8 @@ public void testOrdinalParameters() { } @Test - public void testIndexParams() { - Session s = openSession(); + public void testIndexParams(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); s.createQuery( "from Zoo zoo where zoo.mammals[:name].id = :id" ) .setParameter( "name", "Walrus" ) @@ -2711,8 +2703,8 @@ public void testIndexParams() { } @Test - public void testAggregation() { - Session s = openSession(); + public void testAggregation(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); Human h = new Human(); h.setBodyWeight( (float) 74.0 ); @@ -2731,7 +2723,7 @@ public void testAggregation() { s.getTransaction().commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); s.beginTransaction(); h = new Human(); h.setFloatValue( 2.5F ); @@ -2762,8 +2754,8 @@ public void testAggregation() { } @Test - public void testSelectClauseCase() { - Session s = openSession(); + public void testSelectClauseCase(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); Human h = new Human(); h.setBodyWeight( (float) 74.0 ); @@ -2782,9 +2774,9 @@ public void testSelectClauseCase() { } @Test - @RequiresDialectFeature( DialectChecks.SupportsSubqueryInSelect.class ) - public void testImplicitPolymorphism() { - Session s = openSession(); + @RequiresDialectFeature( feature = DialectFeatureChecks.SupportsSubqueryInSelect.class ) + public void testImplicitPolymorphism(SessionFactoryScope scope) { + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); Product product = new Product(); @@ -2807,8 +2799,8 @@ public void testImplicitPolymorphism() { } @Test - public void testCoalesce() { - Session session = openSession(); + public void testCoalesce(SessionFactoryScope scope) { + Session session = scope.getSessionFactory().openSession(); Transaction txn = session.beginTransaction(); session.createQuery("from Human h where coalesce(h.nickName, h.name.first, h.name.last) = 'max'").list(); session.createQuery("select nullif(nickName, '1e1') from Human").list(); @@ -2817,14 +2809,14 @@ public void testCoalesce() { } @Test - public void testStr() { - Session session = openSession(); + public void testStr(SessionFactoryScope scope) { + Session session = scope.getSessionFactory().openSession(); Transaction txn = session.beginTransaction(); Animal an = new Animal(); an.setBodyWeight(123.45f); session.persist( an ); String str = (String) session.createQuery("select str(an.bodyWeight) from Animal an where str(an.bodyWeight) like '%1%'").uniqueResult(); - if ( getDialect() instanceof DB2Dialect ) { + if ( scope.getSessionFactory().getJdbcServices().getDialect() instanceof DB2Dialect ) { assertTrue( str.startsWith( "1.234" ) ); } else { @@ -2847,10 +2839,10 @@ public void testStr() { } @Test - @SkipForDialect( MySQLDialect.class ) - @SkipForDialect( DB2Dialect.class ) - public void testCast() { - Session session = openSession(); + @SkipForDialect( dialectClass = MySQLDialect.class, matchSubTypes = true ) + @SkipForDialect( dialectClass = DB2Dialect.class, matchSubTypes = true ) + public void testCast(SessionFactoryScope scope) { + Session session = scope.getSessionFactory().openSession(); Transaction txn = session.beginTransaction(); session.createQuery("from Human h where h.nickName like 'G%'").list(); session.createQuery("from Animal a where cast(a.bodyWeight as string) like '1.%'").list(); @@ -2860,12 +2852,12 @@ public void testCast() { } @Test - public void testExtract() { - Session session = openSession(); + public void testExtract(SessionFactoryScope scope) { + Session session = scope.getSessionFactory().openSession(); Transaction txn = session.beginTransaction(); session.createQuery("select second(current_timestamp()), minute(current_timestamp()), hour(current_timestamp()) from Mammal m").list(); session.createQuery("select day(m.birthdate), month(m.birthdate), year(m.birthdate) from Mammal m").list(); - if ( !(getDialect() instanceof DB2Dialect) ) { //no ANSI extract + if ( !(scope.getSessionFactory().getJdbcServices().getDialect() instanceof DB2Dialect) ) { //no ANSI extract session.createQuery("select extract(second from current_timestamp()), extract(minute from current_timestamp()), extract(hour from current_timestamp()) from Mammal m").list(); session.createQuery("select extract(day from m.birthdate), extract(month from m.birthdate), extract(year from m.birthdate) from Mammal m").list(); } @@ -2874,11 +2866,11 @@ public void testExtract() { } @Test - @SkipForDialect(value = CockroachDialect.class, comment = "https://github.com/cockroachdb/cockroach/issues/41943") + @SkipForDialect(dialectClass = CockroachDialect.class, matchSubTypes = true, reason = "https://github.com/cockroachdb/cockroach/issues/41943") @SuppressWarnings( {"UnusedAssignment", "UnusedDeclaration"}) - public void testSelectExpressions() { - createTestBaseData(); - Session session = openSession(); + public void testSelectExpressions(SessionFactoryScope scope) { + createTestBaseData( scope ); + Session session = scope.getSessionFactory().openSession(); Transaction txn = session.beginTransaction(); Human h = new Human(); h.setName( new Name( "Gavin", 'A', "King" ) ); @@ -2906,11 +2898,11 @@ public void testSelectExpressions() { session.delete(h); txn.commit(); session.close(); - destroyTestBaseData(); + destroyTestBaseData( scope ); } - private void createTestBaseData() { - Session session = openSession(); + private void createTestBaseData(SessionFactoryScope scope) { + Session session = scope.getSessionFactory().openSession(); Transaction txn = session.beginTransaction(); Mammal m1 = new Mammal(); @@ -2933,8 +2925,8 @@ private void createTestBaseData() { createdAnimalIds.add( m2.getId() ); } - private void destroyTestBaseData() { - Session session = openSession(); + private void destroyTestBaseData(SessionFactoryScope scope) { + Session session = scope.getSessionFactory().openSession(); Transaction txn = session.beginTransaction(); for ( Long createdAnimalId : createdAnimalIds ) { @@ -2949,8 +2941,8 @@ private void destroyTestBaseData() { } @Test - public void testImplicitJoin() throws Exception { - Session session = openSession(); + public void testImplicitJoin(SessionFactoryScope scope) throws Exception { + Session session = scope.getSessionFactory().openSession(); Transaction t = session.beginTransaction(); Animal a = new Animal(); a.setBodyWeight(0.5f); @@ -2975,48 +2967,48 @@ public void testImplicitJoin() throws Exception { } @Test - public void testFromOnly() throws Exception { - createTestBaseData(); - Session session = openSession(); + public void testFromOnly(SessionFactoryScope scope) throws Exception { + createTestBaseData( scope ); + Session session = scope.getSessionFactory().openSession(); Transaction t = session.beginTransaction(); List results = session.createQuery( "from Animal" ).list(); assertEquals( "Incorrect result size", 2, results.size() ); assertTrue( "Incorrect result return type", results.get( 0 ) instanceof Animal ); t.commit(); session.close(); - destroyTestBaseData(); + destroyTestBaseData( scope ); } @Test - public void testSimpleSelect() throws Exception { - createTestBaseData(); - Session session = openSession(); + public void testSimpleSelect(SessionFactoryScope scope) throws Exception { + createTestBaseData( scope ); + Session session = scope.getSessionFactory().openSession(); Transaction t = session.beginTransaction(); List results = session.createQuery( "select a from Animal as a" ).list(); assertEquals( "Incorrect result size", 2, results.size() ); assertTrue( "Incorrect result return type", results.get( 0 ) instanceof Animal ); t.commit(); session.close(); - destroyTestBaseData(); + destroyTestBaseData( scope ); } @Test - public void testEntityPropertySelect() throws Exception { - createTestBaseData(); - Session session = openSession(); + public void testEntityPropertySelect(SessionFactoryScope scope) throws Exception { + createTestBaseData( scope ); + Session session = scope.getSessionFactory().openSession(); Transaction t = session.beginTransaction(); List results = session.createQuery( "select a.mother from Animal as a" ).list(); assertTrue( "Incorrect result return type", results.get( 0 ) instanceof Animal ); t.commit(); session.close(); - destroyTestBaseData(); + destroyTestBaseData( scope ); } @Test - public void testWhere() throws Exception { - createTestBaseData(); + public void testWhere(SessionFactoryScope scope) throws Exception { + createTestBaseData( scope ); - Session session = openSession(); + Session session = scope.getSessionFactory().openSession(); Transaction t = session.beginTransaction(); List results = session.createQuery( "from Animal an where an.bodyWeight > 10" ).list(); assertEquals( "Incorrect result size", 1, results.size() ); @@ -3039,14 +3031,14 @@ public void testWhere() throws Exception { t.commit(); session.close(); - destroyTestBaseData(); + destroyTestBaseData( scope ); } @Test - public void testEntityFetching() throws Exception { - createTestBaseData(); + public void testEntityFetching(SessionFactoryScope scope) throws Exception { + createTestBaseData( scope ); - Session session = openSession(); + Session session = scope.getSessionFactory().openSession(); Transaction t = session.beginTransaction(); List results = session.createQuery( "from Animal an join fetch an.mother" ).list(); @@ -3064,14 +3056,14 @@ public void testEntityFetching() throws Exception { t.commit(); session.close(); - destroyTestBaseData(); + destroyTestBaseData( scope ); } @Test - public void testCollectionFetching() throws Exception { - createTestBaseData(); + public void testCollectionFetching(SessionFactoryScope scope) throws Exception { + createTestBaseData( scope ); - Session session = openSession(); + Session session = scope.getSessionFactory().openSession(); Transaction t = session.beginTransaction(); List results = session.createQuery( "from Animal an join fetch an.offspring" ).list(); assertEquals( "Incorrect result size", 1, results.size() ); @@ -3088,12 +3080,12 @@ public void testCollectionFetching() throws Exception { t.commit(); session.close(); - destroyTestBaseData(); + destroyTestBaseData( scope ); } @Test @SuppressWarnings( {"unchecked"}) - public void testJoinFetchedCollectionOfJoinedSubclass() throws Exception { + public void testJoinFetchedCollectionOfJoinedSubclass(SessionFactoryScope scope) throws Exception { Mammal mammal = new Mammal(); mammal.setDescription( "A Zebra" ); Zoo zoo = new Zoo(); @@ -3101,14 +3093,14 @@ public void testJoinFetchedCollectionOfJoinedSubclass() throws Exception { zoo.getMammals().put( "zebra", mammal ); mammal.setZoo( zoo ); - Session session = openSession(); + Session session = scope.getSessionFactory().openSession(); Transaction txn = session.beginTransaction(); session.save( mammal ); session.save( zoo ); txn.commit(); session.close(); - session = openSession(); + session = scope.getSessionFactory().openSession(); txn = session.beginTransaction(); List results = session.createQuery( "from Zoo z join fetch z.mammals" ).list(); assertEquals( "Incorrect result size", 1, results.size() ); @@ -3126,7 +3118,7 @@ public void testJoinFetchedCollectionOfJoinedSubclass() throws Exception { @Test @SuppressWarnings( {"unchecked"}) - public void testJoinedCollectionOfJoinedSubclass() throws Exception { + public void testJoinedCollectionOfJoinedSubclass(SessionFactoryScope scope) throws Exception { Mammal mammal = new Mammal(); mammal.setDescription( "A Zebra" ); Zoo zoo = new Zoo(); @@ -3134,14 +3126,14 @@ public void testJoinedCollectionOfJoinedSubclass() throws Exception { zoo.getMammals().put( "zebra", mammal ); mammal.setZoo( zoo ); - Session session = openSession(); + Session session = scope.getSessionFactory().openSession(); Transaction txn = session.beginTransaction(); session.save( mammal ); session.save( zoo ); txn.commit(); session.close(); - session = openSession(); + session = scope.getSessionFactory().openSession(); txn = session.beginTransaction(); List results = session.createQuery( "select z, m from Zoo z join z.mammals m" ).list(); assertEquals( "Incorrect result size", 1, results.size() ); @@ -3159,7 +3151,7 @@ public void testJoinedCollectionOfJoinedSubclass() throws Exception { @Test @SuppressWarnings( {"unchecked"}) - public void testJoinedCollectionOfJoinedSubclassProjection() throws Exception { + public void testJoinedCollectionOfJoinedSubclassProjection(SessionFactoryScope scope) throws Exception { Mammal mammal = new Mammal(); mammal.setDescription( "A Zebra" ); Zoo zoo = new Zoo(); @@ -3167,14 +3159,14 @@ public void testJoinedCollectionOfJoinedSubclassProjection() throws Exception { zoo.getMammals().put( "zebra", mammal ); mammal.setZoo( zoo ); - Session session = openSession(); + Session session = scope.getSessionFactory().openSession(); Transaction txn = session.beginTransaction(); session.save( mammal ); session.save( zoo ); txn.commit(); session.close(); - session = openSession(); + session = scope.getSessionFactory().openSession(); txn = session.beginTransaction(); List results = session.createQuery( "select z, m from Zoo z join z.mammals m" ).list(); assertEquals( "Incorrect result size", 1, results.size() ); @@ -3191,9 +3183,9 @@ public void testJoinedCollectionOfJoinedSubclassProjection() throws Exception { } @Test - public void testProjectionQueries() throws Exception { - createTestBaseData(); - Session session = openSession(); + public void testProjectionQueries(SessionFactoryScope scope) throws Exception { + createTestBaseData( scope ); + Session session = scope.getSessionFactory().openSession(); Transaction t = session.beginTransaction(); List results = session.createQuery( "select an.mother.id, max(an.bodyWeight) from Animal an group by an.mother.id" ).list(); @@ -3204,13 +3196,13 @@ public void testProjectionQueries() throws Exception { t.commit(); session.close(); - destroyTestBaseData(); + destroyTestBaseData( scope ); } @Test - @SkipForDialect(value = CockroachDialect.class, strictMatching = true) - public void testStandardFunctions() { - Session session = openSession(); + @SkipForDialect(dialectClass = CockroachDialect.class) + public void testStandardFunctions(SessionFactoryScope scope) { + Session session = scope.getSessionFactory().openSession(); Transaction t = session.beginTransaction(); Product p = new Product(); p.setDescription( "a product" ); @@ -3232,10 +3224,10 @@ public void testStandardFunctions() { } @Test - public void testDynamicInstantiationQueries() throws Exception { - createTestBaseData(); + public void testDynamicInstantiationQueries(SessionFactoryScope scope) throws Exception { + createTestBaseData( scope ); - Session session = openSession(); + Session session = scope.getSessionFactory().openSession(); Transaction t = session.beginTransaction(); List results = session.createQuery( "select new Animal(an.description, an.bodyWeight) from Animal an" ).list(); @@ -3281,7 +3273,7 @@ public void testDynamicInstantiationQueries() throws Exception { } // caching... - QueryStatistics stats = sessionFactory().getStatistics().getQueryStatistics( "select new Animal(an.description, an.bodyWeight) from Animal an" ); + QueryStatistics stats = scope.getSessionFactory().getStatistics().getQueryStatistics( "select new Animal(an.description, an.bodyWeight) from Animal an" ); results = session.createQuery( "select new Animal(an.description, an.bodyWeight) from Animal an" ) .setCacheable( true ) .list(); @@ -3298,12 +3290,12 @@ public void testDynamicInstantiationQueries() throws Exception { t.commit(); session.close(); - destroyTestBaseData(); + destroyTestBaseData( scope ); } @Test - @TestForIssue( jiraKey = "HHH-9305") - public void testDynamicInstantiationWithToOneQueries() throws Exception { + @JiraKey( "HHH-9305") + public void testDynamicInstantiationWithToOneQueries(SessionFactoryScope scope) throws Exception { final Employee employee1 = new Employee(); employee1.setFirstName( "Jane" ); employee1.setLastName( "Doe" ); @@ -3321,7 +3313,7 @@ public void testDynamicInstantiationWithToOneQueries() throws Exception { title2.setDescription( "John's title" ); employee2.setTitle( title2 ); - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.getTransaction().begin(); s.persist( title1 ); s.persist( dept1 ); @@ -3336,64 +3328,64 @@ public void testDynamicInstantiationWithToOneQueries() throws Exception { // at the beginning of the FROM clause, avoiding failures on DBs that cannot handle cross joins // interleaved with ANSI joins (e.g., PostgreSql). - s = openSession(); + s = scope.getSessionFactory().openSession(); s.getTransaction().begin(); - List results = session.createQuery( + List results = s.createQuery( "select new Employee(e.id, e.lastName, e.title.id, e.title.description, e.department, e.firstName) from Employee e inner join e.title" ).list(); assertEquals( "Incorrect result size", 1, results.size() ); assertClassAssignability( results.get( 0 ).getClass(), Employee.class ); - results = session.createQuery( + results = s.createQuery( "select new Employee(e.id, e.lastName, t.id, t.description, e.department, e.firstName) from Employee e inner join e.title t" ).list(); assertEquals( "Incorrect result size", 1, results.size() ); assertClassAssignability( results.get( 0 ).getClass(), Employee.class ); - results = session.createQuery( + results = s.createQuery( "select new Employee(e.id, e.lastName, e.title.id, e.title.description, e.department, e.firstName) from Employee e inner join e.department" ).list(); assertEquals( "Incorrect result size", 1, results.size() ); assertClassAssignability( results.get( 0 ).getClass(), Employee.class ); - results = session.createQuery( + results = s.createQuery( "select new Employee(e.id, e.lastName, e.title.id, e.title.description, d, e.firstName) from Employee e inner join e.department d" ).list(); assertEquals( "Incorrect result size", 1, results.size() ); assertClassAssignability( results.get( 0 ).getClass(), Employee.class ); - results = session.createQuery( + results = s.createQuery( "select new Employee(e.id, e.lastName, e.title.id, e.title.description, e.department, e.firstName) from Employee e left outer join e.department" ).list(); assertEquals( "Incorrect result size", 2, results.size() ); assertClassAssignability( results.get( 0 ).getClass(), Employee.class ); - results = session.createQuery( + results = s.createQuery( "select new Employee(e.id, e.lastName, e.title.id, e.title.description, d, e.firstName) from Employee e left outer join e.department d" ).list(); assertEquals( "Incorrect result size", 2, results.size() ); assertClassAssignability( results.get( 0 ).getClass(), Employee.class ); - results = session.createQuery( + results = s.createQuery( "select new Employee(e.id, e.lastName, e.title.id, e.title.description, e.department, e.firstName) from Employee e left outer join e.department inner join e.title" ).list(); assertEquals( "Incorrect result size", 2, results.size() ); assertClassAssignability( results.get( 0 ).getClass(), Employee.class ); - results = session.createQuery( + results = s.createQuery( "select new Employee(e.id, e.lastName, t.id, t.description, d, e.firstName) from Employee e left outer join e.department d inner join e.title t" ).list(); assertEquals( "Incorrect result size", 2, results.size() ); assertClassAssignability( results.get( 0 ).getClass(), Employee.class ); - results = session.createQuery( + results = s.createQuery( "select new Employee(e.id, e.lastName, e.title.id, e.title.description, e.department, e.firstName) from Employee e left outer join e.department left outer join e.title" ).list(); assertEquals( "Incorrect result size", 2, results.size() ); assertClassAssignability( results.get( 0 ).getClass(), Employee.class ); - results = session.createQuery( + results = s.createQuery( "select new Employee(e.id, e.lastName, t.id, t.description, d, e.firstName) from Employee e left outer join e.department d left outer join e.title t" ).list(); assertEquals( "Incorrect result size", 2, results.size() ); assertClassAssignability( results.get( 0 ).getClass(), Employee.class ); - results = session.createQuery( + results = s.createQuery( "select new Employee(e.id, e.lastName, e.title.id, e.title.description, e.department, e.firstName) from Employee e left outer join e.department order by e.title.description" ).list(); assertEquals( "Incorrect result size", 2, results.size() ); assertClassAssignability( results.get( 0 ).getClass(), Employee.class ); - results = session.createQuery( + results = s.createQuery( "select new Employee(e.id, e.lastName, e.title.id, e.title.description, e.department, e.firstName) from Employee e left outer join e.department d order by e.title.description" ).list(); assertEquals( "Incorrect result size", 2, results.size() ); @@ -3402,7 +3394,7 @@ public void testDynamicInstantiationWithToOneQueries() throws Exception { s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); s.getTransaction().begin(); s.delete( employee1 ); s.delete( title1 ); @@ -3415,7 +3407,7 @@ public void testDynamicInstantiationWithToOneQueries() throws Exception { @Test @SuppressWarnings( {"UnusedAssignment"}) - public void testCachedJoinedAndJoinFetchedManyToOne() throws Exception { + public void testCachedJoinedAndJoinFetchedManyToOne(SessionFactoryScope scope) throws Exception { Animal a = new Animal(); a.setDescription( "an animal" ); @@ -3434,7 +3426,7 @@ public void testCachedJoinedAndJoinFetchedManyToOne() throws Exception { a.addOffspring( offspring2 ); offspring2.setMother( a ); - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); s.save( mother ); s.save( a ); @@ -3443,20 +3435,20 @@ public void testCachedJoinedAndJoinFetchedManyToOne() throws Exception { t.commit(); s.close(); - sessionFactory().getCache().evictQueryRegions(); - sessionFactory().getStatistics().clear(); + scope.getSessionFactory().getCache().evictQueryRegions(); + scope.getSessionFactory().getStatistics().clear(); - s = openSession(); + s = scope.getSessionFactory().openSession(); t = s.beginTransaction(); List list = s.createQuery( "from Animal a left join fetch a.mother" ).setCacheable( true ).list(); - assertEquals( 0, sessionFactory().getStatistics().getQueryCacheHitCount() ); - assertEquals( 1, sessionFactory().getStatistics().getQueryCachePutCount() ); + assertEquals( 0, scope.getSessionFactory().getStatistics().getQueryCacheHitCount() ); + assertEquals( 1, scope.getSessionFactory().getStatistics().getQueryCachePutCount() ); list = s.createQuery( "select a from Animal a left join fetch a.mother" ).setCacheable( true ).list(); - assertEquals( 1, sessionFactory().getStatistics().getQueryCacheHitCount() ); - assertEquals( 1, sessionFactory().getStatistics().getQueryCachePutCount() ); + assertEquals( 1, scope.getSessionFactory().getStatistics().getQueryCacheHitCount() ); + assertEquals( 1, scope.getSessionFactory().getStatistics().getQueryCachePutCount() ); list = s.createQuery( "select a, m from Animal a left join a.mother m" ).setCacheable( true ).list(); - assertEquals( 1, sessionFactory().getStatistics().getQueryCacheHitCount() ); - assertEquals( 2, sessionFactory().getStatistics().getQueryCachePutCount() ); + assertEquals( 1, scope.getSessionFactory().getStatistics().getQueryCacheHitCount() ); + assertEquals( 2, scope.getSessionFactory().getStatistics().getQueryCachePutCount() ); list = s.createQuery( "from Animal" ).list(); for(Object obj : list){ s.delete( obj ); @@ -3467,7 +3459,7 @@ public void testCachedJoinedAndJoinFetchedManyToOne() throws Exception { @Test @SuppressWarnings( {"UnusedAssignment", "UnusedDeclaration"}) - public void testCachedJoinedAndJoinFetchedOneToMany() throws Exception { + public void testCachedJoinedAndJoinFetchedOneToMany(SessionFactoryScope scope) throws Exception { Animal a = new Animal(); a.setDescription( "an animal" ); Animal mother = new Animal(); @@ -3483,10 +3475,10 @@ public void testCachedJoinedAndJoinFetchedOneToMany() throws Exception { a.addOffspring( offspring2 ); offspring2.setMother( a ); - sessionFactory().getCache().evictQueryRegions(); - sessionFactory().getStatistics().clear(); + scope.getSessionFactory().getCache().evictQueryRegions(); + scope.getSessionFactory().getStatistics().clear(); - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); Transaction t = s.beginTransaction(); s.save( mother ); s.save( a ); @@ -3495,17 +3487,17 @@ public void testCachedJoinedAndJoinFetchedOneToMany() throws Exception { t.commit(); s.close(); - s = openSession(); + s = scope.getSessionFactory().openSession(); t = s.beginTransaction(); List list = s.createQuery( "from Animal a left join fetch a.offspring" ).setCacheable( true ).list(); - assertEquals( 0, sessionFactory().getStatistics().getQueryCacheHitCount() ); - assertEquals( 1, sessionFactory().getStatistics().getQueryCachePutCount() ); + assertEquals( 0, scope.getSessionFactory().getStatistics().getQueryCacheHitCount() ); + assertEquals( 1, scope.getSessionFactory().getStatistics().getQueryCachePutCount() ); list = s.createQuery( "select a from Animal a left join fetch a.offspring" ).setCacheable( true ).list(); - assertEquals( 1, sessionFactory().getStatistics().getQueryCacheHitCount() ); - assertEquals( 1, sessionFactory().getStatistics().getQueryCachePutCount() ); + assertEquals( 1, scope.getSessionFactory().getStatistics().getQueryCacheHitCount() ); + assertEquals( 1, scope.getSessionFactory().getStatistics().getQueryCachePutCount() ); list = s.createQuery( "select a, o from Animal a left join a.offspring o" ).setCacheable( true ).list(); - assertEquals( 1, sessionFactory().getStatistics().getQueryCacheHitCount() ); - assertEquals( 2, sessionFactory().getStatistics().getQueryCachePutCount() ); + assertEquals( 1, scope.getSessionFactory().getStatistics().getQueryCacheHitCount() ); + assertEquals( 2, scope.getSessionFactory().getStatistics().getQueryCachePutCount() ); list = s.createQuery( "from Animal" ).list(); for ( Object obj : list ) { s.delete( obj ); @@ -3515,9 +3507,9 @@ public void testCachedJoinedAndJoinFetchedOneToMany() throws Exception { } @Test - public void testSelectNewTransformerQueries() { - createTestBaseData(); - Session session = openSession(); + public void testSelectNewTransformerQueries(SessionFactoryScope scope) { + createTestBaseData( scope ); + Session session = scope.getSessionFactory().openSession(); Transaction t = session.beginTransaction(); List list = session.createQuery( "select new Animal(an.description, an.bodyWeight) as animal from Animal an order by an.description" ) .setResultTransformer( Transformers.ALIAS_TO_ENTITY_MAP ) @@ -3531,16 +3523,16 @@ public void testSelectNewTransformerQueries() { assertEquals( "Mammal #2", m2.get( "animal" ).getDescription() ); t.commit(); session.close(); - destroyTestBaseData(); + destroyTestBaseData( scope ); } @Test - public void testResultTransformerScalarQueries() throws Exception { - createTestBaseData(); + public void testResultTransformerScalarQueries(SessionFactoryScope scope) throws Exception { + createTestBaseData( scope ); String query = "select an.description as description, an.bodyWeight as bodyWeight from Animal an order by bodyWeight desc"; - Session session = openSession(); + Session session = scope.getSessionFactory().openSession(); Transaction t = session.beginTransaction(); List results = session.createQuery( query ) @@ -3556,11 +3548,11 @@ public void testResultTransformerScalarQueries() throws Exception { t.commit(); session.close(); - session = openSession(); + session = scope.getSessionFactory().openSession(); t = session.beginTransaction(); try (ScrollableResults sr = session.createQuery( query ) - .setResultTransformer(Transformers.aliasToBean(Animal.class)).scroll()) { + .setResultTransformer( Transformers.aliasToBean( Animal.class ) ).scroll()) { assertTrue( "Incorrect result size", sr.next() ); assertTrue( "Incorrect return type", sr.get() instanceof Animal ); assertFalse( session.contains( sr.get() ) ); @@ -3569,7 +3561,7 @@ public void testResultTransformerScalarQueries() throws Exception { t.commit(); session.close(); - session = openSession(); + session = scope.getSessionFactory().openSession(); t = session.beginTransaction(); results = session.createQuery( "select a from Animal a, Animal b order by a.id" ) @@ -3590,16 +3582,16 @@ public Object transformTuple(Object[] tuple, String[] aliases) { t.commit(); session.close(); - destroyTestBaseData(); + destroyTestBaseData( scope ); } @Test - public void testResultTransformerEntityQueries() throws Exception { - createTestBaseData(); + public void testResultTransformerEntityQueries(SessionFactoryScope scope) throws Exception { + createTestBaseData( scope ); String query = "select an as an from Animal an order by bodyWeight desc"; - Session session = openSession(); + Session session = scope.getSessionFactory().openSession(); Transaction t = session.beginTransaction(); List results = session.createQuery( query ) @@ -3618,7 +3610,7 @@ public void testResultTransformerEntityQueries() throws Exception { t.commit(); session.close(); - session = openSession(); + session = scope.getSessionFactory().openSession(); t = session.beginTransaction(); try (ScrollableResults sr = session.createQuery( query ) @@ -3630,12 +3622,12 @@ public void testResultTransformerEntityQueries() throws Exception { t.commit(); session.close(); - destroyTestBaseData(); + destroyTestBaseData( scope ); } @Test - public void testEJBQLFunctions() throws Exception { - Session session = openSession(); + public void testEJBQLFunctions(SessionFactoryScope scope) throws Exception { + Session session = scope.getSessionFactory().openSession(); Transaction t = session.beginTransaction(); String hql = "from Animal a where a.description = concat('1', concat('2','3'), '4'||'5')||'0'"; @@ -3665,17 +3657,21 @@ public void testEJBQLFunctions() throws Exception { hql = "select length(a.description) from Animal a"; session.createQuery(hql).list(); - //note: postgres and db2 don't have a 3-arg form, it gets transformed to 2-args - hql = "from Animal a where locate('abc', a.description, 2) = 2"; - session.createQuery(hql).list(); + Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect(); + // Informix before version 12 didn't support finding the index of substrings + if ( !( dialect instanceof InformixDialect && dialect.getVersion().isBefore( 12 ) ) ) { + //note: postgres and db2 don't have a 3-arg form, it gets transformed to 2-args + hql = "from Animal a where locate('abc', a.description, 2) = 2"; + session.createQuery( hql ).list(); - hql = "from Animal a where locate('abc', a.description) = 2"; - session.createQuery(hql).list(); + hql = "from Animal a where locate('abc', a.description) = 2"; + session.createQuery( hql ).list(); - hql = "select locate('cat', a.description, 2) from Animal a"; - session.createQuery(hql).list(); + hql = "select locate('cat', a.description, 2) from Animal a"; + session.createQuery( hql ).list(); + } - if ( !( getDialect() instanceof DB2Dialect ) ) { + if ( !( dialect instanceof DB2Dialect ) ) { hql = "from Animal a where trim(trailing '_' from a.description) = 'cat'"; session.createQuery(hql).list(); @@ -3689,7 +3685,7 @@ public void testEJBQLFunctions() throws Exception { session.createQuery(hql).list(); } - if ( !(getDialect() instanceof HSQLDialect) ) { //HSQL doesn't like trim() without specification + if ( !(dialect instanceof HSQLDialect) ) { //HSQL doesn't like trim() without specification hql = "from Animal a where trim(a.description) = 'cat'"; session.createQuery(hql).list(); } @@ -3728,10 +3724,10 @@ public void testEJBQLFunctions() throws Exception { } @Test - @TestForIssue( jiraKey = "HHH-11942" ) - public void testOrderByExtraParenthesis() throws Exception { + @JiraKey( "HHH-11942" ) + public void testOrderByExtraParenthesis(SessionFactoryScope scope) throws Exception { try { - doInHibernate( this::sessionFactory, session -> { + scope.inTransaction( session -> { session.createQuery( "select a from Product a " + "where " + @@ -3750,22 +3746,22 @@ public void testOrderByExtraParenthesis() throws Exception { } @RequiresDialectFeature( - value = DialectChecks.SupportSubqueryAsLeftHandSideInPredicate.class, + feature = DialectFeatureChecks.SupportsSubqueryAsLeftHandSideInPredicate.class, comment = "Database does not support using subquery as singular value expression" ) - public void testSubqueryAsSingularValueExpression() { - assertResultSize( "from Animal x where (select max(a.bodyWeight) from Animal a) in (1,2,3)", 0 ); - assertResultSize( "from Animal x where (select max(a.bodyWeight) from Animal a) between 0 and 100", 0 ); - assertResultSize( "from Animal x where (select max(a.description) from Animal a) like 'big%'", 0 ); - assertResultSize( "from Animal x where (select max(a.bodyWeight) from Animal a) is not null", 0 ); + public void testSubqueryAsSingularValueExpression(SessionFactoryScope scope) { + assertResultSize( scope, "from Animal x where (select max(a.bodyWeight) from Animal a) in (1,2,3)", 0 ); + assertResultSize( scope,"from Animal x where (select max(a.bodyWeight) from Animal a) between 0 and 100", 0 ); + assertResultSize( scope,"from Animal x where (select max(a.description) from Animal a) like 'big%'", 0 ); + assertResultSize( scope,"from Animal x where (select max(a.bodyWeight) from Animal a) is not null", 0 ); } - public void testExistsSubquery() { - assertResultSize( "from Animal x where exists (select max(a.bodyWeight) from Animal a)", 0 ); + public void testExistsSubquery(SessionFactoryScope scope) { + assertResultSize( scope, "from Animal x where exists (select max(a.bodyWeight) from Animal a)", 0 ); } - private void assertResultSize(String hql, int size) { - Session session = openSession(); + private void assertResultSize(SessionFactoryScope scope, String hql, int size) { + Session session = scope.getSessionFactory().openSession(); Transaction txn = session.beginTransaction(); assertEquals( size, session.createQuery(hql).list().size() ); txn.commit(); @@ -3782,14 +3778,16 @@ public void prepare(Query query) { }; private class SyntaxChecker { + private final SessionFactoryScope scope; private final String hql; private final QueryPreparer preparer; - public SyntaxChecker(String hql) { - this( hql, DEFAULT_PREPARER ); + public SyntaxChecker(SessionFactoryScope scope, String hql) { + this( scope, hql, DEFAULT_PREPARER ); } - public SyntaxChecker(String hql, QueryPreparer preparer) { + public SyntaxChecker(SessionFactoryScope scope, String hql, QueryPreparer preparer) { + this.scope = scope; this.hql = hql; this.preparer = preparer; } @@ -3800,7 +3798,7 @@ public void checkAll() { } public SyntaxChecker checkList() { - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); Query query = s.createQuery( hql ); preparer.prepare( query ); @@ -3811,7 +3809,7 @@ public SyntaxChecker checkList() { } public SyntaxChecker checkScroll() { - Session s = openSession(); + Session s = scope.getSessionFactory().openSession(); s.beginTransaction(); Query query = s.createQuery( hql ); preparer.prepare( query ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/HQLTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/HQLTest.java index aa71355e8678..752f6118f586 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/HQLTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/HQLTest.java @@ -25,6 +25,7 @@ import org.hibernate.ScrollableResults; import org.hibernate.Session; import org.hibernate.community.dialect.FirebirdDialect; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.H2Dialect; @@ -1523,6 +1524,7 @@ public void test_hql_aggregate_functions_filter_example() { @SkipForDialect(dialectClass = DerbyDialect.class) @SkipForDialect(dialectClass = SybaseASEDialect.class) @SkipForDialect(dialectClass = FirebirdDialect.class, reason = "order by not supported in list") + @SkipForDialect(dialectClass = InformixDialect.class) public void test_hql_aggregate_functions_within_group_example() { doInJPA(this::entityManagerFactory, entityManager -> { //tag::hql-aggregate-functions-within-group-example[] diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/ManyToOneJoinReuseTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/ManyToOneJoinReuseTest.java index 4722974bc7e9..b62d05aea280 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/ManyToOneJoinReuseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/ManyToOneJoinReuseTest.java @@ -78,7 +78,7 @@ public void joinAndImplicitPath(SessionFactoryScope scope) { query.where( cb.and( root.get( "book" ).isNotNull(), - join.isNotNull() + cb.fk( root.get( "book" ) ).isNotNull() ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/array/PrimitiveByteArrayIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/array/PrimitiveByteArrayIdTest.java index fdc58d6266b5..6acf9d91f470 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/id/array/PrimitiveByteArrayIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/array/PrimitiveByteArrayIdTest.java @@ -13,6 +13,7 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.query.Query; @@ -35,6 +36,7 @@ */ @SkipForDialect(dialectClass = MySQLDialect.class, majorVersion = 5, reason = "BLOB/TEXT column 'id' used in key specification without a key length") @SkipForDialect(dialectClass = OracleDialect.class, matchSubTypes = true, reason = "ORA-02329: column of datatype LOB cannot be unique or a primary key") +@SkipForDialect(dialectClass = InformixDialect.class, reason = "Informix does not support unique / primary constraints on binary columns") @DomainModel( annotatedClasses = PrimitiveByteArrayIdTest.DemoEntity.class ) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/idClass/IdClassSyntheticAttributesTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/idClass/IdClassSyntheticAttributesTest.java index 5c6b6c82a92b..22fb93f9149b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/id/idClass/IdClassSyntheticAttributesTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/idClass/IdClassSyntheticAttributesTest.java @@ -1,8 +1,6 @@ /* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.orm.test.id.idClass; @@ -16,23 +14,23 @@ import static org.assertj.core.api.Assertions.assertThat; @DomainModel( - annotatedClasses = MyEntity.class + annotatedClasses = MyEntity.class ) @SessionFactory public class IdClassSyntheticAttributesTest { - @Jira("https://hibernate.atlassian.net/browse/HHH-18841") - @Test - public void test(DomainModelScope scope) { - final PersistentClass entityBinding = scope.getDomainModel().getEntityBinding(MyEntity.class.getName()); - assertThat(entityBinding.getProperties()).hasSize(2) - .anySatisfy(p -> { - assertThat(p.isSynthetic()).isTrue(); - assertThat(p.getName()).isEqualTo("_identifierMapper"); - }) - .anySatisfy(p -> { - assertThat(p.isSynthetic()).isFalse(); - assertThat(p.getName()).isEqualTo("notes"); - }); - } + @Jira("https://hibernate.atlassian.net/browse/HHH-18841") + @Test + public void test(DomainModelScope scope) { + final PersistentClass entityBinding = scope.getDomainModel().getEntityBinding(MyEntity.class.getName()); + assertThat(entityBinding.getProperties()).hasSize(2) + .anySatisfy(p -> { + assertThat(p.isSynthetic()).isTrue(); + assertThat(p.getName()).isEqualTo("_identifierMapper"); + }) + .anySatisfy(p -> { + assertThat(p.isSynthetic()).isFalse(); + assertThat(p.getName()).isEqualTo("notes"); + }); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/sqlrep/sqlbinary/UUIDBinaryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/sqlrep/sqlbinary/UUIDBinaryTest.java index e59b38b77e4a..9f12fa719818 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/sqlrep/sqlbinary/UUIDBinaryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/sqlrep/sqlbinary/UUIDBinaryTest.java @@ -10,6 +10,7 @@ import java.util.UUID; import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.dialect.SybaseDialect; import org.hibernate.metamodel.MappingMetamodel; @@ -39,6 +40,7 @@ @SkipForDialect(dialectClass = PostgreSQLDialect.class, reason = "Postgres has its own UUID type") @SkipForDialect( dialectClass = SybaseDialect.class, matchSubTypes = true, reason = "Skipped for Sybase to avoid problems with UUIDs potentially ending with a trailing 0 byte") +@SkipForDialect(dialectClass = InformixDialect.class, reason = "Informix does not support unique / primary constraints on binary columns") public class UUIDBinaryTest { private static class UUIDPair { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/idclass/mappedsuperclass/BaseSummary.java b/hibernate-core/src/test/java/org/hibernate/orm/test/idclass/mappedsuperclass/BaseSummary.java new file mode 100644 index 000000000000..822bd83011df --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/idclass/mappedsuperclass/BaseSummary.java @@ -0,0 +1,62 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.idclass.mappedsuperclass; + +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.MappedSuperclass; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Objects; + +@IdClass(PKey.class) +@MappedSuperclass +public class BaseSummary implements Serializable { + + @Id + private Integer year; + @Id + private Integer month; + private BigDecimal value; + + public Integer getYear() { + return year; + } + + public void setYear(Integer year) { + this.year = year; + } + + public Integer getMonth() { + return month; + } + + public void setMonth(Integer month) { + this.month = month; + } + + public BigDecimal getValue() { + return value; + } + + public void setValue(BigDecimal value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + BaseSummary that = (BaseSummary) o; + return Objects.equals( year, that.year ) && Objects.equals( month, that.month ); + } + + @Override + public int hashCode() { + return Objects.hash( year, month ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/idclass/mappedsuperclass/MappedSuperclassIdClassAttributesTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/idclass/mappedsuperclass/MappedSuperclassIdClassAttributesTest.java new file mode 100644 index 000000000000..9782f919a4d3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/idclass/mappedsuperclass/MappedSuperclassIdClassAttributesTest.java @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.idclass.mappedsuperclass; + +import jakarta.persistence.metamodel.SingularAttribute; +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.assertj.core.api.Assertions.assertThat; + +@DomainModel(annotatedClasses = {Summary.class, BaseSummary.class}) +@SessionFactory +@JiraKey("HHH-18858") +public class MappedSuperclassIdClassAttributesTest { + @Test + public void test(SessionFactoryScope scope) { + scope.inSession( entityManager -> { + final var yearAttribute = Summary_.year.getDeclaringType().getAttribute( "year" ); + assertThat( yearAttribute ).isEqualTo( Summary_.year ); + assertThat( ((SingularAttribute) yearAttribute).isId() ).isTrue(); + + final var monthAttribute = Summary_.month.getDeclaringType().getAttribute( "month" ); + assertThat( monthAttribute ).isEqualTo( Summary_.month ); + assertThat( ((SingularAttribute) monthAttribute).isId() ).isTrue(); + } ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/idclass/mappedsuperclass/PKey.java b/hibernate-core/src/test/java/org/hibernate/orm/test/idclass/mappedsuperclass/PKey.java new file mode 100644 index 000000000000..6757d23edcc4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/idclass/mappedsuperclass/PKey.java @@ -0,0 +1,44 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.idclass.mappedsuperclass; + +import java.io.Serializable; +import java.util.Objects; + +public class PKey implements Serializable { + + private Integer year; + private Integer month; + + public Integer getYear() { + return year; + } + + public void setYear(Integer year) { + this.year = year; + } + + public Integer getMonth() { + return month; + } + + public void setMonth(Integer month) { + this.month = month; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + PKey pKey = (PKey) o; + return Objects.equals( year, pKey.year ) && Objects.equals( month, pKey.month ); + } + + @Override + public int hashCode() { + return Objects.hash( year, month ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/idclass/mappedsuperclass/Summary.java b/hibernate-core/src/test/java/org/hibernate/orm/test/idclass/mappedsuperclass/Summary.java new file mode 100644 index 000000000000..108602a2ce16 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/idclass/mappedsuperclass/Summary.java @@ -0,0 +1,11 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.idclass.mappedsuperclass; + +import jakarta.persistence.Entity; + +@Entity +public class Summary extends BaseSummary { +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/InheritanceQueryGroupByTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/InheritanceQueryGroupByTest.java index 56590e7cc0be..f7a43ba705da 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/InheritanceQueryGroupByTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/InheritanceQueryGroupByTest.java @@ -6,6 +6,7 @@ */ package org.hibernate.orm.test.inheritance; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.FunctionalDependencyAnalysisSupport; import org.hibernate.metamodel.mapping.EntityMappingType; @@ -16,6 +17,7 @@ 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.junit.SkipForDialect; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -86,6 +88,7 @@ public void testGroupBySingleTable(SessionFactoryScope scope) { } @Test + @SkipForDialect( dialectClass = InformixDialect.class , reason = "Informix does not support case expressions within the GROUP BY clause") public void testGroupByJoined(SessionFactoryScope scope) { testGroupBy( scope, "joinedParent", JoinedParent.class, "joined_child_one", 1 ); } @@ -149,8 +152,8 @@ private void testGroupByNotSelected( Long.class ).getSingleResult(); assertThat( sum ).isEqualTo( 3L ); - // When not selected, group by should only use the foreign key (parent_id) - statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, parentFkName, 2 ); + // Association is joined, so every use of the join alias will make use of target table columns + statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, parentFkName, 1 ); statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_one_col", childPropCount ); statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_two_col", childPropCount ); } ); @@ -162,6 +165,7 @@ public void testGroupByAndOrderBySingleTable(SessionFactoryScope scope) { } @Test + @SkipForDialect( dialectClass = InformixDialect.class , reason = "Informix does not support case expressions within the GROUP BY clause") public void testGroupByAndOrderByJoined(SessionFactoryScope scope) { testGroupByAndOrderBy( scope, "joinedParent", JoinedParent.class, "joined_child_one", 1 ); } @@ -232,8 +236,8 @@ private void testGroupByAndOrderByNotSelected( Long.class ).getSingleResult(); assertThat( sum ).isEqualTo( 3L ); - // When not selected, group by should only use the foreign key (parent_id) - statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, parentFkName, 3 ); + // Association is joined, so every use of the join alias will make use of target table columns + statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, parentFkName, 1 ); statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_one_col", childPropCount ); statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_two_col", childPropCount ); } ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/JoinedInheritanceDiscriminatorSelectionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/JoinedInheritanceDiscriminatorSelectionTest.java index 932c6ae6ffa9..c14cf0d79eee 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/JoinedInheritanceDiscriminatorSelectionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/JoinedInheritanceDiscriminatorSelectionTest.java @@ -38,6 +38,7 @@ @SessionFactory( useCollectingStatementInspector = true ) @Jira( "https://hibernate.atlassian.net/browse/HHH-17727" ) @Jira( "https://hibernate.atlassian.net/browse/HHH-17806" ) +@Jira( "https://hibernate.atlassian.net/browse/HHH-18583" ) public class JoinedInheritanceDiscriminatorSelectionTest { @BeforeAll public void setUp(SessionFactoryScope scope) { @@ -51,7 +52,7 @@ public void setUp(SessionFactoryScope scope) { @AfterAll public void tearDown(SessionFactoryScope scope) { - scope.inTransaction( session -> session.createMutationQuery( "delete from ParentEntity" ).executeUpdate() ); + scope.getSessionFactory().getSchemaManager().truncateMappedObjects(); } @Test @@ -93,6 +94,23 @@ public void testSelectParentAttribute(SessionFactoryScope scope) { String.class ).getResultList() ).containsOnly( "parent", "child_a" ); inspector.assertNumberOfJoins( 0, 0 ); + inspector.clear(); + + // With treat() we preserve the join + + assertThat( session.createQuery( + "select p.name from ParentEntity p where treat(p as ChildA).id is not null", + String.class + ).getResultList() ).containsExactlyInAnyOrder( "child_a", "sub_child_a" ); + inspector.assertNumberOfJoins( 0, 1 ); + inspector.clear(); + + assertThat( session.createQuery( + "select p.name from ParentEntity p where treat(p as ChildB).id is not null", + String.class + ).getSingleResult() ).isEqualTo( "child_b" ); + inspector.assertNumberOfJoins( 0, 1 ); + inspector.clear(); } ); } @@ -123,8 +141,9 @@ public void testSelectInstance(SessionFactoryScope scope) { inspector.clear(); scope.inTransaction( session -> { - // NOTE: we currently always join all subclasses when selecting the entity instance. We could - // maybe avoid this when we have a physical discriminator column and a type filter + // With type filters we still join all subclasses when selecting the entity instance + // because we are not aware of the type restriction when processing the selection + assertThat( session.createQuery( "from ParentEntity p where type(p) = ParentEntity", ParentEntity.class @@ -144,6 +163,22 @@ public void testSelectInstance(SessionFactoryScope scope) { ParentEntity.class ).getResultList() ).hasSize( 1 ); inspector.assertNumberOfJoins( 0, 3 ); + inspector.clear(); + + // With treat() we only join the needed subclasses + + assertThat( session.createQuery( + "select treat(p as ChildA) from ParentEntity p", + ParentEntity.class + ).getResultList() ).hasSize( 2 ); + inspector.assertNumberOfJoins( 0, 2 ); + inspector.clear(); + + assertThat( session.createQuery( + "select treat(p as ChildB) from ParentEntity p", + ParentEntity.class + ).getResultList() ).hasSize( 1 ); + inspector.assertNumberOfJoins( 0, 1 ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/QueryBuilderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/QueryBuilderTest.java index 8c0abed81806..5c5996a90fa8 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/QueryBuilderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/QueryBuilderTest.java @@ -20,6 +20,8 @@ import jakarta.persistence.criteria.SetJoin; import jakarta.persistence.metamodel.EntityType; import jakarta.persistence.metamodel.Metamodel; + +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.CockroachDialect; import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; import org.hibernate.orm.test.jpa.metamodel.Address; @@ -38,8 +40,8 @@ import org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate; import org.hibernate.testing.FailureExpected; -import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.SkipForDialect; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; @@ -229,7 +231,7 @@ public void testMultiselectWithPredicates() { } @Test - @SkipForDialect(value = CockroachDialect.class, strictMatching = true) + @SkipForDialect(dialectClass = CockroachDialect.class) public void testDateTimeFunctions() { EntityManager em = getOrCreateEntityManager(); em.getTransaction().begin(); @@ -255,6 +257,7 @@ public void testDateTimeFunctions() { } @Test + @SkipForDialect(dialectClass = InformixDialect.class, majorVersion = 11, minorVersion = 70, reason = "Informix does not support count literals") public void testFunctionDialectFunctions() { EntityManager em = getOrCreateEntityManager(); em.getTransaction().begin(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ql/MapIssueTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ql/MapIssueTest.java index c735fa79af3d..64705998e664 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ql/MapIssueTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ql/MapIssueTest.java @@ -52,13 +52,27 @@ public void testOnlyCollectionTableJoined(SessionFactoryScope scope) { } @Test - public void testMapKeyJoinIsOmitted(SessionFactoryScope scope) { + public void testMapKeyJoinIsNotOmitted(SessionFactoryScope scope) { SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); scope.inTransaction( s -> { s.createQuery( "select c from MapOwner as o join o.contents c join c.relationship r where r.id is not null" ).list(); statementInspector.assertExecutedCount( 1 ); + // Assert 3 joins, collection table, collection element and collection key (relationship) + statementInspector.assertNumberOfJoins( 0, 3 ); + } + ); + } + + @Test + public void testMapKeyJoinIsOmitted2(SessionFactoryScope scope) { + SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + scope.inTransaction( + s -> { + s.createQuery( "select c from MapOwner as o join o.contents c where c.relationship.id is not null" ).list(); + statementInspector.assertExecutedCount( 1 ); // Assert 2 joins, collection table and collection element. No need to join the relationship because it is not nullable statementInspector.assertNumberOfJoins( 0, 2 ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/length/LengthTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/length/LengthTest.java index d1aba0d93c2c..bc1178e65fd9 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/length/LengthTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/length/LengthTest.java @@ -1,6 +1,7 @@ package org.hibernate.orm.test.length; import org.hibernate.Length; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.Dialect; import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.type.SqlTypes; @@ -8,12 +9,14 @@ import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.SkipForDialect; import org.junit.jupiter.api.Test; import static org.junit.Assert.assertEquals; @SessionFactory @DomainModel(annotatedClasses = {WithLongStrings.class,WithLongTypeStrings.class}) +@SkipForDialect(dialectClass = InformixDialect.class, reason = "Informix rowsize to exceed the allowable limit (32767).") public class LengthTest { @Test public void testLength(SessionFactoryScope scope) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/JsonMappingTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/JsonMappingTests.java index f3187767f214..6195e5556d1d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/JsonMappingTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/JsonMappingTests.java @@ -12,25 +12,24 @@ import java.util.List; import java.util.Map; -import com.fasterxml.jackson.databind.JsonNode; -import jakarta.json.JsonValue; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.cfg.AvailableSettings; import org.hibernate.community.dialect.AltibaseDialect; import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.OracleDialect; -import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.dialect.SybaseDialect; import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping; import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.query.MutationQuery; +import org.hibernate.query.criteria.HibernateCriteriaBuilder; import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.SessionFactory; @@ -41,15 +40,19 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import com.fasterxml.jackson.databind.JsonNode; +import jakarta.json.JsonValue; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Table; +import jakarta.persistence.criteria.CriteriaUpdate; +import jakarta.persistence.criteria.Root; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.isOneOf; import static org.hamcrest.Matchers.isA; +import static org.hamcrest.Matchers.isOneOf; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; @@ -124,7 +127,8 @@ public void verifyMappings(SessionFactoryScope scope) { "objectMap" ); final BasicAttributeMapping listAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( "list" ); - final BasicAttributeMapping jsonAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( "jsonString" ); + final BasicAttributeMapping jsonAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( + "jsonString" ); assertThat( stringMapAttribute.getJavaType().getJavaTypeClass(), equalTo( Map.class ) ); assertThat( objectMapAttribute.getJavaType().getJavaTypeClass(), equalTo( Map.class ) ); @@ -146,8 +150,8 @@ public void verifyReadWorks(SessionFactoryScope scope) { assertThat( entityWithJson.stringMap, is( stringMap ) ); assertThat( entityWithJson.objectMap, is( objectMap ) ); assertThat( entityWithJson.list, is( list ) ); - assertThat( entityWithJson.jsonNode, is( nullValue() )); - assertThat( entityWithJson.jsonValue, is( nullValue() )); + assertThat( entityWithJson.jsonNode, is( nullValue() ) ); + assertThat( entityWithJson.jsonValue, is( nullValue() ) ); } ); } @@ -167,14 +171,14 @@ public void verifyMergeWorks(SessionFactoryScope scope) { assertThat( entityWithJson.objectMap, is( nullValue() ) ); assertThat( entityWithJson.list, is( nullValue() ) ); assertThat( entityWithJson.jsonString, is( nullValue() ) ); - assertThat( entityWithJson.jsonNode, is( nullValue() )); - assertThat( entityWithJson.jsonValue, is( nullValue() )); + assertThat( entityWithJson.jsonNode, is( nullValue() ) ); + assertThat( entityWithJson.jsonValue, is( nullValue() ) ); } ); } @Test - @JiraKey( "HHH-16682" ) + @JiraKey("HHH-16682") public void verifyDirtyChecking(SessionFactoryScope scope) { scope.inTransaction( (session) -> { @@ -198,7 +202,7 @@ public void verifyDirtyChecking(SessionFactoryScope scope) { @SkipForDialect(dialectClass = AltibaseDialect.class, reason = "Altibase doesn't support comparing CLOBs with the = operator") public void verifyComparisonWorks(SessionFactoryScope scope) { scope.inTransaction( - (session) -> { + (session) -> { // PostgreSQL returns the JSON slightly formatted String alternativeJson = "{\"name\": \"abc\"}"; EntityWithJson entityWithJson = session.createQuery( @@ -243,6 +247,32 @@ else if ( nativeJson instanceof Clob ) { ); } + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-18709") + public void verifyCriteriaUpdateQueryWorks(SessionFactoryScope scope) { + final Map newMap = Map.of( "name", "ABC" ); + final List newList = List.of( new StringNode( "ABC" ) ); + final String newJson = "{\"count\":123}"; + scope.inTransaction( session -> { + final HibernateCriteriaBuilder builder = session.getCriteriaBuilder(); + final CriteriaUpdate criteria = builder.createCriteriaUpdate( EntityWithJson.class ); + final Root root = criteria.from( EntityWithJson.class ); + criteria.set( root.get( "stringMap" ), newMap ); + criteria.set( "list", newList ); + criteria.set( root.get( "jsonString" ), newJson ); + criteria.where( builder.equal( root.get( "id" ), 1 ) ); + final MutationQuery query = session.createMutationQuery( criteria ); + final int count = query.executeUpdate(); + assertThat( count, is( 1 ) ); + } ); + scope.inSession( session -> { + final EntityWithJson entityWithJson = session.find( EntityWithJson.class, 1 ); + assertThat( entityWithJson.stringMap, is( newMap ) ); + assertThat( entityWithJson.list, is( newList ) ); + assertThat( entityWithJson.jsonString.replaceAll( "\\s", "" ), is( newJson ) ); + } ); + } + @Entity(name = "EntityWithJson") @Table(name = "EntityWithJson") public static class EntityWithJson { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/always/GeneratedAlwaysTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/always/GeneratedAlwaysTest.java index ec6ac851b948..0fd10efb51d4 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/always/GeneratedAlwaysTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/always/GeneratedAlwaysTest.java @@ -6,6 +6,7 @@ import org.hibernate.annotations.GeneratedColumn; import org.hibernate.community.dialect.AltibaseDialect; import org.hibernate.dialect.DerbyDialect; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.PostgreSQLDialect; @@ -33,6 +34,7 @@ @SkipForDialect(dialectClass = PostgreSQLDialect.class, majorVersion = 10, matchSubTypes = true) @SkipForDialect(dialectClass = PostgreSQLDialect.class, majorVersion = 11, matchSubTypes = true) // 'generated always' was added in 12 @SkipForDialect(dialectClass = AltibaseDialect.class, reason = "generated always is not supported in Altibase") +@SkipForDialect(dialectClass = InformixDialect.class) public class GeneratedAlwaysTest { @Test diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/merge/BidirectionalOneToManyMergeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/merge/BidirectionalOneToManyMergeTest.java new file mode 100644 index 000000000000..d9261f6be747 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/merge/BidirectionalOneToManyMergeTest.java @@ -0,0 +1,178 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.merge; + +import org.hibernate.testing.orm.junit.JiraKey; +import org.junit.Before; +import org.junit.Test; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Lisandro Fernandez (kelechul at gmail dot com) + */ +@JiraKey("HHH-13815") +public class BidirectionalOneToManyMergeTest extends org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + Post.class, + PostComment.class, + }; + } + + @Before + public void setUp() { + doInJPA(this::entityManagerFactory, entityManager -> { + entityManager.persist( + new Post("High-Performance Java Persistence").setId(1L) + ); + }); + } + + @Test + public void testMerge() { + doInJPA(this::entityManagerFactory, entityManager -> { + Post post = entityManager.find(Post.class, 1L); + post.addComment(new PostComment("This post rocks!", post)); + post.getComments().isEmpty(); + entityManager.merge(post); + }); + } + + @Entity + public static class Post { + + @Id + private Long id; + + private String title; + + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) + private List comments = new ArrayList<>(); + + public Post() { + } + + public Post(String title) { + this.title = title; + } + + public Long getId() { + return id; + } + + public Post setId(Long id) { + this.id = id; + return this; + } + + public String getTitle() { + return title; + } + + public Post setTitle(String title) { + this.title = title; + return this; + } + + public List getComments() { + return comments; + } + + private Post setComments(List comments) { + this.comments = comments; + return this; + } + + public Post addComment(PostComment comment) { + comments.add(comment); + comment.setPost(this); + + return this; + } + + public Post removeComment(PostComment comment) { + comments.remove(comment); + comment.setPost(null); + + return this; + } + } + + @Entity + public static class PostComment { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String review; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id") + private Post post; + + public PostComment() { + } + + public PostComment(String review, Post post) { + this.review = review; + this.post = post; + } + + public Long getId() { + return id; + } + + public PostComment setId(Long id) { + this.id = id; + return this; + } + + public String getReview() { + return review; + } + + public PostComment setReview(String review) { + this.review = review; + return this; + } + + public Post getPost() { + return post; + } + + public PostComment setPost(Post post) { + this.post = post; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PostComment)) return false; + return id != null && id.equals(((PostComment) o).getId()); + } + + @Override + public int hashCode() { + return 31; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/embeddedid/OneToOneJoinColumnsEmbeddedIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/embeddedid/OneToOneJoinColumnsEmbeddedIdTest.java new file mode 100644 index 000000000000..7e0c2375ea98 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/embeddedid/OneToOneJoinColumnsEmbeddedIdTest.java @@ -0,0 +1,195 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.onetoone.embeddedid; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinColumns; +import jakarta.persistence.OneToOne; +import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced; +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.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@DomainModel( + annotatedClasses = { + OneToOneJoinColumnsEmbeddedIdTest.EntityA.class, + OneToOneJoinColumnsEmbeddedIdTest.EntityB.class, + } +) +@SessionFactory +@BytecodeEnhanced(runNotEnhancedAsWell = true) +@JiraKey("HHH-17838") +public class OneToOneJoinColumnsEmbeddedIdTest { + + @BeforeEach + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + EntityAKey entityAKey = new EntityAKey( 1, "1" ); + EntityA entityA = new EntityA( entityAKey, "te1" ); + + EntityBKey entityBKey = new EntityBKey( 1, "1" ); + EntityB entityB = new EntityB( entityBKey, entityA ); + + session.persist( entityA ); + session.persist( entityB ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncateMappedObjects(); + } + + @Test + public void testFind(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + EntityAKey entityAKey = new EntityAKey( 1, "1" ); + EntityA entityA = session.find( EntityA.class, entityAKey ); + assertThat( entityA ).isNotNull(); + + EntityB entityB = entityA.getEntityB(); + assertThat( entityB ).isNotNull(); + + EntityBKey key = entityB.getEntityBKey(); + assertThat( key.id1 ).isEqualTo( 1 ); + assertThat( key.id2 ).isEqualTo( "1" ); + } + ); + } + + @Test + public void testFind2(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + EntityBKey entityBKey = new EntityBKey( 1, "1" ); + EntityB entityB = session.find( EntityB.class, entityBKey ); + assertThat( entityB ).isNotNull(); + + EntityA entityA = entityB.getEntityA(); + assertThat( entityA ).isNotNull(); + + EntityAKey entityAKey = entityA.getEntityAKey(); + assertThat( entityAKey.id1 ).isEqualTo( 1 ); + assertThat( entityAKey.id2 ).isEqualTo( "1" ); + + assertThat( entityA.getName() ).isEqualTo( "te1" ); + } + ); + } + + @Entity(name = "EntityA") + public static class EntityA { + + @EmbeddedId + private EntityAKey entityAKey; + + private String name; + + @OneToOne(mappedBy = "entityA", fetch = FetchType.LAZY) + private EntityB entityB; + + public EntityA() { + } + + public EntityA(EntityAKey key, String name) { + this.entityAKey = key; + this.name = name; + } + + public EntityAKey getEntityAKey() { + return entityAKey; + } + + public String getName() { + return name; + } + + public EntityB getEntityB() { + return entityB; + } + } + + @Embeddable + public static class EntityAKey { + + @Column(name = "id1") + private Integer id1; + + @Column(name = "id2") + private String id2; + + public EntityAKey() { + } + + public EntityAKey(Integer id1, String id2) { + this.id1 = id1; + this.id2 = id2; + } + } + + @Entity(name = "EntityB") + public static class EntityB { + @EmbeddedId + private EntityBKey entityBKey; + + @OneToOne(optional = false, fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name = "id1", referencedColumnName = "id1", nullable = false, + insertable = false, updatable = false), + @JoinColumn(name = "id2", referencedColumnName = "id2", nullable = false, + insertable = false, updatable = false) + }) + private EntityA entityA; + + public EntityB() { + } + + public EntityB(EntityBKey key, EntityA testEntity) { + this.entityBKey = key; + this.entityA = testEntity; + testEntity.entityB = this; + } + + public EntityBKey getEntityBKey() { + return entityBKey; + } + + public EntityA getEntityA() { + return entityA; + } + } + + @Embeddable + public static class EntityBKey { + + @Column(name = "id1") + private Integer id1; + + @Column(name = "id2") + private String id2; + + public EntityBKey() { + } + + public EntityBKey(Integer documentType, String no) { + this.id1 = documentType; + this.id2 = no; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/proxy/concrete/ConcreteProxyToOneSecondLevelCacheTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/proxy/concrete/ConcreteProxyToOneSecondLevelCacheTest.java new file mode 100644 index 000000000000..e9380cc4bc66 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/proxy/concrete/ConcreteProxyToOneSecondLevelCacheTest.java @@ -0,0 +1,220 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.proxy.concrete; + +import jakarta.persistence.Cacheable; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import org.hibernate.Hibernate; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.ConcreteProxy; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.Statistics; +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.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@DomainModel(annotatedClasses = { + ConcreteProxyToOneSecondLevelCacheTest.TestNode.class, + ConcreteProxyToOneSecondLevelCacheTest.TestCompositeNode.class +}) +@ServiceRegistry(settings = { + @Setting(name = AvailableSettings.GENERATE_STATISTICS, value = "true"), + @Setting(name = AvailableSettings.USE_SECOND_LEVEL_CACHE, value = "true") +}) +@SessionFactory +@BytecodeEnhanced(runNotEnhancedAsWell = true) +@Jira("https://hibernate.atlassian.net/browse/HHH-18872") +public class ConcreteProxyToOneSecondLevelCacheTest { + @Test + public void testToOneInCacheGetReference(SessionFactoryScope scope) { + final Statistics stats = scope.getSessionFactory().getStatistics(); + stats.clear(); + // load only the node with ID 1 into the 2LC + scope.inTransaction( session -> { + assertThat( session.find( TestCompositeNode.class, 1 ) ).isNotNull(); + assertThat( session.find( TestCompositeNode.class, 2 ) ).isNotNull(); + } ); + assertCacheStats( stats, 0, 2, 2 ); + scope.inSession( session -> { + final TestCompositeNode node1 = session.getReference( TestCompositeNode.class, 1 ); + assertThat( Hibernate.isInitialized( node1 ) ).isFalse(); + // this triggers node1 initialization, but should maintain laziness for parent + final TestNode parent = node1.getParent(); + assertThat( Hibernate.isInitialized( node1 ) ).isTrue(); + // node 1 will be loaded from cache + assertCacheStats( stats, 1, 2, 2 ); + assertParent( parent, stats, 2 ); + } ); + } + + @Test + public void testToOneNotInCacheGetReference(SessionFactoryScope scope) { + final Statistics stats = scope.getSessionFactory().getStatistics(); + stats.clear(); + // load only the node with ID 1 into the 2LC + scope.inTransaction( session -> assertThat( session.find( TestCompositeNode.class, 1 ) ).isNotNull() ); + assertCacheStats( stats, 0, 1, 1 ); + scope.inSession( session -> { + final TestCompositeNode node1 = session.getReference( TestCompositeNode.class, 1 ); + assertThat( Hibernate.isInitialized( node1 ) ).isFalse(); + // this triggers node1 initialization, but should maintain laziness for parent + final TestNode parent = node1.getParent(); + assertThat( Hibernate.isInitialized( node1 ) ).isTrue(); + // node 1 will be loaded from cache + assertCacheStats( stats, 1, 1, 1 ); + assertParent( parent, stats, 1 ); + } ); + } + + @Test + public void testToOneInCacheFind(SessionFactoryScope scope) { + final Statistics stats = scope.getSessionFactory().getStatistics(); + stats.clear(); + // load only the node with ID 1 into the 2LC + scope.inTransaction( session -> { + assertThat( session.find( TestCompositeNode.class, 1 ) ).isNotNull(); + assertThat( session.find( TestCompositeNode.class, 2 ) ).isNotNull(); + } ); + assertCacheStats( stats, 0, 2, 2 ); + scope.inSession( session -> { + final TestCompositeNode node1 = session.find( TestCompositeNode.class, 1 ); + assertThat( Hibernate.isInitialized( node1 ) ).isTrue(); + // node 1 will be loaded from cache + assertCacheStats( stats, 1, 2, 2 ); + assertParent( node1.getParent(), stats, 2 ); + } ); + } + + @Test + public void testToOneNotInCacheFind(SessionFactoryScope scope) { + final Statistics stats = scope.getSessionFactory().getStatistics(); + stats.clear(); + // load only the node with ID 1 into the 2LC + scope.inTransaction( session -> assertThat( session.find( TestCompositeNode.class, 1 ) ).isNotNull() ); + assertCacheStats( stats, 0, 1, 1 ); + scope.inSession( session -> { + final TestCompositeNode node1 = session.find( TestCompositeNode.class, 1 ); + assertThat( Hibernate.isInitialized( node1 ) ).isTrue(); + // node 1 will be loaded from cache + assertCacheStats( stats, 1, 1, 1 ); + assertParent( node1.getParent(), stats, 1 ); + } ); + } + + private static void assertParent(final TestNode parent, final Statistics stats, final long hits) { + assertThat( TestCompositeNode.class ).as( "Expecting parent proxy to be narrowed to concrete type" ) + .isAssignableFrom( parent.getClass() ); + final TestCompositeNode parentComposite = (TestCompositeNode) parent; + assertThat( Hibernate.isInitialized( parentComposite ) ).isFalse(); + assertThat( parentComposite.getName() ).isEqualTo( "parent_node" ); + assertThat( Hibernate.isInitialized( parentComposite ) ).isTrue(); + // node 2 will not be found in cache + assertCacheStats( stats, hits, 2, 2 ); + assertThat( parentComposite ).as( String.format( + "Expecting parent to be an instance of TestCompositeNode but was: [%s]", + parent.getClass() + ) ).isInstanceOf( TestCompositeNode.class ); + } + + private static void assertCacheStats(final Statistics stats, final long hits, final long misses, final long puts) { + assertThat( stats.getSecondLevelCacheHitCount() ).isEqualTo( hits ); + assertThat( stats.getSecondLevelCacheMissCount() ).isEqualTo( misses ); + assertThat( stats.getSecondLevelCachePutCount() ).isEqualTo( puts ); + } + + @BeforeEach + public void clearCache(SessionFactoryScope scope) { + scope.getSessionFactory().getCache().evictAllRegions(); + } + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TestCompositeNode node1 = new TestCompositeNode( 1, "child_node" ); + final TestCompositeNode node2 = new TestCompositeNode( 2, "parent_node" ); + node1.setParent( node2 ); + session.persist( node1 ); + session.persist( node2 ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncateMappedObjects(); + } + + @Entity(name = "TestNode") + @Cacheable + @ConcreteProxy + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + @DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING, name = "disc_col") + @DiscriminatorValue(value = "simple") + public static class TestNode { + @Id + private Integer id; + + private String name; + + public TestNode() { + } + + public TestNode(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + } + + @Entity + @DiscriminatorValue("composite") + public static class TestCompositeNode extends TestNode { + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id") + private TestNode parent; + + public TestCompositeNode() { + } + + public TestCompositeNode(Integer id, String name) { + super( id, name ); + } + + public TestNode getParent() { + return parent; + } + + public void setParent(TestNode parent) { + this.parent = parent; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/CteTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/CteTests.java index f52896c7742e..f4ed251a4a5f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/CteTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/CteTests.java @@ -268,11 +268,11 @@ public void testInSubquery(SessionFactoryScope scope) { final QueryImplementor query = session.createQuery( "select c.name.first from Contact c where c.id in (" + - "with contacts as (" + + "with cte as (" + "select c.id id, c.name.first firstName from Contact c " + "where c.id in (1,2)" + ") " + - "select c.id from contacts c" + + "select c.id from cte c" + ")", String.class ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/QueryTimeOutTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/QueryTimeOutTest.java index 69937a770b76..7cf09d2decec 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/QueryTimeOutTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/QueryTimeOutTest.java @@ -12,6 +12,7 @@ import java.util.Map; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.AbstractTransactSQLDialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.SybaseDialect; @@ -80,6 +81,9 @@ else if ( DialectContext.getDialect() instanceof SybaseDialect ) { else if ( DialectContext.getDialect() instanceof AbstractTransactSQLDialect ) { baseQuery = "update ae1_0 set name=? from AnEntity ae1_0"; } + else if (DialectContext.getDialect() instanceof InformixDialect ) { + baseQuery = "update AnEntity set name=?"; + } else { baseQuery = "update AnEntity ae1_0 set name=?"; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CountQueryTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CountQueryTests.java index aa5215dfc51d..ad3a721c9a14 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CountQueryTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CountQueryTests.java @@ -88,6 +88,48 @@ public void testForHHH17967(SessionFactoryScope scope) { ); } + @Test + @JiraKey( "HHH-18850" ) + public void testForHHH18850(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + HibernateCriteriaBuilder cb = session.getCriteriaBuilder(); + JpaCriteriaQuery cq = cb.createQuery( Contract.class ); + cq.distinct( true ); + Root root = cq.from( Contract.class ); + cq.select( root ); + cq.orderBy( cb.asc( root.get( "customerName" ) ) ); + TypedQuery query = session.createQuery( cq.createCountQuery() ); + try { + // Leads to NPE on pre-6.5 versions + query.getSingleResult(); + } + catch (Exception e) { + fail( e ); + } + } + ); + + scope.inTransaction( + session -> { + HibernateCriteriaBuilder cb = session.getCriteriaBuilder(); + JpaCriteriaQuery cq = cb.createQuery( Contract.class ); + cq.distinct( false ); + Root root = cq.from( Contract.class ); + cq.select( root ); + cq.orderBy( cb.desc( root.get( "customerName" ) ) ); + TypedQuery query = session.createQuery( cq.createCountQuery() ); + try { + // Leads to NPE on pre-6.5 versions + query.getSingleResult(); + } + catch (Exception e) { + fail( e ); + } + } + ); + } + @Test @JiraKey("HHH-17410") public void testBasic(SessionFactoryScope scope) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java index 1809da568c51..92b4355d84ee 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java @@ -12,6 +12,7 @@ import org.hibernate.JDBCException; import org.hibernate.QueryException; import org.hibernate.community.dialect.AltibaseDialect; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.DerbyDialect; @@ -606,6 +607,7 @@ public void testDateTruncFunction(SessionFactoryScope scope) { @Test @SkipForDialect(dialectClass = DerbyDialect.class, reason = "Derby doesn't support any form of date truncation") @SkipForDialect(dialectClass = OracleDialect.class, reason = "See HHH-16442, Oracle trunc() throws away the timezone") + @SkipForDialect(dialectClass = InformixDialect.class, reason = "Informix doesn't support any form of date truncation") public void testDateTruncWithOffsetFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { @@ -1108,6 +1110,7 @@ public void testCastDoubleToString(SessionFactoryScope scope) { @SkipForDialect(dialectClass = OracleDialect.class, reason = "Oracle treats the cast value as a hexadecimal literal") @SkipForDialect(dialectClass = HSQLDialect.class, reason = "HSQL treats the cast value as a hexadecimal literal") @SkipForDialect(dialectClass = AltibaseDialect.class, reason = "Altibase doesn't support casting varchar to binary") + @SkipForDialect(dialectClass = InformixDialect.class, reason = "Informix doesn't support casting varchar to byte") public void testCastFunctionBinary(SessionFactoryScope scope) { scope.inTransaction( session -> { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/LiteralTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/LiteralTests.java index 2aef7e6ceddc..ef0a7ad92973 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/LiteralTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/LiteralTests.java @@ -13,6 +13,7 @@ import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.SkipForDialect; import org.junit.jupiter.api.Test; import java.math.BigDecimal; @@ -25,6 +26,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; @@ -45,6 +47,7 @@ public class LiteralTests { @Test + @SkipForDialect(dialectClass = InformixDialect.class, reason = "Informix does not support binary literals") public void testBinaryLiteral(SessionFactoryScope scope) { scope.inTransaction( session -> { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/instantiation/InstantiationWithGenericsExpressionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/instantiation/InstantiationWithGenericsExpressionTest.java new file mode 100644 index 000000000000..78592a687767 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/instantiation/InstantiationWithGenericsExpressionTest.java @@ -0,0 +1,224 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.query.hql.instantiation; + +import java.io.Serializable; + +import org.hibernate.annotations.Imported; + +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.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@DomainModel(annotatedClasses = { + InstantiationWithGenericsExpressionTest.AbstractEntity.class, + InstantiationWithGenericsExpressionTest.ConcreteEntity.class, + InstantiationWithGenericsExpressionTest.ConstructorDto.class, + InstantiationWithGenericsExpressionTest.InjectionDto.class, +}) +@SessionFactory +@Jira("https://hibernate.atlassian.net/browse/HHH-18218") +public class InstantiationWithGenericsExpressionTest { + + @BeforeEach + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final ConcreteEntity entity = new ConcreteEntity(); + entity.setId( 1 ); + entity.setGen( 1L ); + entity.setData( "entity_1" ); + session.persist( entity ); + } ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createMutationQuery( "delete from ConcreteEntity" ).executeUpdate() ); + } + + @Test + public void testConstructorBinaryExpression(SessionFactoryScope scope) { + scope.inTransaction( session -> assertThat( session.createQuery( + "select new ConstructorDto(e.gen+e.gen, e.data) from ConcreteEntity e", + ConstructorDto.class + ).getSingleResult() ).extracting( ConstructorDto::getGen, ConstructorDto::getData ) + .containsExactly( 2L, "entity_1" ) ); + } + + @Test + public void testImplicitConstructorBinaryExpression(SessionFactoryScope scope) { + scope.inTransaction( session -> assertThat( session.createQuery( + "select e.gen+e.gen, e.data from ConcreteEntity e", + ConstructorDto.class + ).getSingleResult() ).extracting( ConstructorDto::getGen, ConstructorDto::getData ) + .containsExactly( 2L, "entity_1" ) ); + } + + @Test + public void testInjectionBinaryExpression(SessionFactoryScope scope) { + scope.inTransaction( session -> assertThat( session.createQuery( + "select new InjectionDto(e.gen+e.gen as gen, e.data as data) from ConcreteEntity e", + InjectionDto.class + ).getSingleResult() ).extracting( InjectionDto::getGen, InjectionDto::getData ) + .containsExactly( 2L, "entity_1" ) ); + } + + @Test + public void testConstructorUnaryExpression(SessionFactoryScope scope) { + scope.inTransaction( session -> assertThat( session.createQuery( + "select new ConstructorDto(-e.gen, e.data) from ConcreteEntity e", + ConstructorDto.class + ).getSingleResult() ).extracting( ConstructorDto::getGen, ConstructorDto::getData ) + .containsExactly( -1L, "entity_1" ) ); + } + + @Test + public void testImplicitConstructorUnaryExpression(SessionFactoryScope scope) { + scope.inTransaction( session -> assertThat( session.createQuery( + "select -e.gen, e.data from ConcreteEntity e", + ConstructorDto.class + ).getSingleResult() ).extracting( ConstructorDto::getGen, ConstructorDto::getData ) + .containsExactly( -1L, "entity_1" ) ); + } + + @Test + public void testInjectionUnaryExpression(SessionFactoryScope scope) { + scope.inTransaction( session -> assertThat( session.createQuery( + "select new InjectionDto(-e.gen as gen, e.data as data) from ConcreteEntity e", + InjectionDto.class + ).getSingleResult() ).extracting( InjectionDto::getGen, InjectionDto::getData ) + .containsExactly( -1L, "entity_1" ) ); + } + + @Test + public void testConstructorFunction(SessionFactoryScope scope) { + scope.inTransaction( session -> { + assertThat( session.createQuery( + "select new ConstructorDto(abs(e.gen), e.data) from ConcreteEntity e", + ConstructorDto.class + ).getSingleResult() ).extracting( ConstructorDto::getGen, ConstructorDto::getData ) + .containsExactly( 1L, "entity_1" ); + } ); + } + + @Test + public void testImplicitFunction(SessionFactoryScope scope) { + scope.inTransaction( session -> { + assertThat( session.createQuery( + "select abs(e.gen), e.data from ConcreteEntity e", + ConstructorDto.class + ).getSingleResult() ).extracting( ConstructorDto::getGen, ConstructorDto::getData ) + .containsExactly( 1L, "entity_1" ); + } ); + } + + @Test + public void testInjectionFunction(SessionFactoryScope scope) { + scope.inTransaction( session -> { + assertThat( session.createQuery( + "select new InjectionDto(abs(e.gen) as gen, e.data as data) from ConcreteEntity e", + InjectionDto.class + ).getSingleResult() ).extracting( InjectionDto::getGen, InjectionDto::getData ) + .containsExactly( 1L, "entity_1" ); + } ); + } + + @MappedSuperclass + static abstract class AbstractEntity { + @Id + protected Integer id; + + protected K gen; + + public Integer getId() { + return id; + } + + public void setId(final Integer id) { + this.id = id; + } + + public K getGen() { + return gen; + } + + public void setGen(final K gen) { + this.gen = gen; + } + } + + @Entity(name = "ConcreteEntity") + static class ConcreteEntity extends AbstractEntity { + protected String data; + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + } + + @Imported + public static class ConstructorDto { + private final Long gen; + + private final String data; + + public ConstructorDto(Long gen, String data) { + this.gen = gen; + this.data = data; + } + + public Long getGen() { + return gen; + } + + public String getData() { + return data; + } + } + + @Imported + public static class InjectionDto { + private long gen; + + private String data; + + public long getGen() { + return gen; + } + + public void setGen(final long gen) { + this.gen = gen; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderTests.java index b04e132ed4ea..147af62497c7 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderTests.java @@ -20,6 +20,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping; +import org.hibernate.testing.orm.domain.gambit.BasicEntity; import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.converter.spi.JpaAttributeConverter; import org.hibernate.query.sql.spi.NativeQueryImplementor; @@ -28,6 +29,7 @@ import org.hibernate.testing.orm.domain.StandardDomainModel; import org.hibernate.testing.orm.domain.gambit.EntityOfBasics; 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.AfterEach; @@ -279,6 +281,45 @@ public void testConvertedAttributeBasedBuilder(SessionFactoryScope scope) { ); } + @Test + @JiraKey("HHH-18629") + public void testNativeQueryWithResultClass(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final String sql = "select data, id from BasicEntity"; + final NativeQueryImplementor query = session.createNativeQuery( sql, BasicEntity.class ); + + final List results = query.list(); + assertThat( results.size(), is( 1 ) ); + + final BasicEntity result = (BasicEntity) results.get( 0 ); + + assertThat( result.getData(), is( STRING_VALUE ) ); + assertThat( result.getId(), is( 1 ) ); + } + ); + } + + @Test + @JiraKey("HHH-18629") + public void testNativeQueryWithResultClassAndPlaceholders(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final String sql = "select {be.*} from BasicEntity be"; + final NativeQueryImplementor query = session.createNativeQuery( sql, BasicEntity.class ); + query.addEntity( "be", BasicEntity.class ); + + final List results = query.list(); + assertThat( results.size(), is( 1 ) ); + + final BasicEntity result = (BasicEntity) results.get( 0 ); + + assertThat( result.getData(), is( STRING_VALUE ) ); + assertThat( result.getId(), is( 1 ) ); + } + ); + } + @BeforeAll public void verifyModel(SessionFactoryScope scope) { final EntityMappingType entityDescriptor = scope.getSessionFactory() @@ -317,13 +358,16 @@ public void prepareData(SessionFactoryScope scope) throws MalformedURLException entityOfBasics.setTheInstant( Instant.EPOCH ); session.persist( entityOfBasics ); + + session.persist( new BasicEntity( 1, STRING_VALUE ) ); } ); scope.inTransaction( session -> { - final EntityOfBasics entity = session.get( EntityOfBasics.class, 1 ); - assertThat( entity, notNullValue() ); + assertThat( session.get( EntityOfBasics.class, 1 ), notNullValue() ); + + assertThat( session.get( BasicEntity.class, 1 ), notNullValue() ); } ); } @@ -331,7 +375,10 @@ public void prepareData(SessionFactoryScope scope) throws MalformedURLException @AfterEach public void cleanUpData(SessionFactoryScope scope) { scope.inTransaction( - session -> session.createQuery( "delete EntityOfBasics" ).executeUpdate() + session -> { + session.createQuery( "delete EntityOfBasics" ).executeUpdate(); + session.createQuery( "delete BasicEntity" ).executeUpdate(); + } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/SchemaMigratorHaltOnErrorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/SchemaMigratorHaltOnErrorTest.java index 5607fe94d5f6..1338da0ed814 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/SchemaMigratorHaltOnErrorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/SchemaMigratorHaltOnErrorTest.java @@ -17,6 +17,7 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.community.dialect.AltibaseDialect; import org.hibernate.community.dialect.FirebirdDialect; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.DerbyDialect; import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; @@ -31,6 +32,7 @@ @SkipForDialect(value = DerbyDialect.class, comment = "Derby is far more resistant to the reserved keyword usage.") @SkipForDialect(value = FirebirdDialect.class, comment = "FirebirdDialect has autoQuoteKeywords enabled, so it is far more resistant to the reserved keyword usage.") @SkipForDialect(value = AltibaseDialect.class, comment = "AltibaseDialect has autoQuoteKeywords enabled, so it is far more resistant to the reserved keyword usage.") +@SkipForDialect(value = InformixDialect.class, comment = "Informix is far more resistant to the reserved keyword usage.") public class SchemaMigratorHaltOnErrorTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/SchemaUpdateHaltOnErrorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/SchemaUpdateHaltOnErrorTest.java index 0c5183f120db..3552251c96ed 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/SchemaUpdateHaltOnErrorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/SchemaUpdateHaltOnErrorTest.java @@ -22,6 +22,7 @@ import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.community.dialect.AltibaseDialect; import org.hibernate.community.dialect.FirebirdDialect; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.DB2Dialect; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.CustomRunner; @@ -44,6 +45,7 @@ @SkipForDialect(value = DerbyDialect.class, comment = "Derby is far more resistant to the reserved keyword usage.") @SkipForDialect(value = FirebirdDialect.class, comment = "FirebirdDialect has autoQuoteKeywords enabled, so it is far more resistant to the reserved keyword usage.") @SkipForDialect(value = AltibaseDialect.class, comment = "AltibaseDialect has autoQuoteKeywords enabled, so it is far more resistant to the reserved keyword usage.") +@SkipForDialect(value = InformixDialect.class, comment = "Informix is far more resistant to the reserved keyword usage.") @RunWith(CustomRunner.class) public class SchemaUpdateHaltOnErrorTest { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/type/UUIDTypeConverterTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/type/UUIDTypeConverterTest.java index 6502be63b0de..ea36dfe72f4d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/type/UUIDTypeConverterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/type/UUIDTypeConverterTest.java @@ -11,10 +11,13 @@ import java.util.List; import java.util.UUID; +import org.hibernate.community.dialect.InformixDialect; + import org.hibernate.testing.TestForIssue; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.SkipForDialect; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -38,6 +41,7 @@ ) @SessionFactory @TestForIssue(jiraKey = "HHH-15417") +@SkipForDialect(dialectClass = InformixDialect.class, reason = "Informix does not support unique / primary constraints on binary columns") public class UUIDTypeConverterTest { @AfterEach diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/blob/BasicBlobTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/blob/BasicBlobTest.java new file mode 100644 index 000000000000..e0cfc56a6cff --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/blob/BasicBlobTest.java @@ -0,0 +1,157 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.envers.integration.blob; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import org.hamcrest.Matchers; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.dialect.SybaseDialect; +import org.hibernate.engine.jdbc.BlobProxy; +import org.hibernate.envers.Audited; +import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase; +import org.hibernate.orm.test.envers.Priority; +import org.hibernate.testing.SkipForDialect; +import org.junit.Test; + +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.Blob; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.fail; + +/** + * @author Chris Cranford + */ +public class BasicBlobTest extends BaseEnversJPAFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {Asset.class}; + } + + @Test + @Priority(10) + public void testGenerateProxyNoStream() { + final Path path = Path.of( Thread.currentThread().getContextClassLoader() + .getResource( "org/hibernate/orm/test/envers/integration/blob/blob.txt" ).getPath() ); + doInJPA( this::entityManagerFactory, entityManager -> { + final Asset asset = new Asset(); + asset.setFileName( "blob.txt" ); + + try (final InputStream stream = new BufferedInputStream( Files.newInputStream( path ) )) { + assertThat( stream.markSupported(), Matchers.is( true ) ); + + // We use the method readAllBytes instead of passing the raw stream to the proxy + // since this is the only guaranteed way that will work across all dialects in a + // deterministic way. Postgres and Sybase will automatically close the stream + // after the blob has been written by the driver, which prevents Envers from + // then writing the contents of the stream to the audit table. + // + // If the driver and dialect are known not to close the input stream after the + // contents have been written by the driver, then it's safe to pass the stream + // here instead and the stream will be automatically marked and reset so that + // Envers can serialize the data after Hibernate has done so. Dialects like + // H2, MySQL, Oracle, SQL Server work this way. + // + // + Blob blob = BlobProxy.generateProxy( stream.readAllBytes() ); + + asset.setData( blob ); + entityManager.persist( asset ); + } + catch (Exception e) { + e.printStackTrace(); + fail( "Failed to persist the entity" ); + } + } ); + + } + + @Test + @Priority(10) + @SkipForDialect(value = PostgreSQLDialect.class, + comment = "The driver closes the stream, so it cannot be reused by envers") + @SkipForDialect(value = SQLServerDialect.class, + comment = "The driver closes the stream, so it cannot be reused by envers") + @SkipForDialect(value = SybaseDialect.class, + comment = "The driver closes the stream, so it cannot be reused by envers") + public void testGenerateProxyStream() { + final Path path = Path.of( Thread.currentThread().getContextClassLoader() + .getResource( "org/hibernate/orm/test/envers/integration/blob/blob.txt" ).getPath() ); + + try (final InputStream stream = new BufferedInputStream( Files.newInputStream( path ) )) { + doInJPA( this::entityManagerFactory, entityManager -> { + final Asset asset = new Asset(); + asset.setFileName( "blob.txt" ); + + assertThat( stream.markSupported(), Matchers.is( true ) ); + + // We use the method readAllBytes instead of passing the raw stream to the proxy + // since this is the only guaranteed way that will work across all dialects in a + // deterministic way. Postgres and Sybase will automatically close the stream + // after the blob has been written by the driver, which prevents Envers from + // then writing the contents of the stream to the audit table. + // + // If the driver and dialect are known not to close the input stream after the + // contents have been written by the driver, then it's safe to pass the stream + // here instead and the stream will be automatically marked and reset so that + // Envers can serialize the data after Hibernate has done so. Dialects like + // H2, MySQL, Oracle, SQL Server work this way. + // + // + Blob blob = BlobProxy.generateProxy( stream, 1431 ); + + asset.setData( blob ); + entityManager.persist( asset ); + } ); + } + catch (Exception e) { + e.printStackTrace(); + fail( "Failed to persist the entity" ); + } + } + + @Audited + @Entity(name = "Asset") + public static class Asset { + @Id + @GeneratedValue + private Integer id; + private String fileName; + private Blob data; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public Blob getData() { + return data; + } + + public void setData(Blob data) { + this.data = data; + } + } + +} diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/metamodel/RevisionEntitiesMetamodelTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/metamodel/RevisionEntitiesMetamodelTest.java new file mode 100644 index 000000000000..78504c8bd1c7 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/metamodel/RevisionEntitiesMetamodelTest.java @@ -0,0 +1,99 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.envers.integration.metamodel; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.envers.Audited; +import org.hibernate.envers.configuration.EnversSettings; +import org.hibernate.internal.HEMLogging; +import org.hibernate.metamodel.internal.MetadataContext; +import org.hibernate.testing.logger.LogInspectionHelper; +import org.hibernate.testing.logger.TriggerOnPrefixLogListener; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.util.ServiceRegistryUtil; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import java.time.Instant; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@Jira( "https://hibernate.atlassian.net/browse/HHH-17612" ) +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) +public class RevisionEntitiesMetamodelTest { + private TriggerOnPrefixLogListener trigger; + + @BeforeAll + public void setUp() { + trigger = new TriggerOnPrefixLogListener( "HHH015007: Illegal argument on static metamodel field injection" ); + LogInspectionHelper.registerListener( trigger, HEMLogging.messageLogger( MetadataContext.class ) ); + } + + @Test + public void testDefaultRevisionEntity() { + try (final SessionFactoryImplementor ignored = buildSessionFactory( false, true )) { + assertThat( trigger.wasTriggered() ).isFalse(); + } + } + + @Test + public void testSequenceIdRevisionEntity() { + try (final SessionFactoryImplementor ignored = buildSessionFactory( false, false )) { + assertThat( trigger.wasTriggered() ).isFalse(); + } + } + + @Test + public void testDefaultTrackingModifiedEntitiesRevisionEntity() { + try (final SessionFactoryImplementor ignored = buildSessionFactory( true, true )) { + assertThat( trigger.wasTriggered() ).isFalse(); + } + } + + @Test + public void testSequenceIdTrackingModifiedEntitiesRevisionEntity() { + try (final SessionFactoryImplementor ignored = buildSessionFactory( true, false )) { + assertThat( trigger.wasTriggered() ).isFalse(); + } + } + + @SuppressWarnings( "resource" ) + private static SessionFactoryImplementor buildSessionFactory(boolean trackEntities, boolean nativeId) { + final StandardServiceRegistryBuilder registryBuilder = ServiceRegistryUtil.serviceRegistryBuilder(); + registryBuilder.applySetting( EnversSettings.TRACK_ENTITIES_CHANGED_IN_REVISION, trackEntities ); + registryBuilder.applySetting( EnversSettings.USE_REVISION_ENTITY_WITH_NATIVE_ID, nativeId ); + return new MetadataSources( registryBuilder.build() ) + .addAnnotatedClasses( Customer.class ) + .buildMetadata() + .buildSessionFactory() + .unwrap( SessionFactoryImplementor.class ); + } + + @Audited + @Entity( name = "Customer" ) + @SuppressWarnings( "unused" ) + public static class Customer { + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Column( name = "created_on" ) + @CreationTimestamp + private Instant createdOn; + } +} diff --git a/hibernate-envers/src/test/resources/org/hibernate/orm/test/envers/integration/blob/blob.txt b/hibernate-envers/src/test/resources/org/hibernate/orm/test/envers/integration/blob/blob.txt new file mode 100644 index 000000000000..7a1354040f95 --- /dev/null +++ b/hibernate-envers/src/test/resources/org/hibernate/orm/test/envers/integration/blob/blob.txt @@ -0,0 +1,31 @@ + + + + + + + create-drop + + false + false + + org.hibernate.dialect.H2Dialect + jdbc:h2:mem:envers + org.h2.Driver + sa + + + + + + + + + + + diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/BytecodeEnhancerRunner.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/BytecodeEnhancerRunner.java index dfdd5218d863..22e518345f87 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/BytecodeEnhancerRunner.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/BytecodeEnhancerRunner.java @@ -24,6 +24,7 @@ import org.hibernate.bytecode.enhance.spi.UnloadedField; import org.hibernate.cfg.Environment; +import org.hibernate.bytecode.enhance.spi.UnsupportedEnhancementStrategy; import org.hibernate.testing.junit4.CustomRunner; import org.junit.runner.Runner; import org.junit.runner.notification.RunNotifier; @@ -32,6 +33,7 @@ import org.junit.runners.model.InitializationError; import org.junit.runners.model.RunnerBuilder; +import static org.hibernate.bytecode.enhance.spi.UnsupportedEnhancementStrategy.SKIP; import static org.hibernate.bytecode.internal.BytecodeProviderInitiator.buildDefaultBytecodeProvider; /** @@ -116,6 +118,12 @@ public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) { public boolean isLazyLoadable(UnloadedField field) { return options.lazyLoading() && super.isLazyLoadable( field ); } + + @Override + public UnsupportedEnhancementStrategy getUnsupportedEnhancementStrategy() { + final UnsupportedEnhancementStrategy strategy = options.unsupportedEnhancementStrategy(); + return strategy != SKIP ? strategy : super.getUnsupportedEnhancementStrategy(); + } }; } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementOptions.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementOptions.java index 7853126bec3b..043181934598 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementOptions.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/EnhancementOptions.java @@ -6,6 +6,8 @@ */ package org.hibernate.testing.bytecode.enhancement; +import org.hibernate.bytecode.enhance.spi.UnsupportedEnhancementStrategy; + import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; @@ -23,4 +25,5 @@ boolean inlineDirtyChecking() default false; boolean lazyLoading() default false; boolean extendedEnhancement() default false; + UnsupportedEnhancementStrategy unsupportedEnhancementStrategy() default UnsupportedEnhancementStrategy.SKIP; } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/extension/engine/BytecodeEnhancedClassUtils.java b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/extension/engine/BytecodeEnhancedClassUtils.java index 78cda58e4530..8e18e8457464 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/extension/engine/BytecodeEnhancedClassUtils.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/bytecode/enhancement/extension/engine/BytecodeEnhancedClassUtils.java @@ -6,6 +6,7 @@ */ package org.hibernate.testing.bytecode.enhancement.extension.engine; +import static org.hibernate.bytecode.enhance.spi.UnsupportedEnhancementStrategy.SKIP; import static org.hibernate.bytecode.internal.BytecodeProviderInitiator.buildDefaultBytecodeProvider; import java.io.BufferedInputStream; @@ -29,6 +30,7 @@ import org.hibernate.bytecode.enhance.spi.UnloadedClass; import org.hibernate.bytecode.enhance.spi.UnloadedField; +import org.hibernate.bytecode.enhance.spi.UnsupportedEnhancementStrategy; import org.hibernate.testing.bytecode.enhancement.ClassEnhancementSelector; import org.hibernate.testing.bytecode.enhancement.ClassEnhancementSelectors; import org.hibernate.testing.bytecode.enhancement.ClassSelector; @@ -114,6 +116,12 @@ public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) { public boolean isLazyLoadable(UnloadedField field) { return options.lazyLoading() && super.isLazyLoadable( field ); } + + @Override + public UnsupportedEnhancementStrategy getUnsupportedEnhancementStrategy() { + final UnsupportedEnhancementStrategy strategy = options.unsupportedEnhancementStrategy(); + return strategy != SKIP ? strategy : super.getUnsupportedEnhancementStrategy(); + } }; } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java index d775d6cbc7d4..dd8e2475d133 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java @@ -10,6 +10,7 @@ import org.hibernate.boot.model.TruthValue; import org.hibernate.community.dialect.FirebirdDialect; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.DB2Dialect; @@ -322,8 +323,9 @@ public boolean apply(Dialect dialect) { public static class SupportsRepeat implements DialectFeatureCheck { public boolean apply(Dialect dialect) { dialect = DialectDelegateWrapper.extractRealDialect( dialect ); - // Derby doesn't support the `REPLACE` function - return !( dialect instanceof DerbyDialect ); + // Derby doesn't support the `REPEAT` function + return !( dialect instanceof DerbyDialect + || dialect instanceof InformixDialect ); } } @@ -333,6 +335,12 @@ public boolean apply(Dialect dialect) { } } + public static class SupportsSubqueryInSelect implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsSubqueryInSelect(); + } + } + public static class SupportsValuesListForInsert implements DialectFeatureCheck { public boolean apply(Dialect dialect) { return dialect.supportsValuesListForInsert(); @@ -489,6 +497,7 @@ public boolean apply(Dialect dialect) { || dialect instanceof DerbyDialect || dialect instanceof FirebirdDialect || dialect instanceof DB2Dialect && ( (DB2Dialect) dialect ).getDB2Version().isBefore( 11 ) ) + || dialect instanceof InformixDialect || dialect instanceof MariaDBDialect; } } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMeta.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMeta.java index 8a54dec99c99..4d28e6e20385 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMeta.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMeta.java @@ -155,15 +155,17 @@ private void addAuxiliaryMembersForAnnotation(String annotationName, String pref } private void addAuxiliaryMembersForMirror(AnnotationMirror mirror, String prefix) { - mirror.getElementValues().forEach((key, value) -> { - if ( key.getSimpleName().contentEquals("name") ) { - final String name = value.getValue().toString(); - if ( !name.isEmpty() ) { - putMember( prefix + name, - new NameMetaAttribute( this, name, prefix ) ); + if ( !isJakartaDataStyle() ) { + mirror.getElementValues().forEach((key, value) -> { + if ( key.getSimpleName().contentEquals( "name" ) ) { + final String name = value.getValue().toString(); + if ( !name.isEmpty() ) { + putMember( prefix + name, + new NameMetaAttribute( this, name, prefix ) ); + } } - } - }); + }); + } } protected String getSessionVariableName() { diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NameMetaAttribute.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NameMetaAttribute.java index b2aa1872f46e..3a086348a400 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NameMetaAttribute.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NameMetaAttribute.java @@ -42,8 +42,11 @@ public String getAttributeDeclarationString() { @Override public String getAttributeNameDeclarationString() { - return new StringBuilder() - .append("public static final ") + final StringBuilder declaration = new StringBuilder(); + if ( !annotationMetaEntity.isJakartaDataStyle() ) { + declaration.append( "public static final " ); + } + return declaration .append(annotationMetaEntity.importType(String.class.getName())) .append(" ") .append(prefix) diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/StringUtil.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/StringUtil.java index 7da1be9a2da1..994857eec775 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/StringUtil.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/StringUtil.java @@ -109,13 +109,19 @@ public static String nameToMethodName(String name) { public static String getUpperUnderscoreCaseFromLowerCamelCase(String lowerCamelCaseString) { final StringBuilder result = new StringBuilder(); int position = 0; + boolean wasLowerCase = false; while ( position < lowerCamelCaseString.length() ) { final int codePoint = lowerCamelCaseString.codePointAt( position ); - if ( position>0 && isUpperCase( codePoint ) ) { + final boolean isUpperCase = isUpperCase( codePoint ); + if ( wasLowerCase && isUpperCase ) { result.append('_'); } result.appendCodePoint( toUpperCase( codePoint ) ); position += charCount( codePoint ); + wasLowerCase = !isUpperCase; + } + if ( result.toString().equals( lowerCamelCaseString ) ) { + result.insert(0, '_'); } return result.toString(); } diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/uppercase/Person.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/uppercase/Person.java new file mode 100644 index 000000000000..078e9e4b1b77 --- /dev/null +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/uppercase/Person.java @@ -0,0 +1,14 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.processor.test.uppercase; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity +public class Person { + @Id String SSN; + String UserID; +} diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/uppercase/UppercaseTest.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/uppercase/UppercaseTest.java new file mode 100644 index 000000000000..d765288a8246 --- /dev/null +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/uppercase/UppercaseTest.java @@ -0,0 +1,29 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.processor.test.uppercase; + +import org.hibernate.processor.test.util.CompilationTest; +import org.hibernate.processor.test.util.WithClasses; +import org.junit.Test; + +import static org.hibernate.processor.test.util.TestUtil.assertMetamodelClassGeneratedFor; +import static org.hibernate.processor.test.util.TestUtil.assertPresenceOfFieldInMetamodelFor; +import static org.hibernate.processor.test.util.TestUtil.getMetaModelSourceAsString; + +public class UppercaseTest extends CompilationTest { + + @Test + @WithClasses(value = Person.class) + public void test() { + System.out.println( getMetaModelSourceAsString( Person.class ) ); + + assertMetamodelClassGeneratedFor( Person.class ); + + assertPresenceOfFieldInMetamodelFor( Person.class, "SSN" ); + assertPresenceOfFieldInMetamodelFor( Person.class, "_SSN" ); + assertPresenceOfFieldInMetamodelFor( Person.class, "UserID" ); + assertPresenceOfFieldInMetamodelFor( Person.class, "USER_ID" ); + } +}