diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index 5ff116f84a90..0af5fac7d4ac 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -148,8 +148,10 @@ pipeline { env.RELEASE_VERSION = releaseVersion.toString() env.DEVELOPMENT_VERSION = developmentVersion.toString() -// def matchingFiles = findFiles(glob: "/release_notes.md") -// env.SCRIPT_OPTIONS = " --notes=${matchingFiles[0]}" + def notesFile = new File( "release_notes.md" ) + assert notesFile.exists + env.SCRIPT_OPTIONS = " --notes=${notesFile.absolutePath}" + if ( params.RELEASE_DRY_RUN ) { env.SCRIPT_OPTIONS += " -d" } diff --git a/documentation/src/main/asciidoc/introduction/Advanced.adoc b/documentation/src/main/asciidoc/introduction/Advanced.adoc index 05e621586761..8bc5921f026c 100644 --- a/documentation/src/main/asciidoc/introduction/Advanced.adoc +++ b/documentation/src/main/asciidoc/introduction/Advanced.adoc @@ -582,9 +582,10 @@ We happen to not much like the naming rules defined by JPA, which specify that m We bet you could easily come up with a much better `ImplicitNamingStrategy` than that! (Hint: it should always produce legit mixed case identifiers.) ==== + [TIP] ==== -A popular `PhysicalNamingStrategy` produces snake case identifiers. +The popular link:{doc-javadoc-url}org/hibernate/boot/model/naming/PhysicalNamingStrategySnakeCaseImpl.html[`PhysicalNamingStrategySnakeCaseImpl`] produces snake case identifiers. ==== Custom naming strategies may be enabled using the configuration properties we already mentioned without much explanation back in <>. diff --git a/documentation/src/main/asciidoc/introduction/Configuration.adoc b/documentation/src/main/asciidoc/introduction/Configuration.adoc index 3479293aaf62..22a355462ea7 100644 --- a/documentation/src/main/asciidoc/introduction/Configuration.adoc +++ b/documentation/src/main/asciidoc/introduction/Configuration.adoc @@ -503,10 +503,11 @@ We'll have more to say about them in <>. [[quoted-identifiers]] === Quoting SQL identifiers -By default, Hibernate never quotes SQL table and column names in generated SQL. +By default, Hibernate never quotes a SQL table or column name in generated SQL when the name contains only alphanumeric characters. This behavior is usually much more convenient, especially when working with a legacy schema, since unquoted identifiers aren't case-sensitive, and so Hibernate doesn't need to know or care whether a column is named `NAME`, `name`, or `Name` on the database side. +On the other hand, any table or column name containing a punctuation character like `$` is automatically quoted by default. -The following settings enable automatic quoting: +The following settings enable additional automatic quoting: .Settings for identifier quoting [%breakable,cols="35,~"] diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/DialectOverride.java b/hibernate-core/src/main/java/org/hibernate/annotations/DialectOverride.java index b42fbd22671a..19f320b55b22 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/DialectOverride.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/DialectOverride.java @@ -252,7 +252,7 @@ public interface DialectOverride { org.hibernate.annotations.SQLRestriction override(); } - @Target({METHOD, FIELD}) + @Target({METHOD, FIELD, TYPE}) @Retention(RUNTIME) @interface SQLRestrictions { SQLRestriction[] value(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ColumnsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ColumnsBuilder.java index f98e1c8b314d..c7ac86cc552e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ColumnsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ColumnsBuilder.java @@ -15,7 +15,6 @@ import org.hibernate.boot.models.annotations.internal.JoinColumnJpaAnnotation; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.PropertyData; -import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.models.spi.MemberDetails; import org.hibernate.models.spi.SourceModelBuildingContext; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/DelayedParameterizedTypeBean.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/DelayedParameterizedTypeBean.java index 422ff95ec5ff..00998b39efa9 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/DelayedParameterizedTypeBean.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/DelayedParameterizedTypeBean.java @@ -7,7 +7,6 @@ import java.util.Properties; import org.hibernate.boot.BootLogging; -import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.resource.beans.spi.ManagedBean; import org.hibernate.usertype.ParameterizedType; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/DialectOverridesAnnotationHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/DialectOverridesAnnotationHelper.java index edfcdfbd750c..39cede7fff9c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/DialectOverridesAnnotationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/DialectOverridesAnnotationHelper.java @@ -14,7 +14,6 @@ import org.hibernate.boot.models.annotations.spi.DialectOverrider; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.dialect.Dialect; -import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.models.spi.AnnotationTarget; import org.hibernate.models.spi.SourceModelBuildingContext; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/FilterDefBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/FilterDefBinder.java index aa977b7dbcd0..cf46dc65ea23 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/FilterDefBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/FilterDefBinder.java @@ -17,7 +17,6 @@ import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.engine.spi.FilterDefinition; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.models.spi.AnnotationTarget; import org.hibernate.models.spi.SourceModelBuildingContext; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/QueryHintDefinition.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/QueryHintDefinition.java index 0ddfc3c513cc..f32a270fac10 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/QueryHintDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/QueryHintDefinition.java @@ -4,7 +4,6 @@ */ package org.hibernate.boot.model.internal; -import java.util.Collections; import java.util.Map; import org.hibernate.AnnotationException; @@ -15,7 +14,6 @@ import org.hibernate.MappingException; import org.hibernate.cfg.AvailableSettings; import org.hibernate.internal.util.LockModeConverter; -import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.jpa.HibernateHints; import org.hibernate.jpa.LegacySpecHints; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/CamelCaseToUnderscoresNamingStrategy.java b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/CamelCaseToUnderscoresNamingStrategy.java index c91248449921..51f3e955b0e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/CamelCaseToUnderscoresNamingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/CamelCaseToUnderscoresNamingStrategy.java @@ -4,88 +4,9 @@ */ package org.hibernate.boot.model.naming; -import java.util.Locale; - -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; - /** - * Originally copied from Spring Boot as this strategy is popular there - * (original name is SpringPhysicalNamingStrategy). - * - * @author Phillip Webb - * @author Madhura Bhave + * @deprecated Use {@link PhysicalNamingStrategySnakeCaseImpl}. */ -public class CamelCaseToUnderscoresNamingStrategy implements PhysicalNamingStrategy { - - @Override - public Identifier toPhysicalCatalogName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) { - return apply( logicalName, jdbcEnvironment ); - } - - @Override - public Identifier toPhysicalSchemaName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) { - return apply( logicalName, jdbcEnvironment ); - } - - @Override - public Identifier toPhysicalTableName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) { - return apply( logicalName, jdbcEnvironment ); - } - - @Override - public Identifier toPhysicalSequenceName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) { - return apply( logicalName, jdbcEnvironment ); - } - - @Override - public Identifier toPhysicalColumnName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) { - return apply( logicalName, jdbcEnvironment ); - } - - private Identifier apply(final Identifier name, final JdbcEnvironment jdbcEnvironment) { - if ( name == null ) { - return null; - } - StringBuilder builder = new StringBuilder( name.getText().replace( '.', '_' ) ); - for ( int i = 1; i < builder.length() - 1; i++ ) { - if ( isUnderscoreRequired( builder.charAt( i - 1 ), builder.charAt( i ), builder.charAt( i + 1 ) ) ) { - builder.insert( i++, '_' ); - } - } - return getIdentifier( builder.toString(), name.isQuoted(), jdbcEnvironment ); - } - - /** - * Get an identifier for the specified details. By default this method will return an identifier - * with the name adapted based on the result of {@link #isCaseInsensitive(JdbcEnvironment)} - * - * @param name the name of the identifier - * @param quoted if the identifier is quoted - * @param jdbcEnvironment the JDBC environment - * - * @return an identifier instance - */ - protected Identifier getIdentifier(String name, final boolean quoted, final JdbcEnvironment jdbcEnvironment) { - if ( isCaseInsensitive( jdbcEnvironment ) ) { - name = name.toLowerCase( Locale.ROOT ); - } - return new Identifier( name, quoted ); - } - - /** - * Specify whether the database is case sensitive. - * - * @param jdbcEnvironment the JDBC environment which can be used to determine case - * - * @return true if the database is case insensitive sensitivity - */ - protected boolean isCaseInsensitive(JdbcEnvironment jdbcEnvironment) { - return true; - } - - private boolean isUnderscoreRequired(final char before, final char current, final char after) { - return ( Character.isLowerCase( before ) || Character.isDigit( before ) ) && Character.isUpperCase( current ) && ( Character.isLowerCase( - after ) || Character.isDigit( after ) ); - } - +@Deprecated(since = "7", forRemoval = true) +public class CamelCaseToUnderscoresNamingStrategy extends PhysicalNamingStrategySnakeCaseImpl { } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/DatabaseIdentifier.java b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/DatabaseIdentifier.java index 6578bd276d61..0a9fa4144ec7 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/DatabaseIdentifier.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/DatabaseIdentifier.java @@ -4,7 +4,7 @@ */ package org.hibernate.boot.model.naming; -import org.hibernate.internal.util.StringHelper; +import static org.hibernate.internal.util.StringHelper.isEmpty; /** * Models an identifier (name), retrieved from the database. @@ -24,13 +24,13 @@ protected DatabaseIdentifier(String text) { } public static DatabaseIdentifier toIdentifier(String text) { - if ( StringHelper.isEmpty( text ) ) { + if ( isEmpty( text ) ) { return null; } else if ( isQuoted( text ) ) { // exclude the quotes from text - final String unquotedtext = text.substring( 1, text.length() - 1 ); - return new DatabaseIdentifier( unquotedtext ); + final String unquoted = text.substring( 1, text.length() - 1 ); + return new DatabaseIdentifier( unquoted ); } else { return new DatabaseIdentifier( text ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java index 764034ea56e8..61398e34f694 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java @@ -7,9 +7,12 @@ import java.util.Locale; import org.hibernate.dialect.Dialect; -import org.hibernate.internal.util.StringHelper; +import static java.lang.Character.isLetter; +import static java.lang.Character.isLetterOrDigit; +import static java.lang.Character.isWhitespace; import static org.hibernate.internal.util.StringHelper.isBlank; +import static org.hibernate.internal.util.StringHelper.isEmpty; /** * Models an identifier (name), which may or may not be quoted. @@ -83,13 +86,13 @@ public static Identifier toIdentifier(String text, boolean quote, boolean quoteO int start = 0; int end = text.length(); while ( start < end ) { - if ( !Character.isWhitespace( text.charAt( start ) ) ) { + if ( !isWhitespace( text.charAt( start ) ) ) { break; } start++; } while ( start < end ) { - if ( !Character.isWhitespace( text.charAt( end - 1 ) ) ) { + if ( !isWhitespace( text.charAt( end - 1 ) ) ) { break; } end--; @@ -102,14 +105,14 @@ public static Identifier toIdentifier(String text, boolean quote, boolean quoteO else if ( quoteOnNonIdentifierChar && !quote ) { // Check the letters to determine if we must quote the text char c = text.charAt( start ); - if ( !Character.isLetter( c ) && c != '_' ) { + if ( !isLetter( c ) && c != '_' ) { // SQL identifiers must begin with a letter or underscore quote = true; } else { for ( int i = start + 1; i < end; i++ ) { c = text.charAt( i ); - if ( !Character.isLetterOrDigit( c ) && c != '_' ) { + if ( !isLetterOrDigit( c ) && c != '_' ) { quote = true; break; } @@ -163,7 +166,7 @@ public static String unQuote(String name) { * @param quoted Is this a quoted identifier? */ public Identifier(String text, boolean quoted) { - if ( StringHelper.isEmpty( text ) ) { + if ( isEmpty( text ) ) { throw new IllegalIdentifierException( "Identifier text cannot be null" ); } if ( isQuoted( text ) ) { @@ -234,11 +237,9 @@ public String toString() { @Override public boolean equals(Object o) { - if ( !(o instanceof Identifier) ) { + if ( !(o instanceof Identifier that) ) { return false; } - - final Identifier that = (Identifier) o; return getCanonicalName().equals( that.getCanonicalName() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/ImplicitNamingStrategyJpaCompliantImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/ImplicitNamingStrategyJpaCompliantImpl.java index b3abb6c5f453..200dd27b96c7 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/ImplicitNamingStrategyJpaCompliantImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/ImplicitNamingStrategyJpaCompliantImpl.java @@ -36,7 +36,7 @@ public Identifier determinePrimaryTableName(ImplicitEntityNameSource source) { throw new HibernateException( "Entity naming information was not provided." ); } - String tableName = transformEntityName( source.getEntityNaming() ); + final String tableName = transformEntityName( source.getEntityNaming() ); if ( tableName == null ) { // todo : add info to error message - but how to know what to write since we failed to interpret the naming source @@ -136,9 +136,9 @@ public Identifier determineJoinColumnName(ImplicitJoinColumnNameSource source) { // todo : we need to better account for "referencing relationship property" - final String name; + final String referencedColumnName = source.getReferencedColumnName().getText(); - String referencedColumnName = source.getReferencedColumnName().getText(); + final String name; if ( source.getNature() == ELEMENT_COLLECTION || source.getAttributePath() == null ) { name = transformEntityName( source.getEntityNaming() ) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/PhysicalNamingStrategySnakeCaseImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/PhysicalNamingStrategySnakeCaseImpl.java new file mode 100644 index 000000000000..6107f36c1231 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/PhysicalNamingStrategySnakeCaseImpl.java @@ -0,0 +1,84 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.boot.model.naming; + +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; + +import java.util.Locale; + +import static java.lang.Character.isDigit; +import static java.lang.Character.isLowerCase; +import static java.lang.Character.isUpperCase; + +/** + * Converts {@code camelCase} or {@code MixedCase} logical names to {@code snake_case}. + * + * @author Phillip Webb + * @author Madhura Bhave + */ +// Originally copied from Spring's SpringPhysicalNamingStrategy as this strategy is popular there. +public class PhysicalNamingStrategySnakeCaseImpl implements PhysicalNamingStrategy { + + @Override + public Identifier toPhysicalCatalogName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) { + return apply( logicalName ); + } + + @Override + public Identifier toPhysicalSchemaName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) { + return apply( logicalName ); + } + + @Override + public Identifier toPhysicalTableName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) { + return apply( logicalName ); + } + + @Override + public Identifier toPhysicalSequenceName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) { + return apply( logicalName ); + } + + @Override + public Identifier toPhysicalColumnName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) { + return apply( logicalName ); + } + + private Identifier apply(final Identifier name) { + if ( name == null ) { + return null; + } + else if ( name.isQuoted() ) { + return quotedIdentifier( name ); + } + else { + return unquotedIdentifier( name ); + } + } + + private String camelCaseToSnakeCase(String name) { + final StringBuilder builder = new StringBuilder( name.replace( '.', '_' ) ); + for ( int i = 1; i < builder.length() - 1; i++ ) { + if ( isUnderscoreRequired( builder.charAt( i - 1 ), builder.charAt( i ), builder.charAt( i + 1 ) ) ) { + builder.insert( i++, '_' ); + } + } + return builder.toString(); + } + + protected Identifier unquotedIdentifier(Identifier name) { + return new Identifier( camelCaseToSnakeCase( name.getText() ).toLowerCase( Locale.ROOT ) ); + } + + protected Identifier quotedIdentifier(Identifier quotedName) { + return quotedName; + } + + private boolean isUnderscoreRequired(final char before, final char current, final char after) { + return ( isLowerCase( before ) || isDigit( before ) ) + && isUpperCase( current ) + && ( isLowerCase( after ) || isDigit( after ) ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java index 2da5fd678e17..6d92a76641f4 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java @@ -157,19 +157,21 @@ private void evictCache(Object entity, EntityPersister persister, EventSource se } } - private Object getIdentifier(EventSource session, Object obj) { - Object id = null; - if ( obj != null ) { - id = session.getContextEntityIdentifier( obj ); + private Object getIdentifier(EventSource session, Object object) { + if ( object != null ) { + final Object id = session.getContextEntityIdentifier( object ); if ( id == null ) { - final EntityPersister persister = session.getFactory() - .getRuntimeMetamodels() - .getMappingMetamodel() - .getEntityDescriptor( obj.getClass() ); - id = persister.getIdentifier( obj, session ); + return session.getFactory().getMappingMetamodel() + .getEntityDescriptor( object.getClass() ) + .getIdentifier( object, session ); } + else { + return id; + } + } + else { + return null; } - return id; } private void evict(Object id, CollectionPersister collectionPersister, EventSource session) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java index 42c87f77cf73..40d8c0aff0a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java @@ -42,7 +42,6 @@ import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; -import org.hibernate.type.descriptor.jdbc.JsonArrayJdbcType; import static org.hibernate.dialect.StructHelper.getEmbeddedPart; import static org.hibernate.dialect.StructHelper.instantiate; @@ -363,16 +362,17 @@ public static X fromString( return (X) values; } + // This is also used by Hibernate Reactive public static X arrayFromString( JavaType javaType, - JsonArrayJdbcType jsonArrayJdbcType, + JdbcType elementJdbcType, String string, WrapperOptions options) throws SQLException { if ( string == null ) { return null; } final JavaType elementJavaType = ((BasicPluralJavaType) javaType).getElementJavaType(); - final Class preferredJavaTypeClass = jsonArrayJdbcType.getElementJdbcType().getPreferredJavaTypeClass( options ); + final Class preferredJavaTypeClass = elementJdbcType.getPreferredJavaTypeClass( options ); final JavaType jdbcJavaType; if ( preferredJavaTypeClass == null || preferredJavaTypeClass == elementJavaType.getJavaTypeClass() ) { jdbcJavaType = elementJavaType; @@ -390,7 +390,7 @@ public static X arrayFromString( arrayList, elementJavaType, jdbcJavaType, - jsonArrayJdbcType.getElementJdbcType() + elementJdbcType ); assert string.charAt( i - 1 ) == ']'; return javaType.wrap( arrayList, options ); 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 8e1cef2adc63..fabf0aebf8d8 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 @@ -334,10 +334,11 @@ public static boolean isTransient( * Return the identifier of the persistent or transient object, or throw * an exception if the instance is "unsaved" *

- * Used by OneToOneType and ManyToOneType to determine what id value should + * Used by {@link org.hibernate.type.OneToOneType} and + * {@link org.hibernate.type.ManyToOneType} to determine what id value should * be used for an object that may or may not be associated with the session. * This does a "best guess" using any/all info available to use (not just the - * EntityEntry). + * {@link EntityEntry}). * * @param entityName The name of the entity * @param object The entity instance @@ -357,9 +358,9 @@ public static Object getEntityIdentifierIfNotUnsaved( else { final Object id = session.getContextEntityIdentifier( object ); if ( id == null ) { - // context-entity-identifier returns null explicitly if the entity - // is not associated with the persistence context; so make some - // deeper checks... + // context-entity-identifier always returns null if the + // entity is not associated with the persistence context; + // so make some deeper checks... throwIfTransient( entityName, object, session ); return session.getEntityPersister( entityName, object ).getIdentifier( object, session ); } @@ -369,6 +370,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( "Entity references an unsaved transient instance of '" diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java index 80ccf9fde098..1e090fb7daae 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java @@ -69,8 +69,16 @@ public String perform() { while ( tokens.hasMoreTokens() ) { token = tokens.nextToken(); - lcToken = token.toLowerCase(Locale.ROOT); + if ( "-".equals(token) && result.toString().endsWith("-") ) { + do { + result.append( token ); + token = tokens.nextToken(); + } + while ( !"\n".equals( token ) && tokens.hasMoreTokens() ); + } + + lcToken = token.toLowerCase(Locale.ROOT); switch (lcToken) { case "'": diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParser.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParser.java index 2c8d463a8a5a..5697f5bd3d78 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParser.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParser.java @@ -64,9 +64,9 @@ public static void parseInto( private final SessionFactoryImplementor sessionFactory; - private final Stack graphStack = new StandardStack<>( GraphImplementor.class ); - private final Stack attributeNodeStack = new StandardStack<>( AttributeNodeImplementor.class ); - private final Stack graphSourceStack = new StandardStack<>(SubGraphGenerator.class); + private final Stack graphStack = new StandardStack<>(); + private final Stack attributeNodeStack = new StandardStack<>(); + private final Stack graphSourceStack = new StandardStack<>(); public GraphParser(SessionFactoryImplementor sessionFactory) { this.sessionFactory = sessionFactory; diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java index 5fde5a4b8845..cfb33a1e1436 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java @@ -111,6 +111,13 @@ public QualifiedName getPhysicalName() { return physicalTableName; } + /* + * Used by Hibernate Reactive + */ + public Identifier getLogicalValueColumnNameIdentifier() { + return logicalValueColumnNameIdentifier; + } + @Override public int getInitialValue() { return initialValue; diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/StandardStack.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/StandardStack.java index 52c711453b8e..c27f5a973578 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/StandardStack.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/StandardStack.java @@ -4,7 +4,6 @@ */ package org.hibernate.internal.util.collections; -import java.lang.reflect.Array; import java.util.Arrays; import java.util.NoSuchElementException; import java.util.function.BiFunction; @@ -21,24 +20,34 @@ * @author Marco Belladelli */ public final class StandardStack implements Stack { - private T[] elements; + + private Object[] elements; private int top = 0; - private Class type; + public StandardStack() { + } + public StandardStack(T initialValue) { + push( initialValue ); + } + + /** + * @deprecated use the default constructor instead + */ + @Deprecated(forRemoval = true) public StandardStack(Class type) { - this.type = type; } + /** + * @deprecated use {@link #StandardStack(Object)} instead. + */ + @Deprecated(forRemoval = true) public StandardStack(Class type, T initial) { - this( type ); push( initial ); } - @SuppressWarnings("unchecked") private void init() { - elements = (T[]) Array.newInstance( type, 8 ); - type = null; + elements = new Object[8]; } @Override @@ -57,7 +66,7 @@ public T pop() { if ( isEmpty() ) { throw new NoSuchElementException(); } - T e = elements[--top]; + T e = (T) elements[--top]; elements[top] = null; return e; } @@ -67,7 +76,7 @@ public T getCurrent() { if ( isEmpty() ) { return null; } - return elements[top - 1]; + return (T) elements[top - 1]; } @Override @@ -75,7 +84,7 @@ public T peek(int offsetFromTop) { if ( isEmpty() ) { return null; } - return elements[top - offsetFromTop - 1]; + return (T) elements[top - offsetFromTop - 1]; } @Override @@ -83,7 +92,7 @@ public T getRoot() { if ( isEmpty() ) { return null; } - return elements[0]; + return (T) elements[0]; } @Override @@ -107,14 +116,14 @@ public void clear() { @Override public void visitRootFirst(Consumer action) { for ( int i = 0; i < top; i++ ) { - action.accept( elements[i] ); + action.accept( (T) elements[i] ); } } @Override public X findCurrentFirst(Function function) { for ( int i = top - 1; i >= 0; i-- ) { - final X result = function.apply( elements[i] ); + final X result = function.apply( (T) elements[i] ); if ( result != null ) { return result; } @@ -125,7 +134,7 @@ public X findCurrentFirst(Function function) { @Override public X findCurrentFirstWithParameter(Y parameter, BiFunction biFunction) { for ( int i = top - 1; i >= 0; i-- ) { - final X result = biFunction.apply( elements[i], parameter ); + final X result = biFunction.apply( (T) elements[i], parameter ); if ( result != null ) { return result; } @@ -138,4 +147,5 @@ private void grow() { final int jump = ( oldCapacity < 64 ) ? ( oldCapacity + 2 ) : ( oldCapacity >> 1 ); elements = Arrays.copyOf( elements, oldCapacity + jump ); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java index 1e6191984b50..666524957e9c 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java @@ -737,12 +737,13 @@ private JavaType determineReflectedJavaType() { } } - private java.lang.reflect.Type impliedJavaType(TypeConfiguration typeConfiguration) { + @Incubating + public java.lang.reflect.Type impliedJavaType(TypeConfiguration typeConfiguration) { if ( resolvedJavaType != null ) { return resolvedJavaType; } else if ( implicitJavaTypeAccess != null ) { - return implicitJavaTypeAccess.apply(typeConfiguration); + return implicitJavaTypeAccess.apply( typeConfiguration ); } else if ( ownerName != null && propertyName != null ) { return reflectedPropertyType( ownerName, propertyName, classLoaderService() ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java index 045481c3abc8..bae24ed4e5af 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java @@ -13,6 +13,7 @@ import jakarta.persistence.EmbeddedId; import jakarta.persistence.Id; import jakarta.persistence.IdClass; +import org.hibernate.sql.results.graph.Fetchable; /** @@ -22,7 +23,7 @@ * @see EmbeddedId * @see Nature */ -public interface EntityIdentifierMapping extends ValuedModelPart { +public interface EntityIdentifierMapping extends ValuedModelPart, Fetchable { String ID_ROLE_NAME = "{id}"; String LEGACY_ID_NAME = "id"; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/spi/RuntimeModelCreationContext.java b/hibernate-core/src/main/java/org/hibernate/metamodel/spi/RuntimeModelCreationContext.java index 198d3f967469..78b3ec5b7774 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/spi/RuntimeModelCreationContext.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/spi/RuntimeModelCreationContext.java @@ -14,8 +14,11 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.generator.Generator; import org.hibernate.mapping.GeneratorSettings; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.sqm.function.SqmFunctionRegistry; import org.hibernate.service.ServiceRegistry; +import org.hibernate.tuple.entity.EntityMetamodel; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.spi.TypeConfiguration; @@ -64,4 +67,11 @@ default MetadataImplementor getMetadata() { Map getGenerators(); GeneratorSettings getGeneratorSettings(); + + /* + * Used by Hibernate Reactive + */ + default EntityMetamodel createEntityMetamodel(PersistentClass persistentClass, EntityPersister persister) { + return new EntityMetamodel( persistentClass, persister, this ); + } } 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 8ab2110f512a..80811405f8cc 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 @@ -500,7 +500,7 @@ public AbstractEntityPersister( isLazyPropertiesCacheable = true; } - entityMetamodel = new EntityMetamodel( persistentClass, this, creationContext ); + entityMetamodel = creationContext.createEntityMetamodel( persistentClass, this ); entityEntryFactory = entityMetamodel.isMutable() ? MutableEntityEntryFactory.INSTANCE diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java index c7fdaa885edd..3df9c22c93aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java @@ -160,7 +160,6 @@ public JoinedSubclassEntityPersister( final EntityDataAccess cacheAccessStrategy, final NaturalIdDataAccess naturalIdRegionAccessStrategy, final RuntimeModelCreationContext creationContext) throws HibernateException { - super( persistentClass, cacheAccessStrategy, naturalIdRegionAccessStrategy, creationContext ); final Dialect dialect = creationContext.getDialect(); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java index b1de0a71bb2a..6e3563fc5a94 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java @@ -114,7 +114,6 @@ public SingleTableEntityPersister( final EntityDataAccess cacheAccessStrategy, final NaturalIdDataAccess naturalIdRegionAccessStrategy, final RuntimeModelCreationContext creationContext) throws HibernateException { - super( persistentClass, cacheAccessStrategy, naturalIdRegionAccessStrategy, creationContext ); final Dialect dialect = creationContext.getDialect(); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/AbstractMutationCoordinator.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/AbstractMutationCoordinator.java index 386f5f58de05..a9d029943b78 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/AbstractMutationCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/AbstractMutationCoordinator.java @@ -82,8 +82,7 @@ protected MutationOperationGroup createOperationGroup(ValuesAnalysis valuesAnaly case 0: return MutationOperationGroupFactory.noOperations( mutationGroup ); case 1: { - final MutationOperation operation = mutationGroup.getSingleTableMutation() - .createMutationOperation( valuesAnalysis, factory() ); + final MutationOperation operation = createOperation( valuesAnalysis, mutationGroup.getSingleTableMutation() ); return operation == null ? MutationOperationGroupFactory.noOperations( mutationGroup ) : MutationOperationGroupFactory.singleOperation( mutationGroup, operation ); @@ -116,6 +115,13 @@ protected MutationOperationGroup createOperationGroup(ValuesAnalysis valuesAnaly } } + /* + * Used by Hibernate Reactive + */ + protected MutationOperation createOperation(ValuesAnalysis valuesAnalysis, TableMutation singleTableMutation) { + return singleTableMutation.createMutationOperation( valuesAnalysis, factory() ); + } + protected void handleValueGeneration( AttributeMapping attributeMapping, MutationGroupBuilder mutationGroupBuilder, diff --git a/hibernate-core/src/main/java/org/hibernate/query/NativeQuery.java b/hibernate-core/src/main/java/org/hibernate/query/NativeQuery.java index 45037ea79889..387fce67678b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/NativeQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/NativeQuery.java @@ -21,6 +21,7 @@ import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.model.domain.BasicDomainType; import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.BasicTypeReference; @@ -504,6 +505,8 @@ interface FetchReturn extends ResultNode { String getOwnerAlias(); + Fetchable getFetchable(); + String getFetchableName(); /** diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index 8500c7ffb3f7..fe3ea457f2ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -308,8 +308,8 @@ public static SqmStatement buildSemanticModel( private final Stack dotIdentifierConsumerStack; - private final Stack parameterDeclarationContextStack = new StandardStack<>( ParameterDeclarationContext.class ); - private final Stack processingStateStack = new StandardStack<>( SqmCreationProcessingState.class ); + private final Stack parameterDeclarationContextStack = new StandardStack<>(); + private final Stack processingStateStack = new StandardStack<>(); private final BasicDomainType integerDomainType; private final JavaType> listJavaType; @@ -382,7 +382,6 @@ private SemanticQueryBuilder( this.creationContext = creationContext; this.query = query; this.dotIdentifierConsumerStack = new StandardStack<>( - DotIdentifierConsumer.class, new BasicDotIdentifierConsumer( this ) ); this.parameterStyle = creationOptions.useStrictJpaCompliance() diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/FetchMementoHbmStandard.java b/hibernate-core/src/main/java/org/hibernate/query/internal/FetchMementoHbmStandard.java index c0ee765a9df1..8409bca37367 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/FetchMementoHbmStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/FetchMementoHbmStandard.java @@ -30,6 +30,7 @@ public class FetchMementoHbmStandard implements FetchMemento, FetchMemento.Parent { private static final String ELEMENT_PREFIX = "element."; + private static final int ELEMENT_PREFIX_LENGTH = 8; public interface FetchParentMemento { NavigablePath getNavigablePath(); @@ -74,55 +75,110 @@ public FetchBuilder resolve( Parent parent, Consumer querySpaceConsumer, ResultSetMappingResolutionContext context) { - final Map fetchBuilderMap = new HashMap<>(); - fetchMementoMap.forEach( - (attrName, fetchMemento) -> fetchBuilderMap.put( - attrName, - fetchMemento.resolve(this, querySpaceConsumer, context ) - ) - ); - final DynamicResultBuilderEntityStandard resultBuilder; - if ( fetchable instanceof PluralAttributeMapping ) { - resultBuilder = new DynamicResultBuilderEntityStandard( - (EntityMappingType) ( (PluralAttributeMapping) fetchable ).getElementDescriptor().getPartMappingType(), - tableAlias, - navigablePath - ); - FetchBuilder element = fetchBuilderMap.get( "element" ); - if ( element != null ) { - if ( element instanceof DynamicFetchBuilder ) { - resultBuilder.addIdColumnAliases( - ( (DynamicFetchBuilder) element ).getColumnAliases().toArray( new String[0] ) - ); - } - else { - resultBuilder.addIdColumnAliases( - ( (CompleteFetchBuilderEntityValuedModelPart) element ).getColumnAliases().toArray( new String[0] ) - ); - } - } - FetchBuilder index = fetchBuilderMap.get( "index" ); - if ( index != null ) { - resultBuilder.addFetchBuilder( CollectionPart.Nature.INDEX.getName(), index ); - } - for ( Map.Entry entry : fetchBuilderMap.entrySet() ) { - if ( entry.getKey().startsWith( ELEMENT_PREFIX ) ) { - resultBuilder.addFetchBuilder( entry.getKey().substring( ELEMENT_PREFIX.length() ), entry.getValue() ); - } - } + if ( fetchable instanceof PluralAttributeMapping pluralAttributeMapping ) { + return resolve( pluralAttributeMapping, querySpaceConsumer, context ); } else { - resultBuilder = new DynamicResultBuilderEntityStandard( - ( (ToOneAttributeMapping) fetchable ).getEntityMappingType(), - tableAlias, - navigablePath - ); - fetchBuilderMap.forEach( resultBuilder::addFetchBuilder ); + return resolve( (ToOneAttributeMapping) fetchable, querySpaceConsumer, context ); } + } + + private FetchBuilder resolve( + PluralAttributeMapping pluralAttributeMapping, + Consumer querySpaceConsumer, + ResultSetMappingResolutionContext context) { + final DynamicResultBuilderEntityStandard resultBuilder; + EntityMappingType partMappingType = (EntityMappingType) pluralAttributeMapping.getElementDescriptor() + .getPartMappingType(); + resultBuilder = new DynamicResultBuilderEntityStandard( + partMappingType, + tableAlias, + navigablePath + ); + final Map fetchBuilderMap = new HashMap<>(); + fetchMementoMap.forEach( + (attrName, fetchMemento) -> { + final FetchBuilder fetchBuilder = fetchMemento.resolve( this, querySpaceConsumer, context ); + + if ( attrName.equals( "element" ) ) { + if ( fetchBuilder instanceof DynamicFetchBuilder dynamicFetchBuilder ) { + resultBuilder.addIdColumnAliases( + dynamicFetchBuilder.getColumnAliases().toArray( new String[0] ) + ); + } + else { + resultBuilder.addIdColumnAliases( + ((CompleteFetchBuilderEntityValuedModelPart) fetchBuilder).getColumnAliases() + .toArray( new String[0] ) + ); + } + fetchBuilderMap.put( + pluralAttributeMapping.getElementDescriptor(), + fetchBuilder + ); + } + else if ( attrName.equals( "index" ) ) { + final CollectionPart indexDescriptor = pluralAttributeMapping.getIndexDescriptor(); + resultBuilder.addFetchBuilder( indexDescriptor, fetchBuilder ); + fetchBuilderMap.put( + indexDescriptor, + fetchBuilder + ); + } + else if ( attrName.startsWith( ELEMENT_PREFIX ) ) { + final Fetchable attributeMapping = (Fetchable) partMappingType.findByPath( + attrName.substring( ELEMENT_PREFIX_LENGTH ) ); + resultBuilder.addFetchBuilder( attributeMapping, fetchBuilder ); + fetchBuilderMap.put( + attributeMapping, + fetchBuilder + ); + } + else { + final Fetchable attributeMapping = (Fetchable) partMappingType.findByPath( attrName ); + resultBuilder.addFetchBuilder( attributeMapping, fetchBuilder ); + fetchBuilderMap.put( + attributeMapping, + fetchBuilder + ); + } + } + ); + return new DynamicFetchBuilderLegacy( + tableAlias, + ownerTableAlias, + fetchable, + keyColumnNames, + fetchBuilderMap, + resultBuilder + ); + } + + private FetchBuilder resolve( + ToOneAttributeMapping toOneAttributeMapping, + Consumer querySpaceConsumer, + ResultSetMappingResolutionContext context) { + final Map fetchBuilderMap = new HashMap<>(); + fetchMementoMap.forEach( + (attrName, fetchMemento) -> + fetchBuilderMap.put( + (Fetchable) toOneAttributeMapping.findSubPart( attrName ), + fetchMemento.resolve( this, querySpaceConsumer, context ) + ) + ); + final DynamicResultBuilderEntityStandard resultBuilder; + resultBuilder = new DynamicResultBuilderEntityStandard( + toOneAttributeMapping.getEntityMappingType(), + tableAlias, + navigablePath + ); + fetchBuilderMap.forEach( (fetchable, fetchBuilder) -> + resultBuilder.addFetchBuilder( fetchable, fetchBuilder ) + ); return new DynamicFetchBuilderLegacy( tableAlias, ownerTableAlias, - fetchable.getFetchableName(), + fetchable, keyColumnNames, fetchBuilderMap, resultBuilder diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityJpa.java b/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityJpa.java index fd3e77ba359e..22ca3be001d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityJpa.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityJpa.java @@ -23,6 +23,7 @@ import org.hibernate.query.results.internal.complete.DelayedFetchBuilderBasicPart; import org.hibernate.query.results.internal.implicit.ImplicitFetchBuilderBasic; import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.results.graph.Fetchable; /** * @author Steve Ebersole @@ -70,13 +71,13 @@ public ResultBuilderEntityValued resolve( } } - final HashMap explicitFetchBuilderMap = new HashMap<>(); + final HashMap explicitFetchBuilderMap = new HashMap<>(); // If there are no explicit fetches, we don't register DELAYED builders to get implicit fetching of all basic fetchables if ( !explicitFetchMementoMap.isEmpty() ) { explicitFetchMementoMap.forEach( (relativePath, fetchMemento) -> explicitFetchBuilderMap.put( - relativePath, + (Fetchable) entityDescriptor.findByPath( relativePath ), fetchMemento.resolve( this, querySpaceConsumer, context ) ) ); @@ -87,13 +88,13 @@ public ResultBuilderEntityValued resolve( attributeMapping -> { final BasicValuedModelPart basicPart = attributeMapping.asBasicValuedModelPart(); if ( basicPart != null ) { - final Function fetchBuilderCreator = k -> new DelayedFetchBuilderBasicPart( - navigablePath.append( k ), + final Function fetchBuilderCreator = k -> new DelayedFetchBuilderBasicPart( + navigablePath.append( k.getFetchableName() ), basicPart, isEnhancedForLazyLoading ); explicitFetchBuilderMap.computeIfAbsent( - attributeMapping.getFetchableName(), + attributeMapping, fetchBuilderCreator ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityStandard.java b/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityStandard.java index 6be6cc3463c2..86ac9f19de89 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityStandard.java @@ -10,15 +10,16 @@ import org.hibernate.LockMode; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.query.results.FetchBuilderBasicValued; import org.hibernate.spi.NavigablePath; import org.hibernate.query.QueryLogging; import org.hibernate.query.named.FetchMemento; import org.hibernate.query.named.FetchMementoBasic; import org.hibernate.query.named.ResultMementoEntity; -import org.hibernate.query.results.FetchBuilderBasicValued; import org.hibernate.query.results.FetchBuilder; import org.hibernate.query.results.ResultBuilderEntityValued; import org.hibernate.query.results.internal.complete.CompleteResultBuilderEntityStandard; +import org.hibernate.sql.results.graph.Fetchable; /** * @author Steve Ebersole @@ -64,11 +65,11 @@ public ResultBuilderEntityValued resolve( ? (FetchBuilderBasicValued) discriminatorMemento.resolve( this, querySpaceConsumer, context ) : null; - final HashMap fetchBuilderMap = new HashMap<>(); + final HashMap fetchBuilderMap = new HashMap<>(); fetchMementoMap.forEach( (attrName, fetchMemento) -> fetchBuilderMap.put( - attrName, + (Fetchable) entityDescriptor.findByPath( attrName ), fetchMemento.resolve(this, querySpaceConsumer, context ) ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/FetchBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/FetchBuilder.java index 092cfbd74b36..502be58d1c59 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/FetchBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/FetchBuilder.java @@ -9,6 +9,7 @@ import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import java.util.function.BiConsumer; @@ -33,7 +34,7 @@ Fetch buildFetch( JdbcValuesMetadata jdbcResultsMetadata, DomainResultCreationState domainResultCreationState); - default void visitFetchBuilders(BiConsumer consumer) { + default void visitFetchBuilders(BiConsumer consumer) { } FetchBuilder cacheKeyInstance(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/LegacyFetchBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/LegacyFetchBuilder.java index c1f818b5bf99..fa52fe9393ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/LegacyFetchBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/LegacyFetchBuilder.java @@ -4,6 +4,8 @@ */ package org.hibernate.query.results; +import org.hibernate.sql.results.graph.Fetchable; + /** * Specialized FetchBuilder implementations which handle building fetches defined via:

    *
  • {@code hbm.xml} definitions
  • @@ -30,4 +32,6 @@ public interface LegacyFetchBuilder extends FetchBuilder { @Override LegacyFetchBuilder cacheKeyInstance(); + + Fetchable getFetchable(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/ResultBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/ResultBuilder.java index b7f98b60bc73..3b4c572fe0d3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/ResultBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/ResultBuilder.java @@ -7,6 +7,7 @@ import org.hibernate.Incubating; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import java.util.function.BiConsumer; @@ -46,6 +47,6 @@ DomainResult buildResult( ResultBuilder cacheKeyInstance(); - default void visitFetchBuilders(BiConsumer consumer) { + default void visitFetchBuilders(BiConsumer consumer) { } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/Builders.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/Builders.java index 3a5cf54e1e0a..66b9b3e12629 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/Builders.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/Builders.java @@ -240,8 +240,8 @@ public static DynamicResultBuilderEntityCalculated entityCalculated( return new DynamicResultBuilderEntityCalculated( entityMapping, tableAlias, explicitLockMode ); } - public static DynamicFetchBuilderLegacy fetch(String tableAlias, String ownerTableAlias, String joinPropertyName) { - return new DynamicFetchBuilderLegacy( tableAlias, ownerTableAlias, joinPropertyName, new ArrayList<>(), new HashMap<>() ); + public static DynamicFetchBuilderLegacy fetch(String tableAlias, String ownerTableAlias, Fetchable fetchable) { + return new DynamicFetchBuilderLegacy( tableAlias, ownerTableAlias, fetchable, new ArrayList<>(), new HashMap<>() ); } public static ResultBuilder resultClassBuilder( diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/DomainResultCreationStateImpl.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/DomainResultCreationStateImpl.java index 5351c60dfac7..e7e5f7d8b68b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/DomainResultCreationStateImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/DomainResultCreationStateImpl.java @@ -12,14 +12,10 @@ import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.metamodel.mapping.Association; -import org.hibernate.metamodel.mapping.AttributeMapping; -import org.hibernate.metamodel.mapping.CompositeIdentifierMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ModelPart; -import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping; -import org.hibernate.metamodel.mapping.internal.BasicValuedCollectionPart; import org.hibernate.metamodel.mapping.internal.CaseStatementDiscriminatorMappingImpl; import org.hibernate.query.results.FetchBuilder; import org.hibernate.query.results.LegacyFetchBuilder; @@ -47,7 +43,6 @@ import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.spi.TypeConfiguration; -import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; @@ -82,8 +77,7 @@ public class DomainResultCreationStateImpl private final LegacyFetchResolver legacyFetchResolver; private final SessionFactoryImplementor sessionFactory; - private final Stack fetchBuilderResolverStack = new StandardStack<>( Function.class, fetchableName -> null ); - private final Stack relativePathStack = new StandardStack<>( Map.Entry.class ); + private final Stack fetchBuilderResolverStack = new StandardStack<>( fetchableName -> null ); private Map registeredLockModes; private boolean processingKeyFetches = false; private boolean resolvingCircularFetch; @@ -92,7 +86,7 @@ public class DomainResultCreationStateImpl public DomainResultCreationStateImpl( String stateIdentifier, JdbcValuesMetadata jdbcResultsMetadata, - Map> legacyFetchBuilders, + Map> legacyFetchBuilders, Consumer sqlSelectionConsumer, LoadQueryInfluencers loadQueryInfluencers, SessionFactoryImplementor sessionFactory) { @@ -129,33 +123,26 @@ public JdbcValuesMetadata getJdbcResultsMetadata() { return jdbcResultsMetadata; } - public Map.Entry getCurrentRelativePath() { - //noinspection unchecked - return relativePathStack.getCurrent(); - } - - public void pushExplicitFetchMementoResolver(Function resolver) { + public void pushExplicitFetchMementoResolver(Function resolver) { fetchBuilderResolverStack.push( resolver ); } - public Function getCurrentExplicitFetchMementoResolver() { - //noinspection unchecked + public Function getCurrentExplicitFetchMementoResolver() { return fetchBuilderResolverStack.getCurrent(); } - public Function popExplicitFetchMementoResolver() { - //noinspection unchecked + public Function popExplicitFetchMementoResolver() { return fetchBuilderResolverStack.pop(); } @SuppressWarnings( "unused" ) - public void withExplicitFetchMementoResolver(Function resolver, Runnable runnable) { + public void withExplicitFetchMementoResolver(Function resolver, Runnable runnable) { pushExplicitFetchMementoResolver( resolver ); try { runnable.run(); } finally { - final Function popped = popExplicitFetchMementoResolver(); + final Function popped = popExplicitFetchMementoResolver(); assert popped == resolver; } } @@ -270,7 +257,7 @@ public Expression resolveSqlExpression( sqlSelectionMap.put( key, (ResultSetMappingSqlSelection) created ); sqlSelectionConsumer.accept( (ResultSetMappingSqlSelection) created ); } - else if ( created instanceof ColumnReference columnReference ) { + else if ( created instanceof ColumnReference columnReference) { final String selectableName = columnReference.getSelectableName(); final int valuesArrayPosition; if ( nestingFetchParent != null ) { @@ -328,23 +315,23 @@ public SqlSelection resolveSqlSelection( } private static class LegacyFetchResolver { - private final Map> legacyFetchBuilders; + private final Map> legacyFetchBuilders; - public LegacyFetchResolver(Map> legacyFetchBuilders) { + public LegacyFetchResolver(Map> legacyFetchBuilders) { this.legacyFetchBuilders = legacyFetchBuilders; } - public LegacyFetchBuilder resolve(String ownerTableAlias, String fetchedPartPath) { + public LegacyFetchBuilder resolve(String ownerTableAlias, Fetchable fetchedPart) { if ( legacyFetchBuilders == null ) { return null; } - final Map fetchBuilders = legacyFetchBuilders.get( ownerTableAlias ); + final Map fetchBuilders = legacyFetchBuilders.get( ownerTableAlias ); if ( fetchBuilders == null ) { return null; } - return fetchBuilders.get( fetchedPartPath ); + return fetchBuilders.get( fetchedPart ); } } @@ -362,28 +349,18 @@ public Fetch visitIdentifierFetch(EntityResultGraphNode fetchParent) { final EntityValuedModelPart parentModelPart = fetchParent.getEntityValuedModelPart(); final EntityIdentifierMapping identifierMapping = parentModelPart.getEntityMappingType().getIdentifierMapping(); final String identifierAttributeName = attributeName( identifierMapping ); - //noinspection unchecked - final Map.Entry oldEntry = relativePathStack.getCurrent(); - final String fullPath; - if ( identifierMapping instanceof NonAggregatedIdentifierMapping ) { - fullPath = oldEntry == null ? "" : oldEntry.getKey(); - } - else { - fullPath = oldEntry == null ? - identifierAttributeName : - oldEntry.getKey() + "." + identifierAttributeName; - } - final Fetchable identifierFetchable = (Fetchable) identifierMapping; //noinspection unchecked - final FetchBuilder explicitFetchBuilder = (FetchBuilder) fetchBuilderResolverStack.getCurrent().apply( fullPath ); + final FetchBuilder explicitFetchBuilder = (FetchBuilder) fetchBuilderResolverStack + .getCurrent() + .apply( identifierMapping ); LegacyFetchBuilder fetchBuilderLegacy; if ( explicitFetchBuilder == null ) { fetchBuilderLegacy = legacyFetchResolver.resolve( fromClauseAccess.findTableGroup( fetchParent.getNavigablePath() ) .getPrimaryTableReference() .getIdentificationVariable(), - identifierAttributeName + identifierMapping ); } else { @@ -397,9 +374,6 @@ public Fetch visitIdentifierFetch(EntityResultGraphNode fetchParent) { final boolean processingKeyFetches = this.processingKeyFetches; this.processingKeyFetches = true; - if ( identifierMapping instanceof CompositeIdentifierMapping ) { - relativePathStack.push( new AbstractMap.SimpleEntry<>( fullPath, fetchPath ) ); - } try { final FetchBuilder fetchBuilder; @@ -410,7 +384,7 @@ else if ( fetchBuilderLegacy != null ) { fetchBuilder = fetchBuilderLegacy; } else { - fetchBuilder = Builders.implicitFetchBuilder( fetchPath, identifierFetchable, this ); + fetchBuilder = Builders.implicitFetchBuilder( fetchPath, identifierMapping, this ); } return fetchBuilder.buildFetch( @@ -422,9 +396,6 @@ else if ( fetchBuilderLegacy != null ) { } finally { this.processingKeyFetches = processingKeyFetches; - if ( identifierMapping instanceof CompositeIdentifierMapping ) { - this.relativePathStack.pop(); - } } } @@ -443,34 +414,16 @@ private Consumer createFetchableConsumer(FetchParent fetchParent, Imm if ( !fetchable.isSelectable() ) { return; } - final String fetchableName = fetchable.getFetchableName(); - Map.Entry currentEntry; - if ( relativePathStack.isEmpty() ) { - currentEntry = new AbstractMap.SimpleEntry<>( - getRelativePath( "", fetchable ), - new NavigablePath( fetchableName ) - ); - } - else { - //noinspection unchecked - final Map.Entry oldEntry = relativePathStack.getCurrent(); - final String key = oldEntry.getKey(); - currentEntry = new AbstractMap.SimpleEntry<>( - getRelativePath( key, fetchable ), - oldEntry.getValue().append( fetchableName ) - ); - } - // todo (6.0): figure out if we can somehow create the navigable paths in a better way - final String fullPath = currentEntry.getKey(); - //noinspection unchecked - FetchBuilder explicitFetchBuilder = (FetchBuilder) fetchBuilderResolverStack.getCurrent().apply( fullPath ); + FetchBuilder explicitFetchBuilder = (FetchBuilder) fetchBuilderResolverStack + .getCurrent() + .apply( fetchable ); LegacyFetchBuilder fetchBuilderLegacy; if ( explicitFetchBuilder == null ) { fetchBuilderLegacy = legacyFetchResolver.resolve( fromClauseAccess.findTableGroup( fetchParent.getNavigablePath() ) .getPrimaryTableReference() .getIdentificationVariable(), - fetchableName + fetchable ); } else { @@ -478,67 +431,44 @@ private Consumer createFetchableConsumer(FetchParent fetchParent, Imm } if ( fetchable instanceof Association association && fetchable.getMappedFetchOptions().getTiming() == FetchTiming.DELAYED ) { final ForeignKeyDescriptor foreignKeyDescriptor = association.getForeignKeyDescriptor(); - - final String partName = attributeName( - foreignKeyDescriptor.getSide( association.getSideNature().inverse() ).getModelPart() - ); - // If there are no fetch builders for this association, we only want to fetch the FK - if ( explicitFetchBuilder == null && fetchBuilderLegacy == null && partName != null ) { - currentEntry = new AbstractMap.SimpleEntry<>( - currentEntry.getKey() + "." + partName, - currentEntry.getValue().append( partName ) - ); - //noinspection unchecked - explicitFetchBuilder = (FetchBuilder) fetchBuilderResolverStack.getCurrent().apply( currentEntry.getKey() ); + if ( explicitFetchBuilder == null && fetchBuilderLegacy == null ) { + explicitFetchBuilder = (FetchBuilder) fetchBuilderResolverStack + .getCurrent() + .apply( foreignKeyDescriptor.getSide( association.getSideNature().inverse() ).getModelPart() ); if ( explicitFetchBuilder == null ) { fetchBuilderLegacy = legacyFetchResolver.resolve( fromClauseAccess.findTableGroup( fetchParent.getNavigablePath() ) .getPrimaryTableReference() .getIdentificationVariable(), - fetchableName + fetchable ); } } } - relativePathStack.push( currentEntry ); - try { - final NavigablePath fetchPath = fetchParent.resolveNavigablePath( fetchable ); - final FetchBuilder fetchBuilder; - if ( explicitFetchBuilder != null ) { - fetchBuilder = explicitFetchBuilder; - } - else if ( fetchBuilderLegacy == null ) { + final NavigablePath fetchPath = fetchParent.resolveNavigablePath( fetchable ); + final FetchBuilder fetchBuilder; + if ( explicitFetchBuilder != null ) { + fetchBuilder = explicitFetchBuilder; + } + else { + if ( fetchBuilderLegacy == null ) { fetchBuilder = Builders.implicitFetchBuilder( fetchPath, fetchable, this ); } else { fetchBuilder = fetchBuilderLegacy; } - final Fetch fetch = fetchBuilder.buildFetch( - fetchParent, - fetchPath, - jdbcResultsMetadata, - this - ); - fetches.add( fetch ); } - finally { - relativePathStack.pop(); - } - + final Fetch fetch = fetchBuilder.buildFetch( + fetchParent, + fetchPath, + jdbcResultsMetadata, + this + ); + fetches.add( fetch ); }; } - private String getRelativePath(String oldEntry, Fetchable fetchable) { - if ( fetchable instanceof AttributeMapping || fetchable instanceof BasicValuedCollectionPart ) { - if ( !"".equals( oldEntry ) ) { - return oldEntry + '.' + fetchable.getFetchableName(); - } - return fetchable.getFetchableName(); - } - return oldEntry; - } - @Override public boolean isResolvingCircularFetch() { return resolvingCircularFetch; diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/ResultSetMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/ResultSetMappingImpl.java index 9d99cad9643e..2ba16bc412d6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/ResultSetMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/ResultSetMappingImpl.java @@ -16,6 +16,7 @@ import org.hibernate.query.results.ResultSetMapping; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.basic.BasicResult; import org.hibernate.sql.results.graph.entity.EntityResult; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; @@ -44,7 +45,7 @@ public class ResultSetMappingImpl implements ResultSetMapping { private final String mappingIdentifier; private final boolean isDynamic; private List resultBuilders; - private Map> legacyFetchBuilders; + private Map> legacyFetchBuilders; public ResultSetMappingImpl(String mappingIdentifier) { this( mappingIdentifier, false ); @@ -72,15 +73,15 @@ private ResultSetMappingImpl(ResultSetMappingImpl original) { this.legacyFetchBuilders = null; } else { - final Map> legacyFetchBuilders = new HashMap<>( original.legacyFetchBuilders.size() ); - for ( Map.Entry> entry : original.legacyFetchBuilders.entrySet() ) { - final Map newValue = new HashMap<>( entry.getValue().size() ); - for ( Map.Entry builderEntry : entry.getValue().entrySet() ) { + final Map> builders = new HashMap<>( original.legacyFetchBuilders.size() ); + for ( Map.Entry> entry : original.legacyFetchBuilders.entrySet() ) { + final Map newValue = new HashMap<>( entry.getValue().size() ); + for ( Map.Entry builderEntry : entry.getValue().entrySet() ) { newValue.put( builderEntry.getKey(), builderEntry.getValue().cacheKeyInstance() ); } - legacyFetchBuilders.put( entry.getKey(), newValue ); + builders.put( entry.getKey(), newValue ); } - this.legacyFetchBuilders = legacyFetchBuilders; + this.legacyFetchBuilders = builders; } } @@ -123,7 +124,7 @@ public void visitLegacyFetchBuilders(Consumer resultBuilderC return; } - for ( Map.Entry> entry : legacyFetchBuilders.entrySet() ) { + for ( Map.Entry> entry : legacyFetchBuilders.entrySet() ) { for ( LegacyFetchBuilder fetchBuilder : entry.getValue().values() ) { resultBuilderConsumer.accept( fetchBuilder ); } @@ -140,7 +141,7 @@ public void addResultBuilder(ResultBuilder resultBuilder) { @Override public void addLegacyFetchBuilder(LegacyFetchBuilder fetchBuilder) { - final Map existingFetchBuildersByOwner; + final Map existingFetchBuildersByOwner; if ( legacyFetchBuilders == null ) { legacyFetchBuilders = new HashMap<>(); @@ -150,7 +151,7 @@ public void addLegacyFetchBuilder(LegacyFetchBuilder fetchBuilder) { existingFetchBuildersByOwner = legacyFetchBuilders.get( fetchBuilder.getOwnerAlias() ); } - final Map fetchBuildersByOwner; + final Map fetchBuildersByOwner; if ( existingFetchBuildersByOwner == null ) { fetchBuildersByOwner = new HashMap<>(); legacyFetchBuilders.put( fetchBuilder.getOwnerAlias(), fetchBuildersByOwner ); @@ -159,7 +160,7 @@ public void addLegacyFetchBuilder(LegacyFetchBuilder fetchBuilder) { fetchBuildersByOwner = existingFetchBuildersByOwner; } - fetchBuildersByOwner.put( fetchBuilder.getFetchableName(), fetchBuilder ); + fetchBuildersByOwner.put( fetchBuilder.getFetchable(), fetchBuilder ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/ResultsHelper.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/ResultsHelper.java index 7f1b46a26dfd..562dcf553070 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/ResultsHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/ResultsHelper.java @@ -4,6 +4,7 @@ */ package org.hibernate.query.results.internal; +import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping; @@ -13,6 +14,7 @@ import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnReferenceKey; +import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createDiscriminatorColumnReferenceKey; /** * @author Steve Ebersole @@ -60,6 +62,25 @@ public static Expression resolveSqlExpression( ); } + public static Expression resolveSqlExpression( + DomainResultCreationStateImpl resolver, + JdbcValuesMetadata jdbcValuesMetadata, + TableReference tableReference, + EntityDiscriminatorMapping discriminatorMapping, + String columnAlias) { + return resolver.resolveSqlExpression( + createDiscriminatorColumnReferenceKey( + tableReference, + discriminatorMapping + ), + processingState -> { + final int jdbcPosition = jdbcValuesMetadata.resolveColumnPosition( columnAlias ); + final int valuesArrayPosition = jdbcPositionToValuesArrayPosition( jdbcPosition ); + return new ResultSetMappingSqlSelection( valuesArrayPosition, discriminatorMapping.getJdbcMapping() ); + } + ); + } + public static Expression resolveSqlExpression( DomainResultCreationStateImpl resolver, TableReference tableReference, diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/complete/CompleteResultBuilderEntityJpa.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/complete/CompleteResultBuilderEntityJpa.java index 8cbddb2c8371..9abf4f185d18 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/complete/CompleteResultBuilderEntityJpa.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/complete/CompleteResultBuilderEntityJpa.java @@ -15,6 +15,7 @@ import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.spi.SqlAliasBase; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.entity.EntityResult; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; @@ -37,14 +38,14 @@ public class CompleteResultBuilderEntityJpa implements CompleteResultBuilderEnti private final EntityMappingType entityDescriptor; private final LockMode lockMode; private final FetchBuilderBasicValued discriminatorFetchBuilder; - private final HashMap explicitFetchBuilderMap; + private final HashMap explicitFetchBuilderMap; public CompleteResultBuilderEntityJpa( NavigablePath navigablePath, EntityMappingType entityDescriptor, LockMode lockMode, FetchBuilderBasicValued discriminatorFetchBuilder, - HashMap explicitFetchBuilderMap) { + HashMap explicitFetchBuilderMap) { this.navigablePath = navigablePath; this.entityDescriptor = entityDescriptor; this.lockMode = lockMode; @@ -132,7 +133,7 @@ public EntityResult buildResult( } @Override - public void visitFetchBuilders(BiConsumer consumer) { + public void visitFetchBuilders(BiConsumer consumer) { explicitFetchBuilderMap.forEach( consumer ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/complete/CompleteResultBuilderEntityStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/complete/CompleteResultBuilderEntityStandard.java index ea16de58818c..31e11c89917b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/complete/CompleteResultBuilderEntityStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/complete/CompleteResultBuilderEntityStandard.java @@ -16,6 +16,7 @@ import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.spi.SqlAliasBaseConstant; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.entity.EntityResult; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; @@ -32,7 +33,7 @@ public class CompleteResultBuilderEntityStandard implements CompleteResultBuilde private final EntityMappingType entityDescriptor; private final LockMode lockMode; private final FetchBuilderBasicValued discriminatorFetchBuilder; - private final HashMap explicitFetchBuilderMap; + private final HashMap explicitFetchBuilderMap; public CompleteResultBuilderEntityStandard( String tableAlias, @@ -40,7 +41,7 @@ public CompleteResultBuilderEntityStandard( EntityMappingType entityDescriptor, LockMode lockMode, FetchBuilderBasicValued discriminatorFetchBuilder, - HashMap explicitFetchBuilderMap) { + HashMap explicitFetchBuilderMap) { this.tableAlias = tableAlias; this.navigablePath = navigablePath; this.entityDescriptor = entityDescriptor; @@ -161,7 +162,7 @@ public EntityResult buildResult( } @Override - public void visitFetchBuilders(BiConsumer consumer) { + public void visitFetchBuilders(BiConsumer consumer) { explicitFetchBuilderMap.forEach( consumer ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/AbstractFetchBuilderContainer.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/AbstractFetchBuilderContainer.java index a1bebfb4f297..d568fe3fbcaf 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/AbstractFetchBuilderContainer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/AbstractFetchBuilderContainer.java @@ -11,13 +11,14 @@ import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.query.results.FetchBuilder; +import org.hibernate.sql.results.graph.Fetchable; /** * @author Steve Ebersole */ public abstract class AbstractFetchBuilderContainer> implements DynamicFetchBuilderContainer { - private Map fetchBuilderMap; + private Map fetchBuilderMap; protected AbstractFetchBuilderContainer() { } @@ -25,7 +26,7 @@ protected AbstractFetchBuilderContainer() { protected AbstractFetchBuilderContainer(AbstractFetchBuilderContainer original) { if ( original.fetchBuilderMap != null ) { fetchBuilderMap = new HashMap<>( original.fetchBuilderMap.size() ); - for ( Map.Entry entry : original.fetchBuilderMap.entrySet() ) { + for ( Map.Entry entry : original.fetchBuilderMap.entrySet() ) { final FetchBuilder fetchBuilder = entry.getValue() instanceof DynamicFetchBuilderStandard dynamicFetchBuilderStandard ? dynamicFetchBuilderStandard.cacheKeyInstance( this ) @@ -38,54 +39,55 @@ protected AbstractFetchBuilderContainer(AbstractFetchBuilderContainer origina protected abstract String getPropertyBase(); @Override - public FetchBuilder findFetchBuilder(String fetchableName) { - return fetchBuilderMap == null ? null : fetchBuilderMap.get( fetchableName ); + public FetchBuilder findFetchBuilder(Fetchable fetchable) { + return fetchBuilderMap == null ? null : fetchBuilderMap.get( fetchable ); } @Override - public T addProperty(String propertyName, String columnAlias) { - final DynamicFetchBuilder fetchBuilder = addProperty( propertyName ); + public T addProperty(Fetchable fetchable, String columnAlias) { + final DynamicFetchBuilder fetchBuilder = addProperty( fetchable ); fetchBuilder.addColumnAlias( columnAlias ); return (T) this; } @Override - public T addProperty(String propertyName, String... columnAliases) { - final DynamicFetchBuilder fetchBuilder = addProperty( propertyName ); + public T addProperty(Fetchable fetchable, String... columnAliases) { + final DynamicFetchBuilder fetchBuilder = addProperty( fetchable ); ArrayHelper.forEach( columnAliases, fetchBuilder::addColumnAlias ); return (T) this; } @Override - public DynamicFetchBuilder addProperty(String propertyName) { + public DynamicFetchBuilder addProperty(Fetchable fetchable) { if ( fetchBuilderMap == null ) { fetchBuilderMap = new HashMap<>(); } else { - final FetchBuilder existing = fetchBuilderMap.get( propertyName ); + final FetchBuilder existing = fetchBuilderMap.get( fetchable ); if ( existing != null ) { throw new IllegalArgumentException( String.format( Locale.ROOT, "Fetch was already defined for %s.%s : %s", getPropertyBase(), - propertyName, + fetchable, existing ) ); } } - final DynamicFetchBuilderStandard fetchBuilder = new DynamicFetchBuilderStandard( propertyName ); - fetchBuilderMap.put( propertyName, fetchBuilder ); + final DynamicFetchBuilderStandard fetchBuilder = new DynamicFetchBuilderStandard( fetchable ); + fetchBuilderMap.put( fetchable, fetchBuilder ); return fetchBuilder; } - public void addFetchBuilder(String propertyName, FetchBuilder fetchBuilder) { + @Override + public void addFetchBuilder(Fetchable fetchable, FetchBuilder fetchBuilder) { if ( fetchBuilderMap == null ) { fetchBuilderMap = new HashMap<>(); } - fetchBuilderMap.put( propertyName, fetchBuilder ); + fetchBuilderMap.put( fetchable, fetchBuilder ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderContainer.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderContainer.java index 6dcc3dd3495d..e7cc2c6ccacc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderContainer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderContainer.java @@ -5,6 +5,7 @@ package org.hibernate.query.results.internal.dynamic; import org.hibernate.query.results.FetchBuilder; +import org.hibernate.sql.results.graph.Fetchable; /** * @author Steve Ebersole @@ -13,22 +14,22 @@ public interface DynamicFetchBuilderContainer { /** * Locate an explicit fetch definition for the named fetchable */ - FetchBuilder findFetchBuilder(String fetchableName); + FetchBuilder findFetchBuilder(Fetchable fetchable); /** * Add a property mapped to a single column. */ - DynamicFetchBuilderContainer addProperty(String propertyName, String columnAlias); + DynamicFetchBuilderContainer addProperty(Fetchable fetchable, String columnAlias); /** * Add a property mapped to multiple columns */ - DynamicFetchBuilderContainer addProperty(String propertyName, String... columnAliases); + DynamicFetchBuilderContainer addProperty(Fetchable fetchable, String... columnAliases); /** * Add a property whose columns can later be defined using {@link DynamicFetchBuilder#addColumnAlias} */ - DynamicFetchBuilder addProperty(String propertyName); + DynamicFetchBuilder addProperty(Fetchable fetchable); - void addFetchBuilder(String propertyName, FetchBuilder fetchBuilder); + void addFetchBuilder(Fetchable fetchable, FetchBuilder fetchBuilder); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderLegacy.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderLegacy.java index 28a89a9f13cb..f2d74316ce0f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderLegacy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderLegacy.java @@ -7,11 +7,14 @@ import org.hibernate.AssertionFailure; import org.hibernate.LockMode; import org.hibernate.engine.FetchTiming; -import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.CollectionPart; +import org.hibernate.metamodel.mapping.EntityAssociationMapping; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.metamodel.mapping.ValuedModelPart; +import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; +import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.query.NativeQuery; import org.hibernate.query.results.FetchBuilder; @@ -28,6 +31,7 @@ import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import java.util.HashMap; @@ -46,16 +50,16 @@ public class DynamicFetchBuilderLegacy implements LegacyFetchBuilder, DynamicFetchBuilder, NativeQuery.FetchReturn, NativeQuery.ReturnableResultNode, DynamicFetchBuilderContainer { - private static final String ELEMENT_PREFIX = CollectionPart.Nature.ELEMENT.getName() + "."; - private static final String INDEX_PREFIX = CollectionPart.Nature.INDEX.getName() + "."; + private static final String ELEMENT_PREFIX = "element."; + private static final int ELEMENT_PREFIX_LENGTH = 8; private final String tableAlias; private final String ownerTableAlias; - private final String fetchableName; + private final Fetchable fetchable; private final List columnNames; - private final Map fetchBuilderMap; + private final Map fetchBuilderMap; private final DynamicResultBuilderEntityStandard resultBuilderEntity; private LockMode lockMode; @@ -63,22 +67,22 @@ public class DynamicFetchBuilderLegacy public DynamicFetchBuilderLegacy( String tableAlias, String ownerTableAlias, - String fetchableName, + Fetchable fetchable, List columnNames, - Map fetchBuilderMap) { - this( tableAlias, ownerTableAlias, fetchableName, columnNames, fetchBuilderMap, null ); + Map fetchBuilderMap) { + this( tableAlias, ownerTableAlias, fetchable, columnNames, fetchBuilderMap, null ); } public DynamicFetchBuilderLegacy( String tableAlias, String ownerTableAlias, - String fetchableName, + Fetchable fetchable, List columnNames, - Map fetchBuilderMap, + Map fetchBuilderMap, DynamicResultBuilderEntityStandard resultBuilderEntity) { this.tableAlias = tableAlias; this.ownerTableAlias = ownerTableAlias; - this.fetchableName = fetchableName; + this.fetchable = fetchable; this.columnNames = columnNames; this.fetchBuilderMap = fetchBuilderMap; this.resultBuilderEntity = resultBuilderEntity; @@ -94,9 +98,55 @@ public String getOwnerAlias() { return ownerTableAlias; } + @Override + public Fetchable getFetchable() { + return fetchable; + } + @Override public String getFetchableName() { - return fetchableName; + return fetchable.getFetchableName(); + } + + @Override + public NativeQuery.FetchReturn setLockMode(LockMode lockMode) { + this.lockMode = lockMode; + return this; + } + + @Override + public NativeQuery.FetchReturn addProperty(String propertyName, String columnAlias) { + addProperty( resolveFetchable( propertyName ), columnAlias ); + return this; + } + + private Fetchable resolveFetchable(String propertyName) { + if ( fetchable instanceof EntityAssociationMapping attributeMapping ) { + return (Fetchable) attributeMapping.findByPath( propertyName ); + } + else if ( fetchable instanceof PluralAttributeMapping pluralAttributeMapping ) { + if ( propertyName.equals( "key" ) ) { + return pluralAttributeMapping.getIndexDescriptor(); + } + else if ( propertyName.equals( "element" ) ) { + return pluralAttributeMapping.getElementDescriptor().getCollectionAttribute(); + } + else { + final CollectionPart elementDescriptor = pluralAttributeMapping.getElementDescriptor(); + if ( elementDescriptor instanceof EntityCollectionPart entityCollectionPart ) { + if ( propertyName.startsWith( ELEMENT_PREFIX ) ) { + propertyName = propertyName.substring( ELEMENT_PREFIX_LENGTH ); + } + return (Fetchable) entityCollectionPart.getEntityMappingType().findByPath( propertyName ); + } + } + } + throw new UnsupportedOperationException( "Unsupported fetchable type: " + fetchable.getClass().getName() ); + } + + @Override + public NativeQuery.ReturnProperty addProperty(String propertyName) { + return addProperty( resolveFetchable( propertyName ) ); } @Override @@ -104,20 +154,20 @@ public DynamicFetchBuilderLegacy cacheKeyInstance() { return new DynamicFetchBuilderLegacy( tableAlias, ownerTableAlias, - fetchableName, + fetchable, columnNames == null ? null : List.copyOf( columnNames ), fetchBuilderMap(), resultBuilderEntity == null ? null : resultBuilderEntity.cacheKeyInstance() ); } - private Map fetchBuilderMap() { + private Map fetchBuilderMap() { if ( this.fetchBuilderMap == null ) { return null; } else { - final Map fetchBuilderMap = new HashMap<>( this.fetchBuilderMap.size() ); - for ( Map.Entry entry : this.fetchBuilderMap.entrySet() ) { + final Map fetchBuilderMap = new HashMap<>( this.fetchBuilderMap.size() ); + for ( Map.Entry entry : this.fetchBuilderMap.entrySet() ) { fetchBuilderMap.put( entry.getKey(), entry.getValue().cacheKeyInstance() ); } return fetchBuilderMap; @@ -132,52 +182,69 @@ public Fetch buildFetch( DomainResultCreationState domainResultCreationState) { final DomainResultCreationStateImpl creationState = impl( domainResultCreationState ); final TableGroup ownerTableGroup = creationState.getFromClauseAccess().findByAlias( ownerTableAlias ); - final AttributeMapping attributeMapping = - parent.getReferencedMappingContainer().findContainingEntityMapping() - .findDeclaredAttributeMapping( fetchableName ); - final TableGroup tableGroup = tableGroup( fetchPath, attributeMapping, ownerTableGroup, creationState ); - + final TableGroup tableGroup = tableGroup( fetchPath, ownerTableGroup, creationState ); + if ( lockMode != null ) { + domainResultCreationState.getSqlAstCreationState().registerLockMode( tableAlias, lockMode ); + } if ( columnNames != null ) { - final ForeignKeyDescriptor keyDescriptor = getForeignKeyDescriptor( attributeMapping ); - if ( !columnNames.isEmpty() ) { - keyDescriptor.forEachSelectable( (selectionIndex, selectableMapping) -> { - resolveSqlSelection( - columnNames.get( selectionIndex ), - tableGroup.resolveTableReference( - fetchPath, - keyDescriptor.getKeyPart(), - selectableMapping.getContainingTableExpression() - ), - selectableMapping, - jdbcResultsMetadata, - domainResultCreationState - ); } + if ( fetchable instanceof EmbeddedAttributeMapping embeddedAttributeMapping ) { + embeddedAttributeMapping.forEachSelectable( + (selectionIndex, selectableMapping) -> + resolveSqlSelection( + columnNames.get( selectionIndex ), + tableGroup.resolveTableReference( + fetchPath, + (ValuedModelPart) selectableMapping, + selectableMapping.getContainingTableExpression() + ), + selectableMapping, + jdbcResultsMetadata, + domainResultCreationState + ) ); } - + else { + final ForeignKeyDescriptor keyDescriptor = getForeignKeyDescriptor( fetchable ); + if ( !columnNames.isEmpty() ) { + keyDescriptor.forEachSelectable( (selectionIndex, selectableMapping) -> { + resolveSqlSelection( + columnNames.get( selectionIndex ), + tableGroup.resolveTableReference( + fetchPath, + keyDescriptor.getKeyPart(), + selectableMapping.getContainingTableExpression() + ), + selectableMapping, + jdbcResultsMetadata, + domainResultCreationState + ); + } + ); + } + } // We process the fetch builder such that it contains a resultBuilderEntity before calling this method in ResultSetMappingProcessor if ( resultBuilderEntity != null ) { return resultBuilderEntity.buildFetch( parent, - attributeMapping, + fetchable, jdbcResultsMetadata, creationState ); } + } try { - final String prefix = DynamicResultBuilderEntityStandard.prefix( creationState, ELEMENT_PREFIX, INDEX_PREFIX ); creationState.pushExplicitFetchMementoResolver( - relativePath -> { - if ( relativePath.startsWith( prefix ) ) { - return findFetchBuilder( relativePath.substring( prefix.length() ) ); + fetchable -> { + if ( fetchable != null ) { + return findFetchBuilder( fetchable ); } return null; } ); return parent.generateFetchableFetch( - attributeMapping, - parent.resolveNavigablePath( attributeMapping ), + fetchable, + parent.resolveNavigablePath( fetchable ), FetchTiming.IMMEDIATE, true, null, @@ -191,10 +258,9 @@ public Fetch buildFetch( private TableGroup tableGroup( NavigablePath fetchPath, - AttributeMapping attributeMapping, TableGroup ownerTableGroup, DomainResultCreationStateImpl creationState) { - if ( attributeMapping instanceof TableGroupJoinProducer tableGroupJoinProducer ) { + if ( fetchable instanceof TableGroupJoinProducer tableGroupJoinProducer ) { final TableGroupJoin tableGroupJoin = tableGroupJoinProducer.createTableGroupJoin( fetchPath, ownerTableGroup, @@ -215,11 +281,11 @@ private TableGroup tableGroup( } } - private static ForeignKeyDescriptor getForeignKeyDescriptor(AttributeMapping attributeMapping) { - if ( attributeMapping instanceof PluralAttributeMapping pluralAttributeMapping ) { + private static ForeignKeyDescriptor getForeignKeyDescriptor(Fetchable fetchable) { + if ( fetchable instanceof PluralAttributeMapping pluralAttributeMapping ) { return pluralAttributeMapping.getKeyDescriptor(); } - else if ( attributeMapping instanceof ToOneAttributeMapping toOneAttributeMapping ) { + else if ( fetchable instanceof ToOneAttributeMapping toOneAttributeMapping ) { return toOneAttributeMapping.getForeignKeyDescriptor(); } else { @@ -228,6 +294,11 @@ else if ( attributeMapping instanceof ToOneAttributeMapping toOneAttributeMappin } } + @Override + public void visitFetchBuilders(BiConsumer consumer) { + fetchBuilderMap.forEach( consumer ); + } + private void resolveSqlSelection( String columnAlias, TableReference tableReference, @@ -262,32 +333,27 @@ public List getColumnAliases() { } @Override - public NativeQuery.FetchReturn setLockMode(LockMode lockMode) { - this.lockMode = lockMode; - return this; - } - - @Override - public DynamicFetchBuilderLegacy addProperty(String propertyName, String columnAlias) { - addProperty( propertyName ).addColumnAlias( columnAlias ); - return this; + public DynamicFetchBuilder addProperty(Fetchable fetchable) { + DynamicFetchBuilderStandard fetchBuilder = new DynamicFetchBuilderStandard( fetchable ); + fetchBuilderMap.put( fetchable, fetchBuilder ); + return fetchBuilder; } @Override - public DynamicFetchBuilder addProperty(String propertyName) { - DynamicFetchBuilderStandard fetchBuilder = new DynamicFetchBuilderStandard( propertyName ); - fetchBuilderMap.put( propertyName, fetchBuilder ); - return fetchBuilder; + public FetchBuilder findFetchBuilder(Fetchable fetchable) { + return fetchBuilderMap.get( fetchable ); } @Override - public FetchBuilder findFetchBuilder(String fetchableName) { - return fetchBuilderMap.get( fetchableName ); + public DynamicFetchBuilderContainer addProperty(Fetchable fetchable, String columnAlias) { + final DynamicFetchBuilder fetchBuilder = addProperty( fetchable ); + fetchBuilder.addColumnAlias( columnAlias ); + return this; } @Override - public DynamicFetchBuilderContainer addProperty(String propertyName, String... columnAliases) { - final DynamicFetchBuilder fetchBuilder = addProperty( propertyName ); + public DynamicFetchBuilderContainer addProperty(Fetchable fetchable, String... columnAliases) { + final DynamicFetchBuilder fetchBuilder = addProperty( fetchable ); for ( String columnAlias : columnAliases ) { fetchBuilder.addColumnAlias( columnAlias ); } @@ -295,13 +361,8 @@ public DynamicFetchBuilderContainer addProperty(String propertyName, String... c } @Override - public void addFetchBuilder(String propertyName, FetchBuilder fetchBuilder) { - fetchBuilderMap.put( propertyName, fetchBuilder ); - } - - @Override - public void visitFetchBuilders(BiConsumer consumer) { - fetchBuilderMap.forEach( consumer ); + public void addFetchBuilder(Fetchable fetchable, FetchBuilder fetchBuilder) { + fetchBuilderMap.put( fetchable, fetchBuilder ); } @Override @@ -316,7 +377,8 @@ public boolean equals(Object o) { final DynamicFetchBuilderLegacy that = (DynamicFetchBuilderLegacy) o; return tableAlias.equals( that.tableAlias ) && ownerTableAlias.equals( that.ownerTableAlias ) - && fetchableName.equals( that.fetchableName ) + && fetchable.equals( that.fetchable ) + && lockMode.equals( that.lockMode ) && Objects.equals( columnNames, that.columnNames ) && Objects.equals( fetchBuilderMap, that.fetchBuilderMap ) && Objects.equals( resultBuilderEntity, that.resultBuilderEntity ); @@ -326,7 +388,8 @@ public boolean equals(Object o) { public int hashCode() { int result = tableAlias.hashCode(); result = 31 * result + ownerTableAlias.hashCode(); - result = 31 * result + fetchableName.hashCode(); + result = 31 * result + fetchable.hashCode(); + result = 31 * result + lockMode.hashCode(); result = 31 * result + ( columnNames != null ? columnNames.hashCode() : 0 ); result = 31 * result + ( fetchBuilderMap != null ? fetchBuilderMap.hashCode() : 0 ); result = 31 * result + ( resultBuilderEntity != null ? resultBuilderEntity.hashCode() : 0 ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderStandard.java index b6421a0b3fbc..9f955e8f805e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderStandard.java @@ -36,30 +36,29 @@ public class DynamicFetchBuilderStandard implements DynamicFetchBuilder, NativeQuery.ReturnProperty { - private final String fetchableName; + private Fetchable fetchable; private final List columnNames; - public DynamicFetchBuilderStandard(String fetchableName) { - this.fetchableName = fetchableName; - this.columnNames = new ArrayList<>(); + public DynamicFetchBuilderStandard(Fetchable fetchable) { + this( fetchable, new ArrayList<>() ); } - private DynamicFetchBuilderStandard(String fetchableName, List columnNames) { - this.fetchableName = fetchableName; + private DynamicFetchBuilderStandard(Fetchable fetchable, List columnNames) { + this.fetchable = fetchable; this.columnNames = columnNames; } @Override public DynamicFetchBuilderStandard cacheKeyInstance() { return new DynamicFetchBuilderStandard( - fetchableName, + fetchable, List.copyOf( columnNames ) ); } public DynamicFetchBuilderStandard cacheKeyInstance(DynamicFetchBuilderContainer container) { return new DynamicFetchBuilderStandard( - fetchableName, + fetchable, List.copyOf( columnNames ) ); } @@ -74,14 +73,12 @@ public Fetch buildFetch( final TableGroup ownerTableGroup = creationStateImpl.getFromClauseAccess().getTableGroup( parent.getNavigablePath() ); - final Fetchable attributeMapping = - (Fetchable) parent.getReferencedMappingContainer().findSubPart( fetchableName, null ); final SqlExpressionResolver sqlExpressionResolver = domainResultCreationState.getSqlAstCreationState().getSqlExpressionResolver(); - final BasicValuedModelPart basicPart = attributeMapping.asBasicValuedModelPart(); + final BasicValuedModelPart basicPart = fetchable.asBasicValuedModelPart(); if ( basicPart != null ) { - attributeMapping.forEachSelectable( + fetchable.forEachSelectable( getSelectableConsumer( fetchPath, jdbcResultsMetadata, @@ -93,7 +90,7 @@ public Fetch buildFetch( ) ); return parent.generateFetchableFetch( - attributeMapping, + fetchable, fetchPath, FetchTiming.IMMEDIATE, true, @@ -101,8 +98,8 @@ public Fetch buildFetch( creationStateImpl ); } - else if ( attributeMapping instanceof EmbeddableValuedFetchable ) { - attributeMapping.forEachSelectable( + else if ( fetchable instanceof EmbeddableValuedFetchable embeddableValuedFetchable ) { + fetchable.forEachSelectable( getSelectableConsumer( fetchPath, jdbcResultsMetadata, @@ -110,11 +107,11 @@ else if ( attributeMapping instanceof EmbeddableValuedFetchable ) { creationStateImpl, ownerTableGroup, sqlExpressionResolver, - (EmbeddableValuedFetchable) attributeMapping + embeddableValuedFetchable ) ); return parent.generateFetchableFetch( - attributeMapping, + fetchable, fetchPath, FetchTiming.IMMEDIATE, false, @@ -122,7 +119,7 @@ else if ( attributeMapping instanceof EmbeddableValuedFetchable ) { creationStateImpl ); } - else if ( attributeMapping instanceof ToOneAttributeMapping toOneAttributeMapping ) { + else if ( fetchable instanceof ToOneAttributeMapping toOneAttributeMapping ) { toOneAttributeMapping.getForeignKeyDescriptor() .getPart( toOneAttributeMapping.getSideNature() ) .forEachSelectable( @@ -137,15 +134,15 @@ else if ( attributeMapping instanceof ToOneAttributeMapping toOneAttributeMappin ) ); return parent.generateFetchableFetch( - attributeMapping, + fetchable, fetchPath, - attributeMapping.getMappedFetchOptions().getTiming(), + fetchable.getMappedFetchOptions().getTiming(), false, null, creationStateImpl ); } - else if ( attributeMapping instanceof PluralAttributeMapping pluralAttributeMapping ) { + else if ( fetchable instanceof PluralAttributeMapping pluralAttributeMapping ) { pluralAttributeMapping.getKeyDescriptor().visitTargetSelectables( getSelectableConsumer( fetchPath, @@ -158,9 +155,9 @@ else if ( attributeMapping instanceof PluralAttributeMapping pluralAttributeMapp ) ); return parent.generateFetchableFetch( - attributeMapping, + fetchable, fetchPath, - attributeMapping.getMappedFetchOptions().getTiming(), + fetchable.getMappedFetchOptions().getTiming(), false, null, creationStateImpl @@ -215,7 +212,7 @@ public List getColumnAliases() { @Override public int hashCode() { - int result = fetchableName.hashCode(); + int result = fetchable.hashCode(); result = 31 * result + columnNames.hashCode(); return result; } @@ -230,7 +227,7 @@ public boolean equals(Object o) { } final DynamicFetchBuilderStandard that = (DynamicFetchBuilderStandard) o; - return fetchableName.equals( that.fetchableName ) + return fetchable.equals( that.fetchable ) && columnNames.equals( that.columnNames ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicResultBuilderEntityStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicResultBuilderEntityStandard.java index 08ca18768735..02a044a88184 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicResultBuilderEntityStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicResultBuilderEntityStandard.java @@ -7,13 +7,12 @@ import org.hibernate.LockMode; import org.hibernate.engine.FetchTiming; import org.hibernate.metamodel.mapping.CollectionPart; -import org.hibernate.metamodel.mapping.EntityIdentifierMapping; +import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.internal.ManyToManyCollectionPart; -import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping; import org.hibernate.query.NativeQuery; import org.hibernate.query.results.FetchBuilder; import org.hibernate.query.results.internal.DomainResultCreationStateImpl; @@ -34,7 +33,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.function.Function; @@ -48,9 +46,6 @@ public class DynamicResultBuilderEntityStandard extends AbstractFetchBuilderContainer implements DynamicResultBuilderEntity, NativeQuery.RootReturn { - private static final String ELEMENT_PREFIX = CollectionPart.Nature.ELEMENT.getName() + "."; - private static final String INDEX_PREFIX = CollectionPart.Nature.INDEX.getName() + "."; - private final NavigablePath navigablePath; private final EntityMappingType entityMapping; @@ -84,18 +79,6 @@ private DynamicResultBuilderEntityStandard(DynamicResultBuilderEntityStandard or this.discriminatorColumnName = original.discriminatorColumnName; } - static String prefix(DomainResultCreationStateImpl creationState, String elementPrefix, String indexPrefix) { - final Map.Entry currentRelativePath = creationState.getCurrentRelativePath(); - if ( currentRelativePath == null ) { - return ""; - } - else { - return currentRelativePath.getKey() - .replace( elementPrefix, "" ) - .replace( indexPrefix, "" ) + "."; - } - } - @Override public Class getJavaType() { return entityMapping.getJavaType().getJavaTypeClass(); @@ -277,7 +260,7 @@ else if ( idFetchBuilder != null ) { } if ( discriminatorColumnName != null ) { - resolveSqlSelection( + resolveDiscriminatorSqlSelection( discriminatorColumnName, tableReference, entityMapping.getDiscriminatorMapping(), @@ -287,18 +270,10 @@ else if ( idFetchBuilder != null ) { } try { - final String prefix = prefix( creationState, ELEMENT_PREFIX, INDEX_PREFIX ); creationState.pushExplicitFetchMementoResolver( - relativePath -> { - if ( relativePath.startsWith( prefix ) ) { - final int startIndex; - if ( relativePath.regionMatches( prefix.length(), ELEMENT_PREFIX, 0, ELEMENT_PREFIX.length() ) ) { - startIndex = prefix.length() + ELEMENT_PREFIX.length(); - } - else { - startIndex = prefix.length(); - } - return findFetchBuilder( relativePath.substring( startIndex ) ); + f -> { + if ( f != null ) { + return findFetchBuilder( f ); } return null; } @@ -310,12 +285,27 @@ else if ( idFetchBuilder != null ) { } } + private static void resolveDiscriminatorSqlSelection(String columnAlias, TableReference tableReference, EntityDiscriminatorMapping discriminatorMapping, JdbcValuesMetadata jdbcResultsMetadata, DomainResultCreationState domainResultCreationState) { + final DomainResultCreationStateImpl creationStateImpl = impl( domainResultCreationState ); + creationStateImpl.resolveSqlSelection( + ResultsHelper.resolveSqlExpression( + creationStateImpl, + jdbcResultsMetadata, + tableReference, + discriminatorMapping, + columnAlias + ), + discriminatorMapping.getJdbcMapping().getJdbcJavaType(), + null, + domainResultCreationState.getSqlAstCreationState() + .getCreationContext() + .getSessionFactory() + .getTypeConfiguration() + ); + } + private FetchBuilder findIdFetchBuilder() { - final EntityIdentifierMapping identifierMapping = entityMapping.getIdentifierMapping(); - if ( identifierMapping instanceof SingleAttributeIdentifierMapping ) { - return findFetchBuilder( ( (SingleAttributeIdentifierMapping) identifierMapping ).getAttributeName() ); - } - return findFetchBuilder( identifierMapping.getPartName() ); + return findFetchBuilder( entityMapping.getIdentifierMapping() ); } private void resolveSqlSelection( @@ -354,6 +344,18 @@ public DynamicResultBuilderEntityStandard setDiscriminatorAlias(String columnNam return this; } + @Override + public NativeQuery.RootReturn addProperty(String propertyName, String columnAlias) { + final ModelPart subPart = entityMapping.findSubPart( propertyName ); + addProperty( (Fetchable) subPart, columnAlias ); + return this; + } + + @Override + public NativeQuery.ReturnProperty addProperty(String propertyName) { + return addProperty( (Fetchable) entityMapping.findSubPart( propertyName ) ); + } + @Override public int hashCode() { int result = super.hashCode(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderBasic.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderBasic.java index a2babf7f3be9..331d8463c586 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderBasic.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderBasic.java @@ -15,6 +15,7 @@ import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; @@ -42,9 +43,7 @@ public ImplicitFetchBuilderBasic( DomainResultCreationState creationState) { this.fetchPath = fetchPath; this.fetchable = fetchable; - this.fetchBuilder = - impl( creationState ).getCurrentExplicitFetchMementoResolver() - .apply( fetchable.getFetchableName() ); + this.fetchBuilder = impl( creationState ).getCurrentExplicitFetchMementoResolver().apply( fetchable ); } @Override @@ -135,9 +134,9 @@ public int hashCode() { } @Override - public void visitFetchBuilders(BiConsumer consumer) { + public void visitFetchBuilders(BiConsumer consumer) { if ( fetchBuilder != null ) { - consumer.accept( fetchPath.getLocalName(), fetchBuilder ); + consumer.accept( fetchable, fetchBuilder ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderEmbeddable.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderEmbeddable.java index 3e469ea5a5c2..3b5d36c68f57 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderEmbeddable.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderEmbeddable.java @@ -34,7 +34,7 @@ public class ImplicitFetchBuilderEmbeddable implements ImplicitFetchBuilder { private final NavigablePath fetchPath; private final EmbeddableValuedFetchable fetchable; - private final Map fetchBuilders; + private final Map fetchBuilders; public ImplicitFetchBuilderEmbeddable( NavigablePath fetchPath, @@ -45,20 +45,18 @@ public ImplicitFetchBuilderEmbeddable( this.fetchBuilders = fetchBuilderMap( fetchPath, fetchable, impl( creationState ) ); } - private static Map fetchBuilderMap( + private static Map fetchBuilderMap( NavigablePath fetchPath, EmbeddableValuedFetchable fetchable, DomainResultCreationStateImpl creationStateImpl) { - final Function fetchBuilderResolver = + final Function fetchBuilderResolver = creationStateImpl.getCurrentExplicitFetchMementoResolver(); - final Map.Entry relativePath = creationStateImpl.getCurrentRelativePath(); final int size = fetchable.getNumberOfFetchables(); - final Map fetchBuilders = linkedMapOfSize( size ); + final Map fetchBuilders = linkedMapOfSize( size ); for ( int i = 0; i < size; i++ ) { final Fetchable subFetchable = fetchable.getFetchable( i ); - final NavigablePath subFetchPath = relativePath.getValue().append( subFetchable.getFetchableName() ); - final FetchBuilder explicitFetchBuilder = fetchBuilderResolver.apply( subFetchPath.getFullPath() ); - fetchBuilders.put( subFetchPath, + final FetchBuilder explicitFetchBuilder = fetchBuilderResolver.apply( subFetchable ); + fetchBuilders.put( subFetchable, explicitFetchBuilder == null ? implicitFetchBuilder( fetchPath, subFetchable, creationStateImpl ) : explicitFetchBuilder ); @@ -74,7 +72,7 @@ private ImplicitFetchBuilderEmbeddable(ImplicitFetchBuilderEmbeddable original) } else { fetchBuilders = new HashMap<>( original.fetchBuilders.size() ); - for ( Map.Entry entry : original.fetchBuilders.entrySet() ) { + for ( Map.Entry entry : original.fetchBuilders.entrySet() ) { fetchBuilders.put( entry.getKey(), entry.getValue().cacheKeyInstance() ); } } @@ -169,7 +167,7 @@ public String toString() { } @Override - public void visitFetchBuilders(BiConsumer consumer) { - fetchBuilders.forEach( (k, v) -> consumer.accept( k.getLocalName(), v ) ); + public void visitFetchBuilders(BiConsumer consumer) { + fetchBuilders.forEach( (k, v) -> consumer.accept( k, v ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderEntity.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderEntity.java index 609284f12c0b..60d06e52b914 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderEntity.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderEntity.java @@ -34,7 +34,7 @@ public class ImplicitFetchBuilderEntity implements ImplicitFetchBuilder { private final NavigablePath fetchPath; private final ToOneAttributeMapping fetchable; - private final Map fetchBuilders; + private final Map fetchBuilders; public ImplicitFetchBuilderEntity( NavigablePath fetchPath, @@ -43,30 +43,28 @@ public ImplicitFetchBuilderEntity( this.fetchPath = fetchPath; this.fetchable = fetchable; final DomainResultCreationStateImpl creationStateImpl = impl( creationState ); - final Map.Entry relativePath = creationStateImpl.getCurrentRelativePath(); - final Function fetchBuilderResolver = creationStateImpl.getCurrentExplicitFetchMementoResolver(); + final Function fetchBuilderResolver = creationStateImpl.getCurrentExplicitFetchMementoResolver(); final ForeignKeyDescriptor foreignKeyDescriptor = fetchable.getForeignKeyDescriptor(); final String associationKeyPropertyName; final NavigablePath associationKeyFetchPath; + final Fetchable associationKey; if ( fetchable.getReferencedPropertyName() == null ) { associationKeyPropertyName = fetchable.getEntityMappingType().getIdentifierMapping().getPartName(); - associationKeyFetchPath = relativePath.getValue().append( associationKeyPropertyName ); - } + associationKey = (Fetchable) fetchable.findSubPart( associationKeyPropertyName ); } else { associationKeyPropertyName = fetchable.getReferencedPropertyName(); - NavigablePath path = relativePath.getValue(); + String keyName = associationKeyPropertyName; for ( String part : split( ".", associationKeyPropertyName ) ) { - path = path.append( part ); + keyName = part; } - associationKeyFetchPath = path; + associationKey = (Fetchable) fetchable.findSubPart( keyName ); } final FetchBuilder explicitAssociationKeyFetchBuilder = - fetchBuilderResolver.apply( relativePath.getKey() + "." + associationKeyPropertyName ); + fetchBuilderResolver.apply( fetchable); if ( explicitAssociationKeyFetchBuilder == null ) { if ( foreignKeyDescriptor.getPartMappingType() instanceof EmbeddableMappingType embeddableType ) { fetchBuilders = fetchBuilderMap( fetchPath, - associationKeyFetchPath, fetchBuilderResolver, creationStateImpl, embeddableType @@ -77,22 +75,21 @@ public ImplicitFetchBuilderEntity( } } else { - fetchBuilders = singletonMap( associationKeyFetchPath, explicitAssociationKeyFetchBuilder ); + fetchBuilders = singletonMap( associationKey, explicitAssociationKeyFetchBuilder ); } } - private static Map fetchBuilderMap( - NavigablePath fetchPath, NavigablePath associationKeyFetchPath, - Function fetchBuilderResolver, + private static Map fetchBuilderMap( + NavigablePath fetchPath, + Function fetchBuilderResolver, DomainResultCreationStateImpl creationStateImpl, EmbeddableMappingType embeddableValuedModelPart) { final int size = embeddableValuedModelPart.getNumberOfFetchables(); - final Map fetchBuilders = linkedMapOfSize( size ); + final Map fetchBuilders = linkedMapOfSize( size ); for ( int i = 0; i < size; i++ ) { final Fetchable subFetchable = embeddableValuedModelPart.getFetchable( i ); - final NavigablePath subFetchPath = associationKeyFetchPath.append( subFetchable.getFetchableName() ); - final FetchBuilder explicitFetchBuilder = fetchBuilderResolver.apply( subFetchPath.getFullPath() ); - fetchBuilders.put( subFetchPath, + final FetchBuilder explicitFetchBuilder = fetchBuilderResolver.apply( subFetchable ); + fetchBuilders.put( subFetchable, explicitFetchBuilder == null ? Builders.implicitFetchBuilder( fetchPath, subFetchable, creationStateImpl ) : explicitFetchBuilder ); @@ -108,7 +105,7 @@ private ImplicitFetchBuilderEntity(ImplicitFetchBuilderEntity original) { } else { fetchBuilders = new HashMap<>( original.fetchBuilders.size() ); - for ( Map.Entry entry : original.fetchBuilders.entrySet() ) { + for ( Map.Entry entry : original.fetchBuilders.entrySet() ) { fetchBuilders.put( entry.getKey(), entry.getValue().cacheKeyInstance() ); } } @@ -136,8 +133,8 @@ public Fetch buildFetch( } @Override - public void visitFetchBuilders(BiConsumer consumer) { - fetchBuilders.forEach( (k, v) -> consumer.accept( k.getLocalName(), v ) ); + public void visitFetchBuilders(BiConsumer consumer) { + fetchBuilders.forEach( (k, v) -> consumer.accept( k, v ) ); } @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 1a692df5b35e..76b3fd66bdf9 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 @@ -9,6 +9,7 @@ import java.util.Calendar; import java.util.Collection; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -23,6 +24,12 @@ import org.hibernate.jpa.spi.NativeQueryConstructorTransformer; import org.hibernate.jpa.spi.NativeQueryListTransformer; import org.hibernate.jpa.spi.NativeQueryMapTransformer; +import org.hibernate.metamodel.mapping.EntityAssociationMapping; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.MappingType; +import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.query.QueryFlushMode; import org.hibernate.HibernateException; @@ -90,6 +97,7 @@ 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.graph.Fetchable; import org.hibernate.sql.results.spi.SingleResultConsumer; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.BasicType; @@ -140,6 +148,7 @@ public class NativeQueryImpl private final Class resultType; private final ResultSetMapping resultSetMapping; private final boolean resultMappingSuppliedToCtor; + private final HashMap entityMappingTypeByTableAlias = new HashMap<>(); private final QueryOptionsImpl queryOptions = new QueryOptionsImpl(); @@ -1093,6 +1102,7 @@ public DynamicResultBuilderEntityStandard addRoot(String tableAlias, String enti final DynamicResultBuilderEntityStandard resultBuilder = Builders.entity( tableAlias, entityName, getSessionFactory() ); resultSetMapping.addResultBuilder( resultBuilder ); + entityMappingTypeByTableAlias.put( tableAlias, resultBuilder.getEntityMapping() ); return resultBuilder; } @@ -1108,13 +1118,19 @@ public NativeQueryImplementor addEntity(String entityName) { @Override public NativeQueryImplementor addEntity(String tableAlias, String entityName) { - registerBuilder( Builders.entityCalculated( tableAlias, entityName, getSessionFactory() ) ); + final DynamicResultBuilderEntityCalculated builder = Builders.entityCalculated( tableAlias, entityName, + getSessionFactory() ); + entityMappingTypeByTableAlias.put( tableAlias, builder.getEntityMapping() ); + registerBuilder( builder ); return this; } @Override public NativeQueryImplementor addEntity(String tableAlias, String entityName, LockMode lockMode) { - registerBuilder( Builders.entityCalculated( tableAlias, entityName, lockMode, getSessionFactory() ) ); + final DynamicResultBuilderEntityCalculated builder = Builders.entityCalculated( tableAlias, entityName, lockMode, + getSessionFactory() ); + entityMappingTypeByTableAlias.put( tableAlias, builder.getEntityMapping() ); + registerBuilder( builder ); return this; } @@ -1140,11 +1156,29 @@ public NativeQueryImplementor addEntity(String tableAlias, @SuppressWarnings( @Override public FetchReturn addFetch(String tableAlias, String ownerTableAlias, String joinPropertyName) { - final DynamicFetchBuilderLegacy fetchBuilder = Builders.fetch( tableAlias, ownerTableAlias, joinPropertyName ); + final ModelPart subPart = entityMappingTypeByTableAlias.get( ownerTableAlias ).findSubPart( joinPropertyName ); + addEntityMappingType( tableAlias, subPart ); + final DynamicFetchBuilderLegacy fetchBuilder = Builders.fetch( tableAlias, ownerTableAlias, (Fetchable) subPart ); resultSetMapping.addLegacyFetchBuilder( fetchBuilder ); return fetchBuilder; } + private void addEntityMappingType(String tableAlias, ModelPart part) { + if ( part instanceof PluralAttributeMapping pluralAttributeMapping ) { + final MappingType partMappingType = pluralAttributeMapping.getElementDescriptor() + .getPartMappingType(); + if ( partMappingType instanceof EntityMappingType entityMappingType ) { + entityMappingTypeByTableAlias.put( tableAlias, entityMappingType ); + } + } + else if ( part instanceof EntityAssociationMapping entityAssociationMapping ) { + entityMappingTypeByTableAlias.put( tableAlias, entityAssociationMapping.asEntityMappingType() ); + } + else if ( part instanceof EmbeddedAttributeMapping ) { + throw new UnsupportedOperationException(); + } + } + @Override public NativeQueryImplementor addJoin(String tableAlias, String path) { createFetchJoin( tableAlias, path ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/ResultSetMappingProcessor.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/ResultSetMappingProcessor.java index 2305da1b083d..141aad3e7359 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/ResultSetMappingProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/ResultSetMappingProcessor.java @@ -21,11 +21,12 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.loader.internal.AliasConstantsHelper; -import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.SingleTableEntityPersister; import org.hibernate.query.NativeQuery; import org.hibernate.query.results.FetchBuilder; import org.hibernate.query.results.LegacyFetchBuilder; @@ -35,6 +36,7 @@ import org.hibernate.query.results.internal.dynamic.DynamicFetchBuilderLegacy; import org.hibernate.query.results.internal.dynamic.DynamicResultBuilderEntityStandard; import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.type.CollectionType; import org.hibernate.type.ComponentType; import org.hibernate.type.EntityType; @@ -133,7 +135,7 @@ else if ( resultBuilder instanceof NativeQuery.CollectionReturn collectionReturn return this; } - private void processFetchBuilder(String attributeName, FetchBuilder fetchBuilder) { + private void processFetchBuilder(Fetchable attributeName, FetchBuilder fetchBuilder) { if ( fetchBuilder instanceof LegacyFetchBuilder ) { resultSetMapping.addLegacyFetchBuilder( (LegacyFetchBuilder) fetchBuilder ); } @@ -230,7 +232,7 @@ private void applyFetchBuilder( final EntityPersister loadable = alias2Persister.get( fetchBuilder.getOwnerAlias() ); final List columnNames; final String[] columnAliases = loadable.getSubclassPropertyColumnAliases( - fetchBuilder.getFetchableName(), + fetchBuilder.getFetchable().getFetchableName(), alias2Suffix.get( fetchBuilder.getOwnerAlias() ) ); if ( columnAliases.length == 0 ) { @@ -244,7 +246,7 @@ private void applyFetchBuilder( columnNames = Arrays.asList( keyColumnAliases ); if ( collectionPersister.hasIndex() ) { resultBuilderEntity.addProperty( - CollectionPart.Nature.INDEX.getName(), + ((PluralAttributeMapping) fetchBuilder.getFetchable()).getIndexDescriptor(), collectionPersister.getIndexColumnAliases( collectionSuffix ) ); } @@ -254,11 +256,11 @@ private void applyFetchBuilder( columnNames = Arrays.asList( columnAliases ); } ownerBuilder.addFetchBuilder( - fetchBuilder.getFetchableName(), + fetchBuilder.getFetchable(), new DynamicFetchBuilderLegacy( fetchBuilder.getTableAlias(), fetchBuilder.getOwnerAlias(), - fetchBuilder.getFetchableName(), + fetchBuilder.getFetchable(), columnNames, Collections.emptyMap(), resultBuilderEntity @@ -273,11 +275,11 @@ private NavigablePath determineNavigablePath(LegacyFetchBuilder fetchBuilder) { final NativeQuery.ResultNode ownerResult = alias2Return.get( fetchBuilder.getOwnerAlias() ); if ( ownerResult instanceof NativeQuery.RootReturn ) { return ( (NativeQuery.RootReturn) ownerResult ).getNavigablePath() - .append( fetchBuilder.getFetchableName() ); + .append( fetchBuilder.getFetchable().getFetchableName() ); } else { return determineNavigablePath( ( DynamicFetchBuilderLegacy) ownerResult ) - .append( fetchBuilder.getFetchableName() ); + .append( fetchBuilder.getFetchable().getFetchableName() ); } } @@ -311,28 +313,33 @@ private DynamicResultBuilderEntityStandard createSuffixedResultBuilder( resultBuilderEntity.addIdColumnAliases( identifierAliases ); resultBuilderEntity.setDiscriminatorAlias( loadable.getDiscriminatorAlias( suffix ) ); if ( loadable.hasIdentifierProperty() ) { - resultBuilderEntity.addProperty( loadable.getIdentifierPropertyName(), identifierAliases ); + resultBuilderEntity.addProperty( loadable.getIdentifierMapping(), identifierAliases ); } - final String[] propertyNames = loadable.getPropertyNames(); - for ( int i = 0; i < propertyNames.length; i++ ) { - if ( !loadable.isPropertySelectable( i ) ) { - continue; - } - final String propertyName = propertyNames[i]; - final String[] columnAliases = loadable.getSubclassPropertyColumnAliases( propertyName, suffix ); - final Type propertyType = loadable.getPropertyType( propertyName ); - addFetchBuilder( - suffix, - loadable, - resultBuilderEntity, - tableAlias, - identifierAliases, - propertyName, - columnAliases, - propertyType - ); - } + loadable.visitFetchables( + (index, fetchable) -> { + if ( fetchable.isSelectable() ) { + final Type propertyType; + if ( loadable instanceof SingleTableEntityPersister singleTableEntityPersister ) { + propertyType = singleTableEntityPersister.getSubclassPropertyType( index ); + } + else { + propertyType = loadable.getPropertyType( fetchable.getFetchableName() ); + } + addFetchBuilder( + suffix, + loadable, + resultBuilderEntity, + tableAlias, + identifierAliases, + fetchable, + loadable.getSubclassPropertyColumnAliases( fetchable.getFetchableName(), suffix ), + propertyType + ); + } + }, + null + ); return resultBuilderEntity; } @@ -342,7 +349,7 @@ private void addFetchBuilder( DynamicFetchBuilderContainer resultBuilderEntity, String tableAlias, String[] identifierAliases, - String propertyName, + Fetchable fetchable, String[] columnAliases, Type propertyType) { if ( propertyType instanceof CollectionType collectionType ) { @@ -356,15 +363,15 @@ private void addFetchBuilder( suffix ); } - resultBuilderEntity.addProperty( propertyName, keyColumnAliases ); + resultBuilderEntity.addProperty( fetchable, keyColumnAliases ); } else if ( propertyType instanceof ComponentType componentType ) { - final Map fetchBuilderMap = new HashMap<>(); + final Map fetchBuilderMap = new HashMap<>(); final DynamicFetchBuilderLegacy fetchBuilder = new DynamicFetchBuilderLegacy( "", tableAlias, - propertyName, - null, + fetchable, + Arrays.asList( columnAliases ), fetchBuilderMap ); final String[] propertyNames = componentType.getPropertyNames(); @@ -378,28 +385,28 @@ else if ( propertyType instanceof ComponentType componentType ) { fetchBuilder, tableAlias, identifierAliases, - propertyNames[i], + fetchable, ArrayHelper.slice( columnAliases, aliasIndex, columnSpan ), propertyTypes[i] ); aliasIndex += columnSpan; } - resultBuilderEntity.addFetchBuilder( propertyName, fetchBuilder ); + resultBuilderEntity.addFetchBuilder( fetchable, fetchBuilder ); } else if ( columnAliases.length != 0 ) { if ( propertyType instanceof EntityType ) { - final ToOneAttributeMapping toOne = (ToOneAttributeMapping) loadable.findAttributeMapping( propertyName ); + final ToOneAttributeMapping toOne = (ToOneAttributeMapping) fetchable; if ( !toOne.getIdentifyingColumnsTableExpression().equals( loadable.getTableName() ) ) { // The to-one has a join-table, use the plain join column name instead of the alias assert columnAliases.length == 1; final String[] targetAliases = new String[1]; targetAliases[0] = toOne.getTargetKeyPropertyName(); - resultBuilderEntity.addProperty( propertyName, targetAliases ); + resultBuilderEntity.addProperty( fetchable, targetAliases ); return; } } - resultBuilderEntity.addProperty( propertyName, columnAliases ); + resultBuilderEntity.addProperty( fetchable, columnAliases ); } } @@ -560,10 +567,10 @@ private void processFetchReturn(NativeQuery.FetchReturn fetchReturn) { } EntityPersister ownerPersister = alias2Persister.get( ownerAlias ); - Type returnType = ownerPersister.getPropertyType( fetchReturn.getFetchableName() ); + Type returnType = ownerPersister.getPropertyType( fetchReturn.getFetchable().getFetchableName() ); if ( returnType instanceof CollectionType ) { - String role = ownerPersister.getEntityName() + '.' + fetchReturn.getFetchableName(); + String role = ownerPersister.getEntityName() + '.' + fetchReturn.getFetchable().getFetchableName(); Map propertyResultsMap = Collections.emptyMap();//fetchReturn.getPropertyResultsMap() addCollection( role, alias, propertyResultsMap ); // collectionOwnerAliases.add( ownerAlias ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index 6a9163094f5a..fea924ee637f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -488,7 +488,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base private boolean deduplicateSelectionItems; private ForeignKeyDescriptor.Nature currentlyResolvingForeignKeySide; private SqmStatement currentSqmStatement; - private Stack sqmQueryPartStack = new StandardStack<>( SqmQueryPart.class ); + private Stack sqmQueryPartStack = new StandardStack<>(); private CteContainer cteContainer; /** * A map from {@link SqmCteTable#getCteName()} to the final SQL name. @@ -506,8 +506,8 @@ public abstract class BaseSqmToSqlAstConverter extends Base private List> orderByFragments; private final SqlAliasBaseManager sqlAliasBaseManager = new SqlAliasBaseManager(); - private final Stack processingStateStack = new StandardStack<>( SqlAstProcessingState.class ); - private final Stack fromClauseIndexStack = new StandardStack<>( FromClauseIndex.class ); + private final Stack processingStateStack = new StandardStack<>(); + private final Stack fromClauseIndexStack = new StandardStack<>(); // Captures all entity name uses under which a table group is being used within the current conjunct. // Outside a top level conjunct, it represents the "global uses" i.e. select, from, group and order by clauses. @@ -521,9 +521,9 @@ public abstract class BaseSqmToSqlAstConverter extends Base private SqmJoin currentlyProcessingJoin; protected Predicate additionalRestrictions; - private final Stack currentClauseStack = new StandardStack<>( Clause.class ); - private final Stack inferrableTypeAccessStack = new StandardStack<>( Supplier.class ); - private final Stack queryTransformers = new StandardStack<>( List.class ); + private final Stack currentClauseStack = new StandardStack<>(); + private final Stack inferrableTypeAccessStack = new StandardStack<>(); + private final Stack queryTransformers = new StandardStack<>(); private boolean inTypeInference; private boolean inImpliedResultTypeInference; private boolean inNestedContext; @@ -1982,9 +1982,13 @@ public QueryGroup visitQueryGroup(SqmQueryGroup queryGroup) { sqmQueryPartStack.push( queryGroup ); pushProcessingState( processingState ); + FromClauseIndex firstQueryPartIndex = null; + SqlAstProcessingState firstPoppedProcessingState = null; try { newQueryParts.add( visitQueryPart( queryParts.get( 0 ) ) ); + firstQueryPartIndex = lastPoppedFromClauseIndex; + firstPoppedProcessingState = lastPoppedProcessingState; collector.setSqmAliasedNodeCollector( (SqmAliasedNodeCollector) lastPoppedProcessingState.getSqlExpressionResolver() ); @@ -2002,6 +2006,8 @@ public QueryGroup visitQueryGroup(SqmQueryGroup queryGroup) { finally { popProcessingStateStack(); sqmQueryPartStack.pop(); + lastPoppedFromClauseIndex = firstQueryPartIndex; + lastPoppedProcessingState = firstPoppedProcessingState; } } 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 89cbf6e15ef9..862a382a433e 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 @@ -180,7 +180,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; } @@ -192,15 +194,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() ); applyAssignment( sqmPath, expression ); return this; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java index 516ab521f286..d9096e88e556 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java @@ -304,9 +304,9 @@ public abstract class AbstractSqlAstTranslator implemen private final ParameterMarkerStrategy parameterMarkerStrategy; - private final Stack clauseStack = new StandardStack<>( Clause.class ); - private final Stack queryPartStack = new StandardStack<>( QueryPart.class ); - private final Stack statementStack = new StandardStack<>( Statement.class ); + private final Stack clauseStack = new StandardStack<>(); + private final Stack queryPartStack = new StandardStack<>(); + private final Stack statementStack = new StandardStack<>(); private final Dialect dialect; private final Set affectedTableNames = new HashSet<>(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlExpressionResolver.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlExpressionResolver.java index b52df9099747..956fa98b6622 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlExpressionResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlExpressionResolver.java @@ -6,6 +6,7 @@ import java.util.function.Function; +import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.SelectablePath; @@ -90,6 +91,15 @@ static ColumnReferenceKey createColumnReferenceKey(TableReference tableReference return createColumnReferenceKey( tableReference, selectable.getSelectablePath(), selectable.getJdbcMapping() ); } + /** + * Convenience form for creating a key from TableReference and EntityDiscriminatorMapping + */ + static ColumnReferenceKey createDiscriminatorColumnReferenceKey(TableReference tableReference, EntityDiscriminatorMapping discriminatorMapping) { + assert tableReference.containsAffectedTableName( discriminatorMapping.getContainingTableExpression() ) + : String.format( ROOT, "Expecting tables to match between TableReference (%s) and SelectableMapping (%s)", tableReference.getTableId(), discriminatorMapping.getContainingTableExpression() ); + return createColumnReferenceKey( tableReference, discriminatorMapping.getSelectablePath(), discriminatorMapping.getUnderlyingJdbcMapping() ); + } + default Expression resolveSqlExpression(TableReference tableReference, SelectableMapping selectableMapping) { return resolveSqlExpression( createColumnReferenceKey( tableReference, selectableMapping ), diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/DeleteOrUpsertOperation.java b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/DeleteOrUpsertOperation.java index a324e5f5f475..375a4e836415 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/DeleteOrUpsertOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/DeleteOrUpsertOperation.java @@ -44,7 +44,6 @@ public class DeleteOrUpsertOperation implements SelfExecutingUpdateOperation { private final OptionalTableUpdate optionalTableUpdate; - public DeleteOrUpsertOperation( EntityMutationTarget mutationTarget, EntityTableMapping tableMapping, @@ -56,6 +55,16 @@ public DeleteOrUpsertOperation( this.optionalTableUpdate = optionalTableUpdate; } + /* + * Used by Hibernate Reactive + */ + protected DeleteOrUpsertOperation(DeleteOrUpsertOperation original) { + this.mutationTarget = original.mutationTarget; + this.tableMapping = original.tableMapping; + this.upsertOperation = original.upsertOperation; + this.optionalTableUpdate = original.optionalTableUpdate; + } + @Override public MutationType getMutationType() { return MutationType.UPDATE; @@ -197,4 +206,18 @@ private void performUpsert(JdbcValueBindings jdbcValueBindings, SharedSessionCon MODEL_MUTATION_LOGGER.tracef( "`%s` rows upserted into `%s`", rowCount, tableMapping.getTableName() ); } + + /* + * Used by Hibernate Reactive + */ + public UpsertOperation getUpsertOperation() { + return upsertOperation; + } + + /* + * Used by Hibernate Reactive + */ + public OptionalTableUpdate getOptionalTableUpdate() { + return optionalTableUpdate; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java index 52b10472b104..e213f3cc594c 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java @@ -263,7 +263,10 @@ private static void bindKeyValue( } } - private JdbcDeleteMutation createJdbcDelete(SharedSessionContractImplementor session) { + /* + * Used by Hibernate Reactive + */ + protected JdbcDeleteMutation createJdbcDelete(SharedSessionContractImplementor session) { final TableDelete tableDelete; if ( tableMapping.getDeleteDetails() != null && tableMapping.getDeleteDetails().getCustomSql() != null ) { @@ -305,39 +308,7 @@ private boolean performUpdate( JdbcValueBindings jdbcValueBindings, SharedSessionContractImplementor session) { MODEL_MUTATION_LOGGER.tracef( "#performUpdate(%s)", tableMapping.getTableName() ); - - final TableUpdate tableUpdate; - if ( tableMapping.getUpdateDetails() != null - && tableMapping.getUpdateDetails().getCustomSql() != null ) { - tableUpdate = new TableUpdateCustomSql( - new MutatingTableReference( tableMapping ), - mutationTarget, - "upsert update for " + mutationTarget.getRolePath(), - valueBindings, - keyBindings, - optimisticLockBindings, - parameters - ); - } - else { - tableUpdate = new TableUpdateStandard( - new MutatingTableReference( tableMapping ), - mutationTarget, - "upsert update for " + mutationTarget.getRolePath(), - valueBindings, - keyBindings, - optimisticLockBindings, - parameters - ); - } - - final SqlAstTranslator translator = session - .getJdbcServices() - .getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildModelMutationTranslator( tableUpdate, session.getFactory() ); - - final JdbcMutationOperation jdbcUpdate = translator.translate( null, MutationQueryOptions.INSTANCE ); + final JdbcMutationOperation jdbcUpdate = createJdbcUpdate( session ); final PreparedStatementGroupSingleTable statementGroup = new PreparedStatementGroupSingleTable( jdbcUpdate, session ); final PreparedStatementDetails statementDetails = statementGroup.resolvePreparedStatementDetails( tableMapping.getTableName() ); @@ -374,6 +345,44 @@ private boolean performUpdate( } } + /* + * Used by Hibernate Reactive + */ + protected JdbcMutationOperation createJdbcUpdate(SharedSessionContractImplementor session) { + final TableUpdate tableUpdate; + if ( tableMapping.getUpdateDetails() != null + && tableMapping.getUpdateDetails().getCustomSql() != null ) { + tableUpdate = new TableUpdateCustomSql( + new MutatingTableReference( tableMapping ), + mutationTarget, + "upsert update for " + mutationTarget.getRolePath(), + valueBindings, + keyBindings, + optimisticLockBindings, + parameters + ); + } + else { + tableUpdate = new TableUpdateStandard( + new MutatingTableReference( tableMapping ), + mutationTarget, + "upsert update for " + mutationTarget.getRolePath(), + valueBindings, + keyBindings, + optimisticLockBindings, + parameters + ); + } + + final SqlAstTranslator translator = session + .getJdbcServices() + .getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildModelMutationTranslator( tableUpdate, session.getFactory() ); + + return translator.translate( null, MutationQueryOptions.INSTANCE ); + } + private void performInsert(JdbcValueBindings jdbcValueBindings, SharedSessionContractImplementor session) { final JdbcInsertMutation jdbcInsert = createJdbcInsert( session ); @@ -414,7 +423,10 @@ private void performInsert(JdbcValueBindings jdbcValueBindings, SharedSessionCon } } - private JdbcInsertMutation createJdbcInsert(SharedSessionContractImplementor session) { + /* + * Used by Hibernate Reactive + */ + protected JdbcInsertMutation createJdbcInsert(SharedSessionContractImplementor session) { final TableInsert tableInsert; if ( tableMapping.getInsertDetails() != null && tableMapping.getInsertDetails().getCustomSql() != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/DomainResultGraphPrinter.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/DomainResultGraphPrinter.java index dd272270a0b7..3a50c6629b45 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/DomainResultGraphPrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/DomainResultGraphPrinter.java @@ -45,7 +45,7 @@ public static void logDomainResultGraph(String header, List> dom } private final StringBuilder buffer; - private final Stack fetchParentStack = new StandardStack<>( FetchParent.class ); + private final Stack fetchParentStack = new StandardStack<>(); private DomainResultGraphPrinter(String header) { buffer = new StringBuilder( header + ":" + System.lineSeparator() ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/AbstractNonJoinedEntityFetch.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/AbstractNonJoinedEntityFetch.java index dc657564a117..303afde07473 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/AbstractNonJoinedEntityFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/AbstractNonJoinedEntityFetch.java @@ -142,11 +142,10 @@ public boolean isSelectByUniqueKey() { public DomainResultAssembler createAssembler( InitializerParent parent, AssemblerCreationState creationState) { - final EntityInitializer entityInitializer = - creationState.resolveInitializer( this, parent, this ) - .asEntityInitializer(); + final EntityInitializer entityInitializer = creationState.resolveInitializer( this, parent, this ) + .asEntityInitializer(); assert entityInitializer != null; - return new EntityAssembler<>( getFetchedMapping().getJavaType(), entityInitializer ); + return buildEntityAssembler( entityInitializer ); } @Override @@ -160,4 +159,10 @@ public EntityInitializer createInitializer( @Override public abstract EntityInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState); + /** + * Used By Hibernate Reactive + */ + protected EntityAssembler buildEntityAssembler(EntityInitializer entityInitializer) { + return new EntityAssembler<>( getFetchedMapping().getJavaType(), entityInitializer ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchJoinedImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchJoinedImpl.java index e9e83cb5edaf..243af813c5dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchJoinedImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchJoinedImpl.java @@ -129,9 +129,14 @@ public FetchParent getFetchParent() { public DomainResultAssembler createAssembler( InitializerParent parent, AssemblerCreationState creationState) { - return new EntityAssembler<>( getFetchedMapping().getJavaType(), - creationState.resolveInitializer( this, parent, this ) - .asEntityInitializer() ); + return buildEntityAssembler( creationState.resolveInitializer( this, parent, this ).asEntityInitializer() ); + } + + /** + * Used by Hibernate Reactive + */ + protected EntityAssembler buildEntityAssembler(EntityInitializer entityInitializer) { + return new EntityAssembler<>( getFetchedMapping().getJavaType(), entityInitializer ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java index f3192d5d4452..1bfc1a5d6180 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java @@ -1583,14 +1583,14 @@ protected void registerPossibleUniqueKeyEntries( final Type type = entry.getPropertyType(); // polymorphism not really handled completely correctly, - // perhaps...well, actually its ok, assuming that the + // perhaps...well, actually it's ok, assuming that the // entity name used in the lookup is the same as the // one used here, which it will be if ( resolvedEntityState[index] != null ) { final Object key; if ( type instanceof ManyToOneType manyToOneType ) { - key = ForeignKeys.getEntityIdentifierIfNotUnsaved( + key = ForeignKeys.getEntityIdentifier( manyToOneType.getAssociatedEntityName(), resolvedEntityState[index], session diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/LoadContexts.java b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/LoadContexts.java index 90dc0738fd5d..44a95421d23b 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/LoadContexts.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/LoadContexts.java @@ -24,7 +24,7 @@ public class LoadContexts { private static final CoreMessageLogger log = CoreLogging.messageLogger( LoadContexts.class ); private final PersistenceContext persistenceContext; - private final StandardStack jdbcValuesSourceProcessingStateStack = new StandardStack<>( JdbcValuesSourceProcessingState.class ); + private final StandardStack jdbcValuesSourceProcessingStateStack = new StandardStack<>(); public LoadContexts(PersistenceContext persistenceContext) { this.persistenceContext = persistenceContext; diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/AbstractInformationExtractorImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/AbstractInformationExtractorImpl.java index 5fd9a9b8dd2d..49253e2a3d79 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/AbstractInformationExtractorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/AbstractInformationExtractorImpl.java @@ -638,7 +638,10 @@ private void populateTablesWithColumns( } } - private ColumnInformationImpl columnInformation(TableInformation tableInformation, ResultSet resultSet) + /* + * Hibernate Reactive overrides this + */ + protected ColumnInformationImpl columnInformation(TableInformation tableInformation, ResultSet resultSet) throws SQLException { return new ColumnInformationImpl( tableInformation, @@ -864,7 +867,10 @@ protected void addColumns(TableInformation tableInformation) { } } - private Boolean interpretTruthValue(String nullable) { + /* + * Used by Hibernate Reactive + */ + protected Boolean interpretTruthValue(String nullable) { if ( "yes".equalsIgnoreCase( nullable ) ) { return Boolean.TRUE; } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java index f23eb84c3984..744f3417fa98 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import org.hibernate.HibernateException; import org.hibernate.MappingException; @@ -149,6 +150,18 @@ public EntityMetamodel( PersistentClass persistentClass, EntityPersister persister, RuntimeModelCreationContext creationContext) { + this( persistentClass, persister, creationContext, + rootName -> buildIdGenerator( rootName, persistentClass, creationContext ) ); + } + + /* + * Used by Hibernate Reactive to adapt the id generators + */ + public EntityMetamodel( + PersistentClass persistentClass, + EntityPersister persister, + RuntimeModelCreationContext creationContext, + Function generatorSupplier) { this.sessionFactory = creationContext.getSessionFactory(); // Improves performance of EntityKey#equals by avoiding content check in String#equals @@ -160,7 +173,7 @@ public EntityMetamodel( subclassId = persistentClass.getSubclassId(); - final Generator idgenerator = buildIdGenerator( persistentClass, creationContext ); + final Generator idgenerator = generatorSupplier.apply( rootName ); identifierAttribute = PropertyFactory.buildIdentifierAttribute( persistentClass, idgenerator ); versioned = persistentClass.isVersioned(); @@ -483,7 +496,7 @@ private static boolean writePropertyValue(OnExecutionGenerator generator) { return writePropertyValue; } - private Generator buildIdGenerator(PersistentClass persistentClass, RuntimeModelCreationContext creationContext) { + private static Generator buildIdGenerator(String rootName, PersistentClass persistentClass, RuntimeModelCreationContext creationContext) { final Generator existing = creationContext.getGenerators().get( rootName ); if ( existing != null ) { return existing; 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 4d5027555dec..6edc24bccbda 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java @@ -25,6 +25,7 @@ import org.hibernate.proxy.LazyInitializer; import org.hibernate.type.spi.TypeConfiguration; +import static org.hibernate.engine.internal.ForeignKeys.getEntityIdentifierIfNotUnsaved; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; @@ -342,33 +343,12 @@ public Object replace( public int getHashCode(Object x, SessionFactoryImplementor factory) { final EntityPersister persister = getAssociatedEntityPersister( factory ); if ( isReferenceToPrimaryKey() ) { - final Object id; - final LazyInitializer lazyInitializer = extractLazyInitializer( x ); - if ( lazyInitializer != null ) { - id = lazyInitializer.getInternalIdentifier(); - } - else { - final Class mappedClass = persister.getMappedClass(); - if ( mappedClass.isInstance( x ) ) { - id = persister.getIdentifier( x ); - } - else { - id = x; - } - } - return persister.getIdentifierType().getHashCode( id, factory ); + return persister.getIdentifierType().getHashCode( getId( x, persister ), factory ); } else { assert uniqueKeyPropertyName != null; - final Object uniqueKey; final Type keyType = persister.getPropertyType( uniqueKeyPropertyName ); - if ( keyType.getReturnedClass().isInstance( x ) ) { - uniqueKey = x; - } - else { - uniqueKey = persister.getPropertyValue( x, uniqueKeyPropertyName ); - } - return keyType.getHashCode( uniqueKey, factory ); + return keyType.getHashCode( getUniqueKey( x, keyType, persister ), factory ); } } @@ -384,60 +364,41 @@ public boolean isEqual(Object x, Object y, SessionFactoryImplementor factory) { final EntityPersister persister = getAssociatedEntityPersister( factory ); if ( isReferenceToPrimaryKey() ) { - final Class mappedClass = persister.getMappedClass(); - Object xid; - final LazyInitializer lazyInitializerX = extractLazyInitializer( x ); - if ( lazyInitializerX != null ) { - xid = lazyInitializerX.getInternalIdentifier(); - } - else { - if ( mappedClass.isInstance( x ) ) { - xid = persister.getIdentifier( x ); - } - else { - //JPA 2 case where @IdClass contains the id and not the associated entity - xid = x; - } - } - - Object yid; - final LazyInitializer lazyInitializerY = extractLazyInitializer( y ); - if ( lazyInitializerY != null ) { - yid = lazyInitializerY.getInternalIdentifier(); - } - else { - if ( mappedClass.isInstance( y ) ) { - yid = persister.getIdentifier( y ); - } - else { - //JPA 2 case where @IdClass contains the id and not the associated entity - yid = y; - } - } - + final Object xid = getId( x, persister ); + final Object yid = getId( y, persister ); // Check for reference equality first as the type-specific checks by IdentifierType are sometimes non-trivial - return (xid == yid) || persister.getIdentifierType().isEqual( xid, yid, factory ); + return xid == yid || persister.getIdentifierType().isEqual( xid, yid, factory ); } else { assert uniqueKeyPropertyName != null; - final Object xUniqueKey; final Type keyType = persister.getPropertyType( uniqueKeyPropertyName ); - if ( keyType.getReturnedClass().isInstance( x ) ) { - xUniqueKey = x; - } - else { - xUniqueKey = persister.getPropertyValue( x, uniqueKeyPropertyName ); - } + final Object xUniqueKey = getUniqueKey( x, keyType, persister ); + final Object yUniqueKey = getUniqueKey( y, keyType, persister ); + return xUniqueKey == yUniqueKey + || keyType.isEqual( xUniqueKey, yUniqueKey, factory ); + } + } + + private Object getUniqueKey(Object entity, Type keyType, EntityPersister persister) { + return keyType.getReturnedClass().isInstance( entity ) + ? entity + : persister.getPropertyValue( entity, uniqueKeyPropertyName ); + } - final Object yUniqueKey; - if ( keyType.getReturnedClass().isInstance( y ) ) { - yUniqueKey = y; + private static Object getId(Object entity, EntityPersister persister) { + final LazyInitializer lazyInitializer = extractLazyInitializer( entity ); + if ( lazyInitializer != null ) { + return lazyInitializer.getInternalIdentifier(); + } + else { + final Class mappedClass = persister.getMappedClass(); + if ( mappedClass.isInstance( entity ) ) { + return persister.getIdentifier( entity ); } else { - yUniqueKey = persister.getPropertyValue( y, uniqueKeyPropertyName ); + //JPA 2 case where @IdClass contains the id and not the associated entity + return entity; } - return (xUniqueKey == yUniqueKey) - || keyType.isEqual( xUniqueKey, yUniqueKey, factory ); } } @@ -475,10 +436,9 @@ public EntityPersister getAssociatedEntityPersister(final SessionFactoryImplemen //form it returns the local variable to avoid a second volatile read: associatedEntityPersister //needs to be volatile as the initialization might happen by a different thread than the readers. if ( persister == null ) { - associatedEntityPersister = factory - .getRuntimeMetamodels() - .getMappingMetamodel() - .getEntityDescriptor( getAssociatedEntityName() ); + associatedEntityPersister = + factory.getMappingMetamodel() + .getEntityDescriptor( getAssociatedEntityName() ); return associatedEntityPersister; } else { @@ -486,13 +446,10 @@ public EntityPersister getAssociatedEntityPersister(final SessionFactoryImplemen } } - protected final Object getIdentifier(Object value, SharedSessionContractImplementor session) throws HibernateException { + protected final Object getIdentifier(Object value, SharedSessionContractImplementor session) + throws HibernateException { if ( isReferenceToIdentifierProperty() ) { - return ForeignKeys.getEntityIdentifierIfNotUnsaved( - getAssociatedEntityName(), - value, - session - ); //tolerates nulls + return getEntityIdentifierIfNotUnsaved( getAssociatedEntityName(), value, session ); //tolerates nulls } else if ( value == null ) { return null; @@ -500,22 +457,19 @@ else if ( value == null ) { else { final LazyInitializer lazyInitializer = extractLazyInitializer( value ); if ( lazyInitializer != null ) { - /* - If the value is a Proxy and the property access is field, the value returned by - `attributeMapping.getAttributeMetadata().getPropertyAccess().getGetter().get( object )` - is always null except for the id, we need the to use the proxy implementation to - extract the property value. - */ + // If the value is a Proxy and the property access is field, the value returned by + // attributeMapping.getAttributeMetadata().getPropertyAccess().getGetter().get( object ) + // is always null except for the id, we need the to use the proxy implementation to + // extract the property value. value = lazyInitializer.getImplementation(); } else if ( isPersistentAttributeInterceptable( value ) ) { - /* - If the value is an instance of PersistentAttributeInterceptable, and it is not initialized - we need to force initialization the get the property value - */ - final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( value ).$$_hibernate_getInterceptor(); - if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { - ( (EnhancementAsProxyLazinessInterceptor) interceptor ).forceInitialize( value, null ); + // If the value is an instance of PersistentAttributeInterceptable, and it is + // not initialized, we need to force initialization the get the property value + final PersistentAttributeInterceptor interceptor = + asPersistentAttributeInterceptable( value ).$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor lazinessInterceptor ) { + lazinessInterceptor.forceInitialize( value, null ); } } final EntityPersister entityPersister = getAssociatedEntityPersister( session.getFactory() ); @@ -524,20 +478,17 @@ else if ( isPersistentAttributeInterceptable( value ) ) { // we need to dig a little deeper, as that property might also be // an entity type, in which case we need to resolve its identifier final Type type = entityPersister.getPropertyType( uniqueKeyPropertyName ); - if ( type instanceof EntityType ) { - return ( (EntityType) type ).getIdentifier( propertyValue, session ); - } - else { - return propertyValue; - } + return type instanceof EntityType entityType + ? entityType.getIdentifier( propertyValue, session ) + : propertyValue; } } - protected final Object getIdentifier(Object value, SessionFactoryImplementor sessionFactory) throws HibernateException { + protected final Object getIdentifier(Object value, SessionFactoryImplementor sessionFactory) + throws HibernateException { if ( isReferenceToIdentifierProperty() ) { return getAssociatedEntityPersister( sessionFactory ) - .getIdentifierMapping() - .getIdentifier( value ); + .getIdentifierMapping().getIdentifier( value ); } else if ( value == null ) { return null; @@ -549,12 +500,9 @@ else if ( value == null ) { // we need to dig a little deeper, as that property might also be // an entity type, in which case we need to resolve its identifier final Type type = entityPersister.getPropertyType( uniqueKeyPropertyName ); - if ( type instanceof EntityType ) { - return ( (EntityType) type ).getIdentifier( propertyValue, sessionFactory ); - } - else { - return propertyValue; - } + return type instanceof EntityType entityType + ? entityType.getIdentifier( propertyValue, sessionFactory ) + : propertyValue; } } @@ -570,35 +518,32 @@ else if ( value == null ) { */ @Override public String toLoggableString(Object value, SessionFactoryImplementor factory) { - if ( value == null ) { - return "null"; - } + return value == null + ? "null" + : loggableString( value, getAssociatedEntityPersister( factory ) ); + } - final EntityPersister persister = getAssociatedEntityPersister( factory ); - if ( ! persister.isInstance( value ) ) { + private String loggableString(Object entity, EntityPersister persister) { + if ( !persister.isInstance( entity ) // it should be the id type... - if ( persister.getIdentifierType().getReturnedClass().isInstance( value ) ) { - return associatedEntityName + "#" + value; - } + && persister.getIdentifierType().getReturnedClass().isInstance( entity ) ) { + return associatedEntityName + "#" + entity; } - - final StringBuilder result = new StringBuilder().append( associatedEntityName ); - - if ( persister.hasIdentifierProperty() ) { - final Object id; - final LazyInitializer lazyInitializer = extractLazyInitializer( value ); - if ( lazyInitializer != null ) { - id = lazyInitializer.getInternalIdentifier(); - } - else { - id = persister.getIdentifier( value ); + else { + final StringBuilder result = new StringBuilder().append( associatedEntityName ); + if ( persister.hasIdentifierProperty() ) { + result.append( '#' ).append( identifierString( entity, persister ) ); } - - result.append( '#' ) - .append( persister.getIdentifierType().toLoggableString( id, factory ) ); + return result.toString(); } + } - return result.toString(); + private static String identifierString(Object entity, EntityPersister persister) { + final LazyInitializer lazyInitializer = extractLazyInitializer( entity ); + final Object id = lazyInitializer != null + ? lazyInitializer.getInternalIdentifier() + : persister.getIdentifier( entity ); + return persister.getIdentifierType().toLoggableString( id, persister.getFactory() ); } /** @@ -681,9 +626,11 @@ public final Type getIdentifierOrUniqueKeyType(Mapping factory) throws MappingEx } /** - * Determine the type of either (1) the identifier if we reference the - * associated entity's PK or (2) the unique key to which we refer (i.e. - * the property-ref). + * Determine the type of either: + *
      + *
    1. the identifier if we reference the primary key of the associated entity, or + *
    2. the unique key to which we refer, that is, the property-ref. + *
    * * @param mappingContext The mappings context {@see MappingContext} * @@ -697,13 +644,11 @@ public final Type getIdentifierOrUniqueKeyType(MappingContext mappingContext) th return getIdentifierType( mappingContext ); } else { - final Type type = mappingContext.getReferencedPropertyType( getAssociatedEntityName(), uniqueKeyPropertyName ); - if ( type instanceof EntityType entityType ) { - return entityType.getIdentifierOrUniqueKeyType( mappingContext ); - } - else { - return type; - } + final Type type = + mappingContext.getReferencedPropertyType( getAssociatedEntityName(), uniqueKeyPropertyName ); + return type instanceof EntityType entityType + ? entityType.getIdentifierOrUniqueKeyType( mappingContext ) + : type; } } @@ -716,7 +661,10 @@ public final Type getIdentifierOrUniqueKeyType(MappingContext mappingContext) th * @return The appropriate property name. * * @throws MappingException Generally, if unable to resolve the associated entity name + * + * @deprecated No longer used */ + @Deprecated(since = "7", forRemoval = true) public final String getIdentifierOrUniqueKeyPropertyName(Mapping factory) throws MappingException { return getIdentifierOrUniqueKeyPropertyName( (MappingContext) factory); @@ -761,17 +709,23 @@ public boolean isReferenceToIdentifierProperty() { * * @throws HibernateException Indicates problems performing the load. */ - protected final Object resolveIdentifier(Object id, SharedSessionContractImplementor session, Boolean overridingEager) throws HibernateException { + protected final Object resolveIdentifier(Object id, SharedSessionContractImplementor session, Boolean overridingEager) + throws HibernateException { - final boolean isProxyUnwrapEnabled = unwrapProxy && - getAssociatedEntityPersister( session.getFactory() ) - .isInstrumented(); + 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( getAssociatedEntityName(), id, isEager( overridingEager ), isNullable() ); + final Object proxyOrEntity = session.internalLoad( entityName, id, isEager, isNullable() ); final LazyInitializer lazyInitializer = extractLazyInitializer( proxyOrEntity ); if ( lazyInitializer != null ) { + final boolean isProxyUnwrapEnabled = + unwrapProxy && getAssociatedEntityPersister( session.getFactory() ).isInstrumented(); lazyInitializer.setUnwrap( isProxyUnwrapEnabled ); } @@ -804,13 +758,10 @@ public Object loadByUniqueKey( Object key, SharedSessionContractImplementor session) throws HibernateException { final SessionFactoryImplementor factory = session.getFactory(); - final EntityPersister persister = - factory.getMappingMetamodel() - .getEntityDescriptor( entityName ); //TODO: implement 2nd level caching?! natural id caching ?! proxies?! - final EntityUniqueKey euk = new EntityUniqueKey( + final EntityUniqueKey entityUniqueKey = new EntityUniqueKey( entityName, uniqueKeyPropertyName, key, @@ -819,29 +770,33 @@ public Object loadByUniqueKey( ); final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - Object result = persistenceContext.getEntity( euk ); - if ( result == null ) { - result = persister.loadByUniqueKey( uniqueKeyPropertyName, key, session ); - - // If the entity was not in the Persistence Context, but was found now, - // add it to the Persistence Context - if (result != null) { - persistenceContext.addEntity(euk, result); + final Object entity = persistenceContext.getEntity( entityUniqueKey ); + final Object result; + if ( entity == null ) { + result = factory.getMappingMetamodel().getEntityDescriptor( entityName ) + .loadByUniqueKey( uniqueKeyPropertyName, key, session ); + if ( result != null ) { + // If the entity was not in the persistence context, + // but was found now, add it to the persistence context + persistenceContext.addEntity( entityUniqueKey, result ); } } + else { + result = entity; + } return result == null ? null : persistenceContext.proxyFor( result ); } protected Type requireIdentifierOrUniqueKeyType(MappingContext mapping) { - final Type fkTargetType = getIdentifierOrUniqueKeyType( mapping ); - if ( fkTargetType == null ) { + final Type targetType = getIdentifierOrUniqueKeyType( mapping ); + if ( targetType == null ) { throw new MappingException( - "Unable to determine FK target Type for many-to-one or one-to-one mapping: " + + "Unable to determine foreign key target Type for many-to-one or one-to-one mapping: " + "referenced-entity-name=[" + getAssociatedEntityName() + "], referenced-entity-attribute-name=[" + getLHSPropertyName() + "]" ); } - return fkTargetType; + return targetType; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java index d3e545b26190..e5f9fa1896e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java @@ -10,7 +10,6 @@ import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; import org.hibernate.MappingException; -import org.hibernate.engine.internal.ForeignKeys; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -18,6 +17,8 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.type.spi.TypeConfiguration; +import static org.hibernate.engine.internal.ForeignKeys.getEntityIdentifierIfNotUnsaved; + /** * A many-to-one association to an entity. * @@ -117,7 +118,8 @@ public ForeignKeyDirection getForeignKeyDirection() { /** * Register the entity as batch loadable, if enabled */ - private void scheduleBatchLoadIfNeeded(Object id, SharedSessionContractImplementor session) throws MappingException { + private void scheduleBatchLoadIfNeeded(Object id, SharedSessionContractImplementor session) + throws MappingException { //cannot batch fetch by unique key (property-ref associations) if ( uniqueKeyPropertyName == null && id != null ) { final EntityPersister persister = getAssociatedEntityPersister( session.getFactory() ); @@ -141,26 +143,28 @@ public boolean isModified( Object old, Object current, boolean[] checkable, - SharedSessionContractImplementor session) throws HibernateException { + SharedSessionContractImplementor session) + throws HibernateException { if ( current == null ) { return old != null; } - if ( old == null ) { + else if ( old == null ) { // we already know current is not null... return true; } - - - // the ids are fully resolved, so compare them with isDirty(), not isModified() - return getIdentifierOrUniqueKeyType( session.getFactory() ) - .isDirty( old, getIdentifier( current, session ), session ); + else { + // the ids are fully resolved, so compare them with isDirty(), not isModified() + return getIdentifierOrUniqueKeyType( session.getFactory() ) + .isDirty( old, getIdentifier( current, session ), session ); + } } @Override public Serializable disassemble( Object value, SharedSessionContractImplementor session, - Object owner) throws HibernateException { + Object owner) + throws HibernateException { if ( value == null ) { return null; @@ -168,57 +172,44 @@ public Serializable disassemble( else { // cache the actual id of the object, not the value of the // property-ref, which might not be initialized - Object id = ForeignKeys.getEntityIdentifierIfNotUnsaved( - getAssociatedEntityName(), - value, - session - ); - if ( id == null ) { - throw new AssertionFailure( - "cannot cache a reference to an object with a null id: " + - getAssociatedEntityName() - ); - } + final Object id = getEntityIdentifierIfNotUnsaved( getAssociatedEntityName(), value, session ); + checkIdNotNull( id ); return getIdentifierType( session ).disassemble( id, session, owner ); } } @Override - public Serializable disassemble(Object value, SessionFactoryImplementor sessionFactory) throws HibernateException { + public Serializable disassemble(Object value, SessionFactoryImplementor sessionFactory) + throws HibernateException { if ( value == null ) { return null; } else { // cache the actual id of the object, not the value of the // property-ref, which might not be initialized - Object id = getIdentifier( value, sessionFactory ); - if ( id == null ) { - throw new AssertionFailure( - "cannot cache a reference to an object with a null id: " + - getAssociatedEntityName() - ); - } + final Object id = getIdentifier( value, sessionFactory ); + checkIdNotNull( id ); return getIdentifierType( sessionFactory ).disassemble( id, sessionFactory ); } } + private void checkIdNotNull(Object id) { + if ( id == null ) { + throw new AssertionFailure( + "cannot cache a reference to an object with a null id: " + getAssociatedEntityName() + ); + } + } + @Override public Object assemble( Serializable oid, SharedSessionContractImplementor session, Object owner) throws HibernateException { - //TODO: currently broken for unique-key references (does not detect // change to unique key property of the associated object) - - Object id = assembleId( oid, session ); - - if ( id == null ) { - return null; - } - else { - return resolveIdentifier( id, session ); - } + final Object id = assembleId( oid, session ); + return id == null ? null : resolveIdentifier( id, session ); } private Object assembleId(Serializable oid, SharedSessionContractImplementor session) { @@ -233,7 +224,7 @@ public void beforeAssemble(Serializable oid, SharedSessionContractImplementor se @Override public boolean[] toColumnNullness(Object value, MappingContext mapping) { - boolean[] result = new boolean[ getColumnSpan( mapping ) ]; + final boolean[] result = new boolean[ getColumnSpan( mapping ) ]; if ( value != null ) { Arrays.fill( result, true ); } @@ -248,9 +239,11 @@ public boolean isDirty( if ( isSame( old, current ) ) { return false; } - Object oldid = getIdentifier( old, session ); - Object newid = getIdentifier( current, session ); - return getIdentifierOrUniqueKeyType( session.getFactory() ).isDirty( oldid, newid, session ); + else { + final Object oldid = getIdentifier( old, session ); + final Object newid = getIdentifier( current, session ); + return getIdentifierOrUniqueKeyType( session.getFactory() ).isDirty( oldid, newid, session ); + } } @Override @@ -262,15 +255,14 @@ public boolean isDirty( if ( isAlwaysDirtyChecked() ) { return isDirty( old, current, session ); } + else if ( isSame( old, current ) ) { + return false; + } else { - if ( isSame( old, current ) ) { - return false; - } - Object oldid = getIdentifier( old, session ); - Object newid = getIdentifier( current, session ); + final Object oldid = getIdentifier( old, session ); + final Object newid = getIdentifier( current, session ); return getIdentifierOrUniqueKeyType( session.getFactory() ).isDirty( oldid, newid, checkable, session ); } - } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java index 49c0b7aa46da..0221765350ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java @@ -12,10 +12,12 @@ import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.type.spi.TypeConfiguration; +import static org.hibernate.internal.util.collections.ArrayHelper.EMPTY_BOOLEAN_ARRAY; +import static org.hibernate.internal.util.collections.ArrayHelper.EMPTY_INT_ARRAY; + /** * A one-to-one association to an entity * @@ -62,10 +64,9 @@ public String getPropertyName() { @Override public boolean isNull(Object owner, SharedSessionContractImplementor session) { if ( propertyName != null ) { - final EntityPersister ownerPersister = session.getFactory() - .getRuntimeMetamodels() - .getMappingMetamodel() - .getEntityDescriptor( entityName ); + final EntityPersister ownerPersister = + session.getFactory().getMappingMetamodel() + .getEntityDescriptor( entityName ); final Object id = session.getContextEntityIdentifier( owner ); final EntityKey entityKey = session.generateEntityKey( id, ownerPersister ); return session.getPersistenceContextInternal().isPropertyNull( entityKey, getPropertyName() ); @@ -81,17 +82,22 @@ public int getColumnSpan(MappingContext session) throws MappingException { } @Override - public int[] getSqlTypeCodes(MappingContext mappingContext) throws MappingException { - return ArrayHelper.EMPTY_INT_ARRAY; + public int[] getSqlTypeCodes(MappingContext mappingContext) { + return EMPTY_INT_ARRAY; } @Override public boolean[] toColumnNullness(Object value, MappingContext mapping) { - return ArrayHelper.EMPTY_BOOLEAN_ARRAY; + return EMPTY_BOOLEAN_ARRAY; } @Override - public void nullSafeSet(PreparedStatement st, Object value, int index, boolean[] settable, SharedSessionContractImplementor session) { + public void nullSafeSet( + PreparedStatement st, + Object value, + int index, + boolean[] settable, + SharedSessionContractImplementor session) { //nothing to do } @@ -131,21 +137,22 @@ public boolean useLHSPrimaryKey() { } @Override - public Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException { + public Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) { return null; } @Override - public Serializable disassemble(Object value, SessionFactoryImplementor sessionFactory) throws HibernateException { + public Serializable disassemble(Object value, SessionFactoryImplementor sessionFactory) { return null; } @Override - public Object assemble(Serializable oid, SharedSessionContractImplementor session, Object owner) throws HibernateException { + public Object assemble(Serializable oid, SharedSessionContractImplementor session, Object owner) + throws HibernateException { //this should be a call to resolve(), not resolveIdentifier(), //because it might be a property-ref, and we did not cache the //referenced value - return resolve( session.getContextEntityIdentifier(owner), session, owner ); + return resolve( session.getContextEntityIdentifier( owner ), session, owner ); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimestampJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimestampJavaType.java index edbecdeaf42e..0a309d920187 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimestampJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimestampJavaType.java @@ -24,7 +24,6 @@ import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.sql.ast.spi.SqlAppender; -import org.hibernate.type.descriptor.DateTimeUtils; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; @@ -224,7 +223,7 @@ public void appendEncodedString(SqlAppender sb, Date value) { @Override public Date fromEncodedString(CharSequence charSequence, int start, int end) { try { - final TemporalAccessor accessor = DateTimeUtils.DATE_TIME.parse( subSequence( charSequence, start, end ) ); + final TemporalAccessor accessor = ENCODED_FORMATTER.parse( subSequence( charSequence, start, end ) ); final Timestamp timestamp; if ( accessor.isSupported( ChronoField.INSTANT_SECONDS ) ) { timestamp = new Timestamp( accessor.getLong( ChronoField.INSTANT_SECONDS ) * 1000L ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonArrayJdbcType.java index c856686e782f..e7d3cf7fc521 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonArrayJdbcType.java @@ -59,7 +59,7 @@ protected X fromString(String string, JavaType javaType, WrapperOptions o if ( string == null ) { return null; } - return JsonHelper.arrayFromString( javaType, this, string, options ); + return JsonHelper.arrayFromString( javaType, this.getElementJdbcType(), string, options ); } protected String toString(X value, JavaType javaType, WrapperOptions options) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/namingstrategy/CamelCaseToUnderscoresNamingStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/namingstrategy/CamelCaseToUnderscoresNamingStrategyTest.java index b5d87aae685e..5a42bd8c1949 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/namingstrategy/CamelCaseToUnderscoresNamingStrategyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/namingstrategy/CamelCaseToUnderscoresNamingStrategyTest.java @@ -4,12 +4,12 @@ */ package org.hibernate.orm.test.annotations.namingstrategy; +import jakarta.persistence.Column; import org.hibernate.boot.Metadata; import org.hibernate.boot.MetadataSources; -import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy; +import org.hibernate.boot.model.naming.PhysicalNamingStrategySnakeCaseImpl; import org.hibernate.cfg.Environment; import org.hibernate.mapping.PersistentClass; -import org.hibernate.mapping.Selectable; import org.hibernate.service.ServiceRegistry; import org.hibernate.testing.ServiceRegistryBuilder; @@ -50,29 +50,33 @@ public void testWithWordWithDigitNamingStrategy() throws Exception { Metadata metadata = new MetadataSources( serviceRegistry ) .addAnnotatedClass( B.class ) .getMetadataBuilder() - .applyPhysicalNamingStrategy( new CamelCaseToUnderscoresNamingStrategy() ) + .applyPhysicalNamingStrategy( new PhysicalNamingStrategySnakeCaseImpl() ) .build(); PersistentClass entityBinding = metadata.getEntityBinding( B.class.getName() ); assertEquals( "word_with_digit_d1", - ( (Selectable) entityBinding.getProperty( "wordWithDigitD1" ).getSelectables().get( 0 ) ).getText() + entityBinding.getProperty( "wordWithDigitD1" ).getSelectables().get( 0 ).getText() ); assertEquals( "abcd_efgh_i21", - ( (Selectable) entityBinding.getProperty( "AbcdEfghI21" ).getSelectables().get( 0 ) ).getText() + entityBinding.getProperty( "AbcdEfghI21" ).getSelectables().get( 0 ).getText() ); assertEquals( "hello1", - ( (Selectable) entityBinding.getProperty( "hello1" ).getSelectables().get( 0 ) ).getText() + entityBinding.getProperty( "hello1" ).getSelectables().get( 0 ).getText() ); assertEquals( "hello1_d2", - ( (Selectable) entityBinding.getProperty( "hello1D2" ).getSelectables().get( 0 ) ).getText() + entityBinding.getProperty( "hello1D2" ).getSelectables().get( 0 ).getText() ); assertEquals( "hello3d4", - ( (Selectable) entityBinding.getProperty( "hello3d4" ).getSelectables().get( 0 ) ).getText() + entityBinding.getProperty( "hello3d4" ).getSelectables().get( 0 ).getText() + ); + assertEquals( + "Quoted-ColumnName", + entityBinding.getProperty( "quoted" ).getSelectables().get( 0 ).getText() ); } @@ -85,6 +89,8 @@ class B implements java.io.Serializable { protected String hello1; protected String hello1D2; protected String hello3d4; + @Column(name = "\"Quoted-ColumnName\"") + protected String quoted; public String getAbcdEfghI21() { return AbcdEfghI21; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/onetomany/orderby/IdClassAndOrderByTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/onetomany/orderby/IdClassAndOrderByTest.java index 8d7e4bca4de1..7ca5c7da2047 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/onetomany/orderby/IdClassAndOrderByTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/onetomany/orderby/IdClassAndOrderByTest.java @@ -4,10 +4,10 @@ */ package org.hibernate.orm.test.annotations.onetomany.orderby; -import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.ImplicitJoinTableNameSource; import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl; +import org.hibernate.boot.model.naming.PhysicalNamingStrategySnakeCaseImpl; import org.hibernate.cfg.AvailableSettings; import org.hibernate.testing.orm.junit.JiraKey; @@ -42,7 +42,7 @@ public class IdClassAndOrderByTest { public static class PhysicalNamingStrategyProvider implements SettingProvider.Provider { @Override public String getSetting() { - return CamelCaseToUnderscoresNamingStrategy.class.getName(); + return PhysicalNamingStrategySnakeCaseImpl.class.getName(); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/batch/HandleVersionNumbersInitializedToNegativeValueTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/batch/HandleVersionNumbersInitializedToNegativeValueTests.java new file mode 100644 index 000000000000..157531501887 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/batch/HandleVersionNumbersInitializedToNegativeValueTests.java @@ -0,0 +1,177 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhancement.batch; + +import jakarta.persistence.*; +import org.hibernate.Hibernate; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.testing.orm.junit.*; +import org.junit.jupiter.api.Test; + +import java.util.Objects; +import java.util.UUID; + +import static jakarta.persistence.FetchType.LAZY; +import static org.assertj.core.api.Assertions.assertThat; + +@DomainModel( + annotatedClasses = { + HandleVersionNumbersInitializedToNegativeValueTests.RootEntity.class, + HandleVersionNumbersInitializedToNegativeValueTests.ChildEntity.class + } +) +@ServiceRegistry( + settings = { + // For your own convenience to see generated queries: + @Setting(name = AvailableSettings.SHOW_SQL, value = "true"), + @Setting(name = AvailableSettings.FORMAT_SQL, value = "true"), + } +) +@SessionFactory +class HandleVersionNumbersInitializedToNegativeValueTests { + + @Test @JiraKey("HHH-18883") + void hhh18883Test(SessionFactoryScope scope) { + var id = UUID.randomUUID(); + scope.inTransaction(session -> { + RootEntity rootEntity = new RootEntity(id, new ChildEntity()); + session.persist(rootEntity); + }); + + scope.inTransaction(session -> { + RootEntity rootEntity = session.find(RootEntity.class, id); + assertThat(rootEntity).isNotNull(); + assertThat(rootEntity.getChildEntity()).isNotNull(); + }); + } + + + @Entity + @Table + public static class RootEntity { + + @Id + private UUID id; + + @OneToOne(mappedBy = "rootEntity", cascade = CascadeType.ALL) + @PrimaryKeyJoinColumn + private ChildEntity childEntity; + + @Version + private int version = -1; + + public RootEntity() { + } + + public RootEntity(UUID id, ChildEntity childEntity) { + setId(id); + setChildEntity(childEntity); + } + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public ChildEntity getChildEntity() { + return childEntity; + } + + public void setChildEntity(ChildEntity childEntity) { + this.childEntity = childEntity; + if (childEntity != null) { + childEntity.setRootEntity(this); + } + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) { + return false; + } + RootEntity event = (RootEntity) o; + return Objects.equals(id, event.id); + } + + @Override + public int hashCode() { + return 0; + } + } + + @Entity + @Table + public static class ChildEntity { + + @Id + private UUID id; + + @OneToOne(fetch = LAZY) + @MapsId + private RootEntity rootEntity; + + @Version + private int version = -1; + + public ChildEntity() { + } + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public RootEntity getRootEntity() { + return rootEntity; + } + + public void setRootEntity(RootEntity rootEntity) { + this.rootEntity = rootEntity; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) { + return false; + } + ChildEntity event = (ChildEntity) o; + return Objects.equals(id, event.id); + } + + @Override + public int hashCode() { + return 0; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/cache/FullLayoutQueryCacheTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/cache/FullLayoutQueryCacheTest.java new file mode 100644 index 000000000000..71f0888f6337 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/cache/FullLayoutQueryCacheTest.java @@ -0,0 +1,183 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.cache; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.NoResultException; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Version; +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.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.sql.Statement; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@DomainModel( + annotatedClasses = { + FullLayoutQueryCacheTest.FirstEntity.class, + FullLayoutQueryCacheTest.SecondEntity.class, + } +) +@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" + ) + } +) +@SessionFactory +@JiraKey("HHH-18323") +public class FullLayoutQueryCacheTest { + + private static final String FIRST_ENTITY_NAME = "FirstEntity"; + + @BeforeEach + public void setup(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + SecondEntity secondEntity = new SecondEntity( "second" ); + session.persist( new FirstEntity( FIRST_ENTITY_NAME, secondEntity ) ); + session.persist( secondEntity ); + } + ); + } + + @Test + public void testQueryCache(SessionFactoryScope scope) { + scope.inSession( + session -> { + session.createQuery( + "select f from FirstEntity f where f.name = :name", FirstEntity.class ) + .setParameter( "name", FIRST_ENTITY_NAME ) + .setCacheable( true ) + .getSingleResult(); + } + ); + + deleteEntitiesSilently( scope ); + + scope.inSession( + session -> { + FirstEntity firstEntity = session.createQuery( + "select f from FirstEntity f where f.name = :name", FirstEntity.class ) + .setParameter( "name", FIRST_ENTITY_NAME ) + .setCacheable( true ) + .getSingleResult(); + assertThat( firstEntity ).isNotNull(); + } + ); + + clearCache( scope ); + + scope.inSession( session -> { + assertThatThrownBy( () -> + session.createQuery( + "select f from FirstEntity f where f.name = :name", FirstEntity.class ) + .setParameter( "name", FIRST_ENTITY_NAME ) + .setCacheable( true ) + .getSingleResult() + ).isInstanceOf( NoResultException.class ); + + } ); + } + + private static void clearCache(SessionFactoryScope scope) { + scope.getSessionFactory().getCache().evictAll(); + scope.getSessionFactory().getCache().evictQueryRegions(); + } + + private void deleteEntitiesSilently(SessionFactoryScope scope) { + scope.inSession( + session -> + session.doWork( + connection -> { + Statement stmt = null; + try { + stmt = connection.createStatement(); + stmt.executeUpdate( "DELETE FROM SecondEntity" ); + stmt.executeUpdate( "DELETE FROM FirstEntity" ); + } + finally { + if ( stmt != null ) { + stmt.close(); + } + } + } + ) + ); + } + + @MappedSuperclass + public static abstract class BaseEntity { + + @Id + @GeneratedValue + protected Long id; + + @Version + protected int version; + + protected String name; + + public BaseEntity() { + } + + public BaseEntity(String name) { + this.name = name; + } + } + + @Entity(name = "FirstEntity") + @Cache(usage = CacheConcurrencyStrategy.READ_ONLY) + public static class FirstEntity extends BaseEntity { + + @OneToOne(mappedBy = "firstEntity") + private SecondEntity secondEntity; + + public FirstEntity() { + } + + public FirstEntity(String name, SecondEntity secondEntity) { + super( name ); + this.secondEntity = secondEntity; + secondEntity.firstEntity = this; + } + } + + @Entity(name = "SecondEntity") + @Cache(usage = CacheConcurrencyStrategy.READ_ONLY) + public static class SecondEntity extends BaseEntity { + + @OneToOne + private FirstEntity firstEntity; + + public SecondEntity() { + } + + public SecondEntity(String baseName) { + super( baseName ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/customsql/CustomSqlRestrictionOverridesTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/customsql/CustomSqlRestrictionOverridesTest.java new file mode 100644 index 000000000000..2a898baa9633 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/customsql/CustomSqlRestrictionOverridesTest.java @@ -0,0 +1,67 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.customsql; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import org.hibernate.annotations.DialectOverride; +import org.hibernate.annotations.SQLRestriction; +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@SessionFactory +@DomainModel(annotatedClasses = CustomSqlRestrictionOverridesTest.Secure.class) +@RequiresDialect(H2Dialect.class) +@RequiresDialect(MySQLDialect.class) +@RequiresDialect(SQLServerDialect.class) +@RequiresDialect(PostgreSQLDialect.class) +@RequiresDialect(DB2Dialect.class) +@RequiresDialect(OracleDialect.class) +public class CustomSqlRestrictionOverridesTest { + @Test + public void testCustomSql(SessionFactoryScope scope) throws NoSuchAlgorithmException { + Secure sec = new Secure(); + sec.hash = MessageDigest.getInstance( "SHA-256" ).digest("hello".getBytes()); + scope.inTransaction(s -> s.persist(sec) ); + Secure secure = scope.fromTransaction( s -> s.find( Secure.class, sec.id ) ); + assertNotNull(secure); + } + @Entity + @Table(name = "SecureTable") + @DialectOverride.SQLRestriction(dialect = H2Dialect.class, + override = @SQLRestriction("hash = hash('SHA-256', 'hello')")) + @DialectOverride.SQLRestriction(dialect = MySQLDialect.class, + override = @SQLRestriction("hash = unhex(sha2('hello', 256))")) + @DialectOverride.SQLRestriction(dialect = PostgreSQLDialect.class, + override = @SQLRestriction("hash = sha256('hello')")) + @DialectOverride.SQLRestriction(dialect = SQLServerDialect.class, + override = @SQLRestriction("hash = hashbytes('SHA2_256', 'hello')")) + @DialectOverride.SQLRestriction(dialect = DB2Dialect.class, + override = @SQLRestriction("hash = hash('hello', 2)")) + @DialectOverride.SQLRestriction(dialect = OracleDialect.class, + override = @SQLRestriction("hash = standard_hash('hello', 'SHA256')")) + static class Secure { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + byte[] hash; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/idclass/JoinedSubclassWithIdClassTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/idclass/JoinedSubclassWithIdClassTest.java new file mode 100644 index 000000000000..7568bf916b4c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/idclass/JoinedSubclassWithIdClassTest.java @@ -0,0 +1,148 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.idclass; + + +import java.io.Serializable; +import java.util.Objects; +import jakarta.persistence.*; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.orm.junit.JiraKey; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertThat; + +/** + * @author Aber Tian + */ +@JiraKey("HHH-16054") +public class JoinedSubclassWithIdClassTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + BaseEntity.class, + ConcreteEntity.class + }; + } + + @Test + public void testJoinedSubClassWithIdClassComposeKey() { + + ConcreteEntity entity = new ConcreteEntity(); + entity.setId( 1L ); + entity.setDealer( "dealer" ); + entity.setName( "aber" ); + entity.setAge( 18 ); + + Pk pk = new Pk(); + pk.id = 1L; + pk.dealer = "dealer"; + + doInHibernate( this::sessionFactory, session -> { + session.merge( entity ); + } ); + + doInHibernate( this::sessionFactory, session -> { + entity.setName( "tian" ); + session.merge( entity ); + BaseEntity baseEntity = session.find( BaseEntity.class, pk ); + assertThat( baseEntity.name, is( "tian" ) ); + } ); + + } + + public static class Pk implements Serializable { + private long id; + + private String dealer; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getDealer() { + return dealer; + } + + public void setDealer(String dealer) { + this.dealer = dealer; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Pk pk = (Pk) o; + return id == pk.id && Objects.equals( dealer, pk.dealer ); + } + + @Override + public int hashCode() { + return Objects.hash( id, dealer ); + } + } + + @Entity(name = "BaseEntity") + @Inheritance(strategy = InheritanceType.JOINED) + @IdClass(Pk.class) + public static abstract class BaseEntity { + @Id + private long id; + + @Id + private String dealer; + + private String name; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getDealer() { + return dealer; + } + + public void setDealer(String dealer) { + this.dealer = dealer; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "ConcreteEntity") + public static class ConcreteEntity extends BaseEntity { + + private int age; + + 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/inheritance/SingleTableNativeQueryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/SingleTableNativeQueryTest.java new file mode 100644 index 000000000000..cc1f292fcb69 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/SingleTableNativeQueryTest.java @@ -0,0 +1,456 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.inheritance; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +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 java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.core.Is.is; + +@JiraKey("HHH-18610") +@DomainModel( + annotatedClasses = { + SingleTableNativeQueryTest.Toy.class, + SingleTableNativeQueryTest.Color.class, + SingleTableNativeQueryTest.Family.class, + SingleTableNativeQueryTest.Person.class, + SingleTableNativeQueryTest.Child.class, + SingleTableNativeQueryTest.Man.class, + SingleTableNativeQueryTest.Woman.class + } +) +@SessionFactory +public class SingleTableNativeQueryTest { + + @BeforeEach + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Toy marioToy; + Toy fidgetSpinner; + Man john; + Woman jane; + Child susan; + Child mark; + Family family; + List children; + List familyMembers; + + marioToy = new Toy( 1L, "Super Mario retro Mushroom" ); + fidgetSpinner = new Toy( 2L, "Fidget Spinner" ); + john = new Man( "John", "Riding Roller Coasters" ); + jane = new Woman( "Jane", "Hippotherapist" ); + susan = new Child( "Susan", marioToy ); + mark = new Child( "Mark", fidgetSpinner ); + family = new Family( "McCloud" ); + children = new ArrayList<>( Arrays.asList( susan, mark ) ); + familyMembers = Arrays.asList( john, jane, susan, mark ); + + + session.persist( marioToy ); + session.persist( fidgetSpinner ); + + jane.setColor( new Color( "pink" ) ); + jane.setHusband( john ); + jane.setChildren( children ); + + john.setColor( new Color( "blue" ) ); + john.setWife( jane ); + john.setChildren( children ); + + for ( Child child : children ) { + child.setFather( john ); + child.setMother( jane ); + } + + for ( Person person : familyMembers ) { + family.add( person ); + } + + session.persist( family ); + } ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncateMappedObjects(); + } + + @Test + public void itShouldGetPersons(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + List results = session.createNativeQuery( "select {p.*} from person p order by p.name", + Object.class ).addEntity( "p", Person.class ).list(); + assertThat( results.stream().map( p -> ((Person) p).getName() ).collect( Collectors.toList() ), + contains( "Jane", "John", "Mark", "Susan" ) ); + } + ); + } + + @Test + public void itShouldGetWife(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + List results = session.createNativeQuery( + "select {m.*}, {w.*} from person m left join person w on m.wife_name = w.name where m.TYPE = 'MAN'", + Object[].class ) + .addEntity( "m", Person.class ) + .addEntity( "w", Person.class ) + .list(); + assertThat( results.size(), is( 1 ) ); + assertThat( results.get( 0 )[0], instanceOf( Man.class ) ); + assertThat( ((Man) results.get( 0 )[0]).getName(), is( "John" ) ); + assertThat( results.get( 0 )[1], instanceOf( Woman.class ) ); + assertThat( ((Woman) results.get( 0 )[1]).getName(), is( "Jane" ) ); + } + ); + } + + @Test + public void itShouldGetFamilyMembers(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + List results = session.createNativeQuery( "select {f.*} from family f", Object.class ) + .addEntity( "f", Family.class ).list(); + Family family = (Family) results.get( 0 ); + List members = family.getMembers(); + assertThat( members.size(), is( 4 ) ); + } + ); + } + + @Embeddable + public static class Color { + @Column(name = "color") + private String attributes; + + public Color() { + } + + public Color(final String attributes) { + this.attributes = attributes; + } + + public String getAttributes() { + return attributes; + } + + public void setAttributes(final String attributes) { + this.attributes = attributes; + } + } + + @Entity(name = "Family") + @Table(name = "family") + public static class Family { + + @Id + private String name; + + @OneToMany(mappedBy = "familyName", cascade = CascadeType.ALL, orphanRemoval = true) + private List members = new ArrayList<>(); + + public Family() { + } + + public Family(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getMembers() { + return members; + } + + public void setMembers(List members) { + this.members = members; + } + + public void add(Person person) { + person.setFamilyName( this ); + members.add( person ); + } + + @Override + public String toString() { + return "Family [name=" + name + "]"; + } + } + + @Entity(name = "Person") + @Table(name = "person") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + @DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.STRING) + public static class Person { + + @Id + private String name; + + @ManyToOne + private Family familyName; + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Family getFamilyName() { + return familyName; + } + + public void setFamilyName(Family familyName) { + this.familyName = familyName; + } + + @Override + public String toString() { + return name; + } + } + + + @Entity(name = "Toy") + @Table(name = "toy") + public static class Toy { + + @Id + private Long id; + + private String name; + + @OneToMany(mappedBy = "favoriteThing", cascade = CascadeType.ALL, orphanRemoval = true) + List favorite = new ArrayList<>(); + + public Toy() { + } + + public Toy(final Long id, final String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + } + + @Entity(name = "Child") + @DiscriminatorValue("CHILD") + public static class Child extends Person { + + @ManyToOne + @JoinColumn(name = "fav_toy") + private Toy favoriteThing; + + @ManyToOne + private Woman mother; + + @ManyToOne + private Man father; + + public Child() { + } + + public Child(String name, Toy favouriteThing) { + super( name ); + this.favoriteThing = favouriteThing; + } + + public Toy getFavoriteThing() { + return favoriteThing; + } + + public void setFavoriteThing(Toy favouriteThing) { + this.favoriteThing = favouriteThing; + } + + public Man getFather() { + return father; + } + + public void setFather(Man father) { + this.father = father; + } + + public Woman getMother() { + return mother; + } + + public void setMother(Woman mother) { + this.mother = mother; + } + } + + @Entity(name = "Man") + @DiscriminatorValue("MAN") + public static class Man extends Person { + + private Color color; + + @Column(name = "fav_hobby") + private String favoriteThing; + + @OneToOne + private Woman wife; + + @OneToMany(mappedBy = "father") + private List children = new ArrayList<>(); + + public Man() { + } + + public Man(String name, String favoriteThing) { + super( name ); + this.favoriteThing = favoriteThing; + } + + public String getFavoriteThing() { + return favoriteThing; + } + + public void setFavoriteThing(String favoriteThing) { + this.favoriteThing = favoriteThing; + } + + public Woman getWife() { + return wife; + } + + public void setWife(Woman wife) { + this.wife = wife; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public Color getColor() { + return color; + } + + public void setColor(final Color color) { + this.color = color; + } + } + + @Entity(name = "Woman") + @DiscriminatorValue("WOMAN") + public static class Woman extends Person { + + private Color color; + + private String job; + + @OneToOne + private Man husband; + + @OneToMany(mappedBy = "mother") + private List children = new ArrayList<>(); + + public Woman() { + } + + public Woman(String name, String job) { + super( name ); + this.job = job; + } + + + public String getJob() { + return job; + } + + public void setJob(String job) { + this.job = job; + } + + public Man getHusband() { + return husband; + } + + public void setHusband(Man husband) { + this.husband = husband; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public Color getColor() { + return color; + } + + public void setColor(final Color color) { + this.color = color; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/CriteriaQueryTypeQueryAdapterTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/CriteriaQueryTypeQueryAdapterTest.java index 050433e94f40..5ee6e62de222 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/CriteriaQueryTypeQueryAdapterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/CriteriaQueryTypeQueryAdapterTest.java @@ -111,6 +111,22 @@ public void testCriteriaQueryGetNonExistingParameter() { } ); } + @Test(expected = IllegalArgumentException.class) + @JiraKey("HHH-13932") + public void testCriteriaQuerySetNonExistingParameter() { + doInJPA( this::entityManagerFactory, entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery( Item.class ); + Root root = query.from( Item.class ); + ParameterExpression parameter = builder.parameter( String.class, "name" ); + Predicate predicate = builder.equal( root.get( "name" ), parameter ); + query.where( predicate ); + TypedQuery criteriaQuery = entityManager.createQuery( query ); + ParameterExpression nonExistingParam = builder.parameter( String.class, "nonExistingParam" ); + criteriaQuery.setParameter( nonExistingParam, "George" ); + } ); + } + @Test public void testSetParameterPassingTypeNotFails() { doInJPA( this::entityManagerFactory, entityManager -> { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/B.java b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/B.java new file mode 100644 index 000000000000..9e08d19233fe --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/B.java @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.locking; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import org.hibernate.annotations.GenericGenerator; + +@Entity +@Table(name = "T_LOCK_B") +public class B { + + private Long id; + private A a; + private String value; + + public B() { + } + + public B(String value) { + this.value = value; + } + + @Id + @GeneratedValue(generator = "increment") + @GenericGenerator(name = "increment", strategy = "increment") + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Column(name = "b_value") + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @OneToOne + @JoinColumn(name = "a_id") + public A getA() { + return a; + } + + public void setA(A a) { + this.a = a; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/PessimisticWriteLockWithAliasTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/PessimisticWriteLockWithAliasTest.java new file mode 100644 index 000000000000..d03c49ccba82 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/PessimisticWriteLockWithAliasTest.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.locking; + +import org.hibernate.LockMode; +import org.hibernate.Session; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.testing.orm.junit.JiraKey; +import org.junit.Before; +import org.junit.Test; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.util.regex.Pattern.CASE_INSENSITIVE; +import static java.util.regex.Pattern.MULTILINE; +import static org.junit.Assert.assertTrue; + +/** + * @author Bin Chen (bin.chen@team.neustar) + */ +public class PessimisticWriteLockWithAliasTest + extends BaseNonConfigCoreFunctionalTestCase { + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ A.class, B.class }; + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + sqlStatementInterceptor = new SQLStatementInterceptor( sfb ); + } + + private A entityA; + private B entityB; + + @Before + public void createTestData() { + Session session = sessionFactory().openSession(); + session.beginTransaction(); + try { + entityA = new A(); + session.persist( entityA ); + entityB = new B( "foo" ); + entityB.setA( entityA ); + session.persist( entityB ); + } + finally { + session.getTransaction().commit(); + session.close(); + } + } + + @Test @JiraKey("HHH-12866") + @RequiresDialect(OracleDialect.class) + @RequiresDialect(PostgreSQLDialect.class) + public void testSetLockModeWithAlias() { + + Session session = sessionFactory().openSession(); + session.beginTransaction(); + try { + session.createQuery( + "select b from B b left join fetch b.a", B.class ) + .unwrap( org.hibernate.query.Query.class ) + .setLockMode( "b", LockMode.PESSIMISTIC_WRITE ) + .list(); + + /* + * The generated SQL would be like:
     select b0_.id as id1_1_0_, a1_.id as id1_0_1_, b0_.a_id as
    +			 * a_id3_1_0_, b0_.b_value as b_value2_1_0_, a1_.a_value as a_value2_0_1_ from T_LOCK_B b0_ left outer join
    +			 * T_LOCK_A a1_ on b0_.a_id=a1_.id for update of b0_.id 
    + */ + String lockingQuery = sqlStatementInterceptor.getSqlQueries().getLast().toLowerCase(); + + // attempt to get the alias that is specified in the from clause + Pattern fromTableAliasPattern = Pattern.compile( "from t_lock_b (\\S+)", CASE_INSENSITIVE | MULTILINE ); + Matcher aliasGroup = fromTableAliasPattern.matcher( lockingQuery ); + assertTrue( "Fail to locate alias in the from clause: " + lockingQuery, aliasGroup.find() ); + assertTrue( "Actual query: " + lockingQuery, + lockingQuery.endsWith( " for update of " + aliasGroup.group( 1 ) + ".id" ) // Oracle + || lockingQuery.endsWith( " for no key update of " + aliasGroup.group( 1 ) ) ); // PostgreSQL + } + finally { + session.getTransaction().commit(); + session.close(); + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/array/MySqlArrayOfTimestampsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/array/MySqlArrayOfTimestampsTest.java new file mode 100644 index 000000000000..4bc69b42226d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/array/MySqlArrayOfTimestampsTest.java @@ -0,0 +1,166 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.mapping.array; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.type.descriptor.java.JdbcTimestampJavaType; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; + +import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.Month; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +@DomainModel(annotatedClasses = MySqlArrayOfTimestampsTest.Foo.class) +@SessionFactory +@JiraKey("HHH-18881") +class MySqlArrayOfTimestampsTest { + + private static final LocalDateTime[] dataArray = { + // Unix epoch start if you're in the UK + LocalDateTime.of( 1970, Month.JANUARY, 1, 0, 0, 0, 0 ), + // pre-Y2K + LocalDateTime.of( 1999, Month.DECEMBER, 31, 23, 59, 59, 0 ), + // We survived! Why was anyone worried? + LocalDateTime.of( 2000, Month.JANUARY, 1, 0, 0, 0, 0 ), + // Silence will fall! + LocalDateTime.of( 2010, Month.JUNE, 26, 20, 4, 0, 0 ), + // 2024 summer time + LocalDateTime.of( 2024, 6, 20, 0, 0, 0 ), + // 2023 winer time + LocalDateTime.of( 2023, 12, 22, 0, 0, 0 ) + }; + + private TimeZone currentDefault; + + @BeforeAll + void setTimeZone() { + currentDefault = TimeZone.getDefault(); + TimeZone.setDefault( TimeZone.getTimeZone( "Europe/Zagreb" ) ); + } + + @AfterAll + void restoreTimeZone() { + TimeZone.setDefault( currentDefault ); + } + + @Test + @Order(1) + @RequiresDialect(MySQLDialect.class) + public void testLocalDateTime(SessionFactoryScope scope) { + + final Integer basicId = scope.fromTransaction( session -> { + Foo basic = new Foo(); + basic.localDateTimeArray = dataArray; + basic.localDateTimeField = dataArray[0]; + session.persist( basic ); + return basic.id; + } ); + + scope.inTransaction( session -> { + Foo found = session.find( Foo.class, basicId ); + assertThat( found.localDateTimeField ).isEqualTo( dataArray[0] ); + assertThat( found.localDateTimeArray ).isEqualTo( dataArray ); + } ); + } + + + @Test + @Order(2) + @RequiresDialect(MySQLDialect.class) + public void testDate(SessionFactoryScope scope) { + Date[] dataArray = {Calendar.getInstance().getTime(), Calendar.getInstance().getTime()}; + + final Integer basicId = scope.fromTransaction( session -> { + Foo basic = new Foo(); + basic.dateArray = dataArray; + basic.dateField = dataArray[0]; + session.persist( basic ); + return basic.id; + } ); + + scope.inTransaction( session -> { + Foo found = session.find( Foo.class, basicId ); + assertThat( found.dateField.getTime() ).isEqualTo( dataArray[0].getTime() ); + for ( int i = 0; i < dataArray.length; i++ ) { + assertThat( found.dateArray[i].getTime() ).isEqualTo( dataArray[i].getTime() ); + } + } ); + } + + private static final LocalDateTime SUMMER = LocalDate.of( 2024, 6, 20 ).atStartOfDay(); + private static final LocalDateTime WINTER = LocalDate.of( 2023, 12, 22 ).atStartOfDay(); + private static final LocalDate EPOCH = LocalDate.of( 1970, Month.JANUARY, 1 ); + + private static final TimeZone[] TEST_TIME_ZONES = Stream.of( + "Africa/Monrovia", + "Europe/Zagreb", + "Asia/Singapore", + "Europe/Tallinn", + "Europe/Minsk", + "America/Anchorage" + ).map( TimeZone::getTimeZone ).toArray( TimeZone[]::new ); + + @Test + void encodeThenDecodeLocalDateTime() { + for ( final TimeZone zone : TEST_TIME_ZONES ) { + final TimeZone currentTimeZone = TimeZone.getDefault(); + TimeZone.setDefault( zone ); + try { + for ( LocalDateTime dateTime : dataArray ) { + final MySqlAppender appender = new MySqlAppender(); + final Timestamp expected = Timestamp.valueOf( dateTime ); + JdbcTimestampJavaType.INSTANCE.appendEncodedString( appender, expected ); + final Date actual = JdbcTimestampJavaType.INSTANCE.fromEncodedString( appender.stringBuilder, 0, + appender.stringBuilder.length() ); + Assertions.assertEquals( expected, actual ); + } + } + finally { + TimeZone.setDefault( currentTimeZone ); + } + } + } + + @Entity(name = "Foo") + public static class Foo { + @Id + @GeneratedValue + public Integer id; + public Date[] dateArray; + public LocalDateTime[] localDateTimeArray; + public Date dateField; + public LocalDateTime localDateTimeField; + } + + private static class MySqlAppender implements SqlAppender { + + private final StringBuilder stringBuilder = new StringBuilder(); + + @Override + public void appendSql(String fragment) { + stringBuilder.append( fragment ); + } + } +} 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 66833e1716bf..593ba12eb780 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 @@ -4,48 +4,51 @@ */ package org.hibernate.orm.test.mapping.basic; -import java.nio.charset.StandardCharsets; -import java.sql.Blob; -import java.sql.Clob; -import java.util.List; -import java.util.Map; - 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 org.hibernate.annotations.JdbcTypeCode; import org.hibernate.cfg.AvailableSettings; import org.hibernate.community.dialect.AltibaseDialect; -import org.hibernate.dialect.HANADialect; import org.hibernate.community.dialect.DerbyDialect; +import org.hibernate.dialect.HANADialect; import org.hibernate.dialect.OracleDialect; 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.type.SqlTypes; -import org.hibernate.type.descriptor.jdbc.JdbcType; -import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; - +import org.hibernate.query.MutationQuery; +import org.hibernate.query.criteria.HibernateCriteriaBuilder; 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; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.Setting; import org.hibernate.testing.orm.junit.SkipForDialect; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; +import java.nio.charset.StandardCharsets; +import java.sql.Blob; +import java.sql.Clob; +import java.util.List; +import java.util.Map; 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; @@ -120,7 +123,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 ) ); @@ -142,8 +146,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() ) ); } ); } @@ -163,14 +167,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) -> { @@ -187,14 +191,19 @@ public void verifyDirtyChecking(SessionFactoryScope scope) { } @Test - @SkipForDialect(dialectClass = DerbyDialect.class, reason = "Derby doesn't support comparing CLOBs with the = operator") - @SkipForDialect(dialectClass = HANADialect.class, matchSubTypes = true, reason = "HANA doesn't support comparing LOBs with the = operator") - @SkipForDialect(dialectClass = SybaseDialect.class, matchSubTypes = true, reason = "Sybase doesn't support comparing LOBs with the = operator") - @SkipForDialect(dialectClass = OracleDialect.class, matchSubTypes = true, reason = "Oracle doesn't support comparing JSON with the = operator") - @SkipForDialect(dialectClass = AltibaseDialect.class, reason = "Altibase doesn't support comparing CLOBs with the = operator") + @SkipForDialect(dialectClass = DerbyDialect.class, + reason = "Derby doesn't support comparing CLOBs with the = operator") + @SkipForDialect(dialectClass = HANADialect.class, matchSubTypes = true, + reason = "HANA doesn't support comparing LOBs with the = operator") + @SkipForDialect(dialectClass = SybaseDialect.class, matchSubTypes = true, + reason = "Sybase doesn't support comparing LOBs with the = operator") + @SkipForDialect(dialectClass = OracleDialect.class, matchSubTypes = true, + reason = "Oracle doesn't support comparing JSON with the = operator") + @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( @@ -239,6 +248,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/namingstrategy/ejb3joincolumn/PhysicalNamingStrategyImpl.java b/hibernate-core/src/test/java/org/hibernate/orm/test/namingstrategy/ejb3joincolumn/PhysicalNamingStrategyImpl.java index fcd70fe594d0..17b1570860b8 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/namingstrategy/ejb3joincolumn/PhysicalNamingStrategyImpl.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/namingstrategy/ejb3joincolumn/PhysicalNamingStrategyImpl.java @@ -8,6 +8,8 @@ import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import static org.hibernate.boot.model.naming.Identifier.toIdentifier; + /** * @author Anton Wimmer * @author Steve Ebersole @@ -20,19 +22,17 @@ public class PhysicalNamingStrategyImpl extends PhysicalNamingStrategyStandardIm @Override public Identifier toPhysicalTableName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) { - return Identifier.toIdentifier(makeCleanIdentifier("tbl_" + logicalName.getText()), logicalName.isQuoted()); + return toIdentifier( makeCleanIdentifier("tbl_" + logicalName.getText()), logicalName.isQuoted() ); } @Override public Identifier toPhysicalColumnName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) { - if ( logicalName.getText().equals("DTYPE") ) { - return logicalName; - } - - return Identifier.toIdentifier(makeCleanIdentifier("c_" + logicalName.getText()), logicalName.isQuoted()); + return logicalName.getText().equals( "DTYPE" ) + ? logicalName + : toIdentifier( makeCleanIdentifier( "c_" + logicalName.getText() ), logicalName.isQuoted() ); } private String makeCleanIdentifier(String s) { - return s.substring(0, Math.min(s.length(), 63)).toLowerCase(); + return s.substring( 0, Math.min(s.length(), 63) ).toLowerCase(); } } 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/hql/set/UnionOfPartitionResultsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/set/UnionOfPartitionResultsTest.java new file mode 100644 index 000000000000..64babea97795 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/set/UnionOfPartitionResultsTest.java @@ -0,0 +1,221 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.query.hql.set; + +import java.time.LocalDate; + +import org.hibernate.query.Query; + +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; + +/** + * @author Jan Schatteman + */ +@DomainModel( + annotatedClasses = {UnionOfPartitionResultsTest.Apple.class, UnionOfPartitionResultsTest.Pie.class} +) +@SessionFactory +@JiraKey( "HHH-18069" ) +public class UnionOfPartitionResultsTest { + + @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsUnion.class) + public void testSubqueryWithUnion(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + String q = """ + SELECT id + FROM + ( + ( SELECT id id, bakedPie bakedPie + FROM Apple c + ) + UNION ALL + ( SELECT id id, bakedPie bakedPie + FROM Apple a + ) + ) + """; + + Query query = session.createQuery( q ); + + query.list(); + } + ); + } + + @Test + public void testSubquery(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + String q = """ + SELECT id + FROM + ( + SELECT id id, bakedPie bakedPie + FROM Apple c + ) + """; + + Query query = session.createQuery( q ); + + query.list(); + } + ); + } + + @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsUnion.class) + public void testUnionQuery(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + String q = """ + ( SELECT id id, bakedPie bakedPie + FROM Apple c + ) + UNION ALL + ( SELECT id id, bakedPie bakedPie + FROM Apple c + ) + """; + + Query query = session.createQuery( q ); + + query.list(); + } + ); + } + + + @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsUnion.class) + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportPartitionBy.class) + public void testUnionOfPartitionResults(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + String q = + "SELECT new CurrentApple(id, bakedPie.id, dir) " + + "FROM (" + + "(" + + "SELECT id id, bakedPie bakedPie, bakedOn bakedOn, MAX(bakedOn) OVER (PARTITION BY bakedPie.id) mbo, -1 dir " + + "FROM Apple c " + + "WHERE bakedPie.id IN (1,2,3,4) AND bakedOn <= :now" + + ") UNION ALL (" + + "SELECT id id, bakedPie bakedPie, bakedOn bakedOn, MIN(bakedOn) OVER (PARTITION BY bakedPie.id) mbo, 1 dir " + + "FROM Apple c " + + "WHERE bakedPie.id IN (1,2,3,4) AND bakedOn > :now" + + ")" + + ") " + + "WHERE bakedOn = mbo ORDER BY dir"; + + Query query = session.createQuery( q, CurrentApple.class ); + query.setParameter( "now", LocalDate.now()); + + query.list(); + } + ); + } + + + public static class CurrentApple { + private final int id; + private final int pieId; + private final int dir; + + public CurrentApple(int id, int pieId, int dir) { + this.id = id; + this.pieId = pieId; + this.dir = dir; + } + + public int getDir() { + return dir; + } + + public int getId() { + return id; + } + + public int getPieId() { + return pieId; + } + } + + @Entity(name = "Apple") + public static class Apple { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + private LocalDate bakedOn; + @ManyToOne + private Pie bakedPie; + + public Integer getId() { + return id; + } + + public Apple setId(Integer id) { + this.id = id; + return this; + } + + public LocalDate getBakedOn() { + return bakedOn; + } + + public Apple setBakedOn(LocalDate bakedOn) { + this.bakedOn = bakedOn; + return this; + } + + public Pie getBakedPie() { + return bakedPie; + } + + public Apple setBakedPie(Pie bakedPie) { + this.bakedPie = bakedPie; + return this; + } + } + + @Entity(name = "Pie") + public static class Pie { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + private String taste; + + public Integer getId() { + return id; + } + + public Pie setId(Integer id) { + this.id = id; + return this; + } + + public String getTaste() { + return taste; + } + + public Pie setTaste(String taste) { + this.taste = taste; + return this; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/hand/query/NativeSQLQueriesTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/hand/query/NativeSQLQueriesTest.java index 8af5856dc0c0..df777a303ca9 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/hand/query/NativeSQLQueriesTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/hand/query/NativeSQLQueriesTest.java @@ -836,6 +836,12 @@ public void testAddJoinForManyToMany(SessionFactoryScope scope) { ); } + @Test @JiraKey( "HHH-15102" ) + @SkipForDialect(dialectClass = MySQLDialect.class, matchSubTypes = true) + public void testCommentInSQLQuery(SessionFactoryScope scope) { + scope.inTransaction( s -> s.createNativeQuery( "select sum(1) --count(*), effectively\nfrom ORGANIZATION" ).getSingleResult() ); + } + @Test public void testTextTypeInSQLQuery(SessionFactoryScope scope) { String description = buildLongString( 15000, 'a' ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/util/StandardStackTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StandardStackTest.java index 8de2c1b92525..7a9bb2fe20fa 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/util/StandardStackTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StandardStackTest.java @@ -146,7 +146,7 @@ public void testFindCurrentFirstWithParameterCleared() { // utility functions private Stack allocateStack(int size) { - final Stack stack = new StandardStack<>( Integer.class ); + final Stack stack = new StandardStack<>(); for ( int i = 0; i < size; i++ ) { stack.push( i ); } diff --git a/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/StaticClassLists.java b/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/StaticClassLists.java index dc0942b293e1..a67dd6cd3524 100644 --- a/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/StaticClassLists.java +++ b/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/StaticClassLists.java @@ -4,24 +4,7 @@ */ package org.hibernate.graalvm.internal; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.function.Supplier; -import org.hibernate.graph.internal.parse.SubGraphGenerator; -import org.hibernate.graph.spi.AttributeNodeImplementor; -import org.hibernate.graph.spi.GraphImplementor; -import org.hibernate.query.hql.spi.DotIdentifierConsumer; -import org.hibernate.query.hql.spi.SqmCreationProcessingState; -import org.hibernate.query.sqm.spi.ParameterDeclarationContext; -import org.hibernate.query.sqm.sql.FromClauseIndex; -import org.hibernate.sql.ast.Clause; -import org.hibernate.sql.ast.spi.SqlAstProcessingState; -import org.hibernate.sql.ast.tree.Statement; -import org.hibernate.sql.ast.tree.select.QueryPart; -import org.hibernate.sql.results.graph.FetchParent; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState; import org.hibernate.tool.schema.internal.script.MultiLineSqlScriptExtractor; import org.hibernate.type.EnumType; @@ -94,25 +77,6 @@ public static Class[] typesNeedingArrayCopy() { org.hibernate.event.spi.PostCollectionRecreateEventListener[].class, org.hibernate.event.spi.PostCollectionRemoveEventListener[].class, org.hibernate.event.spi.PostCollectionUpdateEventListener[].class, - //And other array types, necessary for allocation of generified instances of org.hibernate.internal.util.collections.StandardStack: - //TODO can this list be tested for consistency with the core module? Or generated? e.g. could use Jandex? - AttributeNodeImplementor[].class, - Clause[].class, - DotIdentifierConsumer[].class, - FetchParent[].class, - FromClauseIndex[].class, - Function[].class, - GraphImplementor[].class, - JdbcValuesSourceProcessingState[].class, - List[].class, - Map.Entry[].class, - ParameterDeclarationContext[].class, - QueryPart[].class, - SqlAstProcessingState[].class, - SqmCreationProcessingState[].class, - Statement[].class, - SubGraphGenerator[].class, - Supplier[].class, }; } diff --git a/hibernate-graalvm/src/test/java/org/hibernate/graalvm/internal/StaticClassListsTest.java b/hibernate-graalvm/src/test/java/org/hibernate/graalvm/internal/StaticClassListsTest.java index dac75c6ba277..056e88d50753 100644 --- a/hibernate-graalvm/src/test/java/org/hibernate/graalvm/internal/StaticClassListsTest.java +++ b/hibernate-graalvm/src/test/java/org/hibernate/graalvm/internal/StaticClassListsTest.java @@ -158,27 +158,7 @@ Stream> classes() { // Putting anything here is running the risk of forgetting // why it was necessary in the first place... return Stream.of( - // Java classes -- the why is lost to history - java.util.function.Function[].class, - java.util.List[].class, - java.util.Map.Entry[].class, - java.util.function.Supplier[].class, - // Graphs -- the why is lost to history - org.hibernate.graph.spi.AttributeNodeImplementor[].class, - org.hibernate.sql.results.graph.FetchParent[].class, - org.hibernate.graph.spi.GraphImplementor[].class, - org.hibernate.graph.internal.parse.SubGraphGenerator[].class, - // AST/parsing -- no way to detect this automatically, you just have to know. - org.hibernate.sql.ast.Clause[].class, - org.hibernate.query.hql.spi.DotIdentifierConsumer[].class, - org.hibernate.query.sqm.sql.FromClauseIndex[].class, - org.hibernate.query.sqm.spi.ParameterDeclarationContext[].class, - org.hibernate.sql.ast.tree.select.QueryPart[].class, - org.hibernate.sql.ast.spi.SqlAstProcessingState[].class, - org.hibernate.query.hql.spi.SqmCreationProcessingState[].class, - org.hibernate.sql.ast.tree.Statement[].class, - // Various internals -- the why is lost to history - org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState[].class + // Hopefully to remain empty ); } };